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