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