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