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>
26 #include <freeradius-devel/autoconf.h>
31 #include "eap_mschapv2.h"
33 #include <freeradius-devel/rad_assert.h>
35 typedef struct rlm_eap_mschapv2_t {
36 int with_ntdomain_hack;
40 static CONF_PARSER module_config[] = {
41 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
42 offsetof(rlm_eap_mschapv2_t,with_ntdomain_hack), NULL, "no" },
44 { "send_error", PW_TYPE_BOOLEAN,
45 offsetof(rlm_eap_mschapv2_t,send_error), NULL, "no" },
47 { NULL, -1, 0, NULL, NULL } /* end the list */
51 static void fix_mppe_keys(EAP_HANDLER *handler, mschapv2_opaque_t *data)
53 pairmove2(&data->mppe_keys, &handler->request->reply->vps, 7, VENDORPEC_MICROSOFT);
54 pairmove2(&data->mppe_keys, &handler->request->reply->vps, 8, VENDORPEC_MICROSOFT);
55 pairmove2(&data->mppe_keys, &handler->request->reply->vps, 16, VENDORPEC_MICROSOFT);
56 pairmove2(&data->mppe_keys, &handler->request->reply->vps, 17, VENDORPEC_MICROSOFT);
59 static void free_data(void *ptr)
61 mschapv2_opaque_t *data = ptr;
63 pairfree(&data->mppe_keys);
64 pairfree(&data->reply);
71 static int mschapv2_detach(void *arg)
73 rlm_eap_mschapv2_t *inst = (rlm_eap_mschapv2_t *) arg;
84 static int mschapv2_attach(CONF_SECTION *cs, void **instance)
86 rlm_eap_mschapv2_t *inst;
88 inst = malloc(sizeof(*inst));
90 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
93 memset(inst, 0, sizeof(*inst));
96 * Parse the configuration attributes.
98 if (cf_section_parse(cs, inst, module_config) < 0) {
99 mschapv2_detach(inst);
110 * Compose the response.
112 static int eapmschapv2_compose(EAP_HANDLER *handler, VALUE_PAIR *reply)
116 mschapv2_header_t *hdr;
117 EAP_DS *eap_ds = handler->eap_ds;
119 eap_ds->request->code = PW_EAP_REQUEST;
120 eap_ds->request->type.type = PW_EAP_MSCHAPV2;
122 switch (reply->attribute) {
123 case PW_MSCHAP_CHALLENGE:
126 * 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
127 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
128 * | Code | Identifier | Length |
129 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
130 * | Type | OpCode | MS-CHAPv2-ID | MS-Length...
131 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
132 * | MS-Length | Value-Size | Challenge...
133 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
135 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
137 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
139 length = MSCHAPV2_HEADER_LEN + MSCHAPV2_CHALLENGE_LEN + strlen(handler->identity);
140 eap_ds->request->type.data = malloc(length);
142 * Allocate room for the EAP-MS-CHAPv2 data.
144 if (eap_ds->request->type.data == NULL) {
145 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
148 eap_ds->request->type.length = length;
150 ptr = eap_ds->request->type.data;
151 hdr = (mschapv2_header_t *) ptr;
153 hdr->opcode = PW_EAP_MSCHAPV2_CHALLENGE;
154 hdr->mschapv2_id = eap_ds->response->id + 1;
155 length = htons(length);
156 memcpy(hdr->ms_length, &length, sizeof(uint16_t));
157 hdr->value_size = MSCHAPV2_CHALLENGE_LEN;
159 ptr += MSCHAPV2_HEADER_LEN;
162 * Copy the Challenge, success, or error over.
164 memcpy(ptr, reply->vp_strvalue, reply->length);
165 memcpy((ptr + reply->length), handler->identity, strlen(handler->identity));
168 case PW_MSCHAP2_SUCCESS:
171 * 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
172 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
173 * | Code | Identifier | Length |
174 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
175 * | Type | OpCode | MS-CHAPv2-ID | MS-Length...
176 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
177 * | MS-Length | Message...
178 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
180 DEBUG2("MSCHAP Success\n");
182 eap_ds->request->type.data = malloc(length);
184 * Allocate room for the EAP-MS-CHAPv2 data.
186 if (eap_ds->request->type.data == NULL) {
187 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
190 memset(eap_ds->request->type.data, 0, length);
191 eap_ds->request->type.length = length;
193 eap_ds->request->type.data[0] = PW_EAP_MSCHAPV2_SUCCESS;
194 eap_ds->request->type.data[1] = eap_ds->response->id;
195 length = htons(length);
196 memcpy((eap_ds->request->type.data + 2), &length, sizeof(uint16_t));
197 memcpy((eap_ds->request->type.data + 4), reply->vp_strvalue + 1, 42);
200 case PW_MSCHAP_ERROR:
201 DEBUG2("MSCHAP Failure\n");
202 length = 4 + reply->length - 1;
203 eap_ds->request->type.data = malloc(length);
206 * Allocate room for the EAP-MS-CHAPv2 data.
208 if (eap_ds->request->type.data == NULL) {
209 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
212 memset(eap_ds->request->type.data, 0, length);
213 eap_ds->request->type.length = length;
215 eap_ds->request->type.data[0] = PW_EAP_MSCHAPV2_FAILURE;
216 eap_ds->request->type.data[1] = eap_ds->response->id;
217 length = htons(length);
218 memcpy((eap_ds->request->type.data + 2), &length, sizeof(uint16_t));
220 * Copy the entire failure message.
222 memcpy((eap_ds->request->type.data + 4),
223 reply->vp_strvalue + 1, reply->length - 1);
227 radlog(L_ERR, "rlm_eap_mschapv2: Internal sanity check failed");
237 * Initiate the EAP-MSCHAPV2 session by sending a challenge to the peer.
239 static int mschapv2_initiate(void *type_data, EAP_HANDLER *handler)
242 VALUE_PAIR *challenge;
243 mschapv2_opaque_t *data;
245 type_data = type_data; /* -Wunused */
247 challenge = pairmake("MS-CHAP-Challenge", "0x00", T_OP_EQ);
249 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
254 * Get a random challenge.
256 challenge->length = MSCHAPV2_CHALLENGE_LEN;
257 for (i = 0; i < MSCHAPV2_CHALLENGE_LEN; i++) {
258 challenge->vp_strvalue[i] = fr_rand();
260 DEBUG2("rlm_eap_mschapv2: Issuing Challenge");
263 * Keep track of the challenge.
265 data = malloc(sizeof(mschapv2_opaque_t));
266 rad_assert(data != NULL);
269 * We're at the stage where we're challenging the user.
271 data->code = PW_EAP_MSCHAPV2_CHALLENGE;
272 memcpy(data->challenge, challenge->vp_strvalue, MSCHAPV2_CHALLENGE_LEN);
273 data->mppe_keys = NULL;
276 handler->opaque = data;
277 handler->free_opaque = free_data;
280 * Compose the EAP-MSCHAPV2 packet out of the data structure,
283 eapmschapv2_compose(handler, challenge);
284 pairfree(&challenge);
288 * The EAP session doesn't have enough information to
289 * proxy the "inside EAP" protocol. Disable EAP proxying.
291 handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
295 * We don't need to authorize the user at this point.
297 * We also don't need to keep the challenge, as it's
298 * stored in 'handler->eap_ds', which will be given back
301 handler->stage = AUTHENTICATE;
308 * Do post-proxy processing,
312 * Called from rlm_eap.c, eap_postproxy().
314 static int mschap_postproxy(EAP_HANDLER *handler, void *tunnel_data)
316 VALUE_PAIR *response = NULL;
317 mschapv2_opaque_t *data;
319 data = (mschapv2_opaque_t *) handler->opaque;
320 rad_assert(data != NULL);
322 tunnel_data = tunnel_data; /* -Wunused */
324 DEBUG2(" rlm_eap_mschapv2: Passing reply from proxy back into the tunnel %p %d.",
325 handler->request, handler->request->reply->code);
328 * There is only a limited number of possibilities.
330 switch (handler->request->reply->code) {
331 case PW_AUTHENTICATION_ACK:
332 DEBUG(" rlm_eap_mschapv2: Proxied authentication succeeded.");
334 * Move the attribute, so it doesn't go into
338 &handler->request->reply->vps,
339 PW_MSCHAP2_SUCCESS, VENDORPEC_MICROSOFT);
343 case PW_AUTHENTICATION_REJECT:
344 DEBUG(" rlm_eap_mschapv2: Proxied authentication did not succeed.");
352 radlog(L_ERR, "rlm_eap_mschapv2: Proxied reply contained no MS-CHAPv2-Success or MS-CHAP-Error");
357 * Done doing EAP proxy stuff.
359 handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
360 eapmschapv2_compose(handler, response);
361 data->code = PW_EAP_MSCHAPV2_SUCCESS;
364 * Delete MPPE keys & encryption policy
366 * FIXME: Use intelligent names...
368 fix_mppe_keys(handler, data);
371 * save any other attributes for re-use in the final
372 * access-accept e.g. vlan, etc. This lets the PEAP
373 * use_tunneled_reply code work
375 data->reply = paircopy(handler->request->reply->vps);
378 * And we need to challenge the user, not ack/reject them,
379 * so we re-write the ACK to a challenge. Yuck.
381 handler->request->reply->code = PW_ACCESS_CHALLENGE;
389 * Authenticate a previously sent challenge.
391 static int mschapv2_authenticate(void *arg, EAP_HANDLER *handler)
394 mschapv2_opaque_t *data;
395 EAP_DS *eap_ds = handler->eap_ds;
396 VALUE_PAIR *challenge, *response, *name;
397 rlm_eap_mschapv2_t *inst = (rlm_eap_mschapv2_t *) arg;
399 rad_assert(handler->request != NULL);
400 rad_assert(handler->stage == AUTHENTICATE);
402 data = (mschapv2_opaque_t *) handler->opaque;
405 * Sanity check the response.
407 if (eap_ds->response->length <= 5) {
408 radlog(L_ERR, "rlm_eap_mschapv2: corrupted data");
412 ccode = eap_ds->response->type.data[0];
414 switch (data->code) {
415 case PW_EAP_MSCHAPV2_FAILURE:
416 if (ccode == PW_EAP_MSCHAPV2_RESPONSE) {
417 DEBUG2(" rlm_eap_mschapv2: authentication re-try from client after we sent a failure");
422 * if we sent error 648 (password expired) to the client
423 * we might get an MSCHAP-CPW packet here; turn it into a
424 * regular MS-CHAP2-CPW packet and pass it to rlm_mschap
425 * (or proxy it, I guess)
427 if (ccode == PW_EAP_MSCHAPV2_CHGPASSWD) {
429 int mschap_id = eap_ds->response->type.data[1];
432 DEBUG2(" rlm_eap_mschapv2: password change packet received");
434 challenge = pairmake("MS-CHAP-Challenge", "0x00", T_OP_EQ);
436 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
439 challenge->length = MSCHAPV2_CHALLENGE_LEN;
440 memcpy(challenge->vp_strvalue, data->challenge, MSCHAPV2_CHALLENGE_LEN);
441 pairadd(&handler->request->packet->vps, challenge);
443 cpw = pairmake("MS-CHAP2-CPW", "", T_OP_EQ);
444 cpw->vp_octets[0] = 7;
445 cpw->vp_octets[1] = mschap_id;
446 memcpy(cpw->vp_octets+2, eap_ds->response->type.data + 520, 66);
448 pairadd(&handler->request->packet->vps, cpw);
451 * break the encoded password into VPs (3 of them)
453 while (copied < 516) {
456 int to_copy = 516 - copied;
460 nt_enc = pairmake("MS-CHAP-NT-Enc-PW", "", T_OP_ADD);
461 nt_enc->vp_octets[0] = 6;
462 nt_enc->vp_octets[1] = mschap_id;
463 nt_enc->vp_octets[2] = 0;
464 nt_enc->vp_octets[3] = seq++;
466 memcpy(nt_enc->vp_octets + 4, eap_ds->response->type.data + 4 + copied, to_copy);
468 nt_enc->length = 4 + to_copy;
469 pairadd(&handler->request->packet->vps, nt_enc);
472 DEBUG2(" rlm_eap_mschapv2: built change password packet");
473 debug_pair_list(handler->request->packet->vps);
476 * jump to "authentication"
484 * we sent a failure and are expecting a failure back
486 if (ccode != PW_EAP_MSCHAPV2_FAILURE) {
487 radlog(L_ERR, "rlm_eap_mschapv2: Sent FAILURE expecting FAILURE but got %d", ccode);
491 handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
492 eap_ds->request->code = PW_EAP_FAILURE;
495 case PW_EAP_MSCHAPV2_SUCCESS:
497 * we sent a success to the client; some clients send a
498 * success back as-per the RFC, some send an ACK. Permit
503 case PW_EAP_MSCHAPV2_SUCCESS:
504 eap_ds->request->code = PW_EAP_SUCCESS;
505 pairadd(&handler->request->reply->vps, data->mppe_keys);
506 data->mppe_keys = NULL;
507 /* fall through... */
509 case PW_EAP_MSCHAPV2_ACK:
512 * It's a success. Don't proxy it.
514 handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
516 pairadd(&handler->request->reply->vps, data->reply);
520 radlog(L_ERR, "rlm_eap_mschapv2: Sent SUCCESS expecting SUCCESS (or ACK) but got %d", ccode);
523 case PW_EAP_MSCHAPV2_CHALLENGE:
525 * we sent a challenge, expecting a response
527 if (ccode != PW_EAP_MSCHAPV2_RESPONSE) {
528 radlog(L_ERR, "rlm_eap_mschapv2: Sent CHALLENGE expecting RESPONSE but got %d", ccode);
531 /* authentication happens below */
536 /* should never happen */
537 radlog(L_ERR, "rlm_eap_mschapv2: unknown state %d", data->code);
543 * Ensure that we have at least enough data
544 * to do the following checks.
546 * EAP header (4), EAP type, MS-CHAP opcode,
547 * MS-CHAP ident, MS-CHAP data length (2),
548 * MS-CHAP value length.
550 if (eap_ds->response->length < (4 + 1 + 1 + 1 + 2 + 1)) {
551 radlog(L_ERR, "rlm_eap_mschapv2: Response is too short");
556 * The 'value_size' is the size of the response,
557 * which is supposed to be the response (48
558 * bytes) plus 1 byte of flags at the end.
560 if (eap_ds->response->type.data[4] != 49) {
561 radlog(L_ERR, "rlm_eap_mschapv2: Response is of incorrect length %d", eap_ds->response->type.data[4]);
566 * The MS-Length field is 5 + value_size + length
567 * of name, which is put after the response.
569 if (((eap_ds->response->type.data[2] << 8) |
570 eap_ds->response->type.data[3]) < (5 + 49)) {
571 radlog(L_ERR, "rlm_eap_mschapv2: Response contains contradictory length %d %d",
572 (eap_ds->response->type.data[2] << 8) |
573 eap_ds->response->type.data[3], 5 + 49);
578 * We now know that the user has sent us a response
579 * to the challenge. Let's try to authenticate it.
581 * We do this by taking the challenge from 'data',
582 * the response from the EAP packet, and creating VALUE_PAIR's
583 * to pass to the 'mschap' module. This is a little wonky,
586 challenge = pairmake("MS-CHAP-Challenge", "0x00", T_OP_EQ);
588 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
591 challenge->length = MSCHAPV2_CHALLENGE_LEN;
592 memcpy(challenge->vp_strvalue, data->challenge, MSCHAPV2_CHALLENGE_LEN);
594 response = pairmake("MS-CHAP2-Response", "0x00", T_OP_EQ);
596 pairfree(&challenge);
597 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
601 response->length = MSCHAPV2_RESPONSE_LEN;
602 memcpy(response->vp_strvalue + 2, &eap_ds->response->type.data[5],
603 MSCHAPV2_RESPONSE_LEN - 2);
604 response->vp_strvalue[0] = eap_ds->response->type.data[1];
605 response->vp_strvalue[1] = eap_ds->response->type.data[5 + MSCHAPV2_RESPONSE_LEN];
607 name = pairmake("NTLM-User-Name", "", T_OP_EQ);
609 pairfree(&challenge);
611 radlog(L_ERR, "rlm_eap_mschapv2: Failed creating NTLM-User-Name: %s", fr_strerror());
616 * MS-Length - MS-Value - 5.
618 name->length = (((eap_ds->response->type.data[2] << 8) |
619 eap_ds->response->type.data[3]) -
620 eap_ds->response->type.data[4] - 5);
621 if (name->length >= sizeof(name->vp_strvalue)) {
622 name->length = sizeof(name->vp_strvalue) - 1;
625 memcpy(name->vp_strvalue,
626 &eap_ds->response->type.data[4 + MSCHAPV2_RESPONSE_LEN],
628 name->vp_strvalue[name->length] = '\0';
631 * Add the pairs to the request, and call the 'mschap'
634 pairadd(&handler->request->packet->vps, challenge);
635 pairadd(&handler->request->packet->vps, response);
636 pairadd(&handler->request->packet->vps, name);
642 * If this options is set, then we do NOT authenticate the
643 * user here. Instead, now that we've added the MS-CHAP
644 * attributes to the request, we STOP, and let the outer
645 * tunnel code handle it.
647 * This means that the outer tunnel code will DELETE the
648 * EAP attributes, and proxy the MS-CHAP attributes to a
651 if (handler->request->options & RAD_REQUEST_OPTION_PROXY_EAP) {
652 char *username = NULL;
653 eap_tunnel_data_t *tunnel;
655 DEBUG2("rlm_eap_mschapv2: cancelling authentication and letting it be proxied");
658 * Set up the callbacks for the tunnel
660 tunnel = rad_malloc(sizeof(*tunnel));
661 memset(tunnel, 0, sizeof(*tunnel));
663 tunnel->tls_session = arg;
664 tunnel->callback = mschap_postproxy;
667 * Associate the callback with the request.
669 rcode = request_data_add(handler->request,
670 handler->request->proxy,
671 REQUEST_DATA_EAP_TUNNEL_CALLBACK,
673 rad_assert(rcode == 0);
676 * The State attribute is NOT supposed to
677 * go into the proxied packet, it will confuse
678 * other RADIUS servers, and they will discard
681 * The PEAP module will take care of adding
682 * the State attribute back, before passing
683 * the handler & request back into the tunnel.
685 pairdelete(&handler->request->packet->vps, PW_STATE, 0);
688 * Fix the User-Name when proxying, to strip off
689 * the NT Domain, if we're told to, and a User-Name
690 * exists, and there's a \\, meaning an NT-Domain
691 * in the user name, THEN discard the user name.
693 if (inst->with_ntdomain_hack &&
694 ((challenge = pairfind(handler->request->packet->vps,
695 PW_USER_NAME, 0)) != NULL) &&
696 ((username = strchr(challenge->vp_strvalue, '\\')) != NULL)) {
698 * Wipe out the NT domain.
700 * FIXME: Put it into MS-CHAP-Domain?
702 username++; /* skip the \\ */
703 memmove(challenge->vp_strvalue,
705 strlen(username) + 1); /* include \0 */
706 challenge->length = strlen(challenge->vp_strvalue);
710 * Remember that in the post-proxy stage, we've got
711 * to do the work below, AFTER the call to MS-CHAP
719 * This is a wild & crazy hack.
721 rcode = module_authenticate(PW_AUTHTYPE_MS_CHAP, handler->request);
724 * Delete MPPE keys & encryption policy. We don't
727 fix_mppe_keys(handler, data);
730 * Take the response from the mschap module, and
731 * return success or failure, depending on the result.
734 if (rcode == RLM_MODULE_OK) {
735 pairmove2(&response, &handler->request->reply->vps,
736 PW_MSCHAP2_SUCCESS, VENDORPEC_MICROSOFT);
737 data->code = PW_EAP_MSCHAPV2_SUCCESS;
739 } else if (inst->send_error) {
740 pairmove2(&response, &handler->request->reply->vps,
746 DEBUG2(" MSCHAP-Error: %s", response->vp_strvalue);
749 * Pxarse the new challenge out of the
750 * MS-CHAP-Error, so that if the client
751 * issues a re-try, we will know which
752 * challenge value that they used.
754 n = sscanf(response->vp_strvalue, "%*cE=%d R=%d C=%32s", &err, &retry, &buf);
756 DEBUG2(" Found new challenge from MS-CHAP-Error: err=%d retry=%d challenge=%s", err, retry, buf);
757 fr_hex2bin(buf, data->challenge, 16);
759 DEBUG2(" Could not parse new challenge from MS-CHAP-Error: %d", n);
762 data->code = PW_EAP_MSCHAPV2_FAILURE;
764 eap_ds->request->code = PW_EAP_FAILURE;
772 radlog(L_ERR, "rlm_eap_mschapv2: No MS-CHAPv2-Success or MS-CHAP-Error was found.");
777 * Compose the response (whatever it is),
778 * and return it to the over-lying EAP module.
780 eapmschapv2_compose(handler, response);
787 * The module name should be the only globally exported symbol.
788 * That is, everything else should be 'static'.
790 EAP_TYPE rlm_eap_mschapv2 = {
792 mschapv2_attach, /* attach */
793 mschapv2_initiate, /* Start the initial request */
794 NULL, /* authorization */
795 mschapv2_authenticate, /* authentication */
796 mschapv2_detach /* detach */