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