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