7cd9874e629774db4c0e5ba2bdb5a1702c6c0d2f
[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                         RDEBUG2("External script failed.");
708                         return -1;
709                 }
710
711                 /*
712                  *      Parse the answer as an nthashhash.
713                  *
714                  *      ntlm_auth currently returns:
715                  *      NT_KEY: 000102030405060708090a0b0c0d0e0f
716                  */
717                 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
718                         RDEBUG2("Invalid output from ntlm_auth: expecting NT_KEY");
719                         return -1;
720                 }
721
722                 /*
723                  *      Check the length.  It should be at least 32,
724                  *      with an LF at the end.
725                  */
726                 if (strlen(buffer + 8) < 32) {
727                         RDEBUG2("Invalid output from ntlm_auth: NT_KEY has unexpected length");
728                         return -1;
729                 }
730
731                 /*
732                  *      Update the NT hash hash, from the NT key.
733                  */
734                 if (fr_hex2bin(buffer + 8, nthashhash, 16) != 16) {
735                         RDEBUG2("Invalid output from ntlm_auth: NT_KEY has non-hex values");
736                         return -1;
737                 }
738         }
739
740         return 0;
741 }
742
743
744 /*
745  *      Data for the hashes.
746  */
747 static const uint8_t SHSpad1[40] =
748                { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
749                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
750                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
751                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
752
753 static const uint8_t SHSpad2[40] =
754                { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
755                  0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
756                  0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
757                  0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
758
759 static const uint8_t magic1[27] =
760                { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
761                  0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
762                  0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
763
764 static const uint8_t magic2[84] =
765                { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
766                  0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
767                  0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
768                  0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
769                  0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
770                  0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
771                  0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
772                  0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
773                  0x6b, 0x65, 0x79, 0x2e };
774
775 static const uint8_t magic3[84] =
776                { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
777                  0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
778                  0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
779                  0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
780                  0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
781                  0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
782                  0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
783                  0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
784                  0x6b, 0x65, 0x79, 0x2e };
785
786
787 static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response,
788                               uint8_t *masterkey)
789 {
790        uint8_t digest[20];
791        fr_SHA1_CTX Context;
792
793        fr_SHA1Init(&Context);
794        fr_SHA1Update(&Context,nt_hashhash,16);
795        fr_SHA1Update(&Context,nt_response,24);
796        fr_SHA1Update(&Context,magic1,27);
797        fr_SHA1Final(digest,&Context);
798
799        memcpy(masterkey,digest,16);
800 }
801
802
803 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
804                                        int keylen,int issend)
805 {
806        uint8_t digest[20];
807        const uint8_t *s;
808        fr_SHA1_CTX Context;
809
810        memset(digest,0,20);
811
812        if(issend) {
813                s = magic3;
814        } else {
815                s = magic2;
816        }
817
818        fr_SHA1Init(&Context);
819        fr_SHA1Update(&Context,masterkey,16);
820        fr_SHA1Update(&Context,SHSpad1,40);
821        fr_SHA1Update(&Context,s,84);
822        fr_SHA1Update(&Context,SHSpad2,40);
823        fr_SHA1Final(digest,&Context);
824
825        memcpy(sesskey,digest,keylen);
826 }
827
828
829 static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response,
830                                    uint8_t *sendkey,uint8_t *recvkey)
831 {
832        uint8_t masterkey[16];
833
834        mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
835
836        mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
837        mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
838 }
839
840 /*
841  *      Generate MPPE keys.
842  */
843 static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response,
844                                    uint8_t *sendkey,uint8_t *recvkey)
845 {
846         uint8_t enckey1[16];
847         uint8_t enckey2[16];
848
849         mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
850
851         /*
852          *      dictionary.microsoft defines these attributes as
853          *      'encrypt=2'.  The functions in src/lib/radius.c will
854          *      take care of encrypting/decrypting them as appropriate,
855          *      so that we don't have to.
856          */
857         memcpy (sendkey, enckey1, 16);
858         memcpy (recvkey, enckey2, 16);
859 }
860
861
862 /*
863  *      mschap_authorize() - authorize user if we can authenticate
864  *      it later. Add Auth-Type attribute if present in module
865  *      configuration (usually Auth-Type must be "MS-CHAP")
866  */
867 static int mschap_authorize(void * instance, REQUEST *request)
868 {
869 #define inst ((rlm_mschap_t *)instance)
870         VALUE_PAIR *challenge = NULL, *response = NULL;
871
872         challenge = pairfind(request->packet->vps,
873                              PW_MSCHAP_CHALLENGE,
874                              VENDORPEC_MICROSOFT);
875         if (!challenge) {
876                 return RLM_MODULE_NOOP;
877         }
878
879         response = pairfind(request->packet->vps,
880                             PW_MSCHAP_RESPONSE,
881                             VENDORPEC_MICROSOFT);
882         if (!response)
883                 response = pairfind(request->packet->vps,
884                                     PW_MSCHAP2_RESPONSE,
885                                     VENDORPEC_MICROSOFT);
886
887         /*
888          *      Nothing we recognize.  Don't do anything.
889          */
890         if (!response) {
891                 RDEBUG2("Found MS-CHAP-Challenge, but no MS-CHAP-Response.");
892                 return RLM_MODULE_NOOP;
893         }
894
895         if (pairfind(request->config_items, PW_AUTH_TYPE, 0)) {
896                 RDEBUG2("WARNING: Auth-Type already set.  Not setting to MS-CHAP");
897                 return RLM_MODULE_NOOP;
898         }
899
900         RDEBUG2("Found MS-CHAP attributes.  Setting 'Auth-Type  = %s'", inst->xlat_name);
901
902         /*
903          *      Set Auth-Type to MS-CHAP.  The authentication code
904          *      will take care of turning clear-text passwords into
905          *      NT/LM passwords.
906          */
907         if (!radius_pairmake(request, &request->config_items,
908                              "Auth-Type", inst->auth_type, T_OP_EQ)) {
909                 return RLM_MODULE_FAIL;
910         }
911
912         return RLM_MODULE_OK;
913 #undef inst
914 }
915
916 /*
917  *      mschap_authenticate() - authenticate user based on given
918  *      attributes and configuration.
919  *      We will try to find out password in configuration
920  *      or in configured passwd file.
921  *      If one is found we will check paraneters given by NAS.
922  *
923  *      If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
924  *      one of:
925  *              PAP:      PW_USER_PASSWORD or
926  *              MS-CHAP:  PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
927  *              MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
928  *      In case of password mismatch or locked account we MAY return
929  *      PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
930  *      If MS-CHAP2 succeeds we MUST return
931  *      PW_MSCHAP2_SUCCESS
932  */
933 static int mschap_authenticate(void * instance, REQUEST *request)
934 {
935 #define inst ((rlm_mschap_t *)instance)
936         VALUE_PAIR *challenge = NULL;
937         VALUE_PAIR *response = NULL;
938         VALUE_PAIR *password = NULL;
939         VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
940         VALUE_PAIR *username;
941         uint8_t nthashhash[16];
942         char msch2resp[42];
943         char *username_string;
944         int chap = 0;
945         int             do_ntlm_auth;
946
947         /*
948          *      If we have ntlm_auth configured, use it unless told
949          *      otherwise
950          */
951         do_ntlm_auth = (inst->ntlm_auth != NULL);
952
953         /*
954          *      If we have an ntlm_auth configuration, then we may
955          *      want to suppress it.
956          */
957         if (do_ntlm_auth) {
958                 VALUE_PAIR *vp = pairfind(request->config_items,
959                                           PW_MS_CHAP_USE_NTLM_AUTH, 0);
960                 if (vp) do_ntlm_auth = vp->vp_integer;
961         }
962
963         /*
964          *      Find the SMB-Account-Ctrl attribute, or the
965          *      SMB-Account-Ctrl-Text attribute.
966          */
967         smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL, 0);
968         if (!smb_ctrl) {
969                 password = pairfind(request->config_items,
970                                     PW_SMB_ACCOUNT_CTRL_TEXT, 0);
971                 if (password) {
972                         smb_ctrl = radius_pairmake(request,
973                                                    &request->config_items,
974                                                    "SMB-Account-CTRL", "0",
975                                                    T_OP_SET);
976                         if (smb_ctrl) {
977                                 smb_ctrl->vp_integer = pdb_decode_acct_ctrl(password->vp_strvalue);
978                         }
979                 }
980         }
981
982         /*
983          *      We're configured to do MS-CHAP authentication.
984          *      and account control information exists.  Enforce it.
985          */
986         if (smb_ctrl) {
987                 /*
988                  *      Password is not required.
989                  */
990                 if ((smb_ctrl->vp_integer & ACB_PWNOTREQ) != 0) {
991                         RDEBUG2("SMB-Account-Ctrl says no password is required.");
992                         return RLM_MODULE_OK;
993                 }
994         }
995
996         /*
997          *      Decide how to get the passwords.
998          */
999         password = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD, 0);
1000
1001         /*
1002          *      We need an LM-Password.
1003          */
1004         lm_password = pairfind(request->config_items, PW_LM_PASSWORD, 0);
1005         if (lm_password) {
1006                 /*
1007                  *      Allow raw octets.
1008                  */
1009                 if ((lm_password->length == 16) ||
1010                     ((lm_password->length == 32) &&
1011                      (fr_hex2bin(lm_password->vp_strvalue,
1012                                  lm_password->vp_octets, 16) == 16))) {
1013                         RDEBUG2("Found LM-Password");
1014                         lm_password->length = 16;
1015
1016                 } else {
1017                         radlog_request(L_ERR, 0, request, "Invalid LM-Password");
1018                         lm_password = NULL;
1019                 }
1020
1021         } else if (!password) {
1022                 if (!do_ntlm_auth) RDEBUG2("No Cleartext-Password configured.  Cannot create LM-Password.");
1023
1024         } else {                /* there is a configured Cleartext-Password */
1025                 lm_password = radius_pairmake(request, &request->config_items,
1026                                               "LM-Password", "", T_OP_EQ);
1027                 if (!lm_password) {
1028                         radlog_request(L_ERR, 0, request, "No memory");
1029                 } else {
1030                         smbdes_lmpwdhash(password->vp_strvalue,
1031                                          lm_password->vp_octets);
1032                         lm_password->length = 16;
1033                 }
1034         }
1035
1036         /*
1037          *      We need an NT-Password.
1038          */
1039         nt_password = pairfind(request->config_items, PW_NT_PASSWORD, 0);
1040         if (nt_password) {
1041                 if ((nt_password->length == 16) ||
1042                     ((nt_password->length == 32) &&
1043                      (fr_hex2bin(nt_password->vp_strvalue,
1044                                  nt_password->vp_octets, 16) == 16))) {
1045                         RDEBUG2("Found NT-Password");
1046                         nt_password->length = 16;
1047
1048                 } else {
1049                         radlog_request(L_ERR, 0, request, "Invalid NT-Password");
1050                         nt_password = NULL;
1051                 }
1052         } else if (!password) {
1053                 if (!do_ntlm_auth) RDEBUG2("No Cleartext-Password configured.  Cannot create NT-Password.");
1054
1055         } else {                /* there is a configured Cleartext-Password */
1056                 nt_password = radius_pairmake(request, &request->config_items,
1057                                               "NT-Password", "", T_OP_EQ);
1058                 if (!nt_password) {
1059                         radlog_request(L_ERR, 0, request, "No memory");
1060                         return RLM_MODULE_FAIL;
1061                 } else {
1062                         mschap_ntpwdhash(nt_password->vp_octets,
1063                                   password->vp_strvalue);
1064                         nt_password->length = 16;
1065                 }
1066         }
1067
1068         challenge = pairfind(request->packet->vps,
1069                              PW_MSCHAP_CHALLENGE,
1070                              VENDORPEC_MICROSOFT);
1071         if (!challenge) {
1072                 RDEBUG("ERROR: You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1073                 return RLM_MODULE_REJECT;
1074         }
1075
1076         /*
1077          *      We also require an MS-CHAP-Response.
1078          */
1079         response = pairfind(request->packet->vps,
1080                             PW_MSCHAP_RESPONSE,
1081                             VENDORPEC_MICROSOFT);
1082
1083         /*
1084          *      MS-CHAP-Response, means MS-CHAPv1
1085          */
1086         if (response) {
1087                 int offset;
1088
1089                 /*
1090                  *      MS-CHAPv1 challenges are 8 octets.
1091                  */
1092                 if (challenge->length < 8) {
1093                         radlog_request(L_AUTH, 0, request, "MS-CHAP-Challenge has the wrong format.");
1094                         return RLM_MODULE_INVALID;
1095                 }
1096
1097                 /*
1098                  *      Responses are 50 octets.
1099                  */
1100                 if (response->length < 50) {
1101                         radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
1102                         return RLM_MODULE_INVALID;
1103                 }
1104
1105                 /*
1106                  *      We are doing MS-CHAP.  Calculate the MS-CHAP
1107                  *      response
1108                  */
1109                 if (response->vp_octets[1] & 0x01) {
1110                         RDEBUG2("Told to do MS-CHAPv1 with NT-Password");
1111                         password = nt_password;
1112                         offset = 26;
1113                 } else {
1114                         RDEBUG2("Told to do MS-CHAPv1 with LM-Password");
1115                         password = lm_password;
1116                         offset = 2;
1117                 }
1118
1119                 /*
1120                  *      Do the MS-CHAP authentication.
1121                  */
1122                 if (do_mschap(inst, request, password, challenge->vp_octets,
1123                               response->vp_octets + offset, nthashhash,
1124                               do_ntlm_auth) < 0) {
1125                         RDEBUG2("MS-CHAP-Response is incorrect.");
1126                         mschap_add_reply(request, &request->reply->vps,
1127                                          *response->vp_octets,
1128                                          "MS-CHAP-Error", "E=691 R=1", 9);
1129                         return RLM_MODULE_REJECT;
1130                 }
1131
1132                 chap = 1;
1133
1134         } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT)) != NULL) {
1135                 uint8_t mschapv1_challenge[16];
1136                 VALUE_PAIR *name_attr, *response_name;
1137
1138                 /*
1139                  *      MS-CHAPv2 challenges are 16 octets.
1140                  */
1141                 if (challenge->length < 16) {
1142                         radlog_request(L_AUTH, 0, request, "MS-CHAP-Challenge has the wrong format.");
1143                         return RLM_MODULE_INVALID;
1144                 }
1145
1146                 /*
1147                  *      Responses are 50 octets.
1148                  */
1149                 if (response->length < 50) {
1150                         radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
1151                         return RLM_MODULE_INVALID;
1152                 }
1153
1154                 /*
1155                  *      We also require a User-Name
1156                  */
1157                 username = pairfind(request->packet->vps, PW_USER_NAME, 0);
1158                 if (!username) {
1159                         radlog_request(L_AUTH, 0, request, "We require a User-Name for MS-CHAPv2");
1160                         return RLM_MODULE_INVALID;
1161                 }
1162
1163                 /*
1164                  *      Check for MS-CHAP-User-Name and if found, use it
1165                  *      to construct the MSCHAPv1 challenge.  This is
1166                  *      set by rlm_eap_mschap to the MS-CHAP Response
1167                  *      packet Name field.
1168                  *
1169                  *      We prefer this to the User-Name in the
1170                  *      packet.
1171                  */
1172                 response_name = pairfind(request->packet->vps, PW_MS_CHAP_USER_NAME, 0);
1173                 if (response_name) {
1174                         name_attr = response_name;
1175                 } else {
1176                         name_attr = username;
1177                 }
1178                 
1179                 /*
1180                  *      with_ntdomain_hack moved here, too.
1181                  */
1182                 if ((username_string = strchr(name_attr->vp_strvalue, '\\')) != NULL) {
1183                         if (inst->with_ntdomain_hack) {
1184                                 username_string++;
1185                         } else {
1186                                 RDEBUG2("NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1187                                 username_string = name_attr->vp_strvalue;
1188                         }
1189                 } else {
1190                         username_string = name_attr->vp_strvalue;
1191                 }
1192                 
1193                 if (response_name &&
1194                     ((username->length != response_name->length) ||
1195                      (strncasecmp(username->vp_strvalue, response_name->vp_strvalue, username->length) != 0))) {
1196                         RDEBUG("ERROR: User-Name (%s) is not the same as MS-CHAP Name (%s) from EAP-MSCHAPv2", username->vp_strvalue, response_name->vp_strvalue);
1197                         return RLM_MODULE_REJECT;
1198                 }
1199
1200 #ifdef __APPLE__
1201                 /*
1202                  *  No "known good" NT-Password attribute.  Try to do
1203                  *  OpenDirectory authentication.
1204                  *
1205                  *  If OD determines the user is an AD user it will return noop, which
1206                  *  indicates the auth process should continue directly to AD.
1207                  *  Otherwise OD will determine auth success/fail.
1208                  */
1209                 if (!nt_password && inst->open_directory) {
1210                         RDEBUG2("No NT-Password configured. Trying OpenDirectory Authentication.");
1211                         int odStatus = od_mschap_auth(request, challenge, username);
1212                         if (odStatus != RLM_MODULE_NOOP) {
1213                                 return odStatus;
1214                         }
1215                 }
1216 #endif
1217                 /*
1218                  *      The old "mschapv2" function has been moved to
1219                  *      here.
1220                  *
1221                  *      MS-CHAPv2 takes some additional data to create an
1222                  *      MS-CHAPv1 challenge, and then does MS-CHAPv1.
1223                  */
1224                 RDEBUG2("Creating challenge hash with username: %s",
1225                         username_string);
1226                 mschap_challenge_hash(response->vp_octets + 2, /* peer challenge */
1227                                challenge->vp_octets, /* our challenge */
1228                                username_string, /* user name */
1229                                mschapv1_challenge); /* resulting challenge */
1230
1231                 RDEBUG2("Told to do MS-CHAPv2 for %s with NT-Password",
1232                        username_string);
1233
1234                 if (do_mschap(inst, request, nt_password, mschapv1_challenge,
1235                               response->vp_octets + 26, nthashhash,
1236                               do_ntlm_auth) < 0) {
1237                         RDEBUG2("FAILED: MS-CHAP2-Response is incorrect");
1238                         mschap_add_reply(request, &request->reply->vps,
1239                                          *response->vp_octets,
1240                                          "MS-CHAP-Error", "E=691 R=1", 9);
1241                         return RLM_MODULE_REJECT;
1242                 }
1243
1244                 mschap_auth_response(username_string, /* without the domain */
1245                               nthashhash, /* nt-hash-hash */
1246                               response->vp_octets + 26, /* peer response */
1247                               response->vp_octets + 2, /* peer challenge */
1248                               challenge->vp_octets, /* our challenge */
1249                               msch2resp); /* calculated MPPE key */
1250                 mschap_add_reply(request, &request->reply->vps, *response->vp_octets,
1251                                  "MS-CHAP2-Success", msch2resp, 42);
1252                 chap = 2;
1253
1254         } else {                /* Neither CHAPv1 or CHAPv2 response: die */
1255                 RDEBUG("ERROR: You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1256                 return RLM_MODULE_INVALID;
1257         }
1258
1259         /*
1260          *      We have a CHAP response, but the account may be
1261          *      disabled.  Reject the user with the same error code
1262          *      we use when their password is invalid.
1263          */
1264         if (smb_ctrl) {
1265                 /*
1266                  *      Account is disabled.
1267                  *
1268                  *      They're found, but they don't exist, so we
1269                  *      return 'not found'.
1270                  */
1271                 if (((smb_ctrl->vp_integer & ACB_DISABLED) != 0) ||
1272                     ((smb_ctrl->vp_integer & ACB_NORMAL) == 0)) {
1273                         RDEBUG2("SMB-Account-Ctrl says that the account is disabled, or is not a normal account.");
1274                         mschap_add_reply(request, &request->reply->vps,
1275                                           *response->vp_octets,
1276                                           "MS-CHAP-Error", "E=691 R=1", 9);
1277                         return RLM_MODULE_NOTFOUND;
1278                 }
1279
1280                 /*
1281                  *      User is locked out.
1282                  */
1283                 if ((smb_ctrl->vp_integer & ACB_AUTOLOCK) != 0) {
1284                         RDEBUG2("SMB-Account-Ctrl says that the account is locked out.");
1285                         mschap_add_reply(request, &request->reply->vps,
1286                                           *response->vp_octets,
1287                                           "MS-CHAP-Error", "E=647 R=0", 9);
1288                         return RLM_MODULE_USERLOCK;
1289                 }
1290         }
1291
1292         /* now create MPPE attributes */
1293         if (inst->use_mppe) {
1294                 uint8_t mppe_sendkey[34];
1295                 uint8_t mppe_recvkey[34];
1296
1297                 if (chap == 1){
1298                         RDEBUG2("adding MS-CHAPv1 MPPE keys");
1299                         memset(mppe_sendkey, 0, 32);
1300                         if (lm_password) {
1301                                 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1302                         }
1303
1304                         /*
1305                          *      According to RFC 2548 we
1306                          *      should send NT hash.  But in
1307                          *      practice it doesn't work.
1308                          *      Instead, we should send nthashhash
1309                          *
1310                          *      This is an error on RFC 2548.
1311                          */
1312                         /*
1313                          *      do_mschap cares to zero nthashhash if NT hash
1314                          *      is not available.
1315                          */
1316                         memcpy(mppe_sendkey + 8,
1317                                nthashhash, 16);
1318                         mppe_add_reply(request,
1319                                        "MS-CHAP-MPPE-Keys",
1320                                        mppe_sendkey, 32);
1321                 } else if (chap == 2) {
1322                         RDEBUG2("adding MS-CHAPv2 MPPE keys");
1323                         mppe_chap2_gen_keys128(nthashhash,
1324                                                response->vp_octets + 26,
1325                                                mppe_sendkey, mppe_recvkey);
1326
1327                         mppe_add_reply(request,
1328                                        "MS-MPPE-Recv-Key",
1329                                        mppe_recvkey, 16);
1330                         mppe_add_reply(request,
1331                                        "MS-MPPE-Send-Key",
1332                                        mppe_sendkey, 16);
1333
1334                 }
1335                 radius_pairmake(request, &request->reply->vps,
1336                                 "MS-MPPE-Encryption-Policy",
1337                                 (inst->require_encryption)? "0x00000002":"0x00000001",
1338                                 T_OP_EQ);
1339                 radius_pairmake(request, &request->reply->vps,
1340                                 "MS-MPPE-Encryption-Types",
1341                                 (inst->require_strong)? "0x00000004":"0x00000006",
1342                                 T_OP_EQ);
1343         } /* else we weren't asked to use MPPE */
1344
1345         return RLM_MODULE_OK;
1346 #undef inst
1347 }
1348
1349 module_t rlm_mschap = {
1350         RLM_MODULE_INIT,
1351         "MS-CHAP",
1352         RLM_TYPE_THREAD_SAFE,           /* type */
1353         mschap_instantiate,             /* instantiation */
1354         mschap_detach,          /* detach */
1355         {
1356                 mschap_authenticate,    /* authenticate */
1357                 mschap_authorize,       /* authorize */
1358                 NULL,                   /* pre-accounting */
1359                 NULL,                   /* accounting */
1360                 NULL,                   /* checksimul */
1361                 NULL,                   /* pre-proxy */
1362                 NULL,                   /* post-proxy */
1363                 NULL                    /* post-auth */
1364         },
1365 };