Formatting
[freeradius.git] / src / modules / rlm_eap / types / rlm_eap_mschapv2 / rlm_eap_mschapv2.c
1 /*
2  * rlm_eap_mschapv2.c    Handles that are called from eap
3  *
4  * Version:     $Id$
5  *
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.
10  *
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.
15  *
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
19  *
20  * Copyright 2003,2006  The FreeRADIUS server project
21  */
22
23 RCSID("$Id$")
24
25 #include <stdio.h>
26 #include <stdlib.h>
27
28 #include "eap_mschapv2.h"
29
30 #include <freeradius-devel/rad_assert.h>
31
32 typedef struct rlm_eap_mschapv2_t {
33         bool with_ntdomain_hack;
34         bool send_error;
35         char const *identity;
36 } rlm_eap_mschapv2_t;
37
38 static CONF_PARSER module_config[] = {
39         { "with_ntdomain_hack", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_eap_mschapv2_t, with_ntdomain_hack), "no" },
40
41         { "send_error", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_eap_mschapv2_t, send_error), "no" },
42         { "identity", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_eap_mschapv2_t, identity), NULL },
43         CONF_PARSER_TERMINATOR
44 };
45
46
47 static void fix_mppe_keys(eap_handler_t *handler, mschapv2_opaque_t *data)
48 {
49         fr_pair_list_mcopy_by_num(data, &data->mppe_keys, &handler->request->reply->vps, 7, VENDORPEC_MICROSOFT, TAG_ANY);
50         fr_pair_list_mcopy_by_num(data, &data->mppe_keys, &handler->request->reply->vps, 8, VENDORPEC_MICROSOFT, TAG_ANY);
51         fr_pair_list_mcopy_by_num(data, &data->mppe_keys, &handler->request->reply->vps, 16, VENDORPEC_MICROSOFT, TAG_ANY);
52         fr_pair_list_mcopy_by_num(data, &data->mppe_keys, &handler->request->reply->vps, 17, VENDORPEC_MICROSOFT, TAG_ANY);
53 }
54
55 /*
56  *      Attach the module.
57  */
58 static int mod_instantiate(CONF_SECTION *cs, void **instance)
59 {
60         rlm_eap_mschapv2_t *inst;
61
62         *instance = inst = talloc_zero(cs, rlm_eap_mschapv2_t);
63         if (!inst) return -1;
64
65         /*
66          *      Parse the configuration attributes.
67          */
68         if (cf_section_parse(cs, inst, module_config) < 0) {
69                 return -1;
70         }
71
72         if (inst->identity && (strlen(inst->identity) > 255)) {
73                 cf_log_err_cs(cs, "identity is too long");
74                 return -1;
75         }
76
77         if (!inst->identity) {
78                 inst->identity = talloc_asprintf(inst, "freeradius-%s", RADIUSD_VERSION_STRING);
79         }
80
81         return 0;
82 }
83
84
85 /*
86  *      Compose the response.
87  */
88 static int eapmschapv2_compose(rlm_eap_mschapv2_t *inst, eap_handler_t *handler, VALUE_PAIR *reply)
89 {
90         uint8_t *ptr;
91         int16_t length;
92         mschapv2_header_t *hdr;
93         EAP_DS *eap_ds = handler->eap_ds;
94         REQUEST *request = handler->request;
95
96         eap_ds->request->code = PW_EAP_REQUEST;
97         eap_ds->request->type.num = PW_EAP_MSCHAPV2;
98
99         /*
100          *      Always called with vendor Microsoft
101          */
102         switch (reply->da->attr) {
103         case PW_MSCHAP_CHALLENGE:
104                 /*
105                  *   0                   1                   2                   3
106                  *   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
107                  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
108                  *  |     Code      |   Identifier  |            Length             |
109                  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
110                  *  |     Type      |   OpCode      |  MS-CHAPv2-ID |  MS-Length...
111                  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
112                  *  |   MS-Length   |  Value-Size   |  Challenge...
113                  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
114                  *  |                             Challenge...
115                  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
116                  *  |                             Server Name...
117                  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
118                  */
119                 length = MSCHAPV2_HEADER_LEN + MSCHAPV2_CHALLENGE_LEN + strlen(inst->identity);
120                 eap_ds->request->type.data = talloc_array(eap_ds->request, uint8_t, length);
121
122                 /*
123                  *      Allocate room for the EAP-MS-CHAPv2 data.
124                  */
125                 if (!eap_ds->request->type.data) {
126                         return 0;
127                 }
128                 eap_ds->request->type.length = length;
129
130                 ptr = eap_ds->request->type.data;
131                 hdr = (mschapv2_header_t *) ptr;
132
133                 hdr->opcode = PW_EAP_MSCHAPV2_CHALLENGE;
134                 hdr->mschapv2_id = eap_ds->response->id + 1;
135                 length = htons(length);
136                 memcpy(hdr->ms_length, &length, sizeof(uint16_t));
137                 hdr->value_size = MSCHAPV2_CHALLENGE_LEN;
138
139                 ptr += MSCHAPV2_HEADER_LEN;
140
141                 /*
142                  *      Copy the Challenge, success, or error over.
143                  */
144                 memcpy(ptr, reply->vp_octets, reply->vp_length);
145
146                 memcpy((ptr + reply->vp_length), inst->identity, strlen(inst->identity));
147                 break;
148
149         case PW_MSCHAP2_SUCCESS:
150                 /*
151                  *   0                   1                   2                   3
152                  *   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
153                  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
154                  *  |     Code      |   Identifier  |            Length             |
155                  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
156                  *  |     Type      |   OpCode      |  MS-CHAPv2-ID |  MS-Length...
157                  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
158                  *  |   MS-Length   |                    Message...
159                  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
160                  */
161                 RDEBUG2("MSCHAP Success");
162                 length = 46;
163                 eap_ds->request->type.data = talloc_array(eap_ds->request, uint8_t, length);
164                 /*
165                  *      Allocate room for the EAP-MS-CHAPv2 data.
166                  */
167                 if (!eap_ds->request->type.data) {
168                         return 0;
169                 }
170                 memset(eap_ds->request->type.data, 0, length);
171                 eap_ds->request->type.length = length;
172
173                 eap_ds->request->type.data[0] = PW_EAP_MSCHAPV2_SUCCESS;
174                 eap_ds->request->type.data[1] = eap_ds->response->id;
175                 length = htons(length);
176                 memcpy((eap_ds->request->type.data + 2), &length, sizeof(uint16_t));
177                 memcpy((eap_ds->request->type.data + 4), reply->vp_strvalue + 1, 42);
178                 break;
179
180         case PW_MSCHAP_ERROR:
181                 REDEBUG("MSCHAP Failure");
182                 length = 4 + reply->vp_length - 1;
183                 eap_ds->request->type.data = talloc_array(eap_ds->request, uint8_t, length);
184
185                 /*
186                  *      Allocate room for the EAP-MS-CHAPv2 data.
187                  */
188                 if (!eap_ds->request->type.data) return 0;
189                 memset(eap_ds->request->type.data, 0, length);
190                 eap_ds->request->type.length = length;
191
192                 eap_ds->request->type.data[0] = PW_EAP_MSCHAPV2_FAILURE;
193                 eap_ds->request->type.data[1] = eap_ds->response->id;
194                 length = htons(length);
195                 memcpy((eap_ds->request->type.data + 2), &length, sizeof(uint16_t));
196                 /*
197                  *      Copy the entire failure message.
198                  */
199                 memcpy((eap_ds->request->type.data + 4),
200                        reply->vp_strvalue + 1, reply->vp_length - 1);
201                 break;
202
203         default:
204                 RERROR("Internal sanity check failed");
205                 return 0;
206         }
207
208         return 1;
209 }
210
211
212 /*
213  *      Initiate the EAP-MSCHAPV2 session by sending a challenge to the peer.
214  */
215 static int mod_session_init(void *instance, eap_handler_t *handler)
216 {
217         int             i;
218         VALUE_PAIR      *challenge;
219         mschapv2_opaque_t *data;
220         REQUEST         *request = handler->request;
221         uint8_t         *p;
222         bool            created_challenge = false;
223         rlm_eap_mschapv2_t *inst = instance;
224
225         challenge = fr_pair_find_by_num(request->config, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
226         if (challenge && (challenge->vp_length != MSCHAPV2_CHALLENGE_LEN)) {
227                 RWDEBUG("control:MS-CHAP-Challenge is incorrect length.  Ignoring it.");
228                 challenge = NULL;
229         }
230
231         if (!challenge) {
232                 created_challenge = true;
233                 challenge = fr_pair_make(handler, NULL, "MS-CHAP-Challenge", NULL, T_OP_EQ);
234
235                 /*
236                  *      Get a random challenge.
237                  */
238                 challenge->vp_length = MSCHAPV2_CHALLENGE_LEN;
239                 challenge->vp_octets = p = talloc_array(challenge, uint8_t, challenge->vp_length);
240                 for (i = 0; i < MSCHAPV2_CHALLENGE_LEN; i++) {
241                         p[i] = fr_rand();
242                 }
243         }
244         RDEBUG2("Issuing Challenge");
245
246         /*
247          *      Keep track of the challenge.
248          */
249         data = talloc_zero(handler, mschapv2_opaque_t);
250         rad_assert(data != NULL);
251
252         /*
253          *      We're at the stage where we're challenging the user.
254          */
255         data->code = PW_EAP_MSCHAPV2_CHALLENGE;
256         memcpy(data->challenge, challenge->vp_octets, MSCHAPV2_CHALLENGE_LEN);
257         data->mppe_keys = NULL;
258         data->reply = NULL;
259
260         handler->opaque = data;
261
262         /*
263          *      Compose the EAP-MSCHAPV2 packet out of the data structure,
264          *      and free it.
265          */
266         eapmschapv2_compose(inst, handler, challenge);
267         if (created_challenge) fr_pair_list_free(&challenge);
268
269 #ifdef WITH_PROXY
270         /*
271          *      The EAP session doesn't have enough information to
272          *      proxy the "inside EAP" protocol.  Disable EAP proxying.
273          */
274         handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
275 #endif
276
277         /*
278          *      We don't need to authorize the user at this point.
279          *
280          *      We also don't need to keep the challenge, as it's
281          *      stored in 'handler->eap_ds', which will be given back
282          *      to us...
283          */
284         handler->stage = PROCESS;
285
286         return 1;
287 }
288
289 #ifdef WITH_PROXY
290 /*
291  *      Do post-proxy processing,
292  *      0 = fail
293  *      1 = OK.
294  *
295  *      Called from rlm_eap.c, eap_postproxy().
296  */
297 static int CC_HINT(nonnull) mschap_postproxy(eap_handler_t *handler, UNUSED void *tunnel_data)
298 {
299         VALUE_PAIR *response = NULL;
300         mschapv2_opaque_t *data;
301         REQUEST *request = handler->request;
302
303         data = (mschapv2_opaque_t *) handler->opaque;
304         rad_assert(request != NULL);
305
306         RDEBUG2("Passing reply from proxy back into the tunnel %d", request->reply->code);
307
308         /*
309          *      There is only a limited number of possibilities.
310          */
311         switch (request->reply->code) {
312         case PW_CODE_ACCESS_ACCEPT:
313                 RDEBUG2("Proxied authentication succeeded");
314
315                 /*
316                  *      Move the attribute, so it doesn't go into
317                  *      the reply.
318                  */
319                 fr_pair_list_mcopy_by_num(data, &response, &request->reply->vps, PW_MSCHAP2_SUCCESS, VENDORPEC_MICROSOFT, TAG_ANY);
320                 break;
321
322         default:
323         case PW_CODE_ACCESS_REJECT:
324                 REDEBUG("Proxied authentication was rejected");
325                 return 0;
326         }
327
328         /*
329          *      No response, die.
330          */
331         if (!response) {
332                 REDEBUG("Proxied reply contained no MS-CHAP2-Success or MS-CHAP-Error");
333                 return 0;
334         }
335
336         /*
337          *      Done doing EAP proxy stuff.
338          */
339         request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
340         eapmschapv2_compose(NULL, handler, response);
341         data->code = PW_EAP_MSCHAPV2_SUCCESS;
342
343         /*
344          *      Delete MPPE keys & encryption policy
345          *
346          *      FIXME: Use intelligent names...
347          */
348         fix_mppe_keys(handler, data);
349
350         /*
351          *      Save any other attributes for re-use in the final
352          *      access-accept e.g. vlan, etc. This lets the PEAP
353          *      use_tunneled_reply code work
354          */
355         data->reply = fr_pair_list_copy(data, request->reply->vps);
356
357         /*
358          *      And we need to challenge the user, not ack/reject them,
359          *      so we re-write the ACK to a challenge.  Yuck.
360          */
361         request->reply->code = PW_CODE_ACCESS_CHALLENGE;
362         fr_pair_list_free(&response);
363
364         return 1;
365 }
366 #endif
367
368 /*
369  *      Authenticate a previously sent challenge.
370  */
371 static int CC_HINT(nonnull) mod_process(void *arg, eap_handler_t *handler)
372 {
373         int rcode, ccode;
374         uint8_t *p;
375         size_t length;
376         char *q;
377         mschapv2_opaque_t *data;
378         EAP_DS *eap_ds = handler->eap_ds;
379         VALUE_PAIR *challenge, *response, *name;
380         rlm_eap_mschapv2_t *inst = (rlm_eap_mschapv2_t *) arg;
381         REQUEST *request = handler->request;
382
383         rad_assert(handler->stage == PROCESS);
384
385         data = (mschapv2_opaque_t *) handler->opaque;
386
387         /*
388          *      Sanity check the response.
389          */
390         if (eap_ds->response->length <= 5) {
391                 REDEBUG("corrupted data");
392                 return 0;
393         }
394
395         ccode = eap_ds->response->type.data[0];
396
397         switch (data->code) {
398         case PW_EAP_MSCHAPV2_FAILURE:
399                 if (ccode == PW_EAP_MSCHAPV2_RESPONSE) {
400                         RDEBUG2("Authentication re-try from client after we sent a failure");
401                         break;
402                 }
403
404                 /*
405                  * if we sent error 648 (password expired) to the client
406                  * we might get an MSCHAP-CPW packet here; turn it into a
407                  * regular MS-CHAP2-CPW packet and pass it to rlm_mschap
408                  * (or proxy it, I guess)
409                  */
410                 if (ccode == PW_EAP_MSCHAPV2_CHGPASSWD) {
411                         VALUE_PAIR *cpw;
412                         int mschap_id = eap_ds->response->type.data[1];
413                         int copied = 0 ,seq = 1;
414
415                         RDEBUG2("Password change packet received");
416
417                         challenge = pair_make_request("MS-CHAP-Challenge", NULL, T_OP_EQ);
418                         if (!challenge) return 0;
419                         fr_pair_value_memcpy(challenge, data->challenge, MSCHAPV2_CHALLENGE_LEN);
420
421                         cpw = pair_make_request("MS-CHAP2-CPW", NULL, T_OP_EQ);
422                         cpw->vp_length = 68;
423
424                         cpw->vp_octets = p = talloc_array(cpw, uint8_t, cpw->vp_length);
425                         p[0] = 7;
426                         p[1] = mschap_id;
427                         memcpy(p + 2, eap_ds->response->type.data + 520, 66);
428
429                         /*
430                          * break the encoded password into VPs (3 of them)
431                          */
432                         while (copied < 516) {
433                                 VALUE_PAIR *nt_enc;
434
435                                 int to_copy = 516 - copied;
436                                 if (to_copy > 243) to_copy = 243;
437
438                                 nt_enc = pair_make_request("MS-CHAP-NT-Enc-PW", NULL, T_OP_ADD);
439                                 nt_enc->vp_length = 4 + to_copy;
440
441                                 nt_enc->vp_octets = p = talloc_array(nt_enc, uint8_t, nt_enc->vp_length);
442
443                                 p[0] = 6;
444                                 p[1] = mschap_id;
445                                 p[2] = 0;
446                                 p[3] = seq++;
447
448                                 memcpy(p + 4, eap_ds->response->type.data + 4 + copied, to_copy);
449                                 copied += to_copy;
450                         }
451
452                         RDEBUG2("Built change password packet");
453                         rdebug_pair_list(L_DBG_LVL_2, request, request->packet->vps, NULL);
454
455                         /*
456                          * jump to "authentication"
457                          */
458                         goto packet_ready;
459                 }
460
461                 /*
462                  * we sent a failure and are expecting a failure back
463                  */
464                 if (ccode != PW_EAP_MSCHAPV2_FAILURE) {
465                         REDEBUG("Sent FAILURE expecting FAILURE but got %d", ccode);
466                         return 0;
467                 }
468
469 failure:
470                 request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
471                 eap_ds->request->code = PW_EAP_FAILURE;
472                 return 1;
473
474         case PW_EAP_MSCHAPV2_SUCCESS:
475                 /*
476                  * we sent a success to the client; some clients send a
477                  * success back as-per the RFC, some send an ACK. Permit
478                  * both, I guess...
479                  */
480
481                 switch (ccode) {
482                 case PW_EAP_MSCHAPV2_SUCCESS:
483                         eap_ds->request->code = PW_EAP_SUCCESS;
484
485                         fr_pair_list_mcopy_by_num(request->reply, &request->reply->vps, &data->mppe_keys, 0, 0, TAG_ANY);
486                         /* FALL-THROUGH */
487
488                 case PW_EAP_MSCHAPV2_ACK:
489 #ifdef WITH_PROXY
490                         /*
491                          *      It's a success.  Don't proxy it.
492                          */
493                         request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
494 #endif
495                         fr_pair_list_mcopy_by_num(request->reply, &request->reply->vps, &data->reply, 0, 0, TAG_ANY);
496                         return 1;
497                 }
498                 REDEBUG("Sent SUCCESS expecting SUCCESS (or ACK) but got %d", ccode);
499                 return 0;
500
501         case PW_EAP_MSCHAPV2_CHALLENGE:
502                 if (ccode == PW_EAP_MSCHAPV2_FAILURE) goto failure;
503
504                 /*
505                  * we sent a challenge, expecting a response
506                  */
507                 if (ccode != PW_EAP_MSCHAPV2_RESPONSE) {
508                         REDEBUG("Sent CHALLENGE expecting RESPONSE but got %d", ccode);
509                         return 0;
510                 }
511                 /* authentication happens below */
512                 break;
513
514         default:
515                 /* should never happen */
516                 REDEBUG("Unknown state %d", data->code);
517                 return 0;
518         }
519
520
521         /*
522          *      Ensure that we have at least enough data
523          *      to do the following checks.
524          *
525          *      EAP header (4), EAP type, MS-CHAP opcode,
526          *      MS-CHAP ident, MS-CHAP data length (2),
527          *      MS-CHAP value length.
528          */
529         if (eap_ds->response->length < (4 + 1 + 1 + 1 + 2 + 1)) {
530                 REDEBUG("Response is too short");
531                 return 0;
532         }
533
534         /*
535          *      The 'value_size' is the size of the response,
536          *      which is supposed to be the response (48
537          *      bytes) plus 1 byte of flags at the end.
538          */
539         if (eap_ds->response->type.data[4] != 49) {
540                 REDEBUG("Response is of incorrect length %d", eap_ds->response->type.data[4]);
541                 return 0;
542         }
543
544         /*
545          *      The MS-Length field is 5 + value_size + length
546          *      of name, which is put after the response.
547          */
548         length = (eap_ds->response->type.data[2] << 8) | eap_ds->response->type.data[3];
549         if ((length < (5 + 49)) || (length > (256 + 5 + 49))) {
550                 REDEBUG("Response contains contradictory length %zu %d", length, 5 + 49);
551                 return 0;
552         }
553
554         /*
555          *      We now know that the user has sent us a response
556          *      to the challenge.  Let's try to authenticate it.
557          *
558          *      We do this by taking the challenge from 'data',
559          *      the response from the EAP packet, and creating VALUE_PAIR's
560          *      to pass to the 'mschap' module.  This is a little wonky,
561          *      but it works.
562          */
563         challenge = pair_make_request("MS-CHAP-Challenge", NULL, T_OP_EQ);
564         if (!challenge) return 0;
565         fr_pair_value_memcpy(challenge, data->challenge, MSCHAPV2_CHALLENGE_LEN);
566
567         response = pair_make_request("MS-CHAP2-Response", NULL, T_OP_EQ);
568         if (!response) return 0;
569         response->vp_length = MSCHAPV2_RESPONSE_LEN;
570         response->vp_octets = p = talloc_array(response, uint8_t, response->vp_length);
571
572         p[0] = eap_ds->response->type.data[1];
573         p[1] = eap_ds->response->type.data[5 + MSCHAPV2_RESPONSE_LEN];
574         memcpy(p + 2, &eap_ds->response->type.data[5], MSCHAPV2_RESPONSE_LEN - 2);
575
576         name = pair_make_request("MS-CHAP-User-Name", NULL, T_OP_EQ);
577         if (!name) return 0;
578
579         /*
580          *      MS-Length - MS-Value - 5.
581          */
582         name->vp_length = length - 49 - 5;
583         name->vp_strvalue = q = talloc_array(name, char, name->vp_length + 1);
584         memcpy(q, &eap_ds->response->type.data[4 + MSCHAPV2_RESPONSE_LEN], name->vp_length);
585         q[name->vp_length] = '\0';
586
587 packet_ready:
588
589 #ifdef WITH_PROXY
590         /*
591          *      If this options is set, then we do NOT authenticate the
592          *      user here.  Instead, now that we've added the MS-CHAP
593          *      attributes to the request, we STOP, and let the outer
594          *      tunnel code handle it.
595          *
596          *      This means that the outer tunnel code will DELETE the
597          *      EAP attributes, and proxy the MS-CHAP attributes to a
598          *      home server.
599          */
600         if (request->options & RAD_REQUEST_OPTION_PROXY_EAP) {
601                 char *username = NULL;
602                 eap_tunnel_data_t *tunnel;
603
604                 RDEBUG2("Cancelling authentication and letting it be proxied");
605
606                 /*
607                  *      Set up the callbacks for the tunnel
608                  */
609                 tunnel = talloc_zero(request, eap_tunnel_data_t);
610
611                 tunnel->tls_session = arg;
612                 tunnel->callback = mschap_postproxy;
613
614                 /*
615                  *      Associate the callback with the request.
616                  */
617                 rcode = request_data_add(request,
618                                          request->proxy,
619                                          REQUEST_DATA_EAP_TUNNEL_CALLBACK,
620                                          tunnel, false);
621                 rad_assert(rcode == 0);
622
623                 /*
624                  *      The State attribute is NOT supposed to
625                  *      go into the proxied packet, it will confuse
626                  *      other RADIUS servers, and they will discard
627                  *      the request.
628                  *
629                  *      The PEAP module will take care of adding
630                  *      the State attribute back, before passing
631                  *      the handler & request back into the tunnel.
632                  */
633                 fr_pair_delete_by_num(&request->packet->vps, PW_STATE, 0, TAG_ANY);
634
635                 /*
636                  *      Fix the User-Name when proxying, to strip off
637                  *      the NT Domain, if we're told to, and a User-Name
638                  *      exists, and there's a \\, meaning an NT-Domain
639                  *      in the user name, THEN discard the user name.
640                  */
641                 if (inst->with_ntdomain_hack &&
642                     ((challenge = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY)) != NULL) &&
643                     ((username = strchr(challenge->vp_strvalue, '\\')) != NULL)) {
644                         /*
645                          *      Wipe out the NT domain.
646                          *
647                          *      FIXME: Put it into MS-CHAP-Domain?
648                          */
649                         username++; /* skip the \\ */
650                         fr_pair_value_strcpy(challenge, username);
651                 }
652
653                 /*
654                  *      Remember that in the post-proxy stage, we've got
655                  *      to do the work below, AFTER the call to MS-CHAP
656                  *      authentication...
657                  */
658                 return 1;
659         }
660 #endif
661
662         /*
663          *      This is a wild & crazy hack.
664          */
665         rcode = process_authenticate(PW_AUTH_TYPE_MS_CHAP, request);
666
667         /*
668          *      Delete MPPE keys & encryption policy.  We don't
669          *      want these here.
670          */
671         fix_mppe_keys(handler, data);
672
673         /*
674          *      Take the response from the mschap module, and
675          *      return success or failure, depending on the result.
676          */
677         response = NULL;
678         if (rcode == RLM_MODULE_OK) {
679                 fr_pair_list_mcopy_by_num(data, &response, &request->reply->vps, PW_MSCHAP2_SUCCESS, VENDORPEC_MICROSOFT, TAG_ANY);
680                 data->code = PW_EAP_MSCHAPV2_SUCCESS;
681         } else if (inst->send_error) {
682                 fr_pair_list_mcopy_by_num(data, &response, &request->reply->vps, PW_MSCHAP_ERROR, VENDORPEC_MICROSOFT, TAG_ANY);
683                 if (response) {
684                         int n,err,retry;
685                         char buf[34];
686
687                         VERIFY_VP(response);
688
689                         RDEBUG2("MSCHAP-Error: %s", response->vp_strvalue);
690
691                         /*
692                          *      Parse the new challenge out of the
693                          *      MS-CHAP-Error, so that if the client
694                          *      issues a re-try, we will know which
695                          *      challenge value that they used.
696                          */
697                         n = sscanf(response->vp_strvalue, "%*cE=%d R=%d C=%32s", &err, &retry, &buf[0]);
698                         if (n == 3) {
699                                 RDEBUG2("Found new challenge from MS-CHAP-Error: err=%d retry=%d challenge=%s",
700                                         err, retry, buf);
701                                 fr_hex2bin(data->challenge, 16, buf, strlen(buf));
702                         } else {
703                                 RDEBUG2("Could not parse new challenge from MS-CHAP-Error: %d", n);
704                         }
705                 }
706                 data->code = PW_EAP_MSCHAPV2_FAILURE;
707         } else {
708                 eap_ds->request->code = PW_EAP_FAILURE;
709                 return 1;
710         }
711
712         /*
713          *      No response, die.
714          */
715         if (!response) {
716                 REDEBUG("No MS-CHAP2-Success or MS-CHAP-Error was found");
717                 return 0;
718         }
719
720         /*
721          *      Compose the response (whatever it is),
722          *      and return it to the over-lying EAP module.
723          */
724         eapmschapv2_compose(inst, handler, response);
725         fr_pair_list_free(&response);
726
727         return 1;
728 }
729
730 /*
731  *      The module name should be the only globally exported symbol.
732  *      That is, everything else should be 'static'.
733  */
734 extern rlm_eap_module_t rlm_eap_mschapv2;
735 rlm_eap_module_t rlm_eap_mschapv2 = {
736         .name           = "eap_mschapv2",
737         .instantiate    = mod_instantiate,      /* Create new submodule instance */
738         .session_init   = mod_session_init,     /* Initialise a new EAP session */
739         .process        = mod_process           /* Process next round of EAP method */
740 };