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
28 #include "eap_mschapv2.h"
30 #include <freeradius-devel/rad_assert.h>
32 typedef struct rlm_eap_mschapv2_t {
33 bool with_ntdomain_hack;
37 static CONF_PARSER module_config[] = {
38 { "with_ntdomain_hack", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_eap_mschapv2_t, with_ntdomain_hack), "no" },
40 { "send_error", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_eap_mschapv2_t, send_error), "no" },
42 { NULL, -1, 0, NULL, NULL } /* end the list */
46 static void fix_mppe_keys(eap_handler_t *handler, mschapv2_opaque_t *data)
48 pairfilter(data, &data->mppe_keys, &handler->request->reply->vps, 7, VENDORPEC_MICROSOFT, TAG_ANY);
49 pairfilter(data, &data->mppe_keys, &handler->request->reply->vps, 8, VENDORPEC_MICROSOFT, TAG_ANY);
50 pairfilter(data, &data->mppe_keys, &handler->request->reply->vps, 16, VENDORPEC_MICROSOFT, TAG_ANY);
51 pairfilter(data, &data->mppe_keys, &handler->request->reply->vps, 17, VENDORPEC_MICROSOFT, TAG_ANY);
57 static int mschapv2_attach(CONF_SECTION *cs, void **instance)
59 rlm_eap_mschapv2_t *inst;
61 *instance = inst = talloc_zero(cs, rlm_eap_mschapv2_t);
65 * Parse the configuration attributes.
67 if (cf_section_parse(cs, inst, module_config) < 0) {
76 * Compose the response.
78 static int eapmschapv2_compose(eap_handler_t *handler, VALUE_PAIR *reply)
82 mschapv2_header_t *hdr;
83 EAP_DS *eap_ds = handler->eap_ds;
85 eap_ds->request->code = PW_EAP_REQUEST;
86 eap_ds->request->type.num = PW_EAP_MSCHAPV2;
89 * Always called with vendor Microsoft
91 switch (reply->da->attr) {
92 case PW_MSCHAP_CHALLENGE:
95 * 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
96 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
97 * | Code | Identifier | Length |
98 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
99 * | Type | OpCode | MS-CHAPv2-ID | MS-Length...
100 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
101 * | MS-Length | Value-Size | Challenge...
102 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
104 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
106 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
108 length = MSCHAPV2_HEADER_LEN + MSCHAPV2_CHALLENGE_LEN + strlen(handler->identity);
109 eap_ds->request->type.data = talloc_array(eap_ds->request, uint8_t, length);
111 * Allocate room for the EAP-MS-CHAPv2 data.
113 if (!eap_ds->request->type.data) {
116 eap_ds->request->type.length = length;
118 ptr = eap_ds->request->type.data;
119 hdr = (mschapv2_header_t *) ptr;
121 hdr->opcode = PW_EAP_MSCHAPV2_CHALLENGE;
122 hdr->mschapv2_id = eap_ds->response->id + 1;
123 length = htons(length);
124 memcpy(hdr->ms_length, &length, sizeof(uint16_t));
125 hdr->value_size = MSCHAPV2_CHALLENGE_LEN;
127 ptr += MSCHAPV2_HEADER_LEN;
130 * Copy the Challenge, success, or error over.
132 memcpy(ptr, reply->vp_octets, reply->vp_length);
133 memcpy((ptr + reply->vp_length), handler->identity, strlen(handler->identity));
136 case PW_MSCHAP2_SUCCESS:
139 * 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
140 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
141 * | Code | Identifier | Length |
142 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
143 * | Type | OpCode | MS-CHAPv2-ID | MS-Length...
144 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
145 * | MS-Length | Message...
146 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
148 DEBUG2("MSCHAP Success\n");
150 eap_ds->request->type.data = talloc_array(eap_ds->request,
153 * Allocate room for the EAP-MS-CHAPv2 data.
155 if (!eap_ds->request->type.data) {
158 memset(eap_ds->request->type.data, 0, length);
159 eap_ds->request->type.length = length;
161 eap_ds->request->type.data[0] = PW_EAP_MSCHAPV2_SUCCESS;
162 eap_ds->request->type.data[1] = eap_ds->response->id;
163 length = htons(length);
164 memcpy((eap_ds->request->type.data + 2), &length, sizeof(uint16_t));
165 memcpy((eap_ds->request->type.data + 4), reply->vp_strvalue + 1, 42);
168 case PW_MSCHAP_ERROR:
169 DEBUG2("MSCHAP Failure\n");
170 length = 4 + reply->vp_length - 1;
171 eap_ds->request->type.data = talloc_array(eap_ds->request, uint8_t, length);
174 * Allocate room for the EAP-MS-CHAPv2 data.
176 if (!eap_ds->request->type.data) {
179 memset(eap_ds->request->type.data, 0, length);
180 eap_ds->request->type.length = length;
182 eap_ds->request->type.data[0] = PW_EAP_MSCHAPV2_FAILURE;
183 eap_ds->request->type.data[1] = eap_ds->response->id;
184 length = htons(length);
185 memcpy((eap_ds->request->type.data + 2), &length, sizeof(uint16_t));
187 * Copy the entire failure message.
189 memcpy((eap_ds->request->type.data + 4),
190 reply->vp_strvalue + 1, reply->vp_length - 1);
194 ERROR("rlm_eap_mschapv2: Internal sanity check failed");
203 * Initiate the EAP-MSCHAPV2 session by sending a challenge to the peer.
205 static int mschapv2_initiate(UNUSED void *instance, eap_handler_t *handler)
208 VALUE_PAIR *challenge;
209 mschapv2_opaque_t *data;
210 REQUEST *request = handler->request;
213 challenge = pairmake(handler, NULL,
214 "MS-CHAP-Challenge", NULL, T_OP_EQ);
217 * Get a random challenge.
219 challenge->vp_length = MSCHAPV2_CHALLENGE_LEN;
220 challenge->vp_octets = p = talloc_array(challenge, uint8_t, challenge->vp_length);
221 for (i = 0; i < MSCHAPV2_CHALLENGE_LEN; i++) {
224 RDEBUG2("Issuing Challenge");
227 * Keep track of the challenge.
229 data = talloc_zero(handler, mschapv2_opaque_t);
230 rad_assert(data != NULL);
233 * We're at the stage where we're challenging the user.
235 data->code = PW_EAP_MSCHAPV2_CHALLENGE;
236 memcpy(data->challenge, challenge->vp_octets, MSCHAPV2_CHALLENGE_LEN);
237 data->mppe_keys = NULL;
240 handler->opaque = data;
243 * Compose the EAP-MSCHAPV2 packet out of the data structure,
246 eapmschapv2_compose(handler, challenge);
247 pairfree(&challenge);
251 * The EAP session doesn't have enough information to
252 * proxy the "inside EAP" protocol. Disable EAP proxying.
254 handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
258 * We don't need to authorize the user at this point.
260 * We also don't need to keep the challenge, as it's
261 * stored in 'handler->eap_ds', which will be given back
264 handler->stage = AUTHENTICATE;
271 * Do post-proxy processing,
275 * Called from rlm_eap.c, eap_postproxy().
277 static int CC_HINT(nonnull) mschap_postproxy(eap_handler_t *handler, UNUSED void *tunnel_data)
279 VALUE_PAIR *response = NULL;
280 mschapv2_opaque_t *data;
281 REQUEST *request = handler->request;
283 data = (mschapv2_opaque_t *) handler->opaque;
284 rad_assert(request != NULL);
286 RDEBUG2("Passing reply from proxy back into the tunnel %d.",
287 request->reply->code);
290 * There is only a limited number of possibilities.
292 switch (request->reply->code) {
293 case PW_CODE_ACCESS_ACCEPT:
294 RDEBUG2("Proxied authentication succeeded");
297 * Move the attribute, so it doesn't go into
300 pairfilter(data, &response, &request->reply->vps, PW_MSCHAP2_SUCCESS, VENDORPEC_MICROSOFT, TAG_ANY);
304 case PW_CODE_ACCESS_REJECT:
305 RDEBUG("Proxied authentication did not succeed");
313 REDEBUG("Proxied reply contained no MS-CHAP2-Success or MS-CHAP-Error");
318 * Done doing EAP proxy stuff.
320 request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
321 eapmschapv2_compose(handler, response);
322 data->code = PW_EAP_MSCHAPV2_SUCCESS;
325 * Delete MPPE keys & encryption policy
327 * FIXME: Use intelligent names...
329 fix_mppe_keys(handler, data);
332 * save any other attributes for re-use in the final
333 * access-accept e.g. vlan, etc. This lets the PEAP
334 * use_tunneled_reply code work
336 data->reply = paircopy(data, request->reply->vps);
339 * And we need to challenge the user, not ack/reject them,
340 * so we re-write the ACK to a challenge. Yuck.
342 request->reply->code = PW_CODE_ACCESS_CHALLENGE;
350 * Authenticate a previously sent challenge.
352 static int CC_HINT(nonnull) mschapv2_authenticate(void *arg, eap_handler_t *handler)
358 mschapv2_opaque_t *data;
359 EAP_DS *eap_ds = handler->eap_ds;
360 VALUE_PAIR *challenge, *response, *name;
361 rlm_eap_mschapv2_t *inst = (rlm_eap_mschapv2_t *) arg;
362 REQUEST *request = handler->request;
364 rad_assert(handler->stage == AUTHENTICATE);
366 data = (mschapv2_opaque_t *) handler->opaque;
369 * Sanity check the response.
371 if (eap_ds->response->length <= 5) {
372 REDEBUG("corrupted data");
376 ccode = eap_ds->response->type.data[0];
378 switch (data->code) {
379 case PW_EAP_MSCHAPV2_FAILURE:
380 if (ccode == PW_EAP_MSCHAPV2_RESPONSE) {
381 RDEBUG2("authentication re-try from client after we sent a failure");
386 * if we sent error 648 (password expired) to the client
387 * we might get an MSCHAP-CPW packet here; turn it into a
388 * regular MS-CHAP2-CPW packet and pass it to rlm_mschap
389 * (or proxy it, I guess)
391 if (ccode == PW_EAP_MSCHAPV2_CHGPASSWD) {
393 int mschap_id = eap_ds->response->type.data[1];
396 RDEBUG2("password change packet received");
398 challenge = pairmake_packet("MS-CHAP-Challenge", NULL, T_OP_EQ);
402 pairmemcpy(challenge, data->challenge, MSCHAPV2_CHALLENGE_LEN);
404 cpw = pairmake_packet("MS-CHAP2-CPW", NULL, T_OP_EQ);
407 cpw->vp_octets = p = talloc_array(cpw, uint8_t, cpw->vp_length);
410 memcpy(p + 2, eap_ds->response->type.data + 520, 66);
413 * break the encoded password into VPs (3 of them)
415 while (copied < 516) {
418 int to_copy = 516 - copied;
422 nt_enc = pairmake_packet("MS-CHAP-NT-Enc-PW", NULL, T_OP_ADD);
423 nt_enc->vp_length = 4 + to_copy;
425 nt_enc->vp_octets = p = talloc_array(nt_enc, uint8_t, nt_enc->vp_length);
432 memcpy(p + 4, eap_ds->response->type.data + 4 + copied, to_copy);
436 RDEBUG2("Built change password packet");
437 rdebug_pair_list(L_DBG_LVL_2, request, request->packet->vps, NULL);
440 * jump to "authentication"
446 * we sent a failure and are expecting a failure back
448 if (ccode != PW_EAP_MSCHAPV2_FAILURE) {
449 REDEBUG("Sent FAILURE expecting FAILURE but got %d", ccode);
454 request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
455 eap_ds->request->code = PW_EAP_FAILURE;
458 case PW_EAP_MSCHAPV2_SUCCESS:
460 * we sent a success to the client; some clients send a
461 * success back as-per the RFC, some send an ACK. Permit
466 case PW_EAP_MSCHAPV2_SUCCESS:
467 eap_ds->request->code = PW_EAP_SUCCESS;
469 pairfilter(request->reply,
470 &request->reply->vps,
471 &data->mppe_keys, 0, 0, TAG_ANY);
474 case PW_EAP_MSCHAPV2_ACK:
477 * It's a success. Don't proxy it.
479 request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
481 pairfilter(request->reply,
482 &request->reply->vps,
483 &data->reply, 0, 0, TAG_ANY);
486 REDEBUG("Sent SUCCESS expecting SUCCESS (or ACK) but got %d", ccode);
489 case PW_EAP_MSCHAPV2_CHALLENGE:
490 if (ccode == PW_EAP_MSCHAPV2_FAILURE) goto failure;
493 * we sent a challenge, expecting a response
495 if (ccode != PW_EAP_MSCHAPV2_RESPONSE) {
496 REDEBUG("Sent CHALLENGE expecting RESPONSE but got %d", ccode);
499 /* authentication happens below */
503 /* should never happen */
504 REDEBUG("unknown state %d", data->code);
510 * Ensure that we have at least enough data
511 * to do the following checks.
513 * EAP header (4), EAP type, MS-CHAP opcode,
514 * MS-CHAP ident, MS-CHAP data length (2),
515 * MS-CHAP value length.
517 if (eap_ds->response->length < (4 + 1 + 1 + 1 + 2 + 1)) {
518 REDEBUG("Response is too short");
523 * The 'value_size' is the size of the response,
524 * which is supposed to be the response (48
525 * bytes) plus 1 byte of flags at the end.
527 if (eap_ds->response->type.data[4] != 49) {
528 REDEBUG("Response is of incorrect length %d", eap_ds->response->type.data[4]);
533 * The MS-Length field is 5 + value_size + length
534 * of name, which is put after the response.
536 length = (eap_ds->response->type.data[2] << 8) | eap_ds->response->type.data[3];
537 if ((length < (5 + 49)) || (length > (256 + 5 + 49))) {
538 REDEBUG("Response contains contradictory length %zu %d",
544 * We now know that the user has sent us a response
545 * to the challenge. Let's try to authenticate it.
547 * We do this by taking the challenge from 'data',
548 * the response from the EAP packet, and creating VALUE_PAIR's
549 * to pass to the 'mschap' module. This is a little wonky,
552 challenge = pairmake_packet("MS-CHAP-Challenge", NULL, T_OP_EQ);
556 pairmemcpy(challenge, data->challenge, MSCHAPV2_CHALLENGE_LEN);
558 response = pairmake_packet("MS-CHAP2-Response", NULL, T_OP_EQ);
562 response->vp_length = MSCHAPV2_RESPONSE_LEN;
563 response->vp_octets = p = talloc_array(response, uint8_t, response->vp_length);
565 p[0] = eap_ds->response->type.data[1];
566 p[1] = eap_ds->response->type.data[5 + MSCHAPV2_RESPONSE_LEN];
567 memcpy(p + 2, &eap_ds->response->type.data[5], MSCHAPV2_RESPONSE_LEN - 2);
569 name = pairmake_packet("MS-CHAP-User-Name", NULL, T_OP_EQ);
575 * MS-Length - MS-Value - 5.
577 name->vp_length = length - 49 - 5;
578 name->vp_strvalue = q = talloc_array(name, char, name->vp_length + 1);
580 &eap_ds->response->type.data[4 + MSCHAPV2_RESPONSE_LEN],
582 q[name->vp_length] = '\0';
588 * If this options is set, then we do NOT authenticate the
589 * user here. Instead, now that we've added the MS-CHAP
590 * attributes to the request, we STOP, and let the outer
591 * tunnel code handle it.
593 * This means that the outer tunnel code will DELETE the
594 * EAP attributes, and proxy the MS-CHAP attributes to a
597 if (request->options & RAD_REQUEST_OPTION_PROXY_EAP) {
598 char *username = NULL;
599 eap_tunnel_data_t *tunnel;
601 RDEBUG2("cancelling authentication and letting it be proxied");
604 * Set up the callbacks for the tunnel
606 tunnel = talloc_zero(request, eap_tunnel_data_t);
608 tunnel->tls_session = arg;
609 tunnel->callback = mschap_postproxy;
612 * Associate the callback with the request.
614 rcode = request_data_add(request,
616 REQUEST_DATA_EAP_TUNNEL_CALLBACK,
618 rad_assert(rcode == 0);
621 * The State attribute is NOT supposed to
622 * go into the proxied packet, it will confuse
623 * other RADIUS servers, and they will discard
626 * The PEAP module will take care of adding
627 * the State attribute back, before passing
628 * the handler & request back into the tunnel.
630 pairdelete(&request->packet->vps, PW_STATE, 0, TAG_ANY);
633 * Fix the User-Name when proxying, to strip off
634 * the NT Domain, if we're told to, and a User-Name
635 * exists, and there's a \\, meaning an NT-Domain
636 * in the user name, THEN discard the user name.
638 if (inst->with_ntdomain_hack &&
639 ((challenge = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY)) != NULL) &&
640 ((username = strchr(challenge->vp_strvalue, '\\')) != NULL)) {
642 * Wipe out the NT domain.
644 * FIXME: Put it into MS-CHAP-Domain?
646 username++; /* skip the \\ */
647 pairstrcpy(challenge, username);
651 * Remember that in the post-proxy stage, we've got
652 * to do the work below, AFTER the call to MS-CHAP
660 * This is a wild & crazy hack.
662 rcode = process_authenticate(PW_AUTHTYPE_MS_CHAP, request);
665 * Delete MPPE keys & encryption policy. We don't
668 fix_mppe_keys(handler, data);
671 * Take the response from the mschap module, and
672 * return success or failure, depending on the result.
675 if (rcode == RLM_MODULE_OK) {
676 pairfilter(data, &response, &request->reply->vps, PW_MSCHAP2_SUCCESS, VENDORPEC_MICROSOFT, TAG_ANY);
677 data->code = PW_EAP_MSCHAPV2_SUCCESS;
678 } else if (inst->send_error) {
679 pairfilter(data, &response, &request->reply->vps, PW_MSCHAP_ERROR, VENDORPEC_MICROSOFT, TAG_ANY);
686 RDEBUG2("MSCHAP-Error: %s", response->vp_strvalue);
689 * Pxarse the new challenge out of the
690 * MS-CHAP-Error, so that if the client
691 * issues a re-try, we will know which
692 * challenge value that they used.
694 n = sscanf(response->vp_strvalue, "%*cE=%d R=%d C=%32s", &err, &retry, &buf[0]);
696 DEBUG2("Found new challenge from MS-CHAP-Error: err=%d retry=%d challenge=%s", err, retry, buf);
697 fr_hex2bin(data->challenge, 16, buf, strlen(buf));
699 DEBUG2("Could not parse new challenge from MS-CHAP-Error: %d", n);
702 data->code = PW_EAP_MSCHAPV2_FAILURE;
704 eap_ds->request->code = PW_EAP_FAILURE;
712 REDEBUG("No MS-CHAP2-Success or MS-CHAP-Error was found");
717 * Compose the response (whatever it is),
718 * and return it to the over-lying EAP module.
720 eapmschapv2_compose(handler, response);
727 * The module name should be the only globally exported symbol.
728 * That is, everything else should be 'static'.
730 extern rlm_eap_module_t rlm_eap_mschapv2;
731 rlm_eap_module_t rlm_eap_mschapv2 = {
733 mschapv2_attach, /* attach */
734 mschapv2_initiate, /* Start the initial request */
735 NULL, /* authorization */
736 mschapv2_authenticate, /* authentication */