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