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 The FreeRADIUS server project
23 #include <freeradius-devel/autoconf.h>
28 #include "eap_mschapv2.h"
30 #include <freeradius-devel/rad_assert.h>
32 typedef struct rlm_eap_mschapv2_t {
33 int with_ntdomain_hack;
36 static CONF_PARSER module_config[] = {
37 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
38 offsetof(rlm_eap_mschapv2_t,with_ntdomain_hack), NULL, "no" },
40 { NULL, -1, 0, NULL, NULL } /* end the list */
47 static int mschapv2_detach(void *arg)
49 rlm_eap_mschapv2_t *inst = (rlm_eap_mschapv2_t *) arg;
60 static int mschapv2_attach(CONF_SECTION *cs, void **instance)
62 rlm_eap_mschapv2_t *inst;
64 inst = malloc(sizeof(*inst));
66 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
69 memset(inst, 0, sizeof(*inst));
72 * Parse the configuration attributes.
74 if (cf_section_parse(cs, inst, module_config) < 0) {
75 mschapv2_detach(inst);
86 * Compose the response.
88 static int eapmschapv2_compose(EAP_HANDLER *handler, VALUE_PAIR *reply)
92 mschapv2_header_t *hdr;
93 EAP_DS *eap_ds = handler->eap_ds;
95 eap_ds->request->code = PW_EAP_REQUEST;
96 eap_ds->request->type.type = PW_EAP_MSCHAPV2;
98 switch (reply->attribute) {
99 case PW_MSCHAP_CHALLENGE:
102 * 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
103 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
104 * | Code | Identifier | Length |
105 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
106 * | Type | OpCode | MS-CHAPv2-ID | MS-Length...
107 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
108 * | MS-Length | Value-Size | Challenge...
109 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
111 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
113 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
115 length = MSCHAPV2_HEADER_LEN + MSCHAPV2_CHALLENGE_LEN + strlen(handler->identity);
116 eap_ds->request->type.data = malloc(length);
118 * Allocate room for the EAP-MS-CHAPv2 data.
120 if (eap_ds->request->type.data == NULL) {
121 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
124 eap_ds->request->type.length = length;
126 ptr = eap_ds->request->type.data;
127 hdr = eap_ds->request->type.data;
129 hdr->opcode = PW_EAP_MSCHAPV2_CHALLENGE;
130 hdr->mschapv2_id = eap_ds->response->id + 1;
131 length = htons(length);
132 memcpy(hdr->ms_length, &length, sizeof(uint16_t));
133 hdr->value_size = MSCHAPV2_CHALLENGE_LEN;
135 ptr += MSCHAPV2_HEADER_LEN;
138 * Copy the Challenge, success, or error over.
140 memcpy(ptr, reply->vp_strvalue, reply->length);
141 memcpy((ptr + reply->length), handler->identity, strlen(handler->identity));
144 case PW_MSCHAP2_SUCCESS:
147 * 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
148 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
149 * | Code | Identifier | Length |
150 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
151 * | Type | OpCode | MS-CHAPv2-ID | MS-Length...
152 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
153 * | MS-Length | Message...
154 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
156 DEBUG2("MSCHAP Success\n");
158 eap_ds->request->type.data = malloc(length);
159 memset(eap_ds->request->type.data, 0, length);
161 * Allocate room for the EAP-MS-CHAPv2 data.
163 if (eap_ds->request->type.data == NULL) {
164 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
167 eap_ds->request->type.length = length;
169 eap_ds->request->type.data[0] = PW_EAP_MSCHAPV2_SUCCESS;
170 eap_ds->request->type.data[1] = eap_ds->response->id;
171 length = htons(length);
172 memcpy((eap_ds->request->type.data + 2), &length, sizeof(uint16_t));
173 memcpy((eap_ds->request->type.data + 4), reply->vp_strvalue + 1, 42);
176 case PW_MSCHAP_ERROR:
177 DEBUG2("MSCHAP Failure\n");
178 length = 4 + MSCHAPV2_FAILURE_MESSAGE_LEN;
179 eap_ds->request->type.data = malloc(length);
180 memset(eap_ds->request->type.data, 0, length);
183 * Allocate room for the EAP-MS-CHAPv2 data.
185 if (eap_ds->request->type.data == NULL) {
186 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
189 eap_ds->request->type.length = length;
191 eap_ds->request->type.data[0] = PW_EAP_MSCHAPV2_FAILURE;
192 eap_ds->request->type.data[1] = eap_ds->response->id;
193 length = htons(length);
194 memcpy((eap_ds->request->type.data + 2), &length, sizeof(uint16_t));
195 memcpy((eap_ds->request->type.data + 4), MSCHAPV2_FAILURE_MESSAGE, MSCHAPV2_FAILURE_MESSAGE_LEN);
199 radlog(L_ERR, "rlm_eap_mschapv2: Internal sanity check failed");
209 * Initiate the EAP-MSCHAPV2 session by sending a challenge to the peer.
211 static int mschapv2_initiate(void *type_data, EAP_HANDLER *handler)
214 VALUE_PAIR *challenge;
215 mschapv2_opaque_t *data;
217 type_data = type_data; /* -Wunused */
219 challenge = pairmake("MS-CHAP-Challenge", "0x00", T_OP_EQ);
221 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
226 * Get a random challenge.
228 challenge->length = MSCHAPV2_CHALLENGE_LEN;
229 for (i = 0; i < MSCHAPV2_CHALLENGE_LEN; i++) {
230 challenge->vp_strvalue[i] = lrad_rand();
232 radlog(L_INFO, "rlm_eap_mschapv2: Issuing Challenge");
235 * Keep track of the challenge.
237 data = malloc(sizeof(mschapv2_opaque_t));
238 rad_assert(data != NULL);
241 * We're at the stage where we're challenging the user.
243 data->code = PW_EAP_MSCHAPV2_CHALLENGE;
244 memcpy(data->challenge, challenge->vp_strvalue, MSCHAPV2_CHALLENGE_LEN);
246 handler->opaque = data;
247 handler->free_opaque = free;
250 * Compose the EAP-MSCHAPV2 packet out of the data structure,
253 eapmschapv2_compose(handler, challenge);
254 pairfree(&challenge);
257 * The EAP session doesn't have enough information to
258 * proxy the "inside EAP" protocol. Disable EAP proxying.
260 handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
263 * We don't need to authorize the user at this point.
265 * We also don't need to keep the challenge, as it's
266 * stored in 'handler->eap_ds', which will be given back
269 handler->stage = AUTHENTICATE;
276 * Do post-proxy processing,
280 * Called from rlm_eap.c, eap_postproxy().
282 static int mschap_postproxy(EAP_HANDLER *handler, void *tunnel_data)
284 VALUE_PAIR *response = NULL;
285 mschapv2_opaque_t *data;
287 data = (mschapv2_opaque_t *) handler->opaque;
288 rad_assert(data != NULL);
290 tunnel_data = tunnel_data; /* -Wunused */
292 DEBUG2(" rlm_eap_mschapv2: Passing reply from proxy back into the tunnel %p %d.",
293 handler->request, handler->request->reply->code);
296 * There is only a limited number of possibilities.
298 switch (handler->request->reply->code) {
299 case PW_AUTHENTICATION_ACK:
300 DEBUG(" rlm_eap_mschapv2: Authentication succeeded.");
302 * Move the attribute, so it doesn't go into
306 &handler->request->reply->vps,
311 case PW_AUTHENTICATION_REJECT:
312 DEBUG(" rlm_eap_mschapv2: Authentication did not succeed.");
320 radlog(L_ERR, "rlm_eap_mschapv2: No MS-CHAPv2-Success or MS-CHAP-Error was found.");
325 * Done doing EAP proxy stuff.
327 handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
328 eapmschapv2_compose(handler, response);
329 data->code = PW_EAP_MSCHAPV2_SUCCESS;
332 * Delete MPPE keys & encryption policy
334 * FIXME: Use intelligent names...
336 pairdelete(&handler->request->reply->vps, ((311 << 16) | 7));
337 pairdelete(&handler->request->reply->vps, ((311 << 16) | 8));
338 pairdelete(&handler->request->reply->vps, ((311 << 16) | 16));
339 pairdelete(&handler->request->reply->vps, ((311 << 16) | 17));
342 * And we need to challenge the user, not ack/reject them,
343 * so we re-write the ACK to a challenge. Yuck.
345 handler->request->reply->code = PW_ACCESS_CHALLENGE;
353 * Authenticate a previously sent challenge.
355 static int mschapv2_authenticate(void *arg, EAP_HANDLER *handler)
358 mschapv2_opaque_t *data;
359 EAP_DS *eap_ds = handler->eap_ds;
360 VALUE_PAIR *challenge, *response;
363 * Get the User-Password for this user.
365 rad_assert(handler->request != NULL);
366 rad_assert(handler->stage == AUTHENTICATE);
368 data = (mschapv2_opaque_t *) handler->opaque;
371 * Sanity check the response.
373 if (eap_ds->response->length <= 4) {
374 radlog(L_ERR, "rlm_eap_mschapv2: corrupted data");
379 * Switch over the MS-CHAP type.
381 switch (eap_ds->response->type.data[0]) {
383 * We should get an ACK from the client ONLY if we've
384 * sent them a SUCCESS packet.
386 case PW_EAP_MSCHAPV2_ACK:
387 if (data->code != PW_EAP_MSCHAPV2_SUCCESS) {
388 radlog(L_ERR, "rlm_eap_mschapv2: Unexpected ACK received");
393 * It's a success. Don't proxy it.
395 handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
398 * And upon receiving the clients ACK, we do nothing
399 * other than return EAP-Success, with no EAP-MS-CHAPv2
406 * We should get a response ONLY after we've sent
409 case PW_EAP_MSCHAPV2_RESPONSE:
410 if (data->code != PW_EAP_MSCHAPV2_CHALLENGE) {
411 radlog(L_ERR, "rlm_eap_mschapv2: Unexpected response received");
416 * Ensure that we have at least enough data
417 * to do the following checks.
419 * EAP header (4), EAP type, MS-CHAP opcode,
420 * MS-CHAP ident, MS-CHAP data length (2),
421 * MS-CHAP value length.
423 if (eap_ds->response->length < (4 + 1 + 1 + 1 + 2 + 1)) {
424 radlog(L_ERR, "rlm_eap_mschapv2: Response is too short");
429 * The 'value_size' is the size of the response,
430 * which is supposed to be the response (48
431 * bytes) plus 1 byte of flags at the end.
433 if (eap_ds->response->type.data[4] != 49) {
434 radlog(L_ERR, "rlm_eap_mschapv2: Response is of incorrect length %d", eap_ds->response->type.data[4]);
439 * The MS-Length field is 5 + value_size + length
440 * of name, which is put after the response.
442 if (((eap_ds->response->type.data[2] << 8) |
443 eap_ds->response->type.data[3]) < (5 + 49)) {
444 radlog(L_ERR, "rlm_eap_mschapv2: Response contains contradictory length %d %d",
445 (eap_ds->response->type.data[2] << 8) |
446 eap_ds->response->type.data[3], 5 + 49);
451 case PW_EAP_MSCHAPV2_SUCCESS:
452 if (data->code != PW_EAP_MSCHAPV2_SUCCESS) {
453 radlog(L_ERR, "rlm_eap_mschapv2: Unexpected success received");
458 * It's a success. Don't proxy it.
460 handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
462 eap_ds->request->code = PW_EAP_SUCCESS;
467 * Something else, we don't know what it is.
470 radlog(L_ERR, "rlm_eap_mschapv2: Invalid response type %d",
471 eap_ds->response->type.data[0]);
476 * We now know that the user has sent us a response
477 * to the challenge. Let's try to authenticate it.
479 * We do this by taking the challenge from 'data',
480 * the response from the EAP packet, and creating VALUE_PAIR's
481 * to pass to the 'mschap' module. This is a little wonky,
484 challenge = pairmake("MS-CHAP-Challenge", "0x00", T_OP_EQ);
486 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
489 challenge->length = MSCHAPV2_CHALLENGE_LEN;
490 memcpy(challenge->vp_strvalue, data->challenge, MSCHAPV2_CHALLENGE_LEN);
492 response = pairmake("MS-CHAP2-Response", "0x00", T_OP_EQ);
494 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
498 response->length = MSCHAPV2_RESPONSE_LEN;
499 memcpy(response->vp_strvalue + 2, &eap_ds->response->type.data[5],
500 MSCHAPV2_RESPONSE_LEN - 2);
501 response->vp_strvalue[0] = eap_ds->response->type.data[1];
502 response->vp_strvalue[1] = eap_ds->response->type.data[5 + MSCHAPV2_RESPONSE_LEN];
505 * Add the pairs to the request, and call the 'mschap'
508 pairadd(&handler->request->packet->vps, challenge);
509 pairadd(&handler->request->packet->vps, response);
512 * If this options is set, then we do NOT authenticate the
513 * user here. Instead, now that we've added the MS-CHAP
514 * attributes to the request, we STOP, and let the outer
515 * tunnel code handle it.
517 * This means that the outer tunnel code will DELETE the
518 * EAP attributes, and proxy the MS-CHAP attributes to a
521 if (handler->request->options & RAD_REQUEST_OPTION_PROXY_EAP) {
522 char *username = NULL;
523 eap_tunnel_data_t *tunnel;
524 rlm_eap_mschapv2_t *inst = (rlm_eap_mschapv2_t *) arg;
527 * Set up the callbacks for the tunnel
529 tunnel = rad_malloc(sizeof(*tunnel));
530 memset(tunnel, 0, sizeof(*tunnel));
532 tunnel->tls_session = arg;
533 tunnel->callback = mschap_postproxy;
536 * Associate the callback with the request.
538 rcode = request_data_add(handler->request,
539 handler->request->proxy,
540 REQUEST_DATA_EAP_TUNNEL_CALLBACK,
542 rad_assert(rcode == 0);
545 * The State attribute is NOT supposed to
546 * go into the proxied packet, it will confuse
547 * other RADIUS servers, and they will discard
550 * The PEAP module will take care of adding
551 * the State attribute back, before passing
552 * the handler & request back into the tunnel.
554 pairdelete(&handler->request->packet->vps, PW_STATE);
557 * Fix the User-Name when proxying, to strip off
558 * the NT Domain, if we're told to, and a User-Name
559 * exists, and there's a \\, meaning an NT-Domain
560 * in the user name, THEN discard the user name.
562 if (inst->with_ntdomain_hack &&
563 ((challenge = pairfind(handler->request->packet->vps,
564 PW_USER_NAME)) != NULL) &&
565 ((username = strchr(challenge->vp_strvalue, '\\')) != NULL)) {
567 * Wipe out the NT domain.
569 * FIXME: Put it into MS-CHAP-Domain?
571 username++; /* skip the \\ */
572 memmove(challenge->vp_strvalue,
574 strlen(username) + 1); /* include \0 */
575 challenge->length = strlen(challenge->vp_strvalue);
579 * Remember that in the post-proxy stage, we've got
580 * to do the work below, AFTER the call to MS-CHAP
587 * This is a wild & crazy hack.
589 rcode = module_authenticate(PW_AUTHTYPE_MS_CHAP, handler->request);
592 * Delete MPPE keys & encryption policy. We don't
595 pairdelete(&handler->request->reply->vps, ((311 << 16) | 7));
596 pairdelete(&handler->request->reply->vps, ((311 << 16) | 8));
597 pairdelete(&handler->request->reply->vps, ((311 << 16) | 16));
598 pairdelete(&handler->request->reply->vps, ((311 << 16) | 17));
601 * Take the response from the mschap module, and
602 * return success or failure, depending on the result.
604 if (rcode == RLM_MODULE_OK) {
605 response = paircopy2(handler->request->reply->vps,
607 data->code = PW_EAP_MSCHAPV2_SUCCESS;
610 * Don't return anything in the error message.
612 eap_ds->request->code = PW_EAP_FAILURE;
615 response = paircopy2(handler->request->reply->vps,
617 data->code = PW_EAP_MSCHAPV2_FAILURE;
625 radlog(L_ERR, "rlm_eap_mschapv2: No MS-CHAPv2-Success or MS-CHAP-Error was found.");
630 * Compose the response (whatever it is),
631 * and return it to the over-lying EAP module.
633 eapmschapv2_compose(handler, response);
640 * The module name should be the only globally exported symbol.
641 * That is, everything else should be 'static'.
643 EAP_TYPE rlm_eap_mschapv2 = {
645 mschapv2_attach, /* attach */
646 mschapv2_initiate, /* Start the initial request */
647 NULL, /* authorization */
648 mschapv2_authenticate, /* authentication */
649 mschapv2_detach /* detach */