2 * rlm_eap_mschapv2.c Handles that are called from eap
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * Copyright 2003,2006 The FreeRADIUS server project
23 #include <freeradius-devel/ident.h>
29 #include "eap_mschapv2.h"
31 #include <freeradius-devel/rad_assert.h>
33 typedef struct rlm_eap_mschapv2_t {
34 int with_ntdomain_hack;
38 static CONF_PARSER module_config[] = {
39 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
40 offsetof(rlm_eap_mschapv2_t,with_ntdomain_hack), NULL, "no" },
42 { "send_error", PW_TYPE_BOOLEAN,
43 offsetof(rlm_eap_mschapv2_t,send_error), NULL, "no" },
45 { NULL, -1, 0, NULL, NULL } /* end the list */
49 static void fix_mppe_keys(EAP_HANDLER *handler, mschapv2_opaque_t *data)
51 pairmove2(&data->mppe_keys, &handler->request->reply->vps, 7, VENDORPEC_MICROSOFT, TAG_ANY);
52 pairmove2(&data->mppe_keys, &handler->request->reply->vps, 8, VENDORPEC_MICROSOFT, TAG_ANY);
53 pairmove2(&data->mppe_keys, &handler->request->reply->vps, 16, VENDORPEC_MICROSOFT, TAG_ANY);
54 pairmove2(&data->mppe_keys, &handler->request->reply->vps, 17, VENDORPEC_MICROSOFT, TAG_ANY);
57 static void free_data(void *ptr)
59 mschapv2_opaque_t *data = ptr;
61 pairfree(&data->mppe_keys);
62 pairfree(&data->reply);
70 static int mschapv2_attach(CONF_SECTION *cs, void **instance)
72 rlm_eap_mschapv2_t *inst;
74 *instance = inst = talloc_zero(cs, rlm_eap_mschapv2_t);
78 * Parse the configuration attributes.
80 if (cf_section_parse(cs, inst, module_config) < 0) {
89 * Compose the response.
91 static int eapmschapv2_compose(EAP_HANDLER *handler, VALUE_PAIR *reply)
95 mschapv2_header_t *hdr;
96 EAP_DS *eap_ds = handler->eap_ds;
98 eap_ds->request->code = PW_EAP_REQUEST;
99 eap_ds->request->type.type = PW_EAP_MSCHAPV2;
102 * Always called with vendor Microsoft
104 switch (reply->da->attr) {
105 case PW_MSCHAP_CHALLENGE:
108 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
109 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
110 * | Code | Identifier | Length |
111 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
112 * | Type | OpCode | MS-CHAPv2-ID | MS-Length...
113 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
114 * | MS-Length | Value-Size | Challenge...
115 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
117 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
119 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
121 length = MSCHAPV2_HEADER_LEN + MSCHAPV2_CHALLENGE_LEN + strlen(handler->identity);
122 eap_ds->request->type.data = malloc(length);
124 * Allocate room for the EAP-MS-CHAPv2 data.
126 if (eap_ds->request->type.data == NULL) {
127 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
130 eap_ds->request->type.length = length;
132 ptr = eap_ds->request->type.data;
133 hdr = (mschapv2_header_t *) ptr;
135 hdr->opcode = PW_EAP_MSCHAPV2_CHALLENGE;
136 hdr->mschapv2_id = eap_ds->response->id + 1;
137 length = htons(length);
138 memcpy(hdr->ms_length, &length, sizeof(uint16_t));
139 hdr->value_size = MSCHAPV2_CHALLENGE_LEN;
141 ptr += MSCHAPV2_HEADER_LEN;
144 * Copy the Challenge, success, or error over.
146 memcpy(ptr, reply->vp_strvalue, reply->length);
147 memcpy((ptr + reply->length), handler->identity, strlen(handler->identity));
150 case PW_MSCHAP2_SUCCESS:
153 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
154 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
155 * | Code | Identifier | Length |
156 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
157 * | Type | OpCode | MS-CHAPv2-ID | MS-Length...
158 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
159 * | MS-Length | Message...
160 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
162 DEBUG2("MSCHAP Success\n");
164 eap_ds->request->type.data = malloc(length);
166 * Allocate room for the EAP-MS-CHAPv2 data.
168 if (eap_ds->request->type.data == NULL) {
169 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
172 memset(eap_ds->request->type.data, 0, length);
173 eap_ds->request->type.length = length;
175 eap_ds->request->type.data[0] = PW_EAP_MSCHAPV2_SUCCESS;
176 eap_ds->request->type.data[1] = eap_ds->response->id;
177 length = htons(length);
178 memcpy((eap_ds->request->type.data + 2), &length, sizeof(uint16_t));
179 memcpy((eap_ds->request->type.data + 4), reply->vp_strvalue + 1, 42);
182 case PW_MSCHAP_ERROR:
183 DEBUG2("MSCHAP Failure\n");
184 length = 4 + reply->length - 1;
185 eap_ds->request->type.data = malloc(length);
188 * Allocate room for the EAP-MS-CHAPv2 data.
190 if (eap_ds->request->type.data == NULL) {
191 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
194 memset(eap_ds->request->type.data, 0, length);
195 eap_ds->request->type.length = length;
197 eap_ds->request->type.data[0] = PW_EAP_MSCHAPV2_FAILURE;
198 eap_ds->request->type.data[1] = eap_ds->response->id;
199 length = htons(length);
200 memcpy((eap_ds->request->type.data + 2), &length, sizeof(uint16_t));
202 * Copy the entire failure message.
204 memcpy((eap_ds->request->type.data + 4),
205 reply->vp_strvalue + 1, reply->length - 1);
209 radlog(L_ERR, "rlm_eap_mschapv2: Internal sanity check failed");
219 * Initiate the EAP-MSCHAPV2 session by sending a challenge to the peer.
221 static int mschapv2_initiate(void *type_data, EAP_HANDLER *handler)
224 VALUE_PAIR *challenge;
225 mschapv2_opaque_t *data;
227 type_data = type_data; /* -Wunused */
229 challenge = pairmake("MS-CHAP-Challenge", "0x00", T_OP_EQ);
231 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
236 * Get a random challenge.
238 challenge->length = MSCHAPV2_CHALLENGE_LEN;
239 for (i = 0; i < MSCHAPV2_CHALLENGE_LEN; i++) {
240 challenge->vp_strvalue[i] = fr_rand();
242 DEBUG2("rlm_eap_mschapv2: Issuing Challenge");
245 * Keep track of the challenge.
247 data = malloc(sizeof(mschapv2_opaque_t));
248 rad_assert(data != NULL);
251 * We're at the stage where we're challenging the user.
253 data->code = PW_EAP_MSCHAPV2_CHALLENGE;
254 memcpy(data->challenge, challenge->vp_strvalue, MSCHAPV2_CHALLENGE_LEN);
255 data->mppe_keys = NULL;
258 handler->opaque = data;
259 handler->free_opaque = free_data;
262 * Compose the EAP-MSCHAPV2 packet out of the data structure,
265 eapmschapv2_compose(handler, challenge);
266 pairfree(&challenge);
270 * The EAP session doesn't have enough information to
271 * proxy the "inside EAP" protocol. Disable EAP proxying.
273 handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
277 * We don't need to authorize the user at this point.
279 * We also don't need to keep the challenge, as it's
280 * stored in 'handler->eap_ds', which will be given back
283 handler->stage = AUTHENTICATE;
290 * Do post-proxy processing,
294 * Called from rlm_eap.c, eap_postproxy().
296 static int mschap_postproxy(EAP_HANDLER *handler, void *tunnel_data)
298 VALUE_PAIR *response = NULL;
299 mschapv2_opaque_t *data;
301 data = (mschapv2_opaque_t *) handler->opaque;
302 rad_assert(data != NULL);
304 tunnel_data = tunnel_data; /* -Wunused */
306 DEBUG2(" rlm_eap_mschapv2: Passing reply from proxy back into the tunnel %p %d.",
307 handler->request, handler->request->reply->code);
310 * There is only a limited number of possibilities.
312 switch (handler->request->reply->code) {
313 case PW_AUTHENTICATION_ACK:
314 DEBUG(" rlm_eap_mschapv2: Proxied authentication succeeded.");
316 * Move the attribute, so it doesn't go into
320 &handler->request->reply->vps,
321 PW_MSCHAP2_SUCCESS, VENDORPEC_MICROSOFT, TAG_ANY);
325 case PW_AUTHENTICATION_REJECT:
326 DEBUG(" rlm_eap_mschapv2: Proxied authentication did not succeed.");
334 radlog(L_ERR, "rlm_eap_mschapv2: Proxied reply contained no MS-CHAPv2-Success or MS-CHAP-Error");
339 * Done doing EAP proxy stuff.
341 handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
342 eapmschapv2_compose(handler, response);
343 data->code = PW_EAP_MSCHAPV2_SUCCESS;
346 * Delete MPPE keys & encryption policy
348 * FIXME: Use intelligent names...
350 fix_mppe_keys(handler, data);
353 * save any other attributes for re-use in the final
354 * access-accept e.g. vlan, etc. This lets the PEAP
355 * use_tunneled_reply code work
357 data->reply = paircopy(handler->request->reply->vps);
360 * And we need to challenge the user, not ack/reject them,
361 * so we re-write the ACK to a challenge. Yuck.
363 handler->request->reply->code = PW_ACCESS_CHALLENGE;
371 * Authenticate a previously sent challenge.
373 static int mschapv2_authenticate(void *arg, EAP_HANDLER *handler)
376 mschapv2_opaque_t *data;
377 EAP_DS *eap_ds = handler->eap_ds;
378 VALUE_PAIR *challenge, *response, *name;
379 rlm_eap_mschapv2_t *inst = (rlm_eap_mschapv2_t *) arg;
381 rad_assert(handler->request != NULL);
382 rad_assert(handler->stage == AUTHENTICATE);
384 data = (mschapv2_opaque_t *) handler->opaque;
387 * Sanity check the response.
389 if (eap_ds->response->length <= 5) {
390 radlog(L_ERR, "rlm_eap_mschapv2: corrupted data");
394 ccode = eap_ds->response->type.data[0];
396 switch (data->code) {
397 case PW_EAP_MSCHAPV2_FAILURE:
398 if (ccode == PW_EAP_MSCHAPV2_RESPONSE) {
399 DEBUG2(" rlm_eap_mschapv2: authentication re-try from client after we sent a failure");
404 * if we sent error 648 (password expired) to the client
405 * we might get an MSCHAP-CPW packet here; turn it into a
406 * regular MS-CHAP2-CPW packet and pass it to rlm_mschap
407 * (or proxy it, I guess)
409 if (ccode == PW_EAP_MSCHAPV2_CHGPASSWD) {
411 int mschap_id = eap_ds->response->type.data[1];
414 DEBUG2(" rlm_eap_mschapv2: password change packet received");
416 challenge = pairmake("MS-CHAP-Challenge", "0x00", T_OP_EQ);
418 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
421 challenge->length = MSCHAPV2_CHALLENGE_LEN;
422 memcpy(challenge->vp_strvalue, data->challenge, MSCHAPV2_CHALLENGE_LEN);
423 pairadd(&handler->request->packet->vps, challenge);
425 cpw = pairmake("MS-CHAP2-CPW", "", T_OP_EQ);
426 cpw->vp_octets[0] = 7;
427 cpw->vp_octets[1] = mschap_id;
428 memcpy(cpw->vp_octets+2, eap_ds->response->type.data + 520, 66);
430 pairadd(&handler->request->packet->vps, cpw);
433 * break the encoded password into VPs (3 of them)
435 while (copied < 516) {
438 int to_copy = 516 - copied;
442 nt_enc = pairmake("MS-CHAP-NT-Enc-PW", "", T_OP_ADD);
443 nt_enc->vp_octets[0] = 6;
444 nt_enc->vp_octets[1] = mschap_id;
445 nt_enc->vp_octets[2] = 0;
446 nt_enc->vp_octets[3] = seq++;
448 memcpy(nt_enc->vp_octets + 4, eap_ds->response->type.data + 4 + copied, to_copy);
450 nt_enc->length = 4 + to_copy;
451 pairadd(&handler->request->packet->vps, nt_enc);
454 DEBUG2(" rlm_eap_mschapv2: built change password packet");
455 debug_pair_list(handler->request->packet->vps);
458 * jump to "authentication"
464 * we sent a failure and are expecting a failure back
466 if (ccode != PW_EAP_MSCHAPV2_FAILURE) {
467 radlog(L_ERR, "rlm_eap_mschapv2: Sent FAILURE expecting FAILURE but got %d", ccode);
472 handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
473 eap_ds->request->code = PW_EAP_FAILURE;
476 case PW_EAP_MSCHAPV2_SUCCESS:
478 * we sent a success to the client; some clients send a
479 * success back as-per the RFC, some send an ACK. Permit
484 case PW_EAP_MSCHAPV2_SUCCESS:
485 eap_ds->request->code = PW_EAP_SUCCESS;
486 pairadd(&handler->request->reply->vps, data->mppe_keys);
487 data->mppe_keys = NULL;
488 /* fall through... */
490 case PW_EAP_MSCHAPV2_ACK:
493 * It's a success. Don't proxy it.
495 handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
497 pairadd(&handler->request->reply->vps, data->reply);
501 radlog(L_ERR, "rlm_eap_mschapv2: Sent SUCCESS expecting SUCCESS (or ACK) but got %d", ccode);
504 case PW_EAP_MSCHAPV2_CHALLENGE:
505 if (ccode == PW_EAP_MSCHAPV2_FAILURE) goto failure;
508 * we sent a challenge, expecting a response
510 if (ccode != PW_EAP_MSCHAPV2_RESPONSE) {
511 radlog(L_ERR, "rlm_eap_mschapv2: Sent CHALLENGE expecting RESPONSE but got %d", ccode);
514 /* authentication happens below */
519 /* should never happen */
520 radlog(L_ERR, "rlm_eap_mschapv2: unknown state %d", data->code);
526 * Ensure that we have at least enough data
527 * to do the following checks.
529 * EAP header (4), EAP type, MS-CHAP opcode,
530 * MS-CHAP ident, MS-CHAP data length (2),
531 * MS-CHAP value length.
533 if (eap_ds->response->length < (4 + 1 + 1 + 1 + 2 + 1)) {
534 radlog(L_ERR, "rlm_eap_mschapv2: Response is too short");
539 * The 'value_size' is the size of the response,
540 * which is supposed to be the response (48
541 * bytes) plus 1 byte of flags at the end.
543 if (eap_ds->response->type.data[4] != 49) {
544 radlog(L_ERR, "rlm_eap_mschapv2: Response is of incorrect length %d", eap_ds->response->type.data[4]);
549 * The MS-Length field is 5 + value_size + length
550 * of name, which is put after the response.
552 if (((eap_ds->response->type.data[2] << 8) |
553 eap_ds->response->type.data[3]) < (5 + 49)) {
554 radlog(L_ERR, "rlm_eap_mschapv2: Response contains contradictory length %d %d",
555 (eap_ds->response->type.data[2] << 8) |
556 eap_ds->response->type.data[3], 5 + 49);
561 * We now know that the user has sent us a response
562 * to the challenge. Let's try to authenticate it.
564 * We do this by taking the challenge from 'data',
565 * the response from the EAP packet, and creating VALUE_PAIR's
566 * to pass to the 'mschap' module. This is a little wonky,
569 challenge = pairmake("MS-CHAP-Challenge", "0x00", T_OP_EQ);
571 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
574 challenge->length = MSCHAPV2_CHALLENGE_LEN;
575 memcpy(challenge->vp_strvalue, data->challenge, MSCHAPV2_CHALLENGE_LEN);
577 response = pairmake("MS-CHAP2-Response", "0x00", T_OP_EQ);
579 pairfree(&challenge);
580 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
584 response->length = MSCHAPV2_RESPONSE_LEN;
585 memcpy(response->vp_strvalue + 2, &eap_ds->response->type.data[5],
586 MSCHAPV2_RESPONSE_LEN - 2);
587 response->vp_strvalue[0] = eap_ds->response->type.data[1];
588 response->vp_strvalue[1] = eap_ds->response->type.data[5 + MSCHAPV2_RESPONSE_LEN];
590 name = pairmake("NTLM-User-Name", "", T_OP_EQ);
592 pairfree(&challenge);
594 radlog(L_ERR, "rlm_eap_mschapv2: Failed creating NTLM-User-Name: %s", fr_strerror());
599 * MS-Length - MS-Value - 5.
601 name->length = (((eap_ds->response->type.data[2] << 8) |
602 eap_ds->response->type.data[3]) -
603 eap_ds->response->type.data[4] - 5);
604 if (name->length >= sizeof(name->vp_strvalue)) {
605 name->length = sizeof(name->vp_strvalue) - 1;
608 memcpy(name->vp_strvalue,
609 &eap_ds->response->type.data[4 + MSCHAPV2_RESPONSE_LEN],
611 name->vp_strvalue[name->length] = '\0';
614 * Add the pairs to the request, and call the 'mschap'
617 pairadd(&handler->request->packet->vps, challenge);
618 pairadd(&handler->request->packet->vps, response);
619 pairadd(&handler->request->packet->vps, name);
625 * If this options is set, then we do NOT authenticate the
626 * user here. Instead, now that we've added the MS-CHAP
627 * attributes to the request, we STOP, and let the outer
628 * tunnel code handle it.
630 * This means that the outer tunnel code will DELETE the
631 * EAP attributes, and proxy the MS-CHAP attributes to a
634 if (handler->request->options & RAD_REQUEST_OPTION_PROXY_EAP) {
635 char *username = NULL;
636 eap_tunnel_data_t *tunnel;
638 DEBUG2("rlm_eap_mschapv2: cancelling authentication and letting it be proxied");
641 * Set up the callbacks for the tunnel
643 tunnel = rad_malloc(sizeof(*tunnel));
644 memset(tunnel, 0, sizeof(*tunnel));
646 tunnel->tls_session = arg;
647 tunnel->callback = mschap_postproxy;
650 * Associate the callback with the request.
652 rcode = request_data_add(handler->request,
653 handler->request->proxy,
654 REQUEST_DATA_EAP_TUNNEL_CALLBACK,
656 rad_assert(rcode == 0);
659 * The State attribute is NOT supposed to
660 * go into the proxied packet, it will confuse
661 * other RADIUS servers, and they will discard
664 * The PEAP module will take care of adding
665 * the State attribute back, before passing
666 * the handler & request back into the tunnel.
668 pairdelete(&handler->request->packet->vps, PW_STATE, 0, TAG_ANY);
671 * Fix the User-Name when proxying, to strip off
672 * the NT Domain, if we're told to, and a User-Name
673 * exists, and there's a \\, meaning an NT-Domain
674 * in the user name, THEN discard the user name.
676 if (inst->with_ntdomain_hack &&
677 ((challenge = pairfind(handler->request->packet->vps, PW_USER_NAME, 0, TAG_ANY)) != NULL) &&
678 ((username = strchr(challenge->vp_strvalue, '\\')) != NULL)) {
680 * Wipe out the NT domain.
682 * FIXME: Put it into MS-CHAP-Domain?
684 username++; /* skip the \\ */
685 memmove(challenge->vp_strvalue,
687 strlen(username) + 1); /* include \0 */
688 challenge->length = strlen(challenge->vp_strvalue);
692 * Remember that in the post-proxy stage, we've got
693 * to do the work below, AFTER the call to MS-CHAP
701 * This is a wild & crazy hack.
703 rcode = module_authenticate(PW_AUTHTYPE_MS_CHAP, handler->request);
706 * Delete MPPE keys & encryption policy. We don't
709 fix_mppe_keys(handler, data);
712 * Take the response from the mschap module, and
713 * return success or failure, depending on the result.
716 if (rcode == RLM_MODULE_OK) {
717 pairmove2(&response, &handler->request->reply->vps,
718 PW_MSCHAP2_SUCCESS, VENDORPEC_MICROSOFT, TAG_ANY);
719 data->code = PW_EAP_MSCHAPV2_SUCCESS;
721 } else if (inst->send_error) {
722 pairmove2(&response, &handler->request->reply->vps,
723 PW_MSCHAP_ERROR, VENDORPEC_MICROSOFT, TAG_ANY);
728 DEBUG2(" MSCHAP-Error: %s", response->vp_strvalue);
731 * Pxarse the new challenge out of the
732 * MS-CHAP-Error, so that if the client
733 * issues a re-try, we will know which
734 * challenge value that they used.
736 n = sscanf(response->vp_strvalue, "%*cE=%d R=%d C=%32s", &err, &retry, &buf[0]);
738 DEBUG2(" Found new challenge from MS-CHAP-Error: err=%d retry=%d challenge=%s", err, retry, buf);
739 fr_hex2bin(buf, data->challenge, 16);
741 DEBUG2(" Could not parse new challenge from MS-CHAP-Error: %d", n);
744 data->code = PW_EAP_MSCHAPV2_FAILURE;
746 eap_ds->request->code = PW_EAP_FAILURE;
754 radlog(L_ERR, "rlm_eap_mschapv2: No MS-CHAPv2-Success or MS-CHAP-Error was found.");
759 * Compose the response (whatever it is),
760 * and return it to the over-lying EAP module.
762 eapmschapv2_compose(handler, response);
769 * The module name should be the only globally exported symbol.
770 * That is, everything else should be 'static'.
772 EAP_TYPE rlm_eap_mschapv2 = {
774 mschapv2_attach, /* attach */
775 mschapv2_initiate, /* Start the initial request */
776 NULL, /* authorization */
777 mschapv2_authenticate, /* authentication */