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