When we have a tunneled MS-CHAP2-Success attribute, it goes
[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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  * Copyright 2003  The FreeRADIUS server project
21  */
22
23 #include "autoconf.h"
24
25 #include <stdio.h>
26 #include <stdlib.h>
27
28 #include "eap_mschapv2.h"
29
30 #include <rad_assert.h>
31
32 typedef struct rlm_eap_mschapv2_t {
33         int with_ntdomain_hack;
34 } rlm_eap_mschapv2_t;
35
36 static CONF_PARSER module_config[] = {
37         { "with_ntdomain_hack",     PW_TYPE_BOOLEAN,
38           offsetof(rlm_eap_mschapv2_t,with_ntdomain_hack), NULL, "no" },
39
40         { NULL, -1, 0, NULL, NULL }             /* end the list */
41 };
42
43
44 /*
45  *      Detach the module.
46  */
47 static int mschapv2_detach(void *arg)
48 {
49         rlm_eap_mschapv2_t *inst = (rlm_eap_mschapv2_t *) arg;
50
51         free(inst);
52
53         return 0;
54 }
55
56
57 /*
58  *      Attach the module.
59  */
60 static int mschapv2_attach(CONF_SECTION *cs, void **instance)
61 {
62         rlm_eap_mschapv2_t *inst;
63
64         inst = malloc(sizeof(*inst));
65         if (!inst) {
66                 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
67                 return -1;
68         }
69         memset(inst, 0, sizeof(*inst));
70
71         /*
72          *      Parse the configuration attributes.
73          */
74         if (cf_section_parse(cs, inst, module_config) < 0) {
75                 mschapv2_detach(inst);
76                 return -1;
77         }
78
79         *instance = inst;
80
81         return 0;
82 }
83
84
85 /*
86  *      Compose the response.
87  */
88 static int eapmschapv2_compose(EAP_HANDLER *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
95         eap_ds->request->code = PW_EAP_REQUEST;
96         eap_ds->request->type.type = PW_EAP_MSCHAPV2;
97
98         switch (reply->attribute) {
99         case PW_MSCHAP_CHALLENGE:
100                 /*
101                  *   0                   1                   2                   3
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                  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
110                  *  |                             Challenge...
111                  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
112                  *  |                             Name...
113                  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
114                  */
115                 length = MSCHAPV2_HEADER_LEN + MSCHAPV2_CHALLENGE_LEN + strlen(handler->identity);
116                 eap_ds->request->type.data = malloc(length);
117                 /*
118                  *      Allocate room for the EAP-MS-CHAPv2 data.
119                  */
120                 if (eap_ds->request->type.data == NULL) {
121                         radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
122                         return 0;
123                 }
124                 eap_ds->request->type.length = length;
125
126                 ptr = eap_ds->request->type.data;
127                 hdr = eap_ds->request->type.data;
128
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;
134
135                 ptr += MSCHAPV2_HEADER_LEN;
136
137                 /*
138                  *      Copy the Challenge, success, or error over.
139                  */
140                 memcpy(ptr, reply->strvalue, reply->length);
141                 memcpy((ptr + reply->length), handler->identity, strlen(handler->identity));
142                 break;
143
144         case PW_MSCHAP2_SUCCESS:
145                 /*
146                  *   0                   1                   2                   3
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                  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
155                  */
156                 DEBUG2("MSCHAP Success\n");
157                 length = 46;
158                 eap_ds->request->type.data = malloc(length);
159                 memset(eap_ds->request->type.data, 0, length);
160                 /*
161                  *      Allocate room for the EAP-MS-CHAPv2 data.
162                  */
163                 if (eap_ds->request->type.data == NULL) {
164                         radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
165                         return 0;
166                 }
167                 eap_ds->request->type.length = length;
168
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->strvalue + 1, 42);
174                 break;
175
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);
181
182                 /*
183                  *      Allocate room for the EAP-MS-CHAPv2 data.
184                  */
185                 if (eap_ds->request->type.data == NULL) {
186                         radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
187                         return 0;
188                 }
189                 eap_ds->request->type.length = length;
190
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);
196                 break;
197
198         default:
199                 radlog(L_ERR, "rlm_eap_mschapv2: Internal sanity check failed");
200                 return 0;
201                 break;
202         }
203
204         return 1;
205 }
206
207
208 /*
209  *      Initiate the EAP-MSCHAPV2 session by sending a challenge to the peer.
210  */
211 static int mschapv2_initiate(void *type_data, EAP_HANDLER *handler)
212 {
213         int             i;
214         VALUE_PAIR      *challenge;
215         mschapv2_opaque_t *data;
216
217         type_data = type_data;  /* -Wunused */
218
219         challenge = pairmake("MS-CHAP-Challenge", "0x00", T_OP_EQ);
220         if (!challenge) {
221                 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
222                 return 0;
223         }
224
225         /*
226          *      Get a random challenge.
227          */
228         challenge->length = MSCHAPV2_CHALLENGE_LEN;
229         for (i = 0; i < MSCHAPV2_CHALLENGE_LEN; i++) {
230                 challenge->strvalue[i] = lrad_rand();
231         }
232         radlog(L_INFO, "rlm_eap_mschapv2: Issuing Challenge");
233
234         /*
235          *      Keep track of the challenge.
236          */
237         data = malloc(sizeof(mschapv2_opaque_t));
238         rad_assert(data != NULL);
239
240         /*
241          *      We're at the stage where we're challenging the user.
242          */
243         data->code = PW_EAP_MSCHAPV2_CHALLENGE;
244         memcpy(data->challenge, challenge->strvalue, MSCHAPV2_CHALLENGE_LEN);
245
246         handler->opaque = data;
247         handler->free_opaque = free;
248
249         /*
250          *      Compose the EAP-MSCHAPV2 packet out of the data structure,
251          *      and free it.
252          */
253         eapmschapv2_compose(handler, challenge);
254         pairfree(&challenge);
255
256         /*
257          *      The EAP session doesn't have enough information to
258          *      proxy the "inside EAP" protocol.  Disable EAP proxying.
259          */
260         handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
261
262         /*
263          *      We don't need to authorize the user at this point.
264          *
265          *      We also don't need to keep the challenge, as it's
266          *      stored in 'handler->eap_ds', which will be given back
267          *      to us...
268          */
269         handler->stage = AUTHENTICATE;
270
271         return 1;
272 }
273
274
275 /*
276  *      Do post-proxy processing,
277  *      0 = fail
278  *      1 = OK.
279  *
280  *      Called from rlm_eap.c, eap_postproxy().
281  */
282 static int mschap_postproxy(EAP_HANDLER *handler, void *tunnel_data)
283 {
284         VALUE_PAIR *response = NULL;
285         mschapv2_opaque_t *data;
286
287         data = (mschapv2_opaque_t *) handler->opaque;
288         rad_assert(data != NULL);
289
290         tunnel_data = tunnel_data; /* -Wunused */
291
292         DEBUG2("  rlm_eap_mschapv2: Passing reply from proxy back into the tunnel %p %d.",
293                handler->request, handler->request->reply->code);
294
295         /*
296          *      There is only a limited number of possibilities.
297          */
298         switch (handler->request->reply->code) {
299         case PW_AUTHENTICATION_ACK:
300                 DEBUG("  rlm_eap_mschapv2: Authentication succeeded.");
301                 /*
302                  *      Move the attribute, so it doesn't go into
303                  *      the reply.
304                  */
305                 pairmove2(&response,
306                           &handler->request->reply->vps,
307                           PW_MSCHAP2_SUCCESS);
308                 break;
309
310         default:
311         case PW_AUTHENTICATION_REJECT:
312                 DEBUG("  rlm_eap_mschapv2: Authentication did not succeed.");
313                 return 0;
314         }
315
316         /*
317          *      No response, die.
318          */
319         if (!response) {
320                 radlog(L_ERR, "rlm_eap_mschapv2: No MS-CHAPv2-Success or MS-CHAP-Error was found.");
321                 return 0;
322         }
323
324         /*
325          *      Done doing EAP proxy stuff.
326          */
327         handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
328         eapmschapv2_compose(handler, response);
329         data->code = PW_EAP_MSCHAPV2_SUCCESS;
330
331         /*
332          *      Delete MPPE keys & encryption policy
333          *
334          *      FIXME: Use intelligent names...
335          */
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));
340
341         /*
342          *      And we need to challenge the user, not ack/reject them,
343          *      so we re-write the ACK to a challenge.  Yuck.
344          */
345         handler->request->reply->code = PW_ACCESS_CHALLENGE;
346         pairfree(&response);
347
348         return 1;
349 }
350
351
352 /*
353  *      Authenticate a previously sent challenge.
354  */
355 static int mschapv2_authenticate(void *arg, EAP_HANDLER *handler)
356 {
357         int rcode;
358         mschapv2_opaque_t *data;
359         EAP_DS *eap_ds = handler->eap_ds;
360         VALUE_PAIR *challenge, *response;
361
362         /*
363          *      Get the User-Password for this user.
364          */
365         rad_assert(handler->request != NULL);
366         rad_assert(handler->stage == AUTHENTICATE);
367
368         data = (mschapv2_opaque_t *) handler->opaque;
369
370         /*
371          *      Sanity check the response.
372          */
373         if (eap_ds->response->length <= 4) {
374                 radlog(L_ERR, "rlm_eap_mschapv2: corrupted data");
375                 return 0;
376         }
377
378         /*
379          *      Switch over the MS-CHAP type.
380          */
381         switch (eap_ds->response->type.data[0]) {
382                 /*
383                  *      We should get an ACK from the client ONLY if we've
384                  *      sent them a SUCCESS packet.
385                  */
386                 case PW_EAP_MSCHAPV2_ACK:
387                 if (data->code != PW_EAP_MSCHAPV2_SUCCESS) {
388                         radlog(L_ERR, "rlm_eap_mschapv2: Unexpected ACK received");
389                         return 0;
390                 }
391
392                 /*
393                  *      It's a success.  Don't proxy it.
394                  */
395                 handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
396
397                 /*
398                  *      And upon receiving the clients ACK, we do nothing
399                  *      other than return EAP-Success, with no EAP-MS-CHAPv2
400                  *      data.
401                  */
402                 return 1;
403                 break;
404
405                 /*
406                  *      We should get a response ONLY after we've sent
407                  *      a challenge.
408                  */
409         case PW_EAP_MSCHAPV2_RESPONSE:
410                 if (data->code != PW_EAP_MSCHAPV2_CHALLENGE) {
411                         radlog(L_ERR, "rlm_eap_mschapv2: Unexpected response received");
412                         return 0;
413                 }
414
415                 /*
416                  *      Ensure that we have at least enough data
417                  *      to do the following checks.
418                  *
419                  *      EAP header (4), EAP type, MS-CHAP opcode,
420                  *      MS-CHAP ident, MS-CHAP data length (2),
421                  *      MS-CHAP value length.
422                  */
423                 if (eap_ds->response->length < (4 + 1 + 1 + 1 + 2 + 1)) {
424                         radlog(L_ERR, "rlm_eap_mschapv2: Response is too short");
425                         return 0;
426                 }
427
428                 /*
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.
432                  */
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]);
435                         return 0;
436                 }
437
438                 /*
439                  *      The MS-Length field is 5 + value_size + length
440                  *      of name, which is put after the response.
441                  */
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);
447                         return 0;
448                 }
449                 break;
450
451         case PW_EAP_MSCHAPV2_SUCCESS:
452                 if (data->code != PW_EAP_MSCHAPV2_SUCCESS) {
453                         radlog(L_ERR, "rlm_eap_mschapv2: Unexpected success received");
454                         return 0;
455                 }
456
457                 /*
458                  *      It's a success.  Don't proxy it.
459                  */
460                 handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
461
462                 eap_ds->request->code = PW_EAP_SUCCESS;
463                 return 1;
464                 break;
465
466                 /*
467                  *      Something else, we don't know what it is.
468                  */
469         default:
470                 radlog(L_ERR, "rlm_eap_mschapv2: Invalid response type %d",
471                        eap_ds->response->type.data[0]);
472                 return 0;
473         }
474
475         /*
476          *      We now know that the user has sent us a response
477          *      to the challenge.  Let's try to authenticate it.
478          *
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,
482          *      but it works.
483          */
484         challenge = pairmake("MS-CHAP-Challenge", "0x00", T_OP_EQ);
485         if (!challenge) {
486                 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
487                 return 0;
488         }
489         challenge->length = MSCHAPV2_CHALLENGE_LEN;
490         memcpy(challenge->strvalue, data->challenge, MSCHAPV2_CHALLENGE_LEN);
491
492         response = pairmake("MS-CHAP2-Response", "0x00", T_OP_EQ);
493         if (!response) {
494                 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
495                 return 0;
496         }
497
498         response->length = MSCHAPV2_RESPONSE_LEN;
499         memcpy(response->strvalue + 2, &eap_ds->response->type.data[5],
500                MSCHAPV2_RESPONSE_LEN - 2);
501         response->strvalue[0] = eap_ds->response->type.data[1];
502         response->strvalue[1] = eap_ds->response->type.data[5 + MSCHAPV2_RESPONSE_LEN];
503
504         /*
505          *      Add the pairs to the request, and call the 'mschap'
506          *      module.
507          */
508         pairadd(&handler->request->packet->vps, challenge);
509         pairadd(&handler->request->packet->vps, response);
510
511         /*
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.
516          *
517          *      This means that the outer tunnel code will DELETE the
518          *      EAP attributes, and proxy the MS-CHAP attributes to a
519          *      home server.
520          */
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;
525                 
526                 /*
527                  *      Set up the callbacks for the tunnel
528                  */
529                 tunnel = rad_malloc(sizeof(*tunnel));
530                 memset(tunnel, 0, sizeof(*tunnel));
531                 
532                 tunnel->tls_session = arg;
533                 tunnel->callback = mschap_postproxy;
534                 
535                 /*
536                  *      Associate the callback with the request.
537                  */
538                 rcode = request_data_add(handler->request,
539                                          handler->request->proxy,
540                                          REQUEST_DATA_EAP_TUNNEL_CALLBACK,
541                                          tunnel, free);
542                 rad_assert(rcode == 0);
543
544                 /*
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
548                  *      the request.
549                  *
550                  *      The PEAP module will take care of adding
551                  *      the State attribute back, before passing
552                  *      the handler & request back into the tunnel.
553                  */
554                 pairdelete(&handler->request->packet->vps, PW_STATE);
555
556                 /*
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.
561                  */
562                 if (inst->with_ntdomain_hack &&
563                     ((challenge = pairfind(handler->request->packet->vps,
564                                            PW_USER_NAME)) != NULL) &&
565                     ((username = strchr(challenge->strvalue, '\\')) != NULL)) {
566                         /*
567                          *      Wipe out the NT domain.
568                          *
569                          *      FIXME: Put it into MS-CHAP-Domain?
570                          */
571                         username++; /* skip the \\ */
572                         memmove(challenge->strvalue,
573                                 username,
574                                 strlen(username) + 1); /* include \0 */
575                         challenge->length = strlen(challenge->strvalue);
576                 }
577
578                 /*
579                  *      Remember that in the post-proxy stage, we've got
580                  *      to do the work below, AFTER the call to MS-CHAP
581                  *      authentication...
582                  */
583                 return 1;
584         }
585
586         /*
587          *      This is a wild & crazy hack.
588          */
589         rcode = module_authenticate(PW_AUTHTYPE_MS_CHAP, handler->request);
590
591         /*
592          *      Delete MPPE keys & encryption policy.  We don't
593          *      want these here.
594          */
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));
599
600         /*
601          *      Take the response from the mschap module, and
602          *      return success or failure, depending on the result.
603          */
604         if (rcode == RLM_MODULE_OK) {
605                 response = paircopy2(handler->request->reply->vps,
606                                      PW_MSCHAP2_SUCCESS);
607                 data->code = PW_EAP_MSCHAPV2_SUCCESS;
608         } else {
609                 /*
610                  *      Don't return anything in the error message.
611                  */
612                 eap_ds->request->code = PW_EAP_FAILURE;
613                 return 1;
614 #if 0
615                 response = paircopy2(handler->request->reply->vps,
616                                      PW_MSCHAP_ERROR);
617                 data->code = PW_EAP_MSCHAPV2_FAILURE;
618 #endif
619         }
620
621         /*
622          *      No response, die.
623          */
624         if (!response) {
625                 radlog(L_ERR, "rlm_eap_mschapv2: No MS-CHAPv2-Success or MS-CHAP-Error was found.");
626                 return 0;
627         }
628
629         /*
630          *      Compose the response (whatever it is),
631          *      and return it to the over-lying EAP module.
632          */
633         eapmschapv2_compose(handler, response);
634         pairfree(&response);
635
636         return 1;
637 }
638
639 /*
640  *      The module name should be the only globally exported symbol.
641  *      That is, everything else should be 'static'.
642  */
643 EAP_TYPE rlm_eap_mschapv2 = {
644         "eap_mschapv2",
645         mschapv2_attach,                /* attach */
646         mschapv2_initiate,              /* Start the initial request */
647         NULL,                           /* authorization */
648         mschapv2_authenticate,          /* authentication */
649         mschapv2_detach                 /* detach */
650 };