Include files used to build the server are now <freeradius-devel/*.h>
[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 <freeradius-devel/autoconf.h>
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         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->vp_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->vp_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->vp_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->vp_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                 /*
453                  *      It's a success.  Don't proxy it.
454                  */
455                 handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
456
457                 eap_ds->request->code = PW_EAP_SUCCESS;
458                 return 1;
459                 break;
460
461                 /*
462                  *      Something else, we don't know what it is.
463                  */
464         default:
465                 radlog(L_ERR, "rlm_eap_mschapv2: Invalid response type %d",
466                        eap_ds->response->type.data[0]);
467                 return 0;
468         }
469
470         /*
471          *      We now know that the user has sent us a response
472          *      to the challenge.  Let's try to authenticate it.
473          *
474          *      We do this by taking the challenge from 'data',
475          *      the response from the EAP packet, and creating VALUE_PAIR's
476          *      to pass to the 'mschap' module.  This is a little wonky,
477          *      but it works.
478          */
479         challenge = pairmake("MS-CHAP-Challenge", "0x00", T_OP_EQ);
480         if (!challenge) {
481                 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
482                 return 0;
483         }
484         challenge->length = MSCHAPV2_CHALLENGE_LEN;
485         memcpy(challenge->vp_strvalue, data->challenge, MSCHAPV2_CHALLENGE_LEN);
486
487         response = pairmake("MS-CHAP2-Response", "0x00", T_OP_EQ);
488         if (!response) {
489                 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
490                 return 0;
491         }
492
493         response->length = MSCHAPV2_RESPONSE_LEN;
494         memcpy(response->vp_strvalue + 2, &eap_ds->response->type.data[5],
495                MSCHAPV2_RESPONSE_LEN - 2);
496         response->vp_strvalue[0] = eap_ds->response->type.data[1];
497         response->vp_strvalue[1] = eap_ds->response->type.data[5 + MSCHAPV2_RESPONSE_LEN];
498
499         /*
500          *      Add the pairs to the request, and call the 'mschap'
501          *      module.
502          */
503         pairadd(&handler->request->packet->vps, challenge);
504         pairadd(&handler->request->packet->vps, response);
505
506         /*
507          *      If this options is set, then we do NOT authenticate the
508          *      user here.  Instead, now that we've added the MS-CHAP
509          *      attributes to the request, we STOP, and let the outer
510          *      tunnel code handle it.
511          *
512          *      This means that the outer tunnel code will DELETE the
513          *      EAP attributes, and proxy the MS-CHAP attributes to a
514          *      home server.
515          */
516         if (handler->request->options & RAD_REQUEST_OPTION_PROXY_EAP) {
517                 char *username = NULL;
518                 eap_tunnel_data_t *tunnel;
519                 rlm_eap_mschapv2_t *inst = (rlm_eap_mschapv2_t *) arg;
520                 
521                 /*
522                  *      Set up the callbacks for the tunnel
523                  */
524                 tunnel = rad_malloc(sizeof(*tunnel));
525                 memset(tunnel, 0, sizeof(*tunnel));
526                 
527                 tunnel->tls_session = arg;
528                 tunnel->callback = mschap_postproxy;
529                 
530                 /*
531                  *      Associate the callback with the request.
532                  */
533                 rcode = request_data_add(handler->request,
534                                          handler->request->proxy,
535                                          REQUEST_DATA_EAP_TUNNEL_CALLBACK,
536                                          tunnel, free);
537                 rad_assert(rcode == 0);
538
539                 /*
540                  *      The State attribute is NOT supposed to
541                  *      go into the proxied packet, it will confuse
542                  *      other RADIUS servers, and they will discard
543                  *      the request.
544                  *
545                  *      The PEAP module will take care of adding
546                  *      the State attribute back, before passing
547                  *      the handler & request back into the tunnel.
548                  */
549                 pairdelete(&handler->request->packet->vps, PW_STATE);
550
551                 /*
552                  *      Fix the User-Name when proxying, to strip off
553                  *      the NT Domain, if we're told to, and a User-Name
554                  *      exists, and there's a \\, meaning an NT-Domain
555                  *      in the user name, THEN discard the user name.
556                  */
557                 if (inst->with_ntdomain_hack &&
558                     ((challenge = pairfind(handler->request->packet->vps,
559                                            PW_USER_NAME)) != NULL) &&
560                     ((username = strchr(challenge->vp_strvalue, '\\')) != NULL)) {
561                         /*
562                          *      Wipe out the NT domain.
563                          *
564                          *      FIXME: Put it into MS-CHAP-Domain?
565                          */
566                         username++; /* skip the \\ */
567                         memmove(challenge->vp_strvalue,
568                                 username,
569                                 strlen(username) + 1); /* include \0 */
570                         challenge->length = strlen(challenge->vp_strvalue);
571                 }
572
573                 /*
574                  *      Remember that in the post-proxy stage, we've got
575                  *      to do the work below, AFTER the call to MS-CHAP
576                  *      authentication...
577                  */
578                 return 1;
579         }
580
581         /*
582          *      This is a wild & crazy hack.
583          */
584         rcode = module_authenticate(PW_AUTHTYPE_MS_CHAP, handler->request);
585
586         /*
587          *      Delete MPPE keys & encryption policy.  We don't
588          *      want these here.
589          */
590         pairdelete(&handler->request->reply->vps, ((311 << 16) | 7));
591         pairdelete(&handler->request->reply->vps, ((311 << 16) | 8));
592         pairdelete(&handler->request->reply->vps, ((311 << 16) | 16));
593         pairdelete(&handler->request->reply->vps, ((311 << 16) | 17));
594
595         /*
596          *      Take the response from the mschap module, and
597          *      return success or failure, depending on the result.
598          */
599         if (rcode == RLM_MODULE_OK) {
600                 response = paircopy2(handler->request->reply->vps,
601                                      PW_MSCHAP2_SUCCESS);
602                 data->code = PW_EAP_MSCHAPV2_SUCCESS;
603         } else {
604                 /*
605                  *      Don't return anything in the error message.
606                  */
607                 eap_ds->request->code = PW_EAP_FAILURE;
608                 return 1;
609 #if 0
610                 response = paircopy2(handler->request->reply->vps,
611                                      PW_MSCHAP_ERROR);
612                 data->code = PW_EAP_MSCHAPV2_FAILURE;
613 #endif
614         }
615
616         /*
617          *      No response, die.
618          */
619         if (!response) {
620                 radlog(L_ERR, "rlm_eap_mschapv2: No MS-CHAPv2-Success or MS-CHAP-Error was found.");
621                 return 0;
622         }
623
624         /*
625          *      Compose the response (whatever it is),
626          *      and return it to the over-lying EAP module.
627          */
628         eapmschapv2_compose(handler, response);
629         pairfree(&response);
630
631         return 1;
632 }
633
634 /*
635  *      The module name should be the only globally exported symbol.
636  *      That is, everything else should be 'static'.
637  */
638 EAP_TYPE rlm_eap_mschapv2 = {
639         "eap_mschapv2",
640         mschapv2_attach,                /* attach */
641         mschapv2_initiate,              /* Start the initial request */
642         NULL,                           /* authorization */
643         mschapv2_authenticate,          /* authentication */
644         mschapv2_detach                 /* detach */
645 };