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;
39 static CONF_PARSER module_config[] = {
40 { "with_ntdomain_hack", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_eap_mschapv2_t, with_ntdomain_hack), "no" },
42 { "send_error", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_eap_mschapv2_t, send_error), "no" },
43 { "identity", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_eap_mschapv2_t, identity), NULL },
44 CONF_PARSER_TERMINATOR
48 static void fix_mppe_keys(eap_handler_t *handler, mschapv2_opaque_t *data)
50 fr_pair_list_mcopy_by_num(data, &data->mppe_keys, &handler->request->reply->vps, 7, VENDORPEC_MICROSOFT, TAG_ANY);
51 fr_pair_list_mcopy_by_num(data, &data->mppe_keys, &handler->request->reply->vps, 8, VENDORPEC_MICROSOFT, TAG_ANY);
52 fr_pair_list_mcopy_by_num(data, &data->mppe_keys, &handler->request->reply->vps, 16, VENDORPEC_MICROSOFT, TAG_ANY);
53 fr_pair_list_mcopy_by_num(data, &data->mppe_keys, &handler->request->reply->vps, 17, VENDORPEC_MICROSOFT, TAG_ANY);
59 static int mod_instantiate(CONF_SECTION *cs, void **instance)
61 rlm_eap_mschapv2_t *inst;
64 *instance = inst = talloc_zero(cs, rlm_eap_mschapv2_t);
68 * Parse the configuration attributes.
70 if (cf_section_parse(cs, inst, module_config) < 0) {
74 if (inst->identity && (strlen(inst->identity) > 255)) {
75 cf_log_err_cs(cs, "identity is too long");
79 if (!inst->identity) {
80 inst->identity = talloc_asprintf(inst, "freeradius-%s", RADIUSD_VERSION_STRING);
83 dv = dict_valbyname(PW_AUTH_TYPE, 0, "MSCHAP");
84 if (!dv) dv = dict_valbyname(PW_AUTH_TYPE, 0, "MS-CHAP");
86 cf_log_err_cs(cs, "Failed to find 'Auth-Type MS-CHAP' section. Cannot authenticate users.");
89 inst->auth_type_mschap = dv->value;
96 * Compose the response.
98 static int eapmschapv2_compose(rlm_eap_mschapv2_t *inst, eap_handler_t *handler, VALUE_PAIR *reply)
102 mschapv2_header_t *hdr;
103 EAP_DS *eap_ds = handler->eap_ds;
104 REQUEST *request = handler->request;
106 eap_ds->request->code = PW_EAP_REQUEST;
107 eap_ds->request->type.num = PW_EAP_MSCHAPV2;
110 * Always called with vendor Microsoft
112 switch (reply->da->attr) {
113 case PW_MSCHAP_CHALLENGE:
116 * 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
117 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
118 * | Code | Identifier | Length |
119 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
120 * | Type | OpCode | MS-CHAPv2-ID | MS-Length...
121 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
122 * | MS-Length | Value-Size | Challenge...
123 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
125 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
127 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
129 length = MSCHAPV2_HEADER_LEN + MSCHAPV2_CHALLENGE_LEN + strlen(inst->identity);
130 eap_ds->request->type.data = talloc_array(eap_ds->request, uint8_t, length);
133 * Allocate room for the EAP-MS-CHAPv2 data.
135 if (!eap_ds->request->type.data) {
138 eap_ds->request->type.length = length;
140 ptr = eap_ds->request->type.data;
141 hdr = (mschapv2_header_t *) ptr;
143 hdr->opcode = PW_EAP_MSCHAPV2_CHALLENGE;
144 hdr->mschapv2_id = eap_ds->response->id + 1;
145 length = htons(length);
146 memcpy(hdr->ms_length, &length, sizeof(uint16_t));
147 hdr->value_size = MSCHAPV2_CHALLENGE_LEN;
149 ptr += MSCHAPV2_HEADER_LEN;
152 * Copy the Challenge, success, or error over.
154 memcpy(ptr, reply->vp_octets, reply->vp_length);
156 memcpy((ptr + reply->vp_length), inst->identity, strlen(inst->identity));
159 case PW_MSCHAP2_SUCCESS:
162 * 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
163 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
164 * | Code | Identifier | Length |
165 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
166 * | Type | OpCode | MS-CHAPv2-ID | MS-Length...
167 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
168 * | MS-Length | Message...
169 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
171 RDEBUG2("MSCHAP Success");
173 eap_ds->request->type.data = talloc_array(eap_ds->request, uint8_t, length);
175 * Allocate room for the EAP-MS-CHAPv2 data.
177 if (!eap_ds->request->type.data) {
180 memset(eap_ds->request->type.data, 0, length);
181 eap_ds->request->type.length = length;
183 eap_ds->request->type.data[0] = PW_EAP_MSCHAPV2_SUCCESS;
184 eap_ds->request->type.data[1] = eap_ds->response->id;
185 length = htons(length);
186 memcpy((eap_ds->request->type.data + 2), &length, sizeof(uint16_t));
187 memcpy((eap_ds->request->type.data + 4), reply->vp_strvalue + 1, 42);
190 case PW_MSCHAP_ERROR:
191 REDEBUG("MSCHAP Failure");
192 length = 4 + reply->vp_length - 1;
193 eap_ds->request->type.data = talloc_array(eap_ds->request, uint8_t, length);
196 * Allocate room for the EAP-MS-CHAPv2 data.
198 if (!eap_ds->request->type.data) return 0;
199 memset(eap_ds->request->type.data, 0, length);
200 eap_ds->request->type.length = length;
202 eap_ds->request->type.data[0] = PW_EAP_MSCHAPV2_FAILURE;
203 eap_ds->request->type.data[1] = eap_ds->response->id;
204 length = htons(length);
205 memcpy((eap_ds->request->type.data + 2), &length, sizeof(uint16_t));
207 * Copy the entire failure message.
209 memcpy((eap_ds->request->type.data + 4),
210 reply->vp_strvalue + 1, reply->vp_length - 1);
214 RERROR("Internal sanity check failed");
223 * Initiate the EAP-MSCHAPV2 session by sending a challenge to the peer.
225 static int mod_session_init(void *instance, eap_handler_t *handler)
228 VALUE_PAIR *challenge;
229 mschapv2_opaque_t *data;
230 REQUEST *request = handler->request;
232 bool created_challenge = false;
233 rlm_eap_mschapv2_t *inst = instance;
235 challenge = fr_pair_find_by_num(request->config, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
236 if (challenge && (challenge->vp_length != MSCHAPV2_CHALLENGE_LEN)) {
237 RWDEBUG("control:MS-CHAP-Challenge is incorrect length. Ignoring it.");
242 created_challenge = true;
243 challenge = fr_pair_make(handler, NULL, "MS-CHAP-Challenge", NULL, T_OP_EQ);
246 * Get a random challenge.
248 challenge->vp_length = MSCHAPV2_CHALLENGE_LEN;
249 challenge->vp_octets = p = talloc_array(challenge, uint8_t, challenge->vp_length);
250 for (i = 0; i < MSCHAPV2_CHALLENGE_LEN; i++) {
254 RDEBUG2("Issuing Challenge");
257 * Keep track of the challenge.
259 data = talloc_zero(handler, mschapv2_opaque_t);
260 rad_assert(data != NULL);
263 * We're at the stage where we're challenging the user.
265 data->code = PW_EAP_MSCHAPV2_CHALLENGE;
266 memcpy(data->challenge, challenge->vp_octets, MSCHAPV2_CHALLENGE_LEN);
267 data->mppe_keys = NULL;
270 handler->opaque = data;
273 * Compose the EAP-MSCHAPV2 packet out of the data structure,
276 eapmschapv2_compose(inst, handler, challenge);
277 if (created_challenge) fr_pair_list_free(&challenge);
281 * The EAP session doesn't have enough information to
282 * proxy the "inside EAP" protocol. Disable EAP proxying.
284 handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
288 * We don't need to authorize the user at this point.
290 * We also don't need to keep the challenge, as it's
291 * stored in 'handler->eap_ds', which will be given back
294 handler->stage = PROCESS;
301 * Do post-proxy processing,
305 * Called from rlm_eap.c, eap_postproxy().
307 static int CC_HINT(nonnull) mschap_postproxy(eap_handler_t *handler, UNUSED void *tunnel_data)
309 VALUE_PAIR *response = NULL;
310 mschapv2_opaque_t *data;
311 REQUEST *request = handler->request;
313 data = (mschapv2_opaque_t *) handler->opaque;
314 rad_assert(request != NULL);
316 RDEBUG2("Passing reply from proxy back into the tunnel %d", request->reply->code);
319 * There is only a limited number of possibilities.
321 switch (request->reply->code) {
322 case PW_CODE_ACCESS_ACCEPT:
323 RDEBUG2("Proxied authentication succeeded");
326 * Move the attribute, so it doesn't go into
329 fr_pair_list_mcopy_by_num(data, &response, &request->reply->vps, PW_MSCHAP2_SUCCESS, VENDORPEC_MICROSOFT, TAG_ANY);
333 case PW_CODE_ACCESS_REJECT:
334 REDEBUG("Proxied authentication was rejected");
342 REDEBUG("Proxied reply contained no MS-CHAP2-Success or MS-CHAP-Error");
347 * Done doing EAP proxy stuff.
349 request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
350 eapmschapv2_compose(NULL, handler, response);
351 data->code = PW_EAP_MSCHAPV2_SUCCESS;
354 * Delete MPPE keys & encryption policy
356 * FIXME: Use intelligent names...
358 fix_mppe_keys(handler, data);
361 * Save any other attributes for re-use in the final
362 * access-accept e.g. vlan, etc. This lets the PEAP
363 * use_tunneled_reply code work
365 data->reply = fr_pair_list_copy(data, request->reply->vps);
368 * And we need to challenge the user, not ack/reject them,
369 * so we re-write the ACK to a challenge. Yuck.
371 request->reply->code = PW_CODE_ACCESS_CHALLENGE;
372 fr_pair_list_free(&response);
379 * Authenticate a previously sent challenge.
381 static int CC_HINT(nonnull) mod_process(void *arg, eap_handler_t *handler)
387 mschapv2_opaque_t *data;
388 EAP_DS *eap_ds = handler->eap_ds;
389 VALUE_PAIR *challenge, *response, *name;
390 rlm_eap_mschapv2_t *inst = (rlm_eap_mschapv2_t *) arg;
391 REQUEST *request = handler->request;
393 rad_assert(handler->stage == PROCESS);
395 data = (mschapv2_opaque_t *) handler->opaque;
398 * Sanity check the response.
400 if (eap_ds->response->length <= 5) {
401 REDEBUG("corrupted data");
405 ccode = eap_ds->response->type.data[0];
407 switch (data->code) {
408 case PW_EAP_MSCHAPV2_FAILURE:
409 if (ccode == PW_EAP_MSCHAPV2_RESPONSE) {
410 RDEBUG2("Authentication re-try from client after we sent a failure");
415 * if we sent error 648 (password expired) to the client
416 * we might get an MSCHAP-CPW packet here; turn it into a
417 * regular MS-CHAP2-CPW packet and pass it to rlm_mschap
418 * (or proxy it, I guess)
420 if (ccode == PW_EAP_MSCHAPV2_CHGPASSWD) {
422 int mschap_id = eap_ds->response->type.data[1];
423 int copied = 0 ,seq = 1;
425 RDEBUG2("Password change packet received");
427 challenge = pair_make_request("MS-CHAP-Challenge", NULL, T_OP_EQ);
428 if (!challenge) return 0;
429 fr_pair_value_memcpy(challenge, data->challenge, MSCHAPV2_CHALLENGE_LEN);
431 cpw = pair_make_request("MS-CHAP2-CPW", NULL, T_OP_EQ);
434 cpw->vp_octets = p = talloc_array(cpw, uint8_t, cpw->vp_length);
437 memcpy(p + 2, eap_ds->response->type.data + 520, 66);
440 * break the encoded password into VPs (3 of them)
442 while (copied < 516) {
445 int to_copy = 516 - copied;
446 if (to_copy > 243) to_copy = 243;
448 nt_enc = pair_make_request("MS-CHAP-NT-Enc-PW", NULL, T_OP_ADD);
449 nt_enc->vp_length = 4 + to_copy;
451 nt_enc->vp_octets = p = talloc_array(nt_enc, uint8_t, nt_enc->vp_length);
458 memcpy(p + 4, eap_ds->response->type.data + 4 + copied, to_copy);
462 RDEBUG2("Built change password packet");
463 rdebug_pair_list(L_DBG_LVL_2, request, request->packet->vps, NULL);
466 * jump to "authentication"
472 * we sent a failure and are expecting a failure back
474 if (ccode != PW_EAP_MSCHAPV2_FAILURE) {
475 REDEBUG("Sent FAILURE expecting FAILURE but got %d", ccode);
480 request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
481 eap_ds->request->code = PW_EAP_FAILURE;
484 case PW_EAP_MSCHAPV2_SUCCESS:
486 * we sent a success to the client; some clients send a
487 * success back as-per the RFC, some send an ACK. Permit
492 case PW_EAP_MSCHAPV2_SUCCESS:
493 eap_ds->request->code = PW_EAP_SUCCESS;
495 fr_pair_list_mcopy_by_num(request->reply, &request->reply->vps, &data->mppe_keys, 0, 0, TAG_ANY);
498 case PW_EAP_MSCHAPV2_ACK:
501 * It's a success. Don't proxy it.
503 request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
505 fr_pair_list_mcopy_by_num(request->reply, &request->reply->vps, &data->reply, 0, 0, TAG_ANY);
508 REDEBUG("Sent SUCCESS expecting SUCCESS (or ACK) but got %d", ccode);
511 case PW_EAP_MSCHAPV2_CHALLENGE:
512 if (ccode == PW_EAP_MSCHAPV2_FAILURE) goto failure;
515 * we sent a challenge, expecting a response
517 if (ccode != PW_EAP_MSCHAPV2_RESPONSE) {
518 REDEBUG("Sent CHALLENGE expecting RESPONSE but got %d", ccode);
521 /* authentication happens below */
525 /* should never happen */
526 REDEBUG("Unknown state %d", data->code);
532 * Ensure that we have at least enough data
533 * to do the following checks.
535 * EAP header (4), EAP type, MS-CHAP opcode,
536 * MS-CHAP ident, MS-CHAP data length (2),
537 * MS-CHAP value length.
539 if (eap_ds->response->length < (4 + 1 + 1 + 1 + 2 + 1)) {
540 REDEBUG("Response is too short");
545 * The 'value_size' is the size of the response,
546 * which is supposed to be the response (48
547 * bytes) plus 1 byte of flags at the end.
549 * NOTE: When using Cisco NEAT with EAP-MSCHAPv2, the
550 * switch supplicant will send MSCHAPv2 data (EAP type = 26)
551 * but will always set a value_size of 16 and NULL out the
555 if ((eap_ds->response->type.data[4] != 49) &&
556 (eap_ds->response->type.data[4] != 16)) {
557 REDEBUG("Response is of incorrect length %d", eap_ds->response->type.data[4]);
562 * The MS-Length field is 5 + value_size + length
563 * of name, which is put after the response.
565 length = (eap_ds->response->type.data[2] << 8) | eap_ds->response->type.data[3];
566 if ((length < (5 + 49)) || (length > (256 + 5 + 49))) {
567 REDEBUG("Response contains contradictory length %zu %d", length, 5 + 49);
572 * We now know that the user has sent us a response
573 * to the challenge. Let's try to authenticate it.
575 * We do this by taking the challenge from 'data',
576 * the response from the EAP packet, and creating VALUE_PAIR's
577 * to pass to the 'mschap' module. This is a little wonky,
580 challenge = pair_make_request("MS-CHAP-Challenge", NULL, T_OP_EQ);
581 if (!challenge) return 0;
582 fr_pair_value_memcpy(challenge, data->challenge, MSCHAPV2_CHALLENGE_LEN);
584 response = pair_make_request("MS-CHAP2-Response", NULL, T_OP_EQ);
585 if (!response) return 0;
586 response->vp_length = MSCHAPV2_RESPONSE_LEN;
587 response->vp_octets = p = talloc_array(response, uint8_t, response->vp_length);
589 p[0] = eap_ds->response->type.data[1];
590 p[1] = eap_ds->response->type.data[5 + MSCHAPV2_RESPONSE_LEN];
591 memcpy(p + 2, &eap_ds->response->type.data[5], MSCHAPV2_RESPONSE_LEN - 2);
593 name = pair_make_request("MS-CHAP-User-Name", NULL, T_OP_EQ);
597 * MS-Length - MS-Value - 5.
599 name->vp_length = length - 49 - 5;
600 name->vp_strvalue = q = talloc_array(name, char, name->vp_length + 1);
601 memcpy(q, &eap_ds->response->type.data[4 + MSCHAPV2_RESPONSE_LEN], name->vp_length);
602 q[name->vp_length] = '\0';
608 * If this options is set, then we do NOT authenticate the
609 * user here. Instead, now that we've added the MS-CHAP
610 * attributes to the request, we STOP, and let the outer
611 * tunnel code handle it.
613 * This means that the outer tunnel code will DELETE the
614 * EAP attributes, and proxy the MS-CHAP attributes to a
617 if (request->options & RAD_REQUEST_OPTION_PROXY_EAP) {
618 char *username = NULL;
619 eap_tunnel_data_t *tunnel;
621 RDEBUG2("Cancelling authentication and letting it be proxied");
624 * Set up the callbacks for the tunnel
626 tunnel = talloc_zero(request, eap_tunnel_data_t);
628 tunnel->tls_session = arg;
629 tunnel->callback = mschap_postproxy;
632 * Associate the callback with the request.
634 rcode = request_data_add(request,
636 REQUEST_DATA_EAP_TUNNEL_CALLBACK,
638 rad_assert(rcode == 0);
641 * The State attribute is NOT supposed to
642 * go into the proxied packet, it will confuse
643 * other RADIUS servers, and they will discard
646 * The PEAP module will take care of adding
647 * the State attribute back, before passing
648 * the handler & request back into the tunnel.
650 fr_pair_delete_by_num(&request->packet->vps, PW_STATE, 0, TAG_ANY);
653 * Fix the User-Name when proxying, to strip off
654 * the NT Domain, if we're told to, and a User-Name
655 * exists, and there's a \\, meaning an NT-Domain
656 * in the user name, THEN discard the user name.
658 if (inst->with_ntdomain_hack &&
659 ((challenge = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY)) != NULL) &&
660 ((username = memchr(challenge->vp_octets, '\\', challenge->vp_length)) != NULL)) {
662 * Wipe out the NT domain.
664 * FIXME: Put it into MS-CHAP-Domain?
666 username++; /* skip the \\ */
667 fr_pair_value_strcpy(challenge, username);
671 * Remember that in the post-proxy stage, we've got
672 * to do the work below, AFTER the call to MS-CHAP
680 * This is a wild & crazy hack.
682 rcode = process_authenticate(inst->auth_type_mschap, request);
685 * Delete MPPE keys & encryption policy. We don't
688 fix_mppe_keys(handler, data);
691 * Take the response from the mschap module, and
692 * return success or failure, depending on the result.
695 if (rcode == RLM_MODULE_OK) {
696 fr_pair_list_mcopy_by_num(data, &response, &request->reply->vps, PW_MSCHAP2_SUCCESS, VENDORPEC_MICROSOFT, TAG_ANY);
697 data->code = PW_EAP_MSCHAPV2_SUCCESS;
698 } else if (inst->send_error) {
699 fr_pair_list_mcopy_by_num(data, &response, &request->reply->vps, PW_MSCHAP_ERROR, VENDORPEC_MICROSOFT, TAG_ANY);
706 RDEBUG2("MSCHAP-Error: %s", response->vp_strvalue);
709 * Parse the new challenge out of the
710 * MS-CHAP-Error, so that if the client
711 * issues a re-try, we will know which
712 * challenge value that they used.
714 n = sscanf(response->vp_strvalue, "%*cE=%d R=%d C=%32s", &err, &retry, &buf[0]);
716 RDEBUG2("Found new challenge from MS-CHAP-Error: err=%d retry=%d challenge=%s",
718 fr_hex2bin(data->challenge, 16, buf, strlen(buf));
720 RDEBUG2("Could not parse new challenge from MS-CHAP-Error: %d", n);
723 data->code = PW_EAP_MSCHAPV2_FAILURE;
725 eap_ds->request->code = PW_EAP_FAILURE;
733 REDEBUG("No MS-CHAP2-Success or MS-CHAP-Error was found");
738 * Compose the response (whatever it is),
739 * and return it to the over-lying EAP module.
741 eapmschapv2_compose(inst, handler, response);
742 fr_pair_list_free(&response);
748 * The module name should be the only globally exported symbol.
749 * That is, everything else should be 'static'.
751 extern rlm_eap_module_t rlm_eap_mschapv2;
752 rlm_eap_module_t rlm_eap_mschapv2 = {
753 .name = "eap_mschapv2",
754 .instantiate = mod_instantiate, /* Create new submodule instance */
755 .session_init = mod_session_init, /* Initialise a new EAP session */
756 .process = mod_process /* Process next round of EAP method */