2 * rlm_eap_mschapv2.c Handles that are called from eap
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * Copyright 2003,2006 The FreeRADIUS server project
23 #include <freeradius-devel/ident.h>
29 #include "eap_mschapv2.h"
31 #include <freeradius-devel/rad_assert.h>
33 typedef struct rlm_eap_mschapv2_t {
34 int with_ntdomain_hack;
38 static CONF_PARSER module_config[] = {
39 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
40 offsetof(rlm_eap_mschapv2_t,with_ntdomain_hack), NULL, "no" },
42 { "send_error", PW_TYPE_BOOLEAN,
43 offsetof(rlm_eap_mschapv2_t,send_error), NULL, "no" },
45 { NULL, -1, 0, NULL, NULL } /* end the list */
49 static void fix_mppe_keys(EAP_HANDLER *handler, mschapv2_opaque_t *data)
51 pairmove2(&data->mppe_keys, &handler->request->reply->vps, 7, VENDORPEC_MICROSOFT);
52 pairmove2(&data->mppe_keys, &handler->request->reply->vps, 8, VENDORPEC_MICROSOFT);
53 pairmove2(&data->mppe_keys, &handler->request->reply->vps, 16, VENDORPEC_MICROSOFT);
54 pairmove2(&data->mppe_keys, &handler->request->reply->vps, 17, VENDORPEC_MICROSOFT);
57 static void free_data(void *ptr)
59 mschapv2_opaque_t *data = ptr;
61 pairfree(&data->mppe_keys);
62 pairfree(&data->reply);
69 static int mschapv2_detach(void *arg)
71 rlm_eap_mschapv2_t *inst = (rlm_eap_mschapv2_t *) arg;
82 static int mschapv2_attach(CONF_SECTION *cs, void **instance)
84 rlm_eap_mschapv2_t *inst;
86 inst = malloc(sizeof(*inst));
88 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
91 memset(inst, 0, sizeof(*inst));
94 * Parse the configuration attributes.
96 if (cf_section_parse(cs, inst, module_config) < 0) {
97 mschapv2_detach(inst);
108 * Compose the response.
110 static int eapmschapv2_compose(EAP_HANDLER *handler, VALUE_PAIR *reply)
114 mschapv2_header_t *hdr;
115 EAP_DS *eap_ds = handler->eap_ds;
117 eap_ds->request->code = PW_EAP_REQUEST;
118 eap_ds->request->type.type = PW_EAP_MSCHAPV2;
120 switch (reply->attribute) {
121 case PW_MSCHAP_CHALLENGE:
124 * 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
125 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
126 * | Code | Identifier | Length |
127 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
128 * | Type | OpCode | MS-CHAPv2-ID | MS-Length...
129 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
130 * | MS-Length | Value-Size | Challenge...
131 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
133 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
135 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
137 length = MSCHAPV2_HEADER_LEN + MSCHAPV2_CHALLENGE_LEN + strlen(handler->identity);
138 eap_ds->request->type.data = malloc(length);
140 * Allocate room for the EAP-MS-CHAPv2 data.
142 if (eap_ds->request->type.data == NULL) {
143 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
146 eap_ds->request->type.length = length;
148 ptr = eap_ds->request->type.data;
149 hdr = (mschapv2_header_t *) ptr;
151 hdr->opcode = PW_EAP_MSCHAPV2_CHALLENGE;
152 hdr->mschapv2_id = eap_ds->response->id + 1;
153 length = htons(length);
154 memcpy(hdr->ms_length, &length, sizeof(uint16_t));
155 hdr->value_size = MSCHAPV2_CHALLENGE_LEN;
157 ptr += MSCHAPV2_HEADER_LEN;
160 * Copy the Challenge, success, or error over.
162 memcpy(ptr, reply->vp_strvalue, reply->length);
163 memcpy((ptr + reply->length), handler->identity, strlen(handler->identity));
166 case PW_MSCHAP2_SUCCESS:
169 * 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
170 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
171 * | Code | Identifier | Length |
172 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
173 * | Type | OpCode | MS-CHAPv2-ID | MS-Length...
174 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
175 * | MS-Length | Message...
176 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
178 DEBUG2("MSCHAP Success\n");
180 eap_ds->request->type.data = malloc(length);
182 * Allocate room for the EAP-MS-CHAPv2 data.
184 if (eap_ds->request->type.data == NULL) {
185 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
188 memset(eap_ds->request->type.data, 0, length);
189 eap_ds->request->type.length = length;
191 eap_ds->request->type.data[0] = PW_EAP_MSCHAPV2_SUCCESS;
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), reply->vp_strvalue + 1, 42);
198 case PW_MSCHAP_ERROR:
199 DEBUG2("MSCHAP Failure\n");
200 length = 4 + reply->length - 1;
201 eap_ds->request->type.data = malloc(length);
204 * Allocate room for the EAP-MS-CHAPv2 data.
206 if (eap_ds->request->type.data == NULL) {
207 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
210 memset(eap_ds->request->type.data, 0, length);
211 eap_ds->request->type.length = length;
213 eap_ds->request->type.data[0] = PW_EAP_MSCHAPV2_FAILURE;
214 eap_ds->request->type.data[1] = eap_ds->response->id;
215 length = htons(length);
216 memcpy((eap_ds->request->type.data + 2), &length, sizeof(uint16_t));
218 * Copy the entire failure message.
220 memcpy((eap_ds->request->type.data + 4),
221 reply->vp_strvalue + 1, reply->length - 1);
225 radlog(L_ERR, "rlm_eap_mschapv2: Internal sanity check failed");
235 * Initiate the EAP-MSCHAPV2 session by sending a challenge to the peer.
237 static int mschapv2_initiate(void *type_data, EAP_HANDLER *handler)
240 VALUE_PAIR *challenge;
241 mschapv2_opaque_t *data;
243 type_data = type_data; /* -Wunused */
245 challenge = pairmake("MS-CHAP-Challenge", "0x00", T_OP_EQ);
247 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
252 * Get a random challenge.
254 challenge->length = MSCHAPV2_CHALLENGE_LEN;
255 for (i = 0; i < MSCHAPV2_CHALLENGE_LEN; i++) {
256 challenge->vp_strvalue[i] = fr_rand();
258 DEBUG2("rlm_eap_mschapv2: Issuing Challenge");
261 * Keep track of the challenge.
263 data = malloc(sizeof(mschapv2_opaque_t));
264 rad_assert(data != NULL);
267 * We're at the stage where we're challenging the user.
269 data->code = PW_EAP_MSCHAPV2_CHALLENGE;
270 memcpy(data->challenge, challenge->vp_strvalue, MSCHAPV2_CHALLENGE_LEN);
271 data->mppe_keys = NULL;
274 handler->opaque = data;
275 handler->free_opaque = free_data;
278 * Compose the EAP-MSCHAPV2 packet out of the data structure,
281 eapmschapv2_compose(handler, challenge);
282 pairfree(&challenge);
286 * The EAP session doesn't have enough information to
287 * proxy the "inside EAP" protocol. Disable EAP proxying.
289 handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
293 * We don't need to authorize the user at this point.
295 * We also don't need to keep the challenge, as it's
296 * stored in 'handler->eap_ds', which will be given back
299 handler->stage = AUTHENTICATE;
306 * Do post-proxy processing,
310 * Called from rlm_eap.c, eap_postproxy().
312 static int mschap_postproxy(EAP_HANDLER *handler, void *tunnel_data)
314 VALUE_PAIR *response = NULL;
315 mschapv2_opaque_t *data;
317 data = (mschapv2_opaque_t *) handler->opaque;
318 rad_assert(data != NULL);
320 tunnel_data = tunnel_data; /* -Wunused */
322 DEBUG2(" rlm_eap_mschapv2: Passing reply from proxy back into the tunnel %p %d.",
323 handler->request, handler->request->reply->code);
326 * There is only a limited number of possibilities.
328 switch (handler->request->reply->code) {
329 case PW_AUTHENTICATION_ACK:
330 DEBUG(" rlm_eap_mschapv2: Proxied authentication succeeded.");
332 * Move the attribute, so it doesn't go into
336 &handler->request->reply->vps,
337 PW_MSCHAP2_SUCCESS, VENDORPEC_MICROSOFT);
341 case PW_AUTHENTICATION_REJECT:
342 DEBUG(" rlm_eap_mschapv2: Proxied authentication did not succeed.");
350 radlog(L_ERR, "rlm_eap_mschapv2: Proxied reply contained no MS-CHAPv2-Success or MS-CHAP-Error");
355 * Done doing EAP proxy stuff.
357 handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
358 eapmschapv2_compose(handler, response);
359 data->code = PW_EAP_MSCHAPV2_SUCCESS;
362 * Delete MPPE keys & encryption policy
364 * FIXME: Use intelligent names...
366 fix_mppe_keys(handler, data);
369 * save any other attributes for re-use in the final
370 * access-accept e.g. vlan, etc. This lets the PEAP
371 * use_tunneled_reply code work
373 data->reply = paircopy(handler->request->reply->vps);
376 * And we need to challenge the user, not ack/reject them,
377 * so we re-write the ACK to a challenge. Yuck.
379 handler->request->reply->code = PW_ACCESS_CHALLENGE;
387 * Authenticate a previously sent challenge.
389 static int mschapv2_authenticate(void *arg, EAP_HANDLER *handler)
392 mschapv2_opaque_t *data;
393 EAP_DS *eap_ds = handler->eap_ds;
394 VALUE_PAIR *challenge, *response, *name;
395 rlm_eap_mschapv2_t *inst = (rlm_eap_mschapv2_t *) arg;
397 rad_assert(handler->request != NULL);
398 rad_assert(handler->stage == AUTHENTICATE);
400 data = (mschapv2_opaque_t *) handler->opaque;
403 * Sanity check the response.
405 if (eap_ds->response->length <= 5) {
406 radlog(L_ERR, "rlm_eap_mschapv2: corrupted data");
410 ccode = eap_ds->response->type.data[0];
412 switch (data->code) {
413 case PW_EAP_MSCHAPV2_FAILURE:
414 if (ccode == PW_EAP_MSCHAPV2_RESPONSE) {
415 DEBUG2(" rlm_eap_mschapv2: authentication re-try from client after we sent a failure");
420 * if we sent error 648 (password expired) to the client
421 * we might get an MSCHAP-CPW packet here; turn it into a
422 * regular MS-CHAP2-CPW packet and pass it to rlm_mschap
423 * (or proxy it, I guess)
425 if (ccode == PW_EAP_MSCHAPV2_CHGPASSWD) {
427 int mschap_id = eap_ds->response->type.data[1];
430 DEBUG2(" rlm_eap_mschapv2: password change packet received");
432 challenge = pairmake("MS-CHAP-Challenge", "0x00", T_OP_EQ);
434 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
437 challenge->length = MSCHAPV2_CHALLENGE_LEN;
438 memcpy(challenge->vp_strvalue, data->challenge, MSCHAPV2_CHALLENGE_LEN);
439 pairadd(&handler->request->packet->vps, challenge);
441 cpw = pairmake("MS-CHAP2-CPW", "", T_OP_EQ);
442 cpw->vp_octets[0] = 7;
443 cpw->vp_octets[1] = mschap_id;
444 memcpy(cpw->vp_octets+2, eap_ds->response->type.data + 520, 66);
446 pairadd(&handler->request->packet->vps, cpw);
449 * break the encoded password into VPs (3 of them)
451 while (copied < 516) {
454 int to_copy = 516 - copied;
458 nt_enc = pairmake("MS-CHAP-NT-Enc-PW", "", T_OP_ADD);
459 nt_enc->vp_octets[0] = 6;
460 nt_enc->vp_octets[1] = mschap_id;
461 nt_enc->vp_octets[2] = 0;
462 nt_enc->vp_octets[3] = seq++;
464 memcpy(nt_enc->vp_octets + 4, eap_ds->response->type.data + 4 + copied, to_copy);
466 nt_enc->length = 4 + to_copy;
467 pairadd(&handler->request->packet->vps, nt_enc);
470 DEBUG2(" rlm_eap_mschapv2: built change password packet");
471 debug_pair_list(handler->request->packet->vps);
474 * jump to "authentication"
480 * we sent a failure and are expecting a failure back
482 if (ccode != PW_EAP_MSCHAPV2_FAILURE) {
483 radlog(L_ERR, "rlm_eap_mschapv2: Sent FAILURE expecting FAILURE but got %d", ccode);
488 handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
489 eap_ds->request->code = PW_EAP_FAILURE;
492 case PW_EAP_MSCHAPV2_SUCCESS:
494 * we sent a success to the client; some clients send a
495 * success back as-per the RFC, some send an ACK. Permit
500 case PW_EAP_MSCHAPV2_SUCCESS:
501 eap_ds->request->code = PW_EAP_SUCCESS;
502 pairadd(&handler->request->reply->vps, data->mppe_keys);
503 data->mppe_keys = NULL;
504 /* fall through... */
506 case PW_EAP_MSCHAPV2_ACK:
509 * It's a success. Don't proxy it.
511 handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
513 pairadd(&handler->request->reply->vps, data->reply);
517 radlog(L_ERR, "rlm_eap_mschapv2: Sent SUCCESS expecting SUCCESS (or ACK) but got %d", ccode);
520 case PW_EAP_MSCHAPV2_CHALLENGE:
521 if (ccode == PW_EAP_MSCHAPV2_FAILURE) goto failure;
524 * we sent a challenge, expecting a response
526 if (ccode != PW_EAP_MSCHAPV2_RESPONSE) {
527 radlog(L_ERR, "rlm_eap_mschapv2: Sent CHALLENGE expecting RESPONSE but got %d", ccode);
530 /* authentication happens below */
535 /* should never happen */
536 radlog(L_ERR, "rlm_eap_mschapv2: unknown state %d", data->code);
542 * Ensure that we have at least enough data
543 * to do the following checks.
545 * EAP header (4), EAP type, MS-CHAP opcode,
546 * MS-CHAP ident, MS-CHAP data length (2),
547 * MS-CHAP value length.
549 if (eap_ds->response->length < (4 + 1 + 1 + 1 + 2 + 1)) {
550 radlog(L_ERR, "rlm_eap_mschapv2: Response is too short");
555 * The 'value_size' is the size of the response,
556 * which is supposed to be the response (48
557 * bytes) plus 1 byte of flags at the end.
559 if (eap_ds->response->type.data[4] != 49) {
560 radlog(L_ERR, "rlm_eap_mschapv2: Response is of incorrect length %d", eap_ds->response->type.data[4]);
565 * The MS-Length field is 5 + value_size + length
566 * of name, which is put after the response.
568 if (((eap_ds->response->type.data[2] << 8) |
569 eap_ds->response->type.data[3]) < (5 + 49)) {
570 radlog(L_ERR, "rlm_eap_mschapv2: Response contains contradictory length %d %d",
571 (eap_ds->response->type.data[2] << 8) |
572 eap_ds->response->type.data[3], 5 + 49);
577 * We now know that the user has sent us a response
578 * to the challenge. Let's try to authenticate it.
580 * We do this by taking the challenge from 'data',
581 * the response from the EAP packet, and creating VALUE_PAIR's
582 * to pass to the 'mschap' module. This is a little wonky,
585 challenge = pairmake("MS-CHAP-Challenge", "0x00", T_OP_EQ);
587 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
590 challenge->length = MSCHAPV2_CHALLENGE_LEN;
591 memcpy(challenge->vp_strvalue, data->challenge, MSCHAPV2_CHALLENGE_LEN);
593 response = pairmake("MS-CHAP2-Response", "0x00", T_OP_EQ);
595 pairfree(&challenge);
596 radlog(L_ERR, "rlm_eap_mschapv2: out of memory");
600 response->length = MSCHAPV2_RESPONSE_LEN;
601 memcpy(response->vp_strvalue + 2, &eap_ds->response->type.data[5],
602 MSCHAPV2_RESPONSE_LEN - 2);
603 response->vp_strvalue[0] = eap_ds->response->type.data[1];
604 response->vp_strvalue[1] = eap_ds->response->type.data[5 + MSCHAPV2_RESPONSE_LEN];
606 name = pairmake("NTLM-User-Name", "", T_OP_EQ);
608 pairfree(&challenge);
610 radlog(L_ERR, "rlm_eap_mschapv2: Failed creating NTLM-User-Name: %s", fr_strerror());
615 * MS-Length - MS-Value - 5.
617 name->length = (((eap_ds->response->type.data[2] << 8) |
618 eap_ds->response->type.data[3]) -
619 eap_ds->response->type.data[4] - 5);
620 if (name->length >= sizeof(name->vp_strvalue)) {
621 name->length = sizeof(name->vp_strvalue) - 1;
624 memcpy(name->vp_strvalue,
625 &eap_ds->response->type.data[4 + MSCHAPV2_RESPONSE_LEN],
627 name->vp_strvalue[name->length] = '\0';
630 * Add the pairs to the request, and call the 'mschap'
633 pairadd(&handler->request->packet->vps, challenge);
634 pairadd(&handler->request->packet->vps, response);
635 pairadd(&handler->request->packet->vps, name);
641 * If this options is set, then we do NOT authenticate the
642 * user here. Instead, now that we've added the MS-CHAP
643 * attributes to the request, we STOP, and let the outer
644 * tunnel code handle it.
646 * This means that the outer tunnel code will DELETE the
647 * EAP attributes, and proxy the MS-CHAP attributes to a
650 if (handler->request->options & RAD_REQUEST_OPTION_PROXY_EAP) {
651 char *username = NULL;
652 eap_tunnel_data_t *tunnel;
654 DEBUG2("rlm_eap_mschapv2: cancelling authentication and letting it be proxied");
657 * Set up the callbacks for the tunnel
659 tunnel = rad_malloc(sizeof(*tunnel));
660 memset(tunnel, 0, sizeof(*tunnel));
662 tunnel->tls_session = arg;
663 tunnel->callback = mschap_postproxy;
666 * Associate the callback with the request.
668 rcode = request_data_add(handler->request,
669 handler->request->proxy,
670 REQUEST_DATA_EAP_TUNNEL_CALLBACK,
672 rad_assert(rcode == 0);
675 * The State attribute is NOT supposed to
676 * go into the proxied packet, it will confuse
677 * other RADIUS servers, and they will discard
680 * The PEAP module will take care of adding
681 * the State attribute back, before passing
682 * the handler & request back into the tunnel.
684 pairdelete(&handler->request->packet->vps, PW_STATE, 0, -1);
687 * Fix the User-Name when proxying, to strip off
688 * the NT Domain, if we're told to, and a User-Name
689 * exists, and there's a \\, meaning an NT-Domain
690 * in the user name, THEN discard the user name.
692 if (inst->with_ntdomain_hack &&
693 ((challenge = pairfind(handler->request->packet->vps,
694 PW_USER_NAME, 0)) != NULL) &&
695 ((username = strchr(challenge->vp_strvalue, '\\')) != NULL)) {
697 * Wipe out the NT domain.
699 * FIXME: Put it into MS-CHAP-Domain?
701 username++; /* skip the \\ */
702 memmove(challenge->vp_strvalue,
704 strlen(username) + 1); /* include \0 */
705 challenge->length = strlen(challenge->vp_strvalue);
709 * Remember that in the post-proxy stage, we've got
710 * to do the work below, AFTER the call to MS-CHAP
718 * This is a wild & crazy hack.
720 rcode = module_authenticate(PW_AUTHTYPE_MS_CHAP, handler->request);
723 * Delete MPPE keys & encryption policy. We don't
726 fix_mppe_keys(handler, data);
729 * Take the response from the mschap module, and
730 * return success or failure, depending on the result.
733 if (rcode == RLM_MODULE_OK) {
734 pairmove2(&response, &handler->request->reply->vps,
735 PW_MSCHAP2_SUCCESS, VENDORPEC_MICROSOFT);
736 data->code = PW_EAP_MSCHAPV2_SUCCESS;
738 } else if (inst->send_error) {
739 pairmove2(&response, &handler->request->reply->vps,
740 PW_MSCHAP_ERROR, VENDORPEC_MICROSOFT);
745 DEBUG2(" MSCHAP-Error: %s", response->vp_strvalue);
748 * Pxarse the new challenge out of the
749 * MS-CHAP-Error, so that if the client
750 * issues a re-try, we will know which
751 * challenge value that they used.
753 n = sscanf(response->vp_strvalue, "%*cE=%d R=%d C=%32s", &err, &retry, &buf[0]);
755 DEBUG2(" Found new challenge from MS-CHAP-Error: err=%d retry=%d challenge=%s", err, retry, buf);
756 fr_hex2bin(buf, data->challenge, 16);
758 DEBUG2(" Could not parse new challenge from MS-CHAP-Error: %d", n);
761 data->code = PW_EAP_MSCHAPV2_FAILURE;
763 eap_ds->request->code = PW_EAP_FAILURE;
771 radlog(L_ERR, "rlm_eap_mschapv2: No MS-CHAPv2-Success or MS-CHAP-Error was found.");
776 * Compose the response (whatever it is),
777 * and return it to the over-lying EAP module.
779 eapmschapv2_compose(handler, response);
786 * The module name should be the only globally exported symbol.
787 * That is, everything else should be 'static'.
789 EAP_TYPE rlm_eap_mschapv2 = {
791 mschapv2_attach, /* attach */
792 mschapv2_initiate, /* Start the initial request */
793 NULL, /* authorization */
794 mschapv2_authenticate, /* authentication */
795 mschapv2_detach /* detach */