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