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