Make MSCHAP attribute lookups use Microsofts vendor-id
[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         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                                           VENDORPEC_MICROSOFT);
303                 if (!chap_challenge) {
304                         RDEBUG2("No MS-CHAP-Challenge in the request.");
305                         return 0;
306                 }
307
308                 /*
309                  *      MS-CHAP-Challenges are 8 octets,
310                  *      for MS-CHAPv2
311                  */
312                 if (chap_challenge->length == 8) {
313                         RDEBUG2(" mschap1: %02x",
314                                chap_challenge->vp_octets[0]);
315                         data = chap_challenge->vp_octets;
316                         data_len = 8;
317
318                         /*
319                          *      MS-CHAP-Challenges are 16 octets,
320                          *      for MS-CHAPv2.
321                          */
322                 } else if (chap_challenge->length == 16) {
323                         char *username_string;
324
325                         RDEBUG2(" mschap2: %02x", chap_challenge->vp_octets[0]);
326                         response = pairfind(request->packet->vps,
327                                             PW_MSCHAP2_RESPONSE,
328                                             VENDORPEC_MICROSOFT);
329                         if (!response) {
330                                 RDEBUG2("MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge.");
331                                 return 0;
332                         }
333
334                         /*
335                          *      Responses are 50 octets.
336                          */
337                         if (response->length < 50) {
338                                 radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
339                                 return 0;
340                         }
341
342                         user_name = pairfind(request->packet->vps,
343                                              PW_USER_NAME, 0);
344                         if (!user_name) {
345                                 RDEBUG2("User-Name is required to calculateMS-CHAPv1 Challenge.");
346                                 return 0;
347                         }
348
349                         /*
350                          *      with_ntdomain_hack moved here, too.
351                          */
352                         if ((username_string = strchr(user_name->vp_strvalue, '\\')) != NULL) {
353                                 if (inst->with_ntdomain_hack) {
354                                         username_string++;
355                                 } else {
356                                         RDEBUG2("NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
357                                         username_string = user_name->vp_strvalue;
358                                 }
359                         } else {
360                                 username_string = user_name->vp_strvalue;
361                         }
362
363                         /*
364                          *      Get the MS-CHAPv1 challenge
365                          *      from the MS-CHAPv2 peer challenge,
366                          *      our challenge, and the user name.
367                          */
368                         challenge_hash(response->vp_octets + 2,
369                                        chap_challenge->vp_octets,
370                                        username_string, buffer);
371                         data = buffer;
372                         data_len = 8;
373                 } else {
374                         RDEBUG2("Invalid MS-CHAP challenge length");
375                         return 0;
376                 }
377
378                 /*
379                  *      Get the MS-CHAPv1 response, or the MS-CHAPv2
380                  *      response.
381                  */
382         } else if (strncasecmp(fmt, "NT-Response", 11) == 0) {
383                 response = pairfind(request->packet->vps,
384                                     PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT);
385                 if (!response) response = pairfind(request->packet->vps,
386                                                    PW_MSCHAP2_RESPONSE,
387                                                    VENDORPEC_MICROSOFT);
388                 if (!response) {
389                         RDEBUG2("No MS-CHAP-Response or MS-CHAP2-Response was found in the request.");
390                         return 0;
391                 }
392
393                 /*
394                  *      For MS-CHAPv1, the NT-Response exists only
395                  *      if the second octet says so.
396                  */
397                 if ((response->attribute == PW_MSCHAP_RESPONSE) &&
398                     ((response->vp_octets[1] & 0x01) == 0)) {
399                         RDEBUG2("No NT-Response in MS-CHAP-Response");
400                         return 0;
401                 }
402
403                 /*
404                  *      MS-CHAP-Response and MS-CHAP2-Response have
405                  *      the NT-Response at the same offset, and are
406                  *      the same length.
407                  */
408                 data = response->vp_octets + 26;
409                 data_len = 24;
410
411                 /*
412                  *      LM-Response is deprecated, and exists only
413                  *      in MS-CHAPv1, and not often there.
414                  */
415         } else if (strncasecmp(fmt, "LM-Response", 11) == 0) {
416                 response = pairfind(request->packet->vps,
417                                     PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT);
418                 if (!response) {
419                         RDEBUG2("No MS-CHAP-Response was found in the request.");
420                         return 0;
421                 }
422
423                 /*
424                  *      For MS-CHAPv1, the NT-Response exists only
425                  *      if the second octet says so.
426                  */
427                 if ((response->vp_octets[1] & 0x01) != 0) {
428                         RDEBUG2("No LM-Response in MS-CHAP-Response");
429                         return 0;
430                 }
431                 data = response->vp_octets + 2;
432                 data_len = 24;
433
434                 /*
435                  *      Pull the NT-Domain out of the User-Name, if it exists.
436                  */
437         } else if (strncasecmp(fmt, "NT-Domain", 9) == 0) {
438                 char *p, *q;
439
440                 user_name = pairfind(request->packet->vps, PW_USER_NAME, 0);
441                 if (!user_name) {
442                         RDEBUG2("No User-Name was found in the request.");
443                         return 0;
444                 }
445
446                 /*
447                  *      First check to see if this is a host/ style User-Name
448                  *      (a la Kerberos host principal)
449                  */
450                 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
451                         /*
452                          *      If we're getting a User-Name formatted in this way,
453                          *      it's likely due to PEAP.  The Windows Domain will be
454                          *      the first domain component following the hostname,
455                          *      or the machine name itself if only a hostname is supplied
456                          */
457                         p = strchr(user_name->vp_strvalue, '.');
458                         if (!p) {
459                                 RDEBUG2("setting NT-Domain to same as machine name");
460                                 strlcpy(out, user_name->vp_strvalue + 5, outlen);
461                         } else {
462                                 p++;    /* skip the period */
463                                 q = strchr(p, '.');
464                                 /*
465                                  * use the same hack as below
466                                  * only if another period was found
467                                  */
468                                 if (q) *q = '\0';
469                                 strlcpy(out, p, outlen);
470                                 if (q) *q = '.';
471                         }
472                 } else {
473                         p = strchr(user_name->vp_strvalue, '\\');
474                         if (!p) {
475                                 RDEBUG2("No NT-Domain was found in the User-Name.");
476                                 return 0;
477                         }
478
479                         /*
480                          *      Hack.  This is simpler than the alternatives.
481                          */
482                         *p = '\0';
483                         strlcpy(out, user_name->vp_strvalue, outlen);
484                         *p = '\\';
485                 }
486
487                 return strlen(out);
488
489                 /*
490                  *      Pull the User-Name out of the User-Name...
491                  */
492         } else if (strncasecmp(fmt, "User-Name", 9) == 0) {
493                 char *p;
494
495                 user_name = pairfind(request->packet->vps, PW_USER_NAME, 0);
496                 if (!user_name) {
497                         RDEBUG2("No User-Name was found in the request.");
498                         return 0;
499                 }
500
501                 /*
502                  *      First check to see if this is a host/ style User-Name
503                  *      (a la Kerberos host principal)
504                  */
505                 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
506                         /*
507                          *      If we're getting a User-Name formatted in this way,
508                          *      it's likely due to PEAP.  When authenticating this against
509                          *      a Domain, Windows will expect the User-Name to be in the
510                          *      format of hostname$, the SAM version of the name, so we
511                          *      have to convert it to that here.  We do so by stripping
512                          *      off the first 5 characters (host/), and copying everything
513                          *      from that point to the first period into a string and appending
514                          *      a $ to the end.
515                          */
516                         p = strchr(user_name->vp_strvalue, '.');
517                         /*
518                          * use the same hack as above
519                          * only if a period was found
520                          */
521                         if (p) *p = '\0';
522                         snprintf(out, outlen, "%s$", user_name->vp_strvalue + 5);
523                         if (p) *p = '.';
524                 } else {
525                         p = strchr(user_name->vp_strvalue, '\\');
526                         if (p) {
527                                 p++;    /* skip the backslash */
528                         } else {
529                                 p = user_name->vp_strvalue; /* use the whole User-Name */
530                         }
531                         strlcpy(out, p, outlen);
532                 }
533
534                 return strlen(out);
535
536                 /*
537                  * Return the NT-Hash of the passed string
538                  */
539         } else if (strncasecmp(fmt, "NT-Hash ", 8) == 0) {
540                 char *p;
541                 char buf2[1024];
542
543                 p = fmt + 8;    /* 7 is the length of 'NT-Hash' */
544                 if ((p == '\0')  || (outlen <= 32))
545                         return 0;
546
547                 while (isspace(*p)) p++;
548
549                 if (!radius_xlat(buf2, sizeof(buf2),p,request,NULL)) {
550                         RDEBUG("xlat failed");
551                         *buffer = '\0';
552                         return 0;
553                 }
554
555                 ntpwdhash(buffer,buf2);
556
557                 fr_bin2hex(buffer, out, 16);
558                 out[32] = '\0';
559                 RDEBUG("NT-Hash of %s = %s", buf2, out);
560                 return 32;
561
562                 /*
563                  * Return the LM-Hash of the passed string
564                  */
565         } else if (strncasecmp(fmt, "LM-Hash ", 8) == 0) {
566                 char *p;
567                 char buf2[1024];
568
569                 p = fmt + 8;    /* 7 is the length of 'LM-Hash' */
570                 if ((p == '\0') || (outlen <= 32))
571                         return 0;
572
573                 while (isspace(*p)) p++;
574
575                 if (!radius_xlat(buf2, sizeof(buf2),p,request,NULL)) {
576                         RDEBUG("xlat failed");
577                         *buffer = '\0';
578                         return 0;
579                 }
580
581                 smbdes_lmpwdhash(buf2, buffer);
582                 fr_bin2hex(buffer, out, 16);
583                 out[32] = '\0';
584                 RDEBUG("LM-Hash of %s = %s", buf2, out);
585                 return 32;
586         } else {
587                 RDEBUG2("Unknown expansion string \"%s\"",
588                        fmt);
589                 return 0;
590         }
591
592         if (outlen == 0) return 0; /* nowhere to go, don't do anything */
593
594         /*
595          *      Didn't set anything: this is bad.
596          */
597         if (!data) {
598                 RDEBUG2("Failed to do anything intelligent");
599                 return 0;
600         }
601
602         /*
603          *      Check the output length.
604          */
605         if (outlen < ((data_len * 2) + 1)) {
606                 data_len = (outlen - 1) / 2;
607         }
608
609         /*
610          *
611          */
612         for (i = 0; i < data_len; i++) {
613                 sprintf(out + (2 * i), "%02x", data[i]);
614         }
615         out[data_len * 2] = '\0';
616
617         return data_len * 2;
618 }
619
620
621 static const CONF_PARSER module_config[] = {
622         /*
623          *      Cache the password by default.
624          */
625         { "use_mppe",    PW_TYPE_BOOLEAN,
626           offsetof(rlm_mschap_t,use_mppe), NULL, "yes" },
627         { "require_encryption",    PW_TYPE_BOOLEAN,
628           offsetof(rlm_mschap_t,require_encryption), NULL, "no" },
629         { "require_strong",    PW_TYPE_BOOLEAN,
630           offsetof(rlm_mschap_t,require_strong), NULL, "no" },
631         { "with_ntdomain_hack",     PW_TYPE_BOOLEAN,
632           offsetof(rlm_mschap_t,with_ntdomain_hack), NULL, "no" },
633         { "passwd",   PW_TYPE_STRING_PTR,
634           offsetof(rlm_mschap_t, passwd_file), NULL,  NULL },
635         { "ntlm_auth",   PW_TYPE_STRING_PTR,
636           offsetof(rlm_mschap_t, ntlm_auth), NULL,  NULL },
637 #ifdef __APPLE__
638         { "use_open_directory",    PW_TYPE_BOOLEAN,
639           offsetof(rlm_mschap_t,open_directory), NULL, "yes" },
640 #endif
641
642         { NULL, -1, 0, NULL, NULL }             /* end the list */
643 };
644
645 /*
646  *      deinstantiate module, free all memory allocated during
647  *      mschap_instantiate()
648  */
649 static int mschap_detach(void *instance){
650 #define inst ((rlm_mschap_t *)instance)
651         if (inst->xlat_name) {
652                 xlat_unregister(inst->xlat_name, mschap_xlat);
653                 free(inst->xlat_name);
654         }
655         free(instance);
656         return 0;
657 #undef inst
658 }
659
660 /*
661  *      Create instance for our module. Allocate space for
662  *      instance structure and read configuration parameters
663  */
664 static int mschap_instantiate(CONF_SECTION *conf, void **instance)
665 {
666         rlm_mschap_t *inst;
667
668         inst = *instance = rad_malloc(sizeof(*inst));
669         if (!inst) {
670                 return -1;
671         }
672         memset(inst, 0, sizeof(*inst));
673
674         if (cf_section_parse(conf, inst, module_config) < 0) {
675                 free(inst);
676                 return -1;
677         }
678
679         /*
680          *      This module used to support SMB Password files, but it
681          *      made it too complicated.  If the user tries to
682          *      configure an SMB Password file, then die, with an
683          *      error message.
684          */
685         if (inst->passwd_file) {
686                 radlog(L_ERR, "rlm_mschap: SMB password file is no longer supported in this module.  Use rlm_passwd module instead");
687                 mschap_detach(inst);
688                 return -1;
689         }
690
691         /*
692          *      Create the dynamic translation.
693          */
694         inst->xlat_name = cf_section_name2(conf);
695         if (!inst->xlat_name) inst->xlat_name = cf_section_name1(conf);
696         inst->xlat_name = strdup(inst->xlat_name);
697         xlat_register(inst->xlat_name, mschap_xlat, inst);
698
699         /*
700          *      For backwards compatibility
701          */
702         if (!dict_valbyname(PW_AUTH_TYPE, 0, inst->xlat_name)) {
703                 inst->auth_type = "MS-CHAP";
704         } else {
705                 inst->auth_type = inst->xlat_name;
706         }
707
708         return 0;
709 }
710
711 /*
712  *      add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
713  *      attribute to reply packet
714  */
715 void mschap_add_reply(REQUEST *request, VALUE_PAIR** vp, unsigned char ident,
716                       const char* name, const char* value, int len)
717 {
718         VALUE_PAIR *reply_attr;
719         reply_attr = pairmake(name, "", T_OP_EQ);
720         if (!reply_attr) {
721                 RDEBUG("Failed to create attribute %s: %s\n", name, fr_strerror());
722                 return;
723         }
724
725         reply_attr->vp_octets[0] = ident;
726         memcpy(reply_attr->vp_octets + 1, value, len);
727         reply_attr->length = len + 1;
728         pairadd(vp, reply_attr);
729 }
730
731 /*
732  *      Add MPPE attributes to the reply.
733  */
734 static void mppe_add_reply(REQUEST *request,
735                            const char* name, const uint8_t * value, int len)
736 {
737        VALUE_PAIR *vp;
738        vp = radius_pairmake(request, &request->reply->vps, name, "", T_OP_EQ);
739        if (!vp) {
740                RDEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, fr_strerror());
741                return;
742        }
743
744        memcpy(vp->vp_octets, value, len);
745        vp->length = len;
746 }
747
748
749 /*
750  *      Do the MS-CHAP stuff.
751  *
752  *      This function is here so that all of the MS-CHAP related
753  *      authentication is in one place, and we can perhaps later replace
754  *      it with code to call winbindd, or something similar.
755  */
756 static int do_mschap(rlm_mschap_t *inst,
757                      REQUEST *request, VALUE_PAIR *password,
758                      uint8_t *challenge, uint8_t *response,
759                      uint8_t *nthashhash, int do_ntlm_auth)
760 {
761         uint8_t         calculated[24];
762
763         /*
764          *      Do normal authentication.
765          */
766         if (!do_ntlm_auth) {
767                 /*
768                  *      No password: can't do authentication.
769                  */
770                 if (!password) {
771                         RDEBUG2("FAILED: No NT/LM-Password.  Cannot perform authentication.");
772                         return -1;
773                 }
774
775                 smbdes_mschap(password->vp_strvalue, challenge, calculated);
776                 if (memcmp(response, calculated, 24) != 0) {
777                         return -1;
778                 }
779
780                 /*
781                  *      If the password exists, and is an NT-Password,
782                  *      then calculate the hash of the NT hash.  Doing this
783                  *      here minimizes work for later.
784                  */
785                 if (password && (password->attribute == PW_NT_PASSWORD)) {
786                         fr_md4_calc(nthashhash, password->vp_octets, 16);
787                 } else {
788                         memset(nthashhash, 0, 16);
789                 }
790         } else {                /* run ntlm_auth */
791                 int     result;
792                 char    buffer[256];
793
794                 memset(nthashhash, 0, 16);
795
796                 /*
797                  *      Run the program, and expect that we get 16
798                  */
799                 result = radius_exec_program(inst->ntlm_auth, request,
800                                              TRUE, /* wait */
801                                              buffer, sizeof(buffer),
802                                              NULL, NULL, 1);
803                 if (result != 0) {
804                         RDEBUG2("External script failed.");
805                         return -1;
806                 }
807
808                 /*
809                  *      Parse the answer as an nthashhash.
810                  *
811                  *      ntlm_auth currently returns:
812                  *      NT_KEY: 000102030405060708090a0b0c0d0e0f
813                  */
814                 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
815                         RDEBUG2("Invalid output from ntlm_auth: expecting NT_KEY");
816                         return -1;
817                 }
818
819                 /*
820                  *      Check the length.  It should be at least 32,
821                  *      with an LF at the end.
822                  */
823                 if (strlen(buffer + 8) < 32) {
824                         RDEBUG2("Invalid output from ntlm_auth: NT_KEY has unexpected length");
825                         return -1;
826                 }
827
828                 /*
829                  *      Update the NT hash hash, from the NT key.
830                  */
831                 if (fr_hex2bin(buffer + 8, nthashhash, 16) != 16) {
832                         RDEBUG2("Invalid output from ntlm_auth: NT_KEY has non-hex values");
833                         return -1;
834                 }
835         }
836
837         return 0;
838 }
839
840
841 /*
842  *      Data for the hashes.
843  */
844 static const uint8_t SHSpad1[40] =
845                { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
846                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
847                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
848                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
849
850 static const uint8_t SHSpad2[40] =
851                { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
852                  0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
853                  0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
854                  0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
855
856 static const uint8_t magic1[27] =
857                { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
858                  0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
859                  0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
860
861 static const uint8_t magic2[84] =
862                { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
863                  0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
864                  0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
865                  0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
866                  0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
867                  0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
868                  0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
869                  0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
870                  0x6b, 0x65, 0x79, 0x2e };
871
872 static const uint8_t magic3[84] =
873                { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
874                  0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
875                  0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
876                  0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
877                  0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
878                  0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
879                  0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
880                  0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
881                  0x6b, 0x65, 0x79, 0x2e };
882
883
884 static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response,
885                               uint8_t *masterkey)
886 {
887        uint8_t digest[20];
888        fr_SHA1_CTX Context;
889
890        fr_SHA1Init(&Context);
891        fr_SHA1Update(&Context,nt_hashhash,16);
892        fr_SHA1Update(&Context,nt_response,24);
893        fr_SHA1Update(&Context,magic1,27);
894        fr_SHA1Final(digest,&Context);
895
896        memcpy(masterkey,digest,16);
897 }
898
899
900 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
901                                        int keylen,int issend)
902 {
903        uint8_t digest[20];
904        const uint8_t *s;
905        fr_SHA1_CTX Context;
906
907        memset(digest,0,20);
908
909        if(issend) {
910                s = magic3;
911        } else {
912                s = magic2;
913        }
914
915        fr_SHA1Init(&Context);
916        fr_SHA1Update(&Context,masterkey,16);
917        fr_SHA1Update(&Context,SHSpad1,40);
918        fr_SHA1Update(&Context,s,84);
919        fr_SHA1Update(&Context,SHSpad2,40);
920        fr_SHA1Final(digest,&Context);
921
922        memcpy(sesskey,digest,keylen);
923 }
924
925
926 static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response,
927                                    uint8_t *sendkey,uint8_t *recvkey)
928 {
929        uint8_t masterkey[16];
930
931        mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
932
933        mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
934        mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
935 }
936
937 /*
938  *      Generate MPPE keys.
939  */
940 static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response,
941                                    uint8_t *sendkey,uint8_t *recvkey)
942 {
943         uint8_t enckey1[16];
944         uint8_t enckey2[16];
945
946         mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
947
948         /*
949          *      dictionary.microsoft defines these attributes as
950          *      'encrypt=2'.  The functions in src/lib/radius.c will
951          *      take care of encrypting/decrypting them as appropriate,
952          *      so that we don't have to.
953          */
954         memcpy (sendkey, enckey1, 16);
955         memcpy (recvkey, enckey2, 16);
956 }
957
958
959 /*
960  *      mschap_authorize() - authorize user if we can authenticate
961  *      it later. Add Auth-Type attribute if present in module
962  *      configuration (usually Auth-Type must be "MS-CHAP")
963  */
964 static int mschap_authorize(void * instance, REQUEST *request)
965 {
966 #define inst ((rlm_mschap_t *)instance)
967         VALUE_PAIR *challenge = NULL, *response = NULL;
968
969         challenge = pairfind(request->packet->vps,
970                              PW_MSCHAP_CHALLENGE,
971                              VENDORPEC_MICROSOFT);
972         if (!challenge) {
973                 return RLM_MODULE_NOOP;
974         }
975
976         response = pairfind(request->packet->vps,
977                             PW_MSCHAP_RESPONSE,
978                             VENDORPEC_MICROSOFT);
979         if (!response)
980                 response = pairfind(request->packet->vps,
981                                     PW_MSCHAP2_RESPONSE,
982                                     VENDORPEC_MICROSOFT);
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, 0)) {
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         int             do_ntlm_auth;
1043
1044         /*
1045          *      If we have ntlm_auth configured, use it unless told
1046          *      otherwise
1047          */
1048         do_ntlm_auth = (inst->ntlm_auth != NULL);
1049
1050         /*
1051          *      If we have an ntlm_auth configuration, then we may
1052          *      want to suppress it.
1053          */
1054         if (do_ntlm_auth) {
1055                 VALUE_PAIR *vp = pairfind(request->config_items,
1056                                           PW_MS_CHAP_USE_NTLM_AUTH, 0);
1057                 if (vp) do_ntlm_auth = vp->vp_integer;
1058         }
1059
1060         /*
1061          *      Find the SMB-Account-Ctrl attribute, or the
1062          *      SMB-Account-Ctrl-Text attribute.
1063          */
1064         smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL, 0);
1065         if (!smb_ctrl) {
1066                 password = pairfind(request->config_items,
1067                                     PW_SMB_ACCOUNT_CTRL_TEXT, 0);
1068                 if (password) {
1069                         smb_ctrl = radius_pairmake(request,
1070                                                    &request->config_items,
1071                                                    "SMB-Account-CTRL", "0",
1072                                                    T_OP_SET);
1073                         if (smb_ctrl) {
1074                                 smb_ctrl->vp_integer = pdb_decode_acct_ctrl(password->vp_strvalue);
1075                         }
1076                 }
1077         }
1078
1079         /*
1080          *      We're configured to do MS-CHAP authentication.
1081          *      and account control information exists.  Enforce it.
1082          */
1083         if (smb_ctrl) {
1084                 /*
1085                  *      Password is not required.
1086                  */
1087                 if ((smb_ctrl->vp_integer & ACB_PWNOTREQ) != 0) {
1088                         RDEBUG2("SMB-Account-Ctrl says no password is required.");
1089                         return RLM_MODULE_OK;
1090                 }
1091         }
1092
1093         /*
1094          *      Decide how to get the passwords.
1095          */
1096         password = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD, 0);
1097
1098         /*
1099          *      We need an LM-Password.
1100          */
1101         lm_password = pairfind(request->config_items, PW_LM_PASSWORD, 0);
1102         if (lm_password) {
1103                 /*
1104                  *      Allow raw octets.
1105                  */
1106                 if ((lm_password->length == 16) ||
1107                     ((lm_password->length == 32) &&
1108                      (fr_hex2bin(lm_password->vp_strvalue,
1109                                  lm_password->vp_octets, 16) == 16))) {
1110                         RDEBUG2("Found LM-Password");
1111                         lm_password->length = 16;
1112
1113                 } else {
1114                         radlog_request(L_ERR, 0, request, "Invalid LM-Password");
1115                         lm_password = NULL;
1116                 }
1117
1118         } else if (!password) {
1119                 if (!do_ntlm_auth) RDEBUG2("No Cleartext-Password configured.  Cannot create LM-Password.");
1120
1121         } else {                /* there is a configured Cleartext-Password */
1122                 lm_password = radius_pairmake(request, &request->config_items,
1123                                               "LM-Password", "", T_OP_EQ);
1124                 if (!lm_password) {
1125                         radlog_request(L_ERR, 0, request, "No memory");
1126                 } else {
1127                         smbdes_lmpwdhash(password->vp_strvalue,
1128                                          lm_password->vp_octets);
1129                         lm_password->length = 16;
1130                 }
1131         }
1132
1133         /*
1134          *      We need an NT-Password.
1135          */
1136         nt_password = pairfind(request->config_items, PW_NT_PASSWORD, 0);
1137         if (nt_password) {
1138                 if ((nt_password->length == 16) ||
1139                     ((nt_password->length == 32) &&
1140                      (fr_hex2bin(nt_password->vp_strvalue,
1141                                  nt_password->vp_octets, 16) == 16))) {
1142                         RDEBUG2("Found NT-Password");
1143                         nt_password->length = 16;
1144
1145                 } else {
1146                         radlog_request(L_ERR, 0, request, "Invalid NT-Password");
1147                         nt_password = NULL;
1148                 }
1149         } else if (!password) {
1150                 if (!do_ntlm_auth) RDEBUG2("No Cleartext-Password configured.  Cannot create NT-Password.");
1151
1152         } else {                /* there is a configured Cleartext-Password */
1153                 nt_password = radius_pairmake(request, &request->config_items,
1154                                               "NT-Password", "", T_OP_EQ);
1155                 if (!nt_password) {
1156                         radlog_request(L_ERR, 0, request, "No memory");
1157                         return RLM_MODULE_FAIL;
1158                 } else {
1159                         ntpwdhash(nt_password->vp_octets,
1160                                   password->vp_strvalue);
1161                         nt_password->length = 16;
1162                 }
1163         }
1164
1165         challenge = pairfind(request->packet->vps,
1166                              PW_MSCHAP_CHALLENGE,
1167                              VENDORPEC_MICROSOFT);
1168         if (!challenge) {
1169                 RDEBUG2("No MS-CHAP-Challenge in the request");
1170                 return RLM_MODULE_REJECT;
1171         }
1172
1173         /*
1174          *      We also require an MS-CHAP-Response.
1175          */
1176         response = pairfind(request->packet->vps,
1177                             PW_MSCHAP_RESPONSE,
1178                             VENDORPEC_MICROSOFT);
1179
1180         /*
1181          *      MS-CHAP-Response, means MS-CHAPv1
1182          */
1183         if (response) {
1184                 int offset;
1185
1186                 /*
1187                  *      MS-CHAPv1 challenges are 8 octets.
1188                  */
1189                 if (challenge->length < 8) {
1190                         radlog_request(L_AUTH, 0, request, "MS-CHAP-Challenge has the wrong format.");
1191                         return RLM_MODULE_INVALID;
1192                 }
1193
1194                 /*
1195                  *      Responses are 50 octets.
1196                  */
1197                 if (response->length < 50) {
1198                         radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
1199                         return RLM_MODULE_INVALID;
1200                 }
1201
1202                 /*
1203                  *      We are doing MS-CHAP.  Calculate the MS-CHAP
1204                  *      response
1205                  */
1206                 if (response->vp_octets[1] & 0x01) {
1207                         RDEBUG2("Told to do MS-CHAPv1 with NT-Password");
1208                         password = nt_password;
1209                         offset = 26;
1210                 } else {
1211                         RDEBUG2("Told to do MS-CHAPv1 with LM-Password");
1212                         password = lm_password;
1213                         offset = 2;
1214                 }
1215
1216                 /*
1217                  *      Do the MS-CHAP authentication.
1218                  */
1219                 if (do_mschap(inst, request, password, challenge->vp_octets,
1220                               response->vp_octets + offset, nthashhash,
1221                               do_ntlm_auth) < 0) {
1222                         RDEBUG2("MS-CHAP-Response is incorrect.");
1223                         mschap_add_reply(request, &request->reply->vps,
1224                                          *response->vp_octets,
1225                                          "MS-CHAP-Error", "E=691 R=1", 9);
1226                         return RLM_MODULE_REJECT;
1227                 }
1228
1229                 chap = 1;
1230
1231         } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT)) != NULL) {
1232                 uint8_t mschapv1_challenge[16];
1233
1234                 /*
1235                  *      MS-CHAPv2 challenges are 16 octets.
1236                  */
1237                 if (challenge->length < 16) {
1238                         radlog_request(L_AUTH, 0, request, "MS-CHAP-Challenge has the wrong format.");
1239                         return RLM_MODULE_INVALID;
1240                 }
1241
1242                 /*
1243                  *      Responses are 50 octets.
1244                  */
1245                 if (response->length < 50) {
1246                         radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
1247                         return RLM_MODULE_INVALID;
1248                 }
1249
1250                 /*
1251                  *      We also require a User-Name
1252                  */
1253                 username = pairfind(request->packet->vps, PW_USER_NAME, 0);
1254                 if (!username) {
1255                         radlog_request(L_AUTH, 0, request, "We require a User-Name for MS-CHAPv2");
1256                         return RLM_MODULE_INVALID;
1257                 }
1258
1259
1260                 /*
1261                  *      with_ntdomain_hack moved here
1262                  */
1263                 if ((username_string = strchr(username->vp_strvalue, '\\')) != NULL) {
1264                         if (inst->with_ntdomain_hack) {
1265                                 username_string++;
1266                         } else {
1267                                 RDEBUG2("  NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1268                                 username_string = username->vp_strvalue;
1269                         }
1270                 } else {
1271                         username_string = username->vp_strvalue;
1272                 }
1273
1274 #ifdef __APPLE__
1275                 /*
1276                  *  No "known good" NT-Password attribute.  Try to do
1277                  *  OpenDirectory authentication.
1278                  *
1279                  *  If OD determines the user is an AD user it will return noop, which
1280                  *  indicates the auth process should continue directly to AD.
1281                  *  Otherwise OD will determine auth success/fail.
1282                  */
1283                 if (!nt_password && inst->open_directory) {
1284                         RDEBUG2("No NT-Password configured. Trying OpenDirectory Authentication.");
1285                         int odStatus = od_mschap_auth(request, challenge, username);
1286                         if (odStatus != RLM_MODULE_NOOP) {
1287                                 return odStatus;
1288                         }
1289                 }
1290 #endif
1291                 /*
1292                  *      The old "mschapv2" function has been moved to
1293                  *      here.
1294                  *
1295                  *      MS-CHAPv2 takes some additional data to create an
1296                  *      MS-CHAPv1 challenge, and then does MS-CHAPv1.
1297                  */
1298                 challenge_hash(response->vp_octets + 2, /* peer challenge */
1299                                challenge->vp_octets, /* our challenge */
1300                                username_string, /* user name */
1301                                mschapv1_challenge); /* resulting challenge */
1302
1303                 RDEBUG2("Told to do MS-CHAPv2 for %s with NT-Password",
1304                        username_string);
1305
1306                 if (do_mschap(inst, request, nt_password, mschapv1_challenge,
1307                               response->vp_octets + 26, nthashhash,
1308                               do_ntlm_auth) < 0) {
1309                         RDEBUG2("FAILED: MS-CHAP2-Response is incorrect");
1310                         mschap_add_reply(request, &request->reply->vps,
1311                                          *response->vp_octets,
1312                                          "MS-CHAP-Error", "E=691 R=1", 9);
1313                         return RLM_MODULE_REJECT;
1314                 }
1315
1316                 /*
1317                  *      Get the NT-hash-hash, if necessary
1318                  */
1319                 if (nt_password) {
1320                 }
1321
1322                 auth_response(username_string, /* without the domain */
1323                               nthashhash, /* nt-hash-hash */
1324                               response->vp_octets + 26, /* peer response */
1325                               response->vp_octets + 2, /* peer challenge */
1326                               challenge->vp_octets, /* our challenge */
1327                               msch2resp); /* calculated MPPE key */
1328                 mschap_add_reply(request, &request->reply->vps, *response->vp_octets,
1329                                  "MS-CHAP2-Success", msch2resp, 42);
1330                 chap = 2;
1331
1332         } else {                /* Neither CHAPv1 or CHAPv2 response: die */
1333                 radlog_request(L_AUTH, 0, request, "No MS-CHAP response found");
1334                 return RLM_MODULE_INVALID;
1335         }
1336
1337         /*
1338          *      We have a CHAP response, but the account may be
1339          *      disabled.  Reject the user with the same error code
1340          *      we use when their password is invalid.
1341          */
1342         if (smb_ctrl) {
1343                 /*
1344                  *      Account is disabled.
1345                  *
1346                  *      They're found, but they don't exist, so we
1347                  *      return 'not found'.
1348                  */
1349                 if (((smb_ctrl->vp_integer & ACB_DISABLED) != 0) ||
1350                     ((smb_ctrl->vp_integer & ACB_NORMAL) == 0)) {
1351                         RDEBUG2("SMB-Account-Ctrl says that the account is disabled, or is not a normal account.");
1352                         mschap_add_reply(request, &request->reply->vps,
1353                                           *response->vp_octets,
1354                                           "MS-CHAP-Error", "E=691 R=1", 9);
1355                         return RLM_MODULE_NOTFOUND;
1356                 }
1357
1358                 /*
1359                  *      User is locked out.
1360                  */
1361                 if ((smb_ctrl->vp_integer & ACB_AUTOLOCK) != 0) {
1362                         RDEBUG2("SMB-Account-Ctrl says that the account is locked out.");
1363                         mschap_add_reply(request, &request->reply->vps,
1364                                           *response->vp_octets,
1365                                           "MS-CHAP-Error", "E=647 R=0", 9);
1366                         return RLM_MODULE_USERLOCK;
1367                 }
1368         }
1369
1370         /* now create MPPE attributes */
1371         if (inst->use_mppe) {
1372                 uint8_t mppe_sendkey[34];
1373                 uint8_t mppe_recvkey[34];
1374
1375                 if (chap == 1){
1376                         RDEBUG2("adding MS-CHAPv1 MPPE keys");
1377                         memset(mppe_sendkey, 0, 32);
1378                         if (lm_password) {
1379                                 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1380                         }
1381
1382                         /*
1383                          *      According to RFC 2548 we
1384                          *      should send NT hash.  But in
1385                          *      practice it doesn't work.
1386                          *      Instead, we should send nthashhash
1387                          *
1388                          *      This is an error on RFC 2548.
1389                          */
1390                         /*
1391                          *      do_mschap cares to zero nthashhash if NT hash
1392                          *      is not available.
1393                          */
1394                         memcpy(mppe_sendkey + 8,
1395                                nthashhash, 16);
1396                         mppe_add_reply(request,
1397                                        "MS-CHAP-MPPE-Keys",
1398                                        mppe_sendkey, 32);
1399                 } else if (chap == 2) {
1400                         RDEBUG2("adding MS-CHAPv2 MPPE keys");
1401                         mppe_chap2_gen_keys128(nthashhash,
1402                                                response->vp_octets + 26,
1403                                                mppe_sendkey, mppe_recvkey);
1404
1405                         mppe_add_reply(request,
1406                                        "MS-MPPE-Recv-Key",
1407                                        mppe_recvkey, 16);
1408                         mppe_add_reply(request,
1409                                        "MS-MPPE-Send-Key",
1410                                        mppe_sendkey, 16);
1411
1412                 }
1413                 radius_pairmake(request, &request->reply->vps,
1414                                 "MS-MPPE-Encryption-Policy",
1415                                 (inst->require_encryption)? "0x00000002":"0x00000001",
1416                                 T_OP_EQ);
1417                 radius_pairmake(request, &request->reply->vps,
1418                                 "MS-MPPE-Encryption-Types",
1419                                 (inst->require_strong)? "0x00000004":"0x00000006",
1420                                 T_OP_EQ);
1421         } /* else we weren't asked to use MPPE */
1422
1423         return RLM_MODULE_OK;
1424 #undef inst
1425 }
1426
1427 module_t rlm_mschap = {
1428         RLM_MODULE_INIT,
1429         "MS-CHAP",
1430         RLM_TYPE_THREAD_SAFE,           /* type */
1431         mschap_instantiate,             /* instantiation */
1432         mschap_detach,          /* detach */
1433         {
1434                 mschap_authenticate,    /* authenticate */
1435                 mschap_authorize,       /* authorize */
1436                 NULL,                   /* pre-accounting */
1437                 NULL,                   /* accounting */
1438                 NULL,                   /* checksimul */
1439                 NULL,                   /* pre-proxy */
1440                 NULL,                   /* post-proxy */
1441                 NULL                    /* post-auth */
1442         },
1443 };