Now that we have Cleartext-Password and Password-With-Header, use
[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 *ntlm_auth;
259         char *auth_type;
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         { "ntlm_auth",   PW_TYPE_STRING_PTR,
605           offsetof(rlm_mschap_t, ntlm_auth), NULL,  NULL },
606
607         { NULL, -1, 0, NULL, NULL }             /* end the list */
608 };
609
610 /*
611  *      deinstantiate module, free all memory allocated during
612  *      mschap_instantiate()
613  */
614 static int mschap_detach(void *instance){
615 #define inst ((rlm_mschap_t *)instance)
616         free(inst->passwd_file);
617         free(inst->ntlm_auth);
618         if (inst->xlat_name) {
619                 xlat_unregister(inst->xlat_name, mschap_xlat);
620                 free(inst->xlat_name);
621         }
622         free(instance);
623         return 0;
624 #undef inst
625 }
626
627 /*
628  *      Create instance for our module. Allocate space for
629  *      instance structure and read configuration parameters
630  */
631 static int mschap_instantiate(CONF_SECTION *conf, void **instance)
632 {
633         const char *xlat_name;
634         rlm_mschap_t *inst;
635
636         inst = *instance = rad_malloc(sizeof(*inst));
637         if (!inst) {
638                 return -1;
639         }
640         memset(inst, 0, sizeof(*inst));
641
642         if (cf_section_parse(conf, inst, module_config) < 0) {
643                 free(inst);
644                 return -1;
645         }
646
647         /*
648          *      This module used to support SMB Password files, but it
649          *      made it too complicated.  If the user tries to
650          *      configure an SMB Password file, then die, with an
651          *      error message.
652          */
653         if (inst->passwd_file) {
654                 radlog(L_ERR, "rlm_mschap: SMB password file is no longer supported in this module.  Use rlm_passwd module instead");
655                 mschap_detach(inst);
656                 return -1;
657         }
658
659         /*
660          *      Create the dynamic translation.
661          */
662         if (cf_section_name1(conf))
663                 xlat_register(cf_section_name1(conf),mschap_xlat, inst);
664
665         if ((xlat_name = cf_section_name2(conf)) != NULL)
666                 xlat_register(xlat_name, mschap_xlat, inst);
667         if (xlat_name == NULL)
668                 xlat_name = cf_section_name1(conf);
669         if (xlat_name)
670                 inst->xlat_name = strdup(xlat_name);
671
672         /*
673          *      For backwards compatibility
674          */
675         if (!dict_valbyname(PW_AUTH_TYPE, inst->xlat_name)) {
676                 inst->auth_type = "MS-CHAP";
677         }
678
679         return 0;
680 }
681
682 /*
683  *      add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
684  *      attribute to reply packet
685  */
686 static void add_reply(VALUE_PAIR** vp, unsigned char ident,
687                       const char* name, const char* value, int len)
688 {
689         VALUE_PAIR *reply_attr;
690         reply_attr = pairmake(name, "", T_OP_EQ);
691         if (!reply_attr) {
692                 DEBUG("  rlm_mschap: Failed to create attribute %s: %s\n", name, librad_errstr);
693                 return;
694         }
695
696         reply_attr->vp_octets[0] = ident;
697         memcpy(reply_attr->vp_octets + 1, value, len);
698         reply_attr->length = len + 1;
699         pairadd(vp, reply_attr);
700 }
701
702 /*
703  *      Add MPPE attributes to the reply.
704  */
705 static void mppe_add_reply(VALUE_PAIR **vp,
706                            const char* name, const char* value, int len)
707 {
708        VALUE_PAIR *reply_attr;
709        reply_attr = pairmake(name, "", T_OP_EQ);
710        if (!reply_attr) {
711                DEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, librad_errstr);
712                return;
713        }
714
715        memcpy(reply_attr->vp_octets, value, len);
716        reply_attr->length = len;
717        pairadd(vp, reply_attr);
718 }
719
720
721 /*
722  *      Do the MS-CHAP stuff.
723  *
724  *      This function is here so that all of the MS-CHAP related
725  *      authentication is in one place, and we can perhaps later replace
726  *      it with code to call winbindd, or something similar.
727  */
728 static int do_mschap(rlm_mschap_t *inst,
729                      REQUEST *request, VALUE_PAIR *password,
730                      uint8_t *challenge, uint8_t *response,
731                      uint8_t *nthashhash)
732 {
733         int             do_ntlm_auth = 0;
734         uint8_t         calculated[24];
735         VALUE_PAIR      *vp = NULL;
736
737         /*
738          *      If we have ntlm_auth configured, use it unless told
739          *      otherwise
740          */
741         if (inst->ntlm_auth) do_ntlm_auth = 1;
742
743         /*
744          *      If we have an ntlm_auth configuration, then we may
745          *      want to use it.
746          */
747         vp = pairfind(request->config_items,
748                       PW_MS_CHAP_USE_NTLM_AUTH);
749         if (vp) do_ntlm_auth = vp->lvalue;
750
751         /*
752          *      No ntlm_auth configured, attribute to tell us to
753          *      use it exists, and we're told to use it.  We don't
754          *      know what to do...
755          */
756         if (!inst->ntlm_auth && do_ntlm_auth) {
757                 DEBUG2("  rlm_mschap: Asked to use ntlm_auth, but it was not configured in the mschap{} section.");
758                 return -1;
759         }
760
761         /*
762          *      Do normal authentication.
763          */
764         if (!do_ntlm_auth) {
765                 /*
766                  *      No password: can't do authentication.
767                  */
768                 if (!password) {
769                         DEBUG2("  rlm_mschap: FAILED: No NT/LM-Password.  Cannot perform authentication.");
770                         return -1;
771                 }
772                 
773                 smbdes_mschap(password->vp_strvalue, challenge, calculated);
774                 if (memcmp(response, calculated, 24) != 0) {
775                         return -1;
776                 }
777                 
778                 /*
779                  *      If the password exists, and is an NT-Password,
780                  *      then calculate the hash of the NT hash.  Doing this
781                  *      here minimizes work for later.
782                  */
783                 if (password && (password->attribute == PW_NT_PASSWORD)) {
784                         md4_calc(nthashhash, password->vp_strvalue, 16);
785                 } else {
786                         memset(nthashhash, 0, 16);
787                 }
788         } else {                /* run ntlm_auth */
789                 int     result;
790                 char    buffer[256];
791
792                 memset(nthashhash, 0, 16);
793
794                 /*
795                  *      Run the program, and expect that we get 16 
796                  */
797                 result = radius_exec_program(inst->ntlm_auth, request,
798                                              TRUE, /* wait */
799                                              buffer, sizeof(buffer),
800                                              NULL, NULL, 1);
801                 if (result != 0) {
802                         DEBUG2("  rlm_mschap: External script failed.");
803                         return -1;
804                 }
805
806                 /*
807                  *      Parse the answer as an nthashhash.
808                  *
809                  *      ntlm_auth currently returns:
810                  *      NT_KEY: 000102030405060708090a0b0c0d0e0f
811                  */
812                 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
813                         DEBUG2("  rlm_mschap: Invalid output from ntlm_auth: expecting NT_KEY");
814                         return -1;
815                 }
816
817                 /*
818                  *      Check the length.  It should be at least 32,
819                  *      with an LF at the end.
820                  */
821                 if (strlen(buffer + 8) < 32) {
822                         DEBUG2("  rlm_mschap: Invalid output from ntlm_auth: NT_KEY has unexpected length");
823                         return -1;
824                 }
825
826                 /*
827                  *      Update the NT hash hash, from the NT key.
828                  */
829                 if (lrad_hex2bin(buffer + 8, nthashhash, 16) != 16) {
830                         DEBUG2("  rlm_mschap: Invalid output from ntlm_auth: NT_KEY has non-hex values");
831                         return -1;
832                 }
833         }
834
835         return 0;
836 }
837
838
839 /*
840  *      Data for the hashes.
841  */
842 static const uint8_t SHSpad1[40] =
843                { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
844                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
845                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
846                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
847
848 static const uint8_t SHSpad2[40] =
849                { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
850                  0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
851                  0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
852                  0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
853
854 static const uint8_t magic1[27] =
855                { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
856                  0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
857                  0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
858
859 static const uint8_t magic2[84] =
860                { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
861                  0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
862                  0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
863                  0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
864                  0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
865                  0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
866                  0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
867                  0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
868                  0x6b, 0x65, 0x79, 0x2e };
869
870 static const uint8_t magic3[84] =
871                { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
872                  0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
873                  0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
874                  0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
875                  0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
876                  0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
877                  0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
878                  0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
879                  0x6b, 0x65, 0x79, 0x2e };
880
881
882 static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response,
883                               uint8_t *masterkey)
884 {
885        uint8_t digest[20];
886        SHA1_CTX Context;
887
888        SHA1Init(&Context);
889        SHA1Update(&Context,nt_hashhash,16);
890        SHA1Update(&Context,nt_response,24);
891        SHA1Update(&Context,magic1,27);
892        SHA1Final(digest,&Context);
893
894        memcpy(masterkey,digest,16);
895 }
896
897
898 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
899                                        int keylen,int issend)
900 {
901        uint8_t digest[20];
902        const uint8_t *s;
903        SHA1_CTX Context;
904
905        memset(digest,0,20);
906
907        if(issend) {
908                s = magic3;
909        } else {
910                s = magic2;
911        }
912
913        SHA1Init(&Context);
914        SHA1Update(&Context,masterkey,16);
915        SHA1Update(&Context,SHSpad1,40);
916        SHA1Update(&Context,s,84);
917        SHA1Update(&Context,SHSpad2,40);
918        SHA1Final(digest,&Context);
919
920        memcpy(sesskey,digest,keylen);
921 }
922
923
924 static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response,
925                                    uint8_t *sendkey,uint8_t *recvkey)
926 {
927        uint8_t masterkey[16];
928
929        mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
930
931        mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
932        mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
933 }
934
935 /*
936  *      Generate MPPE keys.
937  */
938 static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response,
939                                    uint8_t *sendkey,uint8_t *recvkey)
940 {
941         uint8_t enckey1[16];
942         uint8_t enckey2[16];
943
944         mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
945
946         /*
947          *      dictionary.microsoft defines these attributes as
948          *      'encrypt=2'.  The functions in src/lib/radius.c will
949          *      take care of encrypting/decrypting them as appropriate,
950          *      so that we don't have to.
951          */
952         memcpy (sendkey, enckey1, 16);
953         memcpy (recvkey, enckey2, 16);
954 }
955
956
957 /*
958  *      mschap_authorize() - authorize user if we can authenticate
959  *      it later. Add Auth-Type attribute if present in module
960  *      configuration (usually Auth-Type must be "MS-CHAP")
961  */
962 static int mschap_authorize(void * instance, REQUEST *request)
963 {
964 #define inst ((rlm_mschap_t *)instance)
965         VALUE_PAIR *challenge = NULL, *response = NULL;
966         VALUE_PAIR *vp;
967
968         challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
969         if (!challenge) {
970                 return RLM_MODULE_NOOP;
971         }
972
973         response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
974         if (!response)
975                 response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE);
976
977         /*
978          *      Nothing we recognize.  Don't do anything.
979          */
980         if (!response) {
981                 DEBUG2("  rlm_mschap: Found MS-CHAP-Challenge, but no MS-CHAP-Response.");
982                 return RLM_MODULE_NOOP;
983         }
984
985         DEBUG2("  rlm_mschap: Found MS-CHAP attributes.  Setting 'Auth-Type  = %s'", inst->xlat_name);
986
987         /*
988          *      Set Auth-Type to MS-CHAP.  The authentication code
989          *      will take care of turning clear-text passwords into
990          *      NT/LM passwords.
991          */
992         vp = pairmake("Auth-Type", inst->auth_type, T_OP_EQ);
993         if (!vp) return RLM_MODULE_FAIL;
994         pairmove(&request->config_items, &vp);
995         pairfree(&vp);          /* may be NULL */
996
997         return RLM_MODULE_OK;
998 #undef inst
999 }
1000
1001 /*
1002  *      mschap_authenticate() - authenticate user based on given
1003  *      attributes and configuration.
1004  *      We will try to find out password in configuration
1005  *      or in configured passwd file.
1006  *      If one is found we will check paraneters given by NAS.
1007  *
1008  *      If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
1009  *      one of:
1010  *              PAP:      PW_USER_PASSWORD or
1011  *              MS-CHAP:  PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
1012  *              MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
1013  *      In case of password mismatch or locked account we MAY return
1014  *      PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
1015  *      If MS-CHAP2 succeeds we MUST return
1016  *      PW_MSCHAP2_SUCCESS
1017  */
1018 static int mschap_authenticate(void * instance, REQUEST *request)
1019 {
1020 #define inst ((rlm_mschap_t *)instance)
1021         VALUE_PAIR *challenge = NULL;
1022         VALUE_PAIR *response = NULL;
1023         VALUE_PAIR *password = NULL;
1024         VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
1025         VALUE_PAIR *username;
1026         VALUE_PAIR *reply_attr;
1027         uint8_t nthashhash[16];
1028         uint8_t msch2resp[42];
1029         char *username_string;
1030         int chap = 0;
1031
1032         /*
1033          *      Find the SMB-Account-Ctrl attribute, or the
1034          *      SMB-Account-Ctrl-Text attribute.
1035          */
1036         smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL);
1037         if (!smb_ctrl) {
1038                 password = pairfind(request->config_items,
1039                                     PW_SMB_ACCOUNT_CTRL_TEXT);
1040                 if (password) {
1041                         smb_ctrl = pairmake("SMB-Account-CTRL", "0", T_OP_SET);
1042                         pairadd(&request->config_items, smb_ctrl);
1043                         smb_ctrl->lvalue = pdb_decode_acct_ctrl(password->vp_strvalue);
1044                 }
1045         }
1046
1047         /*
1048          *      We're configured to do MS-CHAP authentication.
1049          *      and account control information exists.  Enforce it.
1050          */
1051         if (smb_ctrl) {
1052                 /*
1053                  *      Password is not required.
1054                  */
1055                 if ((smb_ctrl->lvalue & ACB_PWNOTREQ) != 0) {
1056                         DEBUG2("  rlm_mschap: SMB-Account-Ctrl says no password is required.");
1057                         return RLM_MODULE_OK;
1058                 }
1059         }
1060
1061         /*
1062          *      Decide how to get the passwords.
1063          */
1064         password = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD);
1065
1066         /*
1067          *      We need an LM-Password.
1068          */
1069         lm_password = pairfind(request->config_items, PW_LM_PASSWORD);
1070         if (lm_password) {
1071                 /*
1072                  *      Allow raw octets.
1073                  */
1074                 if ((lm_password->length == 16) ||
1075                     ((lm_password->length == 32) &&
1076                      (lrad_hex2bin(lm_password->vp_strvalue,
1077                                    lm_password->vp_strvalue, 16) == 16))) {
1078                         DEBUG2("  rlm_mschap: Found LM-Password");
1079                         lm_password->length = 16;
1080
1081                 } else {
1082                         radlog(L_ERR, "rlm_mschap: Invalid LM-Password");
1083                         lm_password = NULL;
1084                 }
1085
1086         } else if (!password) {
1087                 DEBUG2("  rlm_mschap: No Cleartext-Password configured.  Cannot create LM-Password.");
1088
1089         } else {                /* there is a configured Cleartext-Password */
1090                 lm_password = pairmake("LM-Password", "", T_OP_EQ);
1091                 if (!lm_password) {
1092                         radlog(L_ERR, "No memory");
1093                 } else {
1094                         smbdes_lmpwdhash(password->vp_strvalue,
1095                                        lm_password->vp_strvalue);
1096                         lm_password->length = 16;
1097                         pairadd(&request->config_items, lm_password);
1098                 }
1099         }
1100
1101         /*
1102          *      We need an NT-Password.
1103          */
1104         nt_password = pairfind(request->config_items, PW_NT_PASSWORD);
1105         if (nt_password) {
1106                 if ((nt_password->length == 16) ||
1107                     ((nt_password->length == 32) &&
1108                      (lrad_hex2bin(nt_password->vp_strvalue,
1109                                    nt_password->vp_strvalue, 16) == 16))) {
1110                         DEBUG2("  rlm_mschap: Found NT-Password");
1111                         nt_password->length = 16;
1112
1113                 } else {
1114                         radlog(L_ERR, "rlm_mschap: Invalid NT-Password");
1115                         nt_password = NULL;
1116                 }
1117         } else if (!password) {
1118                 DEBUG2("  rlm_mschap: No Cleartext-Password configured.  Cannot create NT-Password.");
1119
1120         } else {                /* there is a configured Cleartext-Password */
1121                 nt_password = pairmake("NT-Password", "", T_OP_EQ);
1122                 if (!nt_password) {
1123                         radlog(L_ERR, "No memory");
1124                         return RLM_MODULE_FAIL;
1125                 } else {
1126                         ntpwdhash(nt_password->vp_strvalue,
1127                                   password->vp_strvalue);
1128                         nt_password->length = 16;
1129                         pairadd(&request->config_items, nt_password);
1130                 }
1131         }
1132
1133         challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
1134         if (!challenge) {
1135                 DEBUG2("  rlm_mschap: No MS-CHAP-Challenge in the request");
1136                 return RLM_MODULE_REJECT;
1137         }
1138
1139         /*
1140          *      We also require an MS-CHAP-Response.
1141          */
1142         response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
1143
1144         /*
1145          *      MS-CHAP-Response, means MS-CHAPv1
1146          */
1147         if (response) {
1148                 int offset;
1149
1150                 /*
1151                  *      MS-CHAPv1 challenges are 8 octets.
1152                  */
1153                 if (challenge->length < 8) {
1154                         radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1155                         return RLM_MODULE_INVALID;
1156                 }
1157
1158                 /*
1159                  *      Responses are 50 octets.
1160                  */
1161                 if (response->length < 50) {
1162                         radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1163                         return RLM_MODULE_INVALID;
1164                 }
1165
1166                 /*
1167                  *      We are doing MS-CHAP.  Calculate the MS-CHAP
1168                  *      response
1169                  */
1170                 if (response->vp_octets[1] & 0x01) {
1171                         DEBUG2("  rlm_mschap: Told to do MS-CHAPv1 with NT-Password");
1172                         password = nt_password;
1173                         offset = 26;
1174                 } else {
1175                         DEBUG2("  rlm_mschap: Told to do MS-CHAPv1 with LM-Password");
1176                         password = lm_password;
1177                         offset = 2;
1178                 }
1179
1180                 /*
1181                  *      Do the MS-CHAP authentication.
1182                  */
1183                 if (do_mschap(inst, request, password, challenge->vp_octets,
1184                               response->vp_octets + offset, nthashhash) < 0) {
1185                         DEBUG2("  rlm_mschap: MS-CHAP-Response is incorrect.");
1186                         add_reply(&request->reply->vps, *response->vp_octets,
1187                                   "MS-CHAP-Error", "E=691 R=1", 9);
1188                         return RLM_MODULE_REJECT;
1189                 }
1190
1191                 chap = 1;
1192
1193         } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE)) != NULL) {
1194                 uint8_t mschapv1_challenge[16];
1195
1196                 /*
1197                  *      MS-CHAPv2 challenges are 16 octets.
1198                  */
1199                 if (challenge->length < 16) {
1200                         radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1201                         return RLM_MODULE_INVALID;
1202                 }
1203
1204                 /*
1205                  *      Responses are 50 octets.
1206                  */
1207                 if (response->length < 50) {
1208                         radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1209                         return RLM_MODULE_INVALID;
1210                 }
1211
1212                 /*
1213                  *      We also require a User-Name
1214                  */
1215                 username = pairfind(request->packet->vps, PW_USER_NAME);
1216                 if (!username) {
1217                         radlog(L_AUTH, "rlm_mschap: We require a User-Name for MS-CHAPv2");
1218                         return RLM_MODULE_INVALID;
1219                 }
1220
1221
1222                 /*
1223                  *      with_ntdomain_hack moved here
1224                  */
1225                 if ((username_string = strchr(username->vp_strvalue, '\\')) != NULL) {
1226                         if (inst->with_ntdomain_hack) {
1227                                 username_string++;
1228                         } else {
1229                                 DEBUG2("  rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1230                                 username_string = username->vp_strvalue;
1231                         }
1232                 } else {
1233                         username_string = username->vp_strvalue;
1234                 }
1235
1236                 /*
1237                  *      The old "mschapv2" function has been moved to
1238                  *      here.
1239                  *
1240                  *      MS-CHAPv2 takes some additional data to create an
1241                  *      MS-CHAPv1 challenge, and then does MS-CHAPv1.
1242                  */
1243                 challenge_hash(response->vp_octets + 2, /* peer challenge */
1244                                challenge->vp_octets, /* our challenge */
1245                                username_string, /* user name */
1246                                mschapv1_challenge); /* resulting challenge */
1247                 
1248                 DEBUG2("  rlm_mschap: Told to do MS-CHAPv2 for %s with NT-Password",
1249                        username_string);
1250
1251                 if (do_mschap(inst, request, nt_password, mschapv1_challenge,
1252                               response->vp_octets + 26, nthashhash) < 0) {
1253                         DEBUG2("  rlm_mschap: FAILED: MS-CHAP2-Response is incorrect");
1254                         add_reply(&request->reply->vps, *response->vp_octets,
1255                                   "MS-CHAP-Error", "E=691 R=1", 9);
1256                         return RLM_MODULE_REJECT;
1257                 }
1258
1259                 /*
1260                  *      Get the NT-hash-hash, if necessary
1261                  */
1262                 if (nt_password) {
1263                 }
1264
1265                 auth_response(username_string, /* without the domain */
1266                               nthashhash, /* nt-hash-hash */
1267                               response->vp_octets + 26, /* peer response */
1268                               response->vp_octets + 2, /* peer challenge */
1269                               challenge->vp_octets, /* our challenge */
1270                               msch2resp); /* calculated MPPE key */
1271                 add_reply( &request->reply->vps, *response->vp_octets,
1272                            "MS-CHAP2-Success", msch2resp, 42);
1273                 chap = 2;
1274
1275         } else {                /* Neither CHAPv1 or CHAPv2 response: die */
1276                 radlog(L_AUTH, "rlm_mschap: No MS-CHAP response found");
1277                 return RLM_MODULE_INVALID;
1278         }
1279
1280         /*
1281          *      We have a CHAP response, but the account may be
1282          *      disabled.  Reject the user with the same error code
1283          *      we use when their password is invalid.
1284          */
1285         if (smb_ctrl) {
1286                 /*
1287                  *      Account is disabled.
1288                  *
1289                  *      They're found, but they don't exist, so we
1290                  *      return 'not found'.
1291                  */
1292                 if (((smb_ctrl->lvalue & ACB_DISABLED) != 0) ||
1293                     ((smb_ctrl->lvalue & ACB_NORMAL) == 0)) {
1294                         DEBUG2("  rlm_mschap: SMB-Account-Ctrl says that the account is disabled, or is not a normal account.");
1295                         add_reply( &request->reply->vps, *response->vp_octets,
1296                                    "MS-CHAP-Error", "E=691 R=1", 9);
1297                         return RLM_MODULE_NOTFOUND;
1298                 }
1299
1300                 /*
1301                  *      User is locked out.
1302                  */
1303                 if ((smb_ctrl->lvalue & ACB_AUTOLOCK) != 0) {
1304                         DEBUG2("  rlm_mschap: SMB-Account-Ctrl says that the account is locked out.");
1305                         add_reply( &request->reply->vps, *response->vp_octets,
1306                                    "MS-CHAP-Error", "E=647 R=0", 9);
1307                         return RLM_MODULE_USERLOCK;
1308                 }
1309         }
1310
1311         /* now create MPPE attributes */
1312         if (inst->use_mppe) {
1313                 uint8_t mppe_sendkey[34];
1314                 uint8_t mppe_recvkey[34];
1315
1316                 if (chap == 1){
1317                         DEBUG2("rlm_mschap: adding MS-CHAPv1 MPPE keys");
1318                         memset(mppe_sendkey, 0, 32);
1319                         if (lm_password) {
1320                                 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1321                         }
1322
1323                         /*
1324                          *      According to RFC 2548 we
1325                          *      should send NT hash.  But in
1326                          *      practice it doesn't work.
1327                          *      Instead, we should send nthashhash
1328                          *
1329                          *      This is an error on RFC 2548.
1330                          */
1331                         /*
1332                          *      do_mschap cares to zero nthashhash if NT hash
1333                          *      is not available.
1334                          */
1335                         memcpy(mppe_sendkey + 8,
1336                                nthashhash, 16);
1337                         mppe_add_reply(&request->reply->vps,
1338                                        "MS-CHAP-MPPE-Keys",
1339                                        mppe_sendkey, 32);
1340                 } else if (chap == 2) {
1341                         DEBUG2("rlm_mschap: adding MS-CHAPv2 MPPE keys");
1342                         mppe_chap2_gen_keys128(nthashhash,
1343                                                response->vp_octets + 26,
1344                                                mppe_sendkey, mppe_recvkey);
1345                         
1346                         mppe_add_reply(&request->reply->vps,
1347                                        "MS-MPPE-Recv-Key",
1348                                        mppe_recvkey, 16);
1349                         mppe_add_reply(&request->reply->vps,
1350                                        "MS-MPPE-Send-Key",
1351                                        mppe_sendkey, 16);
1352
1353                 }
1354                 reply_attr = pairmake("MS-MPPE-Encryption-Policy",
1355                                       (inst->require_encryption)? "0x00000002":"0x00000001",
1356                                       T_OP_EQ);
1357                 rad_assert(reply_attr != NULL);
1358                 pairadd(&request->reply->vps, reply_attr);
1359                 reply_attr = pairmake("MS-MPPE-Encryption-Types",
1360                                       (inst->require_strong)? "0x00000004":"0x00000006",
1361                                       T_OP_EQ);
1362                 rad_assert(reply_attr != NULL);
1363                 pairadd(&request->reply->vps, reply_attr);
1364
1365         } /* else we weren't asked to use MPPE */
1366
1367         return RLM_MODULE_OK;
1368 #undef inst
1369 }
1370
1371 module_t rlm_mschap = {
1372         RLM_MODULE_INIT,
1373         "MS-CHAP",
1374         RLM_TYPE_THREAD_SAFE,           /* type */
1375         mschap_instantiate,             /* instantiation */
1376         mschap_detach,          /* detach */
1377         {
1378                 mschap_authenticate,    /* authenticate */
1379                 mschap_authorize,       /* authorize */
1380                 NULL,                   /* pre-accounting */
1381                 NULL,                   /* accounting */
1382                 NULL,                   /* checksimul */
1383                 NULL,                   /* pre-proxy */
1384                 NULL,                   /* post-proxy */
1385                 NULL                    /* post-auth */
1386         },
1387 };