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