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