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