Move MS-CHAP code to separate files.
[freeradius.git] / src / modules / rlm_mschap / rlm_mschap.c
1 /*
2  * rlm_mschap.c
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 2000,2001,2006  The FreeRADIUS server project
21  */
22
23 /*  MPPE support from Takahiro Wagatsuma <waga@sic.shibaura-it.ac.jp> */
24
25 #include        <freeradius-devel/ident.h>
26 RCSID("$Id$")
27
28 #include        <freeradius-devel/radiusd.h>
29 #include        <freeradius-devel/modules.h>
30 #include        <freeradius-devel/rad_assert.h>
31 #include        <freeradius-devel/md5.h>
32 #include        <freeradius-devel/sha1.h>
33
34 #include        <ctype.h>
35
36 #include        "mschap.h"
37 #include        "smbdes.h"
38
39 #ifdef __APPLE__
40 extern int od_mschap_auth(REQUEST *request, VALUE_PAIR *challenge, VALUE_PAIR * usernamepair);
41 #endif
42
43 /* Allowable account control bits */
44 #define ACB_DISABLED   0x0001  /* 1 = User account disabled */
45 #define ACB_HOMDIRREQ  0x0002  /* 1 = Home directory required */
46 #define ACB_PWNOTREQ   0x0004  /* 1 = User password not required */
47 #define ACB_TEMPDUP    0x0008  /* 1 = Temporary duplicate account */
48 #define ACB_NORMAL     0x0010  /* 1 = Normal user account */
49 #define ACB_MNS        0x0020  /* 1 = MNS logon user account */
50 #define ACB_DOMTRUST   0x0040  /* 1 = Interdomain trust account */
51 #define ACB_WSTRUST    0x0080  /* 1 = Workstation trust account */
52 #define ACB_SVRTRUST   0x0100  /* 1 = Server trust account */
53 #define ACB_PWNOEXP    0x0200  /* 1 = User password does not expire */
54 #define ACB_AUTOLOCK   0x0400  /* 1 = Account auto locked */
55
56 static int pdb_decode_acct_ctrl(const char *p)
57 {
58         int acct_ctrl = 0;
59         int finished = 0;
60
61         /*
62          * Check if the account type bits have been encoded after the
63          * NT password (in the form [NDHTUWSLXI]).
64          */
65
66         if (*p != '[') return 0;
67
68         for (p++; *p && !finished; p++) {
69                 switch (*p) {
70                         case 'N': /* 'N'o password. */
71                           acct_ctrl |= ACB_PWNOTREQ;
72                           break;
73
74                         case 'D':  /* 'D'isabled. */
75                           acct_ctrl |= ACB_DISABLED ;
76                           break;
77
78                         case 'H':  /* 'H'omedir required. */
79                           acct_ctrl |= ACB_HOMDIRREQ;
80                           break;
81
82                         case 'T': /* 'T'emp account. */
83                           acct_ctrl |= ACB_TEMPDUP;
84                           break;
85
86                         case 'U': /* 'U'ser account (normal). */
87                           acct_ctrl |= ACB_NORMAL;
88                           break;
89
90                         case 'M': /* 'M'NS logon user account. What is this? */
91                           acct_ctrl |= ACB_MNS;
92                           break;
93
94                         case 'W': /* 'W'orkstation account. */
95                           acct_ctrl |= ACB_WSTRUST;
96                           break;
97
98                         case 'S': /* 'S'erver account. */
99                           acct_ctrl |= ACB_SVRTRUST;
100                           break;
101
102                         case 'L': /* 'L'ocked account. */
103                           acct_ctrl |= ACB_AUTOLOCK;
104                           break;
105
106                         case 'X': /* No 'X'piry on password */
107                           acct_ctrl |= ACB_PWNOEXP;
108                           break;
109
110                         case 'I': /* 'I'nterdomain trust account. */
111                           acct_ctrl |= ACB_DOMTRUST;
112                           break;
113
114                         case ' ': /* ignore spaces */
115                           break;
116
117                         case ':':
118                         case '\n':
119                         case '\0':
120                         case ']':
121                         default:
122                           finished = 1;
123                           break;
124                 }
125         }
126
127         return acct_ctrl;
128 }
129
130
131 typedef struct rlm_mschap_t {
132         int use_mppe;
133         int require_encryption;
134         int require_strong;
135         int with_ntdomain_hack; /* this should be in another module */
136         char *passwd_file;
137         const char *xlat_name;
138         char *ntlm_auth;
139         const char *auth_type;
140 #ifdef __APPLE__
141         int  open_directory;
142 #endif  
143 } rlm_mschap_t;
144
145
146 /*
147  *      Does dynamic translation of strings.
148  *
149  *      Pulls NT-Response, LM-Response, or Challenge from MSCHAP
150  *      attributes.
151  */
152 static size_t mschap_xlat(void *instance, REQUEST *request,
153                        char *fmt, char *out, size_t outlen,
154                        RADIUS_ESCAPE_STRING func)
155 {
156         size_t          i, data_len;
157         uint8_t         *data = NULL;
158         uint8_t         buffer[32];
159         VALUE_PAIR      *user_name;
160         VALUE_PAIR      *chap_challenge, *response;
161         rlm_mschap_t    *inst = instance;
162
163         response = NULL;
164
165         func = func;            /* -Wunused */
166
167         /*
168          *      Challenge means MS-CHAPv1 challenge, or
169          *      hash of MS-CHAPv2 challenge, and peer challenge.
170          */
171         if (strncasecmp(fmt, "Challenge", 9) == 0) {
172                 chap_challenge = pairfind(request->packet->vps,
173                                           PW_MSCHAP_CHALLENGE,
174                                           VENDORPEC_MICROSOFT);
175                 if (!chap_challenge) {
176                         RDEBUG2("No MS-CHAP-Challenge in the request.");
177                         return 0;
178                 }
179
180                 /*
181                  *      MS-CHAP-Challenges are 8 octets,
182                  *      for MS-CHAPv2
183                  */
184                 if (chap_challenge->length == 8) {
185                         RDEBUG2(" mschap1: %02x",
186                                chap_challenge->vp_octets[0]);
187                         data = chap_challenge->vp_octets;
188                         data_len = 8;
189
190                         /*
191                          *      MS-CHAP-Challenges are 16 octets,
192                          *      for MS-CHAPv2.
193                          */
194                 } else if (chap_challenge->length == 16) {
195                         char *username_string;
196
197                         RDEBUG2(" mschap2: %02x", chap_challenge->vp_octets[0]);
198                         response = pairfind(request->packet->vps,
199                                             PW_MSCHAP2_RESPONSE,
200                                             VENDORPEC_MICROSOFT);
201                         if (!response) {
202                                 RDEBUG2("MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge.");
203                                 return 0;
204                         }
205
206                         /*
207                          *      Responses are 50 octets.
208                          */
209                         if (response->length < 50) {
210                                 radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
211                                 return 0;
212                         }
213
214                         user_name = pairfind(request->packet->vps,
215                                              PW_USER_NAME, 0);
216                         if (!user_name) {
217                                 RDEBUG2("User-Name is required to calculateMS-CHAPv1 Challenge.");
218                                 return 0;
219                         }
220
221                         /*
222                          *      with_ntdomain_hack moved here, too.
223                          */
224                         if ((username_string = strchr(user_name->vp_strvalue, '\\')) != NULL) {
225                                 if (inst->with_ntdomain_hack) {
226                                         username_string++;
227                                 } else {
228                                         RDEBUG2("NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
229                                         username_string = user_name->vp_strvalue;
230                                 }
231                         } else {
232                                 username_string = user_name->vp_strvalue;
233                         }
234
235                         /*
236                          *      Get the MS-CHAPv1 challenge
237                          *      from the MS-CHAPv2 peer challenge,
238                          *      our challenge, and the user name.
239                          */
240                         mschap_challenge_hash(response->vp_octets + 2,
241                                        chap_challenge->vp_octets,
242                                        username_string, buffer);
243                         data = buffer;
244                         data_len = 8;
245                 } else {
246                         RDEBUG2("Invalid MS-CHAP challenge length");
247                         return 0;
248                 }
249
250                 /*
251                  *      Get the MS-CHAPv1 response, or the MS-CHAPv2
252                  *      response.
253                  */
254         } else if (strncasecmp(fmt, "NT-Response", 11) == 0) {
255                 response = pairfind(request->packet->vps,
256                                     PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT);
257                 if (!response) response = pairfind(request->packet->vps,
258                                                    PW_MSCHAP2_RESPONSE,
259                                                    VENDORPEC_MICROSOFT);
260                 if (!response) {
261                         RDEBUG2("No MS-CHAP-Response or MS-CHAP2-Response was found in the request.");
262                         return 0;
263                 }
264
265                 /*
266                  *      For MS-CHAPv1, the NT-Response exists only
267                  *      if the second octet says so.
268                  */
269                 if ((response->attribute == PW_MSCHAP_RESPONSE) &&
270                     ((response->vp_octets[1] & 0x01) == 0)) {
271                         RDEBUG2("No NT-Response in MS-CHAP-Response");
272                         return 0;
273                 }
274
275                 /*
276                  *      MS-CHAP-Response and MS-CHAP2-Response have
277                  *      the NT-Response at the same offset, and are
278                  *      the same length.
279                  */
280                 data = response->vp_octets + 26;
281                 data_len = 24;
282
283                 /*
284                  *      LM-Response is deprecated, and exists only
285                  *      in MS-CHAPv1, and not often there.
286                  */
287         } else if (strncasecmp(fmt, "LM-Response", 11) == 0) {
288                 response = pairfind(request->packet->vps,
289                                     PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT);
290                 if (!response) {
291                         RDEBUG2("No MS-CHAP-Response was found in the request.");
292                         return 0;
293                 }
294
295                 /*
296                  *      For MS-CHAPv1, the NT-Response exists only
297                  *      if the second octet says so.
298                  */
299                 if ((response->vp_octets[1] & 0x01) != 0) {
300                         RDEBUG2("No LM-Response in MS-CHAP-Response");
301                         return 0;
302                 }
303                 data = response->vp_octets + 2;
304                 data_len = 24;
305
306                 /*
307                  *      Pull the NT-Domain out of the User-Name, if it exists.
308                  */
309         } else if (strncasecmp(fmt, "NT-Domain", 9) == 0) {
310                 char *p, *q;
311
312                 user_name = pairfind(request->packet->vps, PW_USER_NAME, 0);
313                 if (!user_name) {
314                         RDEBUG2("No User-Name was found in the request.");
315                         return 0;
316                 }
317
318                 /*
319                  *      First check to see if this is a host/ style User-Name
320                  *      (a la Kerberos host principal)
321                  */
322                 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
323                         /*
324                          *      If we're getting a User-Name formatted in this way,
325                          *      it's likely due to PEAP.  The Windows Domain will be
326                          *      the first domain component following the hostname,
327                          *      or the machine name itself if only a hostname is supplied
328                          */
329                         p = strchr(user_name->vp_strvalue, '.');
330                         if (!p) {
331                                 RDEBUG2("setting NT-Domain to same as machine name");
332                                 strlcpy(out, user_name->vp_strvalue + 5, outlen);
333                         } else {
334                                 p++;    /* skip the period */
335                                 q = strchr(p, '.');
336                                 /*
337                                  * use the same hack as below
338                                  * only if another period was found
339                                  */
340                                 if (q) *q = '\0';
341                                 strlcpy(out, p, outlen);
342                                 if (q) *q = '.';
343                         }
344                 } else {
345                         p = strchr(user_name->vp_strvalue, '\\');
346                         if (!p) {
347                                 RDEBUG2("No NT-Domain was found in the User-Name.");
348                                 return 0;
349                         }
350
351                         /*
352                          *      Hack.  This is simpler than the alternatives.
353                          */
354                         *p = '\0';
355                         strlcpy(out, user_name->vp_strvalue, outlen);
356                         *p = '\\';
357                 }
358
359                 return strlen(out);
360
361                 /*
362                  *      Pull the User-Name out of the User-Name...
363                  */
364         } else if (strncasecmp(fmt, "User-Name", 9) == 0) {
365                 char *p;
366
367                 user_name = pairfind(request->packet->vps, PW_USER_NAME, 0);
368                 if (!user_name) {
369                         RDEBUG2("No User-Name was found in the request.");
370                         return 0;
371                 }
372
373                 /*
374                  *      First check to see if this is a host/ style User-Name
375                  *      (a la Kerberos host principal)
376                  */
377                 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
378                         /*
379                          *      If we're getting a User-Name formatted in this way,
380                          *      it's likely due to PEAP.  When authenticating this against
381                          *      a Domain, Windows will expect the User-Name to be in the
382                          *      format of hostname$, the SAM version of the name, so we
383                          *      have to convert it to that here.  We do so by stripping
384                          *      off the first 5 characters (host/), and copying everything
385                          *      from that point to the first period into a string and appending
386                          *      a $ to the end.
387                          */
388                         p = strchr(user_name->vp_strvalue, '.');
389                         /*
390                          * use the same hack as above
391                          * only if a period was found
392                          */
393                         if (p) *p = '\0';
394                         snprintf(out, outlen, "%s$", user_name->vp_strvalue + 5);
395                         if (p) *p = '.';
396                 } else {
397                         p = strchr(user_name->vp_strvalue, '\\');
398                         if (p) {
399                                 p++;    /* skip the backslash */
400                         } else {
401                                 p = user_name->vp_strvalue; /* use the whole User-Name */
402                         }
403                         strlcpy(out, p, outlen);
404                 }
405
406                 return strlen(out);
407
408                 /*
409                  * Return the NT-Hash of the passed string
410                  */
411         } else if (strncasecmp(fmt, "NT-Hash ", 8) == 0) {
412                 char *p;
413                 char buf2[1024];
414
415                 p = fmt + 8;    /* 7 is the length of 'NT-Hash' */
416                 if ((p == '\0')  || (outlen <= 32))
417                         return 0;
418
419                 while (isspace(*p)) p++;
420
421                 if (!radius_xlat(buf2, sizeof(buf2),p,request,NULL)) {
422                         RDEBUG("xlat failed");
423                         *buffer = '\0';
424                         return 0;
425                 }
426
427                 mschap_ntpwdhash(buffer,buf2);
428
429                 fr_bin2hex(buffer, out, 16);
430                 out[32] = '\0';
431                 RDEBUG("NT-Hash of %s = %s", buf2, out);
432                 return 32;
433
434                 /*
435                  * Return the LM-Hash of the passed string
436                  */
437         } else if (strncasecmp(fmt, "LM-Hash ", 8) == 0) {
438                 char *p;
439                 char buf2[1024];
440
441                 p = fmt + 8;    /* 7 is the length of 'LM-Hash' */
442                 if ((p == '\0') || (outlen <= 32))
443                         return 0;
444
445                 while (isspace(*p)) p++;
446
447                 if (!radius_xlat(buf2, sizeof(buf2),p,request,NULL)) {
448                         RDEBUG("xlat failed");
449                         *buffer = '\0';
450                         return 0;
451                 }
452
453                 smbdes_lmpwdhash(buf2, buffer);
454                 fr_bin2hex(buffer, out, 16);
455                 out[32] = '\0';
456                 RDEBUG("LM-Hash of %s = %s", buf2, out);
457                 return 32;
458         } else {
459                 RDEBUG2("Unknown expansion string \"%s\"",
460                        fmt);
461                 return 0;
462         }
463
464         if (outlen == 0) return 0; /* nowhere to go, don't do anything */
465
466         /*
467          *      Didn't set anything: this is bad.
468          */
469         if (!data) {
470                 RDEBUG2("Failed to do anything intelligent");
471                 return 0;
472         }
473
474         /*
475          *      Check the output length.
476          */
477         if (outlen < ((data_len * 2) + 1)) {
478                 data_len = (outlen - 1) / 2;
479         }
480
481         /*
482          *
483          */
484         for (i = 0; i < data_len; i++) {
485                 sprintf(out + (2 * i), "%02x", data[i]);
486         }
487         out[data_len * 2] = '\0';
488
489         return data_len * 2;
490 }
491
492
493 static const CONF_PARSER module_config[] = {
494         /*
495          *      Cache the password by default.
496          */
497         { "use_mppe",    PW_TYPE_BOOLEAN,
498           offsetof(rlm_mschap_t,use_mppe), NULL, "yes" },
499         { "require_encryption",    PW_TYPE_BOOLEAN,
500           offsetof(rlm_mschap_t,require_encryption), NULL, "no" },
501         { "require_strong",    PW_TYPE_BOOLEAN,
502           offsetof(rlm_mschap_t,require_strong), NULL, "no" },
503         { "with_ntdomain_hack",     PW_TYPE_BOOLEAN,
504           offsetof(rlm_mschap_t,with_ntdomain_hack), NULL, "no" },
505         { "passwd",   PW_TYPE_STRING_PTR,
506           offsetof(rlm_mschap_t, passwd_file), NULL,  NULL },
507         { "ntlm_auth",   PW_TYPE_STRING_PTR,
508           offsetof(rlm_mschap_t, ntlm_auth), NULL,  NULL },
509 #ifdef __APPLE__
510         { "use_open_directory",    PW_TYPE_BOOLEAN,
511           offsetof(rlm_mschap_t,open_directory), NULL, "yes" },
512 #endif
513
514         { NULL, -1, 0, NULL, NULL }             /* end the list */
515 };
516
517 /*
518  *      deinstantiate module, free all memory allocated during
519  *      mschap_instantiate()
520  */
521 static int mschap_detach(void *instance){
522 #define inst ((rlm_mschap_t *)instance)
523         if (inst->xlat_name) {
524                 xlat_unregister(inst->xlat_name, mschap_xlat);
525                 free(inst->xlat_name);
526         }
527         free(instance);
528         return 0;
529 #undef inst
530 }
531
532 /*
533  *      Create instance for our module. Allocate space for
534  *      instance structure and read configuration parameters
535  */
536 static int mschap_instantiate(CONF_SECTION *conf, void **instance)
537 {
538         rlm_mschap_t *inst;
539
540         inst = *instance = rad_malloc(sizeof(*inst));
541         if (!inst) {
542                 return -1;
543         }
544         memset(inst, 0, sizeof(*inst));
545
546         if (cf_section_parse(conf, inst, module_config) < 0) {
547                 free(inst);
548                 return -1;
549         }
550
551         /*
552          *      This module used to support SMB Password files, but it
553          *      made it too complicated.  If the user tries to
554          *      configure an SMB Password file, then die, with an
555          *      error message.
556          */
557         if (inst->passwd_file) {
558                 radlog(L_ERR, "rlm_mschap: SMB password file is no longer supported in this module.  Use rlm_passwd module instead");
559                 mschap_detach(inst);
560                 return -1;
561         }
562
563         /*
564          *      Create the dynamic translation.
565          */
566         inst->xlat_name = cf_section_name2(conf);
567         if (!inst->xlat_name) inst->xlat_name = cf_section_name1(conf);
568         inst->xlat_name = strdup(inst->xlat_name);
569         xlat_register(inst->xlat_name, mschap_xlat, inst);
570
571         /*
572          *      For backwards compatibility
573          */
574         if (!dict_valbyname(PW_AUTH_TYPE, 0, inst->xlat_name)) {
575                 inst->auth_type = "MS-CHAP";
576         } else {
577                 inst->auth_type = inst->xlat_name;
578         }
579
580         return 0;
581 }
582
583 /*
584  *      add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
585  *      attribute to reply packet
586  */
587 void mschap_add_reply(REQUEST *request, VALUE_PAIR** vp, unsigned char ident,
588                       const char* name, const char* value, int len)
589 {
590         VALUE_PAIR *reply_attr;
591         reply_attr = pairmake(name, "", T_OP_EQ);
592         if (!reply_attr) {
593                 RDEBUG("Failed to create attribute %s: %s\n", name, fr_strerror());
594                 return;
595         }
596
597         reply_attr->vp_octets[0] = ident;
598         memcpy(reply_attr->vp_octets + 1, value, len);
599         reply_attr->length = len + 1;
600         pairadd(vp, reply_attr);
601 }
602
603 /*
604  *      Add MPPE attributes to the reply.
605  */
606 static void mppe_add_reply(REQUEST *request,
607                            const char* name, const uint8_t * value, int len)
608 {
609        VALUE_PAIR *vp;
610        vp = radius_pairmake(request, &request->reply->vps, name, "", T_OP_EQ);
611        if (!vp) {
612                RDEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, fr_strerror());
613                return;
614        }
615
616        memcpy(vp->vp_octets, value, len);
617        vp->length = len;
618 }
619
620
621 /*
622  *      Do the MS-CHAP stuff.
623  *
624  *      This function is here so that all of the MS-CHAP related
625  *      authentication is in one place, and we can perhaps later replace
626  *      it with code to call winbindd, or something similar.
627  */
628 static int do_mschap(rlm_mschap_t *inst,
629                      REQUEST *request, VALUE_PAIR *password,
630                      uint8_t *challenge, uint8_t *response,
631                      uint8_t *nthashhash, int do_ntlm_auth)
632 {
633         uint8_t         calculated[24];
634
635         /*
636          *      Do normal authentication.
637          */
638         if (!do_ntlm_auth) {
639                 /*
640                  *      No password: can't do authentication.
641                  */
642                 if (!password) {
643                         RDEBUG2("FAILED: No NT/LM-Password.  Cannot perform authentication.");
644                         return -1;
645                 }
646
647                 smbdes_mschap(password->vp_strvalue, challenge, calculated);
648                 if (memcmp(response, calculated, 24) != 0) {
649                         return -1;
650                 }
651
652                 /*
653                  *      If the password exists, and is an NT-Password,
654                  *      then calculate the hash of the NT hash.  Doing this
655                  *      here minimizes work for later.
656                  */
657                 if (password && (password->attribute == PW_NT_PASSWORD)) {
658                         fr_md4_calc(nthashhash, password->vp_octets, 16);
659                 } else {
660                         memset(nthashhash, 0, 16);
661                 }
662         } else {                /* run ntlm_auth */
663                 int     result;
664                 char    buffer[256];
665
666                 memset(nthashhash, 0, 16);
667
668                 /*
669                  *      Run the program, and expect that we get 16
670                  */
671                 result = radius_exec_program(inst->ntlm_auth, request,
672                                              TRUE, /* wait */
673                                              buffer, sizeof(buffer),
674                                              NULL, NULL, 1);
675                 if (result != 0) {
676                         RDEBUG2("External script failed.");
677                         return -1;
678                 }
679
680                 /*
681                  *      Parse the answer as an nthashhash.
682                  *
683                  *      ntlm_auth currently returns:
684                  *      NT_KEY: 000102030405060708090a0b0c0d0e0f
685                  */
686                 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
687                         RDEBUG2("Invalid output from ntlm_auth: expecting NT_KEY");
688                         return -1;
689                 }
690
691                 /*
692                  *      Check the length.  It should be at least 32,
693                  *      with an LF at the end.
694                  */
695                 if (strlen(buffer + 8) < 32) {
696                         RDEBUG2("Invalid output from ntlm_auth: NT_KEY has unexpected length");
697                         return -1;
698                 }
699
700                 /*
701                  *      Update the NT hash hash, from the NT key.
702                  */
703                 if (fr_hex2bin(buffer + 8, nthashhash, 16) != 16) {
704                         RDEBUG2("Invalid output from ntlm_auth: NT_KEY has non-hex values");
705                         return -1;
706                 }
707         }
708
709         return 0;
710 }
711
712
713 /*
714  *      Data for the hashes.
715  */
716 static const uint8_t SHSpad1[40] =
717                { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
718                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
719                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
720                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
721
722 static const uint8_t SHSpad2[40] =
723                { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
724                  0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
725                  0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
726                  0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
727
728 static const uint8_t magic1[27] =
729                { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
730                  0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
731                  0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
732
733 static const uint8_t magic2[84] =
734                { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
735                  0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
736                  0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
737                  0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
738                  0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
739                  0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
740                  0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
741                  0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
742                  0x6b, 0x65, 0x79, 0x2e };
743
744 static const uint8_t magic3[84] =
745                { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
746                  0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
747                  0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
748                  0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
749                  0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
750                  0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
751                  0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
752                  0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
753                  0x6b, 0x65, 0x79, 0x2e };
754
755
756 static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response,
757                               uint8_t *masterkey)
758 {
759        uint8_t digest[20];
760        fr_SHA1_CTX Context;
761
762        fr_SHA1Init(&Context);
763        fr_SHA1Update(&Context,nt_hashhash,16);
764        fr_SHA1Update(&Context,nt_response,24);
765        fr_SHA1Update(&Context,magic1,27);
766        fr_SHA1Final(digest,&Context);
767
768        memcpy(masterkey,digest,16);
769 }
770
771
772 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
773                                        int keylen,int issend)
774 {
775        uint8_t digest[20];
776        const uint8_t *s;
777        fr_SHA1_CTX Context;
778
779        memset(digest,0,20);
780
781        if(issend) {
782                s = magic3;
783        } else {
784                s = magic2;
785        }
786
787        fr_SHA1Init(&Context);
788        fr_SHA1Update(&Context,masterkey,16);
789        fr_SHA1Update(&Context,SHSpad1,40);
790        fr_SHA1Update(&Context,s,84);
791        fr_SHA1Update(&Context,SHSpad2,40);
792        fr_SHA1Final(digest,&Context);
793
794        memcpy(sesskey,digest,keylen);
795 }
796
797
798 static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response,
799                                    uint8_t *sendkey,uint8_t *recvkey)
800 {
801        uint8_t masterkey[16];
802
803        mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
804
805        mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
806        mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
807 }
808
809 /*
810  *      Generate MPPE keys.
811  */
812 static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response,
813                                    uint8_t *sendkey,uint8_t *recvkey)
814 {
815         uint8_t enckey1[16];
816         uint8_t enckey2[16];
817
818         mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
819
820         /*
821          *      dictionary.microsoft defines these attributes as
822          *      'encrypt=2'.  The functions in src/lib/radius.c will
823          *      take care of encrypting/decrypting them as appropriate,
824          *      so that we don't have to.
825          */
826         memcpy (sendkey, enckey1, 16);
827         memcpy (recvkey, enckey2, 16);
828 }
829
830
831 /*
832  *      mschap_authorize() - authorize user if we can authenticate
833  *      it later. Add Auth-Type attribute if present in module
834  *      configuration (usually Auth-Type must be "MS-CHAP")
835  */
836 static int mschap_authorize(void * instance, REQUEST *request)
837 {
838 #define inst ((rlm_mschap_t *)instance)
839         VALUE_PAIR *challenge = NULL, *response = NULL;
840
841         challenge = pairfind(request->packet->vps,
842                              PW_MSCHAP_CHALLENGE,
843                              VENDORPEC_MICROSOFT);
844         if (!challenge) {
845                 return RLM_MODULE_NOOP;
846         }
847
848         response = pairfind(request->packet->vps,
849                             PW_MSCHAP_RESPONSE,
850                             VENDORPEC_MICROSOFT);
851         if (!response)
852                 response = pairfind(request->packet->vps,
853                                     PW_MSCHAP2_RESPONSE,
854                                     VENDORPEC_MICROSOFT);
855
856         /*
857          *      Nothing we recognize.  Don't do anything.
858          */
859         if (!response) {
860                 RDEBUG2("Found MS-CHAP-Challenge, but no MS-CHAP-Response.");
861                 return RLM_MODULE_NOOP;
862         }
863
864         if (pairfind(request->config_items, PW_AUTH_TYPE, 0)) {
865                 RDEBUG2("Found existing Auth-Type.  Not changing it.");
866                 return RLM_MODULE_NOOP;
867         }
868
869         RDEBUG2("Found MS-CHAP attributes.  Setting 'Auth-Type  = %s'", inst->xlat_name);
870
871         /*
872          *      Set Auth-Type to MS-CHAP.  The authentication code
873          *      will take care of turning clear-text passwords into
874          *      NT/LM passwords.
875          */
876         if (!radius_pairmake(request, &request->config_items,
877                              "Auth-Type", inst->auth_type, T_OP_EQ)) {
878                 return RLM_MODULE_FAIL;
879         }
880
881         return RLM_MODULE_OK;
882 #undef inst
883 }
884
885 /*
886  *      mschap_authenticate() - authenticate user based on given
887  *      attributes and configuration.
888  *      We will try to find out password in configuration
889  *      or in configured passwd file.
890  *      If one is found we will check paraneters given by NAS.
891  *
892  *      If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
893  *      one of:
894  *              PAP:      PW_USER_PASSWORD or
895  *              MS-CHAP:  PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
896  *              MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
897  *      In case of password mismatch or locked account we MAY return
898  *      PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
899  *      If MS-CHAP2 succeeds we MUST return
900  *      PW_MSCHAP2_SUCCESS
901  */
902 static int mschap_authenticate(void * instance, REQUEST *request)
903 {
904 #define inst ((rlm_mschap_t *)instance)
905         VALUE_PAIR *challenge = NULL;
906         VALUE_PAIR *response = NULL;
907         VALUE_PAIR *password = NULL;
908         VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
909         VALUE_PAIR *username;
910         uint8_t nthashhash[16];
911         char msch2resp[42];
912         char *username_string;
913         int chap = 0;
914         int             do_ntlm_auth;
915
916         /*
917          *      If we have ntlm_auth configured, use it unless told
918          *      otherwise
919          */
920         do_ntlm_auth = (inst->ntlm_auth != NULL);
921
922         /*
923          *      If we have an ntlm_auth configuration, then we may
924          *      want to suppress it.
925          */
926         if (do_ntlm_auth) {
927                 VALUE_PAIR *vp = pairfind(request->config_items,
928                                           PW_MS_CHAP_USE_NTLM_AUTH, 0);
929                 if (vp) do_ntlm_auth = vp->vp_integer;
930         }
931
932         /*
933          *      Find the SMB-Account-Ctrl attribute, or the
934          *      SMB-Account-Ctrl-Text attribute.
935          */
936         smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL, 0);
937         if (!smb_ctrl) {
938                 password = pairfind(request->config_items,
939                                     PW_SMB_ACCOUNT_CTRL_TEXT, 0);
940                 if (password) {
941                         smb_ctrl = radius_pairmake(request,
942                                                    &request->config_items,
943                                                    "SMB-Account-CTRL", "0",
944                                                    T_OP_SET);
945                         if (smb_ctrl) {
946                                 smb_ctrl->vp_integer = pdb_decode_acct_ctrl(password->vp_strvalue);
947                         }
948                 }
949         }
950
951         /*
952          *      We're configured to do MS-CHAP authentication.
953          *      and account control information exists.  Enforce it.
954          */
955         if (smb_ctrl) {
956                 /*
957                  *      Password is not required.
958                  */
959                 if ((smb_ctrl->vp_integer & ACB_PWNOTREQ) != 0) {
960                         RDEBUG2("SMB-Account-Ctrl says no password is required.");
961                         return RLM_MODULE_OK;
962                 }
963         }
964
965         /*
966          *      Decide how to get the passwords.
967          */
968         password = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD, 0);
969
970         /*
971          *      We need an LM-Password.
972          */
973         lm_password = pairfind(request->config_items, PW_LM_PASSWORD, 0);
974         if (lm_password) {
975                 /*
976                  *      Allow raw octets.
977                  */
978                 if ((lm_password->length == 16) ||
979                     ((lm_password->length == 32) &&
980                      (fr_hex2bin(lm_password->vp_strvalue,
981                                  lm_password->vp_octets, 16) == 16))) {
982                         RDEBUG2("Found LM-Password");
983                         lm_password->length = 16;
984
985                 } else {
986                         radlog_request(L_ERR, 0, request, "Invalid LM-Password");
987                         lm_password = NULL;
988                 }
989
990         } else if (!password) {
991                 if (!do_ntlm_auth) RDEBUG2("No Cleartext-Password configured.  Cannot create LM-Password.");
992
993         } else {                /* there is a configured Cleartext-Password */
994                 lm_password = radius_pairmake(request, &request->config_items,
995                                               "LM-Password", "", T_OP_EQ);
996                 if (!lm_password) {
997                         radlog_request(L_ERR, 0, request, "No memory");
998                 } else {
999                         smbdes_lmpwdhash(password->vp_strvalue,
1000                                          lm_password->vp_octets);
1001                         lm_password->length = 16;
1002                 }
1003         }
1004
1005         /*
1006          *      We need an NT-Password.
1007          */
1008         nt_password = pairfind(request->config_items, PW_NT_PASSWORD, 0);
1009         if (nt_password) {
1010                 if ((nt_password->length == 16) ||
1011                     ((nt_password->length == 32) &&
1012                      (fr_hex2bin(nt_password->vp_strvalue,
1013                                  nt_password->vp_octets, 16) == 16))) {
1014                         RDEBUG2("Found NT-Password");
1015                         nt_password->length = 16;
1016
1017                 } else {
1018                         radlog_request(L_ERR, 0, request, "Invalid NT-Password");
1019                         nt_password = NULL;
1020                 }
1021         } else if (!password) {
1022                 if (!do_ntlm_auth) RDEBUG2("No Cleartext-Password configured.  Cannot create NT-Password.");
1023
1024         } else {                /* there is a configured Cleartext-Password */
1025                 nt_password = radius_pairmake(request, &request->config_items,
1026                                               "NT-Password", "", T_OP_EQ);
1027                 if (!nt_password) {
1028                         radlog_request(L_ERR, 0, request, "No memory");
1029                         return RLM_MODULE_FAIL;
1030                 } else {
1031                         mschap_ntpwdhash(nt_password->vp_octets,
1032                                   password->vp_strvalue);
1033                         nt_password->length = 16;
1034                 }
1035         }
1036
1037         challenge = pairfind(request->packet->vps,
1038                              PW_MSCHAP_CHALLENGE,
1039                              VENDORPEC_MICROSOFT);
1040         if (!challenge) {
1041                 RDEBUG2("No MS-CHAP-Challenge in the request");
1042                 return RLM_MODULE_REJECT;
1043         }
1044
1045         /*
1046          *      We also require an MS-CHAP-Response.
1047          */
1048         response = pairfind(request->packet->vps,
1049                             PW_MSCHAP_RESPONSE,
1050                             VENDORPEC_MICROSOFT);
1051
1052         /*
1053          *      MS-CHAP-Response, means MS-CHAPv1
1054          */
1055         if (response) {
1056                 int offset;
1057
1058                 /*
1059                  *      MS-CHAPv1 challenges are 8 octets.
1060                  */
1061                 if (challenge->length < 8) {
1062                         radlog_request(L_AUTH, 0, request, "MS-CHAP-Challenge has the wrong format.");
1063                         return RLM_MODULE_INVALID;
1064                 }
1065
1066                 /*
1067                  *      Responses are 50 octets.
1068                  */
1069                 if (response->length < 50) {
1070                         radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
1071                         return RLM_MODULE_INVALID;
1072                 }
1073
1074                 /*
1075                  *      We are doing MS-CHAP.  Calculate the MS-CHAP
1076                  *      response
1077                  */
1078                 if (response->vp_octets[1] & 0x01) {
1079                         RDEBUG2("Told to do MS-CHAPv1 with NT-Password");
1080                         password = nt_password;
1081                         offset = 26;
1082                 } else {
1083                         RDEBUG2("Told to do MS-CHAPv1 with LM-Password");
1084                         password = lm_password;
1085                         offset = 2;
1086                 }
1087
1088                 /*
1089                  *      Do the MS-CHAP authentication.
1090                  */
1091                 if (do_mschap(inst, request, password, challenge->vp_octets,
1092                               response->vp_octets + offset, nthashhash,
1093                               do_ntlm_auth) < 0) {
1094                         RDEBUG2("MS-CHAP-Response is incorrect.");
1095                         mschap_add_reply(request, &request->reply->vps,
1096                                          *response->vp_octets,
1097                                          "MS-CHAP-Error", "E=691 R=1", 9);
1098                         return RLM_MODULE_REJECT;
1099                 }
1100
1101                 chap = 1;
1102
1103         } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT)) != NULL) {
1104                 uint8_t mschapv1_challenge[16];
1105
1106                 /*
1107                  *      MS-CHAPv2 challenges are 16 octets.
1108                  */
1109                 if (challenge->length < 16) {
1110                         radlog_request(L_AUTH, 0, request, "MS-CHAP-Challenge has the wrong format.");
1111                         return RLM_MODULE_INVALID;
1112                 }
1113
1114                 /*
1115                  *      Responses are 50 octets.
1116                  */
1117                 if (response->length < 50) {
1118                         radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
1119                         return RLM_MODULE_INVALID;
1120                 }
1121
1122                 /*
1123                  *      We also require a User-Name
1124                  */
1125                 username = pairfind(request->packet->vps, PW_USER_NAME, 0);
1126                 if (!username) {
1127                         radlog_request(L_AUTH, 0, request, "We require a User-Name for MS-CHAPv2");
1128                         return RLM_MODULE_INVALID;
1129                 }
1130
1131
1132                 /*
1133                  *      with_ntdomain_hack moved here
1134                  */
1135                 if ((username_string = strchr(username->vp_strvalue, '\\')) != NULL) {
1136                         if (inst->with_ntdomain_hack) {
1137                                 username_string++;
1138                         } else {
1139                                 RDEBUG2("  NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1140                                 username_string = username->vp_strvalue;
1141                         }
1142                 } else {
1143                         username_string = username->vp_strvalue;
1144                 }
1145
1146 #ifdef __APPLE__
1147                 /*
1148                  *  No "known good" NT-Password attribute.  Try to do
1149                  *  OpenDirectory authentication.
1150                  *
1151                  *  If OD determines the user is an AD user it will return noop, which
1152                  *  indicates the auth process should continue directly to AD.
1153                  *  Otherwise OD will determine auth success/fail.
1154                  */
1155                 if (!nt_password && inst->open_directory) {
1156                         RDEBUG2("No NT-Password configured. Trying OpenDirectory Authentication.");
1157                         int odStatus = od_mschap_auth(request, challenge, username);
1158                         if (odStatus != RLM_MODULE_NOOP) {
1159                                 return odStatus;
1160                         }
1161                 }
1162 #endif
1163                 /*
1164                  *      The old "mschapv2" function has been moved to
1165                  *      here.
1166                  *
1167                  *      MS-CHAPv2 takes some additional data to create an
1168                  *      MS-CHAPv1 challenge, and then does MS-CHAPv1.
1169                  */
1170                 mschap_challenge_hash(response->vp_octets + 2, /* peer challenge */
1171                                challenge->vp_octets, /* our challenge */
1172                                username_string, /* user name */
1173                                mschapv1_challenge); /* resulting challenge */
1174
1175                 RDEBUG2("Told to do MS-CHAPv2 for %s with NT-Password",
1176                        username_string);
1177
1178                 if (do_mschap(inst, request, nt_password, mschapv1_challenge,
1179                               response->vp_octets + 26, nthashhash,
1180                               do_ntlm_auth) < 0) {
1181                         RDEBUG2("FAILED: MS-CHAP2-Response is incorrect");
1182                         mschap_add_reply(request, &request->reply->vps,
1183                                          *response->vp_octets,
1184                                          "MS-CHAP-Error", "E=691 R=1", 9);
1185                         return RLM_MODULE_REJECT;
1186                 }
1187
1188                 /*
1189                  *      Get the NT-hash-hash, if necessary
1190                  */
1191                 if (nt_password) {
1192                 }
1193
1194                 mschap_auth_response(username_string, /* without the domain */
1195                               nthashhash, /* nt-hash-hash */
1196                               response->vp_octets + 26, /* peer response */
1197                               response->vp_octets + 2, /* peer challenge */
1198                               challenge->vp_octets, /* our challenge */
1199                               msch2resp); /* calculated MPPE key */
1200                 mschap_add_reply(request, &request->reply->vps, *response->vp_octets,
1201                                  "MS-CHAP2-Success", msch2resp, 42);
1202                 chap = 2;
1203
1204         } else {                /* Neither CHAPv1 or CHAPv2 response: die */
1205                 radlog_request(L_AUTH, 0, request, "No MS-CHAP response found");
1206                 return RLM_MODULE_INVALID;
1207         }
1208
1209         /*
1210          *      We have a CHAP response, but the account may be
1211          *      disabled.  Reject the user with the same error code
1212          *      we use when their password is invalid.
1213          */
1214         if (smb_ctrl) {
1215                 /*
1216                  *      Account is disabled.
1217                  *
1218                  *      They're found, but they don't exist, so we
1219                  *      return 'not found'.
1220                  */
1221                 if (((smb_ctrl->vp_integer & ACB_DISABLED) != 0) ||
1222                     ((smb_ctrl->vp_integer & ACB_NORMAL) == 0)) {
1223                         RDEBUG2("SMB-Account-Ctrl says that the account is disabled, or is not a normal account.");
1224                         mschap_add_reply(request, &request->reply->vps,
1225                                           *response->vp_octets,
1226                                           "MS-CHAP-Error", "E=691 R=1", 9);
1227                         return RLM_MODULE_NOTFOUND;
1228                 }
1229
1230                 /*
1231                  *      User is locked out.
1232                  */
1233                 if ((smb_ctrl->vp_integer & ACB_AUTOLOCK) != 0) {
1234                         RDEBUG2("SMB-Account-Ctrl says that the account is locked out.");
1235                         mschap_add_reply(request, &request->reply->vps,
1236                                           *response->vp_octets,
1237                                           "MS-CHAP-Error", "E=647 R=0", 9);
1238                         return RLM_MODULE_USERLOCK;
1239                 }
1240         }
1241
1242         /* now create MPPE attributes */
1243         if (inst->use_mppe) {
1244                 uint8_t mppe_sendkey[34];
1245                 uint8_t mppe_recvkey[34];
1246
1247                 if (chap == 1){
1248                         RDEBUG2("adding MS-CHAPv1 MPPE keys");
1249                         memset(mppe_sendkey, 0, 32);
1250                         if (lm_password) {
1251                                 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1252                         }
1253
1254                         /*
1255                          *      According to RFC 2548 we
1256                          *      should send NT hash.  But in
1257                          *      practice it doesn't work.
1258                          *      Instead, we should send nthashhash
1259                          *
1260                          *      This is an error on RFC 2548.
1261                          */
1262                         /*
1263                          *      do_mschap cares to zero nthashhash if NT hash
1264                          *      is not available.
1265                          */
1266                         memcpy(mppe_sendkey + 8,
1267                                nthashhash, 16);
1268                         mppe_add_reply(request,
1269                                        "MS-CHAP-MPPE-Keys",
1270                                        mppe_sendkey, 32);
1271                 } else if (chap == 2) {
1272                         RDEBUG2("adding MS-CHAPv2 MPPE keys");
1273                         mppe_chap2_gen_keys128(nthashhash,
1274                                                response->vp_octets + 26,
1275                                                mppe_sendkey, mppe_recvkey);
1276
1277                         mppe_add_reply(request,
1278                                        "MS-MPPE-Recv-Key",
1279                                        mppe_recvkey, 16);
1280                         mppe_add_reply(request,
1281                                        "MS-MPPE-Send-Key",
1282                                        mppe_sendkey, 16);
1283
1284                 }
1285                 radius_pairmake(request, &request->reply->vps,
1286                                 "MS-MPPE-Encryption-Policy",
1287                                 (inst->require_encryption)? "0x00000002":"0x00000001",
1288                                 T_OP_EQ);
1289                 radius_pairmake(request, &request->reply->vps,
1290                                 "MS-MPPE-Encryption-Types",
1291                                 (inst->require_strong)? "0x00000004":"0x00000006",
1292                                 T_OP_EQ);
1293         } /* else we weren't asked to use MPPE */
1294
1295         return RLM_MODULE_OK;
1296 #undef inst
1297 }
1298
1299 module_t rlm_mschap = {
1300         RLM_MODULE_INIT,
1301         "MS-CHAP",
1302         RLM_TYPE_THREAD_SAFE,           /* type */
1303         mschap_instantiate,             /* instantiation */
1304         mschap_detach,          /* detach */
1305         {
1306                 mschap_authenticate,    /* authenticate */
1307                 mschap_authorize,       /* authorize */
1308                 NULL,                   /* pre-accounting */
1309                 NULL,                   /* accounting */
1310                 NULL,                   /* checksimul */
1311                 NULL,                   /* pre-proxy */
1312                 NULL,                   /* post-proxy */
1313                 NULL                    /* post-auth */
1314         },
1315 };