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;
39 static CONF_PARSER module_config[] = {
40 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
41 offsetof(rlm_eap_mschapv2_t,with_ntdomain_hack), NULL, "no" },
43 { NULL, -1, 0, NULL, NULL } /* end the list */
47 static void fix_mppe_keys(EAP_HANDLER *handler, mschapv2_opaque_t *data)
49 pairmove2(&data->mppe_keys, &handler->request->reply->vps, ((311 << 16) | 7));
50 pairmove2(&data->mppe_keys, &handler->request->reply->vps, ((311 << 16) | 8));
51 pairmove2(&data->mppe_keys, &handler->request->reply->vps, ((311 << 16) | 16));
52 pairmove2(&data->mppe_keys, &handler->request->reply->vps, ((311 << 16) | 17));
56 static void free_data(void *ptr)
58 mschapv2_opaque_t *data = ptr;
60 pairfree(&data->mppe_keys);
67 static int mschapv2_detach(void *arg)
69 rlm_eap_mschapv2_t *inst = (rlm_eap_mschapv2_t *) arg;
80 static int mschapv2_attach(CONF_SECTION *cs, void **instance)
82 rlm_eap_mschapv2_t *inst;
84 inst = malloc(sizeof(*inst));
86 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
89 memset(inst, 0, sizeof(*inst));
92 * Parse the configuration attributes.
94 if (cf_section_parse(cs, inst, module_config) < 0) {
95 mschapv2_detach(inst);
106 * Compose the response.
108 static int eapmschapv2_compose(EAP_HANDLER *handler, VALUE_PAIR *reply)
112 mschapv2_header_t *hdr;
113 EAP_DS *eap_ds = handler->eap_ds;
115 eap_ds->request->code = PW_EAP_REQUEST;
116 eap_ds->request->type.type = PW_EAP_MSCHAPV2;
118 switch (reply->attribute) {
119 case PW_MSCHAP_CHALLENGE:
122 * 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
123 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
124 * | Code | Identifier | Length |
125 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
126 * | Type | OpCode | MS-CHAPv2-ID | MS-Length...
127 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
128 * | MS-Length | Value-Size | Challenge...
129 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
131 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
133 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
135 length = MSCHAPV2_HEADER_LEN + MSCHAPV2_CHALLENGE_LEN + strlen(handler->identity);
136 eap_ds->request->type.data = malloc(length);
138 * Allocate room for the EAP-MS-CHAPv2 data.
140 if (eap_ds->request->type.data == NULL) {
141 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
144 eap_ds->request->type.length = length;
146 ptr = eap_ds->request->type.data;
147 hdr = (mschapv2_header_t *) ptr;
149 hdr->opcode = PW_EAP_MSCHAPV2_CHALLENGE;
150 hdr->mschapv2_id = eap_ds->response->id + 1;
151 length = htons(length);
152 memcpy(hdr->ms_length, &length, sizeof(uint16_t));
153 hdr->value_size = MSCHAPV2_CHALLENGE_LEN;
155 ptr += MSCHAPV2_HEADER_LEN;
158 * Copy the Challenge, success, or error over.
160 memcpy(ptr, reply->vp_strvalue, reply->length);
161 memcpy((ptr + reply->length), handler->identity, strlen(handler->identity));
164 case PW_MSCHAP2_SUCCESS:
167 * 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
168 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
169 * | Code | Identifier | Length |
170 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
171 * | Type | OpCode | MS-CHAPv2-ID | MS-Length...
172 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
173 * | MS-Length | Message...
174 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
176 DEBUG2("MSCHAP Success\n");
178 eap_ds->request->type.data = malloc(length);
180 * Allocate room for the EAP-MS-CHAPv2 data.
182 if (eap_ds->request->type.data == NULL) {
183 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
186 memset(eap_ds->request->type.data, 0, length);
187 eap_ds->request->type.length = length;
189 eap_ds->request->type.data[0] = PW_EAP_MSCHAPV2_SUCCESS;
190 eap_ds->request->type.data[1] = eap_ds->response->id;
191 length = htons(length);
192 memcpy((eap_ds->request->type.data + 2), &length, sizeof(uint16_t));
193 memcpy((eap_ds->request->type.data + 4), reply->vp_strvalue + 1, 42);
196 case PW_MSCHAP_ERROR:
197 DEBUG2("MSCHAP Failure\n");
198 length = 4 + MSCHAPV2_FAILURE_MESSAGE_LEN;
199 eap_ds->request->type.data = malloc(length);
202 * Allocate room for the EAP-MS-CHAPv2 data.
204 if (eap_ds->request->type.data == NULL) {
205 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
208 memset(eap_ds->request->type.data, 0, length);
209 eap_ds->request->type.length = length;
211 eap_ds->request->type.data[0] = PW_EAP_MSCHAPV2_FAILURE;
212 eap_ds->request->type.data[1] = eap_ds->response->id;
213 length = htons(length);
214 memcpy((eap_ds->request->type.data + 2), &length, sizeof(uint16_t));
215 memcpy((eap_ds->request->type.data + 4), MSCHAPV2_FAILURE_MESSAGE, MSCHAPV2_FAILURE_MESSAGE_LEN);
219 radlog(L_ERR, "rlm_eap_mschapv2: Internal sanity check failed");
229 * Initiate the EAP-MSCHAPV2 session by sending a challenge to the peer.
231 static int mschapv2_initiate(void *type_data, EAP_HANDLER *handler)
234 VALUE_PAIR *challenge;
235 mschapv2_opaque_t *data;
237 type_data = type_data; /* -Wunused */
239 challenge = pairmake("MS-CHAP-Challenge", "0x00", T_OP_EQ);
241 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
246 * Get a random challenge.
248 challenge->length = MSCHAPV2_CHALLENGE_LEN;
249 for (i = 0; i < MSCHAPV2_CHALLENGE_LEN; i++) {
250 challenge->vp_strvalue[i] = fr_rand();
252 DEBUG2("rlm_eap_mschapv2: Issuing Challenge");
255 * Keep track of the challenge.
257 data = malloc(sizeof(mschapv2_opaque_t));
258 rad_assert(data != NULL);
261 * We're at the stage where we're challenging the user.
263 data->code = PW_EAP_MSCHAPV2_CHALLENGE;
264 memcpy(data->challenge, challenge->vp_strvalue, MSCHAPV2_CHALLENGE_LEN);
265 data->mppe_keys = NULL;
267 handler->opaque = data;
268 handler->free_opaque = free_data;
271 * Compose the EAP-MSCHAPV2 packet out of the data structure,
274 eapmschapv2_compose(handler, challenge);
275 pairfree(&challenge);
279 * The EAP session doesn't have enough information to
280 * proxy the "inside EAP" protocol. Disable EAP proxying.
282 handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
286 * We don't need to authorize the user at this point.
288 * We also don't need to keep the challenge, as it's
289 * stored in 'handler->eap_ds', which will be given back
292 handler->stage = AUTHENTICATE;
299 * Do post-proxy processing,
303 * Called from rlm_eap.c, eap_postproxy().
305 static int mschap_postproxy(EAP_HANDLER *handler, void *tunnel_data)
307 VALUE_PAIR *response = NULL;
308 mschapv2_opaque_t *data;
310 data = (mschapv2_opaque_t *) handler->opaque;
311 rad_assert(data != NULL);
313 tunnel_data = tunnel_data; /* -Wunused */
315 DEBUG2(" rlm_eap_mschapv2: Passing reply from proxy back into the tunnel %p %d.",
316 handler->request, handler->request->reply->code);
319 * There is only a limited number of possibilities.
321 switch (handler->request->reply->code) {
322 case PW_AUTHENTICATION_ACK:
323 DEBUG(" rlm_eap_mschapv2: Authentication succeeded.");
325 * Move the attribute, so it doesn't go into
329 &handler->request->reply->vps,
334 case PW_AUTHENTICATION_REJECT:
335 DEBUG(" rlm_eap_mschapv2: Authentication did not succeed.");
343 radlog(L_ERR, "rlm_eap_mschapv2: No MS-CHAPv2-Success or MS-CHAP-Error was found.");
348 * Done doing EAP proxy stuff.
350 handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
351 eapmschapv2_compose(handler, response);
352 data->code = PW_EAP_MSCHAPV2_SUCCESS;
355 * Delete MPPE keys & encryption policy
357 * FIXME: Use intelligent names...
359 fix_mppe_keys(handler, data);
362 * And we need to challenge the user, not ack/reject them,
363 * so we re-write the ACK to a challenge. Yuck.
365 handler->request->reply->code = PW_ACCESS_CHALLENGE;
373 * Authenticate a previously sent challenge.
375 static int mschapv2_authenticate(void *arg, EAP_HANDLER *handler)
378 mschapv2_opaque_t *data;
379 EAP_DS *eap_ds = handler->eap_ds;
380 VALUE_PAIR *challenge, *response, *name;
382 rad_assert(handler->request != NULL);
383 rad_assert(handler->stage == AUTHENTICATE);
385 data = (mschapv2_opaque_t *) handler->opaque;
388 * Sanity check the response.
390 if (eap_ds->response->length <= 4) {
391 radlog(L_ERR, "rlm_eap_mschapv2: corrupted data");
396 * Switch over the MS-CHAP type.
398 switch (eap_ds->response->type.data[0]) {
400 * We should get an ACK from the client ONLY if we've
401 * sent them a SUCCESS packet.
403 case PW_EAP_MSCHAPV2_ACK:
404 if (data->code != PW_EAP_MSCHAPV2_SUCCESS) {
405 radlog(L_ERR, "rlm_eap_mschapv2: Unexpected ACK received");
410 * It's a success. Don't proxy it.
412 handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
415 * And upon receiving the clients ACK, we do nothing
416 * other than return EAP-Success, with no EAP-MS-CHAPv2
423 * We should get a response ONLY after we've sent
426 case PW_EAP_MSCHAPV2_RESPONSE:
427 if (data->code != PW_EAP_MSCHAPV2_CHALLENGE) {
428 radlog(L_ERR, "rlm_eap_mschapv2: Unexpected response received");
433 * Ensure that we have at least enough data
434 * to do the following checks.
436 * EAP header (4), EAP type, MS-CHAP opcode,
437 * MS-CHAP ident, MS-CHAP data length (2),
438 * MS-CHAP value length.
440 if (eap_ds->response->length < (4 + 1 + 1 + 1 + 2 + 1)) {
441 radlog(L_ERR, "rlm_eap_mschapv2: Response is too short");
446 * The 'value_size' is the size of the response,
447 * which is supposed to be the response (48
448 * bytes) plus 1 byte of flags at the end.
450 if (eap_ds->response->type.data[4] != 49) {
451 radlog(L_ERR, "rlm_eap_mschapv2: Response is of incorrect length %d", eap_ds->response->type.data[4]);
456 * The MS-Length field is 5 + value_size + length
457 * of name, which is put after the response.
459 if (((eap_ds->response->type.data[2] << 8) |
460 eap_ds->response->type.data[3]) < (5 + 49)) {
461 radlog(L_ERR, "rlm_eap_mschapv2: Response contains contradictory length %d %d",
462 (eap_ds->response->type.data[2] << 8) |
463 eap_ds->response->type.data[3], 5 + 49);
468 case PW_EAP_MSCHAPV2_SUCCESS:
469 if (data->code != PW_EAP_MSCHAPV2_SUCCESS) {
470 radlog(L_ERR, "rlm_eap_mschapv2: Unexpected success received");
476 * It's a success. Don't proxy it.
478 handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
481 eap_ds->request->code = PW_EAP_SUCCESS;
483 pairadd(&handler->request->reply->vps, data->mppe_keys);
484 data->mppe_keys = NULL;
489 * Something else, we don't know what it is.
492 radlog(L_ERR, "rlm_eap_mschapv2: Invalid response type %d",
493 eap_ds->response->type.data[0]);
498 * We now know that the user has sent us a response
499 * to the challenge. Let's try to authenticate it.
501 * We do this by taking the challenge from 'data',
502 * the response from the EAP packet, and creating VALUE_PAIR's
503 * to pass to the 'mschap' module. This is a little wonky,
506 challenge = pairmake("MS-CHAP-Challenge", "0x00", T_OP_EQ);
508 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
511 challenge->length = MSCHAPV2_CHALLENGE_LEN;
512 memcpy(challenge->vp_strvalue, data->challenge, MSCHAPV2_CHALLENGE_LEN);
514 response = pairmake("MS-CHAP2-Response", "0x00", T_OP_EQ);
516 pairfree(&challenge);
517 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
521 response->length = MSCHAPV2_RESPONSE_LEN;
522 memcpy(response->vp_strvalue + 2, &eap_ds->response->type.data[5],
523 MSCHAPV2_RESPONSE_LEN - 2);
524 response->vp_strvalue[0] = eap_ds->response->type.data[1];
525 response->vp_strvalue[1] = eap_ds->response->type.data[5 + MSCHAPV2_RESPONSE_LEN];
527 name = pairmake("NTLM-User-Name", "", T_OP_EQ);
529 pairfree(&challenge);
531 radlog(L_ERR, "rlm_eap_mschapv2: Failed creating NTLM-User-Name: %s", fr_strerror());
536 * MS-Length - MS-Value - 5.
538 name->length = (((eap_ds->response->type.data[2] << 8) |
539 eap_ds->response->type.data[3]) -
540 eap_ds->response->type.data[4] - 5);
541 if (name->length >= sizeof(name->vp_strvalue)) {
542 name->length = sizeof(name->vp_strvalue) - 1;
545 memcpy(name->vp_strvalue,
546 &eap_ds->response->type.data[4 + MSCHAPV2_RESPONSE_LEN],
548 name->vp_strvalue[name->length] = '\0';
551 * Add the pairs to the request, and call the 'mschap'
554 pairadd(&handler->request->packet->vps, challenge);
555 pairadd(&handler->request->packet->vps, response);
556 pairadd(&handler->request->packet->vps, name);
560 * If this options is set, then we do NOT authenticate the
561 * user here. Instead, now that we've added the MS-CHAP
562 * attributes to the request, we STOP, and let the outer
563 * tunnel code handle it.
565 * This means that the outer tunnel code will DELETE the
566 * EAP attributes, and proxy the MS-CHAP attributes to a
569 if (handler->request->options & RAD_REQUEST_OPTION_PROXY_EAP) {
570 char *username = NULL;
571 eap_tunnel_data_t *tunnel;
572 rlm_eap_mschapv2_t *inst = (rlm_eap_mschapv2_t *) arg;
575 * Set up the callbacks for the tunnel
577 tunnel = rad_malloc(sizeof(*tunnel));
578 memset(tunnel, 0, sizeof(*tunnel));
580 tunnel->tls_session = arg;
581 tunnel->callback = mschap_postproxy;
584 * Associate the callback with the request.
586 rcode = request_data_add(handler->request,
587 handler->request->proxy,
588 REQUEST_DATA_EAP_TUNNEL_CALLBACK,
590 rad_assert(rcode == 0);
593 * The State attribute is NOT supposed to
594 * go into the proxied packet, it will confuse
595 * other RADIUS servers, and they will discard
598 * The PEAP module will take care of adding
599 * the State attribute back, before passing
600 * the handler & request back into the tunnel.
602 pairdelete(&handler->request->packet->vps, PW_STATE);
605 * Fix the User-Name when proxying, to strip off
606 * the NT Domain, if we're told to, and a User-Name
607 * exists, and there's a \\, meaning an NT-Domain
608 * in the user name, THEN discard the user name.
610 if (inst->with_ntdomain_hack &&
611 ((challenge = pairfind(handler->request->packet->vps,
612 PW_USER_NAME)) != NULL) &&
613 ((username = strchr(challenge->vp_strvalue, '\\')) != NULL)) {
615 * Wipe out the NT domain.
617 * FIXME: Put it into MS-CHAP-Domain?
619 username++; /* skip the \\ */
620 memmove(challenge->vp_strvalue,
622 strlen(username) + 1); /* include \0 */
623 challenge->length = strlen(challenge->vp_strvalue);
627 * Remember that in the post-proxy stage, we've got
628 * to do the work below, AFTER the call to MS-CHAP
636 * This is a wild & crazy hack.
638 rcode = module_authenticate(PW_AUTHTYPE_MS_CHAP, handler->request);
641 * Delete MPPE keys & encryption policy. We don't
644 fix_mppe_keys(handler, data);
647 * Take the response from the mschap module, and
648 * return success or failure, depending on the result.
651 if (rcode == RLM_MODULE_OK) {
652 pairmove2(&response, &handler->request->reply->vps,
654 data->code = PW_EAP_MSCHAPV2_SUCCESS;
657 * Don't return anything in the error message.
659 eap_ds->request->code = PW_EAP_FAILURE;
662 pairmove2(&handler->request->reply->vps, &response
664 data->code = PW_EAP_MSCHAPV2_FAILURE;
672 radlog(L_ERR, "rlm_eap_mschapv2: No MS-CHAPv2-Success or MS-CHAP-Error was found.");
677 * Compose the response (whatever it is),
678 * and return it to the over-lying EAP module.
680 eapmschapv2_compose(handler, response);
687 * The module name should be the only globally exported symbol.
688 * That is, everything else should be 'static'.
690 EAP_TYPE rlm_eap_mschapv2 = {
692 mschapv2_attach, /* attach */
693 mschapv2_initiate, /* Start the initial request */
694 NULL, /* authorization */
695 mschapv2_authenticate, /* authentication */
696 mschapv2_detach /* detach */