4d7adb1e66f0308730dff95af0045f5d3cef7b68
[freeradius.git] / src / modules / rlm_mschap / rlm_mschap.c
1 /*
2  *   This program is is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License, version 2 if the
4  *   License as published by the Free Software Foundation.
5  *
6  *   This program is distributed in the hope that it will be useful,
7  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
8  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9  *   GNU General Public License for more details.
10  *
11  *   You should have received a copy of the GNU General Public License
12  *   along with this program; if not, write to the Free Software
13  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
14  */
15
16 /**
17  * $Id$
18  * @file rlm_mschap.c
19  * @brief Implemented mschap authentication.
20  *
21  * @copyright 2000,2001,2006  The FreeRADIUS server project
22  */
23
24 /*  MPPE support from Takahiro Wagatsuma <waga@sic.shibaura-it.ac.jp> */
25 RCSID("$Id$")
26
27 #include        <freeradius-devel/radiusd.h>
28 #include        <freeradius-devel/modules.h>
29 #include        <freeradius-devel/rad_assert.h>
30 #include        <freeradius-devel/md5.h>
31 #include        <freeradius-devel/sha1.h>
32
33 #include        <ctype.h>
34
35 #include        "mschap.h"
36 #include        "smbdes.h"
37
38 #ifdef HAVE_OPENSSL_CRYPTO_H
39 USES_APPLE_DEPRECATED_API       /* OpenSSL API has been deprecated by Apple */
40 #  include      <openssl/rc4.h>
41 #endif
42
43 #ifdef WITH_OPEN_DIRECTORY
44 int od_mschap_auth(REQUEST *request, VALUE_PAIR *challenge, VALUE_PAIR * usernamepair);
45 #endif
46
47 /* Allowable account control bits */
48 #define ACB_DISABLED    0x00010000      //!< User account disabled.
49 #define ACB_HOMDIRREQ   0x00020000      //!< Home directory required.
50 #define ACB_PWNOTREQ    0x00040000      //!< User password not required.
51 #define ACB_TEMPDUP     0x00080000      //!< Temporary duplicate account.
52 #define ACB_NORMAL      0x00100000      //!< Normal user account.
53 #define ACB_MNS         0x00200000      //!< MNS logon user account.
54 #define ACB_DOMTRUST    0x00400000      //!< Interdomain trust account.
55 #define ACB_WSTRUST     0x00800000      //!< Workstation trust account.
56 #define ACB_SVRTRUST    0x01000000      //!< Server trust account.
57 #define ACB_PWNOEXP     0x02000000      //!< User password does not expire.
58 #define ACB_AUTOLOCK    0x04000000      //!< Account auto locked.
59 #define ACB_PW_EXPIRED  0x00020000      //!< Password Expired.
60
61 static int pdb_decode_acct_ctrl(char const *p)
62 {
63         int acct_ctrl = 0;
64         int done = 0;
65
66         /*
67          * Check if the account type bits have been encoded after the
68          * NT password (in the form [NDHTUWSLXI]).
69          */
70
71         if (*p != '[') return 0;
72
73         for (p++; *p && !done; p++) {
74                 switch (*p) {
75                 case 'N': /* 'N'o password. */
76                         acct_ctrl |= ACB_PWNOTREQ;
77                         break;
78
79                 case 'D':  /* 'D'isabled. */
80                         acct_ctrl |= ACB_DISABLED ;
81                         break;
82
83                 case 'H':  /* 'H'omedir required. */
84                         acct_ctrl |= ACB_HOMDIRREQ;
85                         break;
86
87                 case 'T': /* 'T'emp account. */
88                         acct_ctrl |= ACB_TEMPDUP;
89                         break;
90
91                 case 'U': /* 'U'ser account (normal). */
92                         acct_ctrl |= ACB_NORMAL;
93                         break;
94
95                 case 'M': /* 'M'NS logon user account. What is this? */
96                         acct_ctrl |= ACB_MNS;
97                         break;
98
99                 case 'W': /* 'W'orkstation account. */
100                         acct_ctrl |= ACB_WSTRUST;
101                         break;
102
103                 case 'S': /* 'S'erver account. */
104                         acct_ctrl |= ACB_SVRTRUST;
105                         break;
106
107                 case 'L': /* 'L'ocked account. */
108                         acct_ctrl |= ACB_AUTOLOCK;
109                         break;
110
111                 case 'X': /* No 'X'piry on password */
112                         acct_ctrl |= ACB_PWNOEXP;
113                         break;
114
115                 case 'I': /* 'I'nterdomain trust account. */
116                         acct_ctrl |= ACB_DOMTRUST;
117                         break;
118
119                 case 'e': /* 'e'xpired, the password has */
120                         acct_ctrl |= ACB_PW_EXPIRED;
121                         break;
122
123                 case ' ': /* ignore spaces */
124                         break;
125
126                 case ':':
127                 case '\n':
128                 case '\0':
129                 case ']':
130                 default:
131                         done = 1;
132                         break;
133                 }
134         }
135
136         return acct_ctrl;
137 }
138
139
140 typedef struct rlm_mschap_t {
141         bool            use_mppe;
142         bool            require_encryption;
143         bool            require_strong;
144         bool            with_ntdomain_hack;     /* this should be in another module */
145         char const      *xlat_name;
146         char const      *ntlm_auth;
147         uint32_t        ntlm_auth_timeout;
148         char const      *ntlm_cpw;
149         char const      *ntlm_cpw_username;
150         char const      *ntlm_cpw_domain;
151         char const      *local_cpw;
152         char const      *auth_type;
153         bool            allow_retry;
154         char const      *retry_msg;
155 #ifdef WITH_OPEN_DIRECTORY
156         bool            open_directory;
157 #endif
158 } rlm_mschap_t;
159
160
161 /*
162  *      Does dynamic translation of strings.
163  *
164  *      Pulls NT-Response, LM-Response, or Challenge from MSCHAP
165  *      attributes.
166  */
167 static ssize_t mschap_xlat(void *instance, REQUEST *request,
168                            char const *fmt, char *out, size_t outlen)
169 {
170         size_t          i, data_len;
171         uint8_t const   *data = NULL;
172         uint8_t         buffer[32];
173         VALUE_PAIR      *user_name;
174         VALUE_PAIR      *chap_challenge, *response;
175         rlm_mschap_t    *inst = instance;
176
177         response = NULL;
178
179         /*
180          *      Challenge means MS-CHAPv1 challenge, or
181          *      hash of MS-CHAPv2 challenge, and peer challenge.
182          */
183         if (strncasecmp(fmt, "Challenge", 9) == 0) {
184                 chap_challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
185                 if (!chap_challenge) {
186                         REDEBUG("No MS-CHAP-Challenge in the request");
187                         return -1;
188                 }
189
190                 /*
191                  *      MS-CHAP-Challenges are 8 octets,
192                  *      for MS-CHAPv1
193                  */
194                 if (chap_challenge->length == 8) {
195                         RDEBUG2("mschap1: %02x", chap_challenge->vp_octets[0]);
196                         data = chap_challenge->vp_octets;
197                         data_len = 8;
198
199                         /*
200                          *      MS-CHAP-Challenges are 16 octets,
201                          *      for MS-CHAPv2.
202                          */
203                 } else if (chap_challenge->length == 16) {
204                         VALUE_PAIR *name_attr, *response_name;
205                         char const *username_string;
206
207                         response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
208                         if (!response) {
209                                 REDEBUG("MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge");
210                                 return -1;
211                         }
212
213                         /*
214                          *      FIXME: Much of this is copied from
215                          *      below.  We should put it into a
216                          *      separate function.
217                          */
218
219                         /*
220                          *      Responses are 50 octets.
221                          */
222                         if (response->length < 50) {
223                                 REDEBUG("MS-CHAP-Response has the wrong format");
224                                 return -1;
225                         }
226
227                         user_name = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
228                         if (!user_name) {
229                                 REDEBUG("User-Name is required to calculate MS-CHAPv1 Challenge");
230                                 return -1;
231                         }
232
233                         /*
234                          *      Check for MS-CHAP-User-Name and if found, use it
235                          *      to construct the MSCHAPv1 challenge.  This is
236                          *      set by rlm_eap_mschap to the MS-CHAP Response
237                          *      packet Name field.
238                          *
239                          *      We prefer this to the User-Name in the
240                          *      packet.
241                          */
242                         response_name = pairfind(request->packet->vps, PW_MS_CHAP_USER_NAME, 0, TAG_ANY);
243                         if (response_name) {
244                                 name_attr = response_name;
245                         } else {
246                                 name_attr = user_name;
247                         }
248
249                         /*
250                          *      with_ntdomain_hack moved here, too.
251                          */
252                         if ((username_string = strchr(name_attr->vp_strvalue, '\\')) != NULL) {
253                                 if (inst->with_ntdomain_hack) {
254                                         username_string++;
255                                 } else {
256                                         RWDEBUG2("NT Domain delimiter found, should we have enabled with_ntdomain_hack?");
257                                         username_string = name_attr->vp_strvalue;
258                                 }
259                         } else {
260                                 username_string = name_attr->vp_strvalue;
261                         }
262
263                         if (response_name &&
264                             ((user_name->length != response_name->length) ||
265                              (strncasecmp(user_name->vp_strvalue, response_name->vp_strvalue, user_name->length) != 0))) {
266                                 RWDEBUG2("User-Name (%s) is not the same as MS-CHAP Name (%s) from EAP-MSCHAPv2",
267                                          user_name->vp_strvalue, response_name->vp_strvalue);
268                         }
269
270                         /*
271                          *      Get the MS-CHAPv1 challenge
272                          *      from the MS-CHAPv2 peer challenge,
273                          *      our challenge, and the user name.
274                          */
275                         RDEBUG2("Creating challenge hash with username: %s", username_string);
276                         mschap_challenge_hash(response->vp_octets + 2,
277                                        chap_challenge->vp_octets,
278                                        username_string, buffer);
279                         data = buffer;
280                         data_len = 8;
281                 } else {
282                         REDEBUG("Invalid MS-CHAP challenge length");
283                         return -1;
284                 }
285
286                 /*
287                  *      Get the MS-CHAPv1 response, or the MS-CHAPv2
288                  *      response.
289                  */
290         } else if (strncasecmp(fmt, "NT-Response", 11) == 0) {
291                 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
292                 if (!response) response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
293                 if (!response) {
294                         REDEBUG("No MS-CHAP-Response or MS-CHAP2-Response was found in the request");
295                         return -1;
296                 }
297
298                 /*
299                  *      For MS-CHAPv1, the NT-Response exists only
300                  *      if the second octet says so.
301                  */
302                 if ((response->da->vendor == VENDORPEC_MICROSOFT) &&
303                     (response->da->attr == PW_MSCHAP_RESPONSE) &&
304                     ((response->vp_octets[1] & 0x01) == 0)) {
305                         REDEBUG("No NT-Response in MS-CHAP-Response");
306                         return -1;
307                 }
308
309                 /*
310                  *      MS-CHAP-Response and MS-CHAP2-Response have
311                  *      the NT-Response at the same offset, and are
312                  *      the same length.
313                  */
314                 data = response->vp_octets + 26;
315                 data_len = 24;
316
317                 /*
318                  *      LM-Response is deprecated, and exists only
319                  *      in MS-CHAPv1, and not often there.
320                  */
321         } else if (strncasecmp(fmt, "LM-Response", 11) == 0) {
322                 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
323                 if (!response) {
324                         REDEBUG("No MS-CHAP-Response was found in the request");
325                         return -1;
326                 }
327
328                 /*
329                  *      For MS-CHAPv1, the LM-Response exists only
330                  *      if the second octet says so.
331                  */
332                 if ((response->vp_octets[1] & 0x01) != 0) {
333                         REDEBUG("No LM-Response in MS-CHAP-Response");
334                         return -1;
335                 }
336                 data = response->vp_octets + 2;
337                 data_len = 24;
338
339                 /*
340                  *      Pull the NT-Domain out of the User-Name, if it exists.
341                  */
342         } else if (strncasecmp(fmt, "NT-Domain", 9) == 0) {
343                 char *p, *q;
344
345                 user_name = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
346                 if (!user_name) {
347                         REDEBUG("No User-Name was found in the request");
348                         return -1;
349                 }
350
351                 /*
352                  *      First check to see if this is a host/ style User-Name
353                  *      (a la Kerberos host principal)
354                  */
355                 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
356                         /*
357                          *      If we're getting a User-Name formatted in this way,
358                          *      it's likely due to PEAP.  The Windows Domain will be
359                          *      the first domain component following the hostname,
360                          *      or the machine name itself if only a hostname is supplied
361                          */
362                         p = strchr(user_name->vp_strvalue, '.');
363                         if (!p) {
364                                 RDEBUG2("setting NT-Domain to same as machine name");
365                                 strlcpy(out, user_name->vp_strvalue + 5, outlen);
366                         } else {
367                                 p++;    /* skip the period */
368                                 q = strchr(p, '.');
369                                 /*
370                                  * use the same hack as below
371                                  * only if another period was found
372                                  */
373                                 if (q) *q = '\0';
374                                 strlcpy(out, p, outlen);
375                                 if (q) *q = '.';
376                         }
377                 } else {
378                         p = strchr(user_name->vp_strvalue, '\\');
379                         if (!p) {
380                                 REDEBUG("No NT-Domain was found in the User-Name");
381                                 return -1;
382                         }
383
384                         /*
385                          *      Hack.  This is simpler than the alternatives.
386                          */
387                         *p = '\0';
388                         strlcpy(out, user_name->vp_strvalue, outlen);
389                         *p = '\\';
390                 }
391
392                 return strlen(out);
393
394                 /*
395                  *      Pull the User-Name out of the User-Name...
396                  */
397         } else if (strncasecmp(fmt, "User-Name", 9) == 0) {
398                 char const *p, *q;
399
400                 user_name = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
401                 if (!user_name) {
402                         REDEBUG("No User-Name was found in the request");
403                         return -1;
404                 }
405
406                 /*
407                  *      First check to see if this is a host/ style User-Name
408                  *      (a la Kerberos host principal)
409                  */
410                 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
411                         p = user_name->vp_strvalue + 5;
412                         /*
413                          *      If we're getting a User-Name formatted in this way,
414                          *      it's likely due to PEAP.  When authenticating this against
415                          *      a Domain, Windows will expect the User-Name to be in the
416                          *      format of hostname$, the SAM version of the name, so we
417                          *      have to convert it to that here.  We do so by stripping
418                          *      off the first 5 characters (host/), and copying everything
419                          *      from that point to the first period into a string and appending
420                          *      a $ to the end.
421                          */
422                         q = strchr(p, '.');
423
424                         /*
425                          * use the same hack as above
426                          * only if a period was found
427                          */
428                         if (q) {
429                                 snprintf(out, outlen, "%.*s$",
430                                          (int) (q - p), p);
431                         } else {
432                                 snprintf(out, outlen, "%s$", p);
433                         }
434                 } else {
435                         p = strchr(user_name->vp_strvalue, '\\');
436                         if (p) {
437                                 p++;    /* skip the backslash */
438                         } else {
439                                 p = user_name->vp_strvalue; /* use the whole User-Name */
440                         }
441                         strlcpy(out, p, outlen);
442                 }
443
444                 return strlen(out);
445
446                 /*
447                  * Return the NT-Hash of the passed string
448                  */
449         } else if (strncasecmp(fmt, "NT-Hash ", 8) == 0) {
450                 char const *p;
451
452                 p = fmt + 8;    /* 7 is the length of 'NT-Hash' */
453                 if ((p == '\0')  || (outlen <= 32))
454                         return 0;
455
456                 while (isspace(*p)) p++;
457
458                 if (mschap_ntpwdhash(buffer, p) < 0) {
459                         REDEBUG("Failed generating NT-Password");
460                         *buffer = '\0';
461                         return -1;
462                 }
463
464                 fr_bin2hex(out, buffer, NT_DIGEST_LENGTH);
465                 out[32] = '\0';
466                 RDEBUG("NT-Hash of \"known-good\" password: %s", out);
467                 return 32;
468
469                 /*
470                  * Return the LM-Hash of the passed string
471                  */
472         } else if (strncasecmp(fmt, "LM-Hash ", 8) == 0) {
473                 char const *p;
474
475                 p = fmt + 8;    /* 7 is the length of 'LM-Hash' */
476                 if ((p == '\0') || (outlen <= 32))
477                         return 0;
478
479                 while (isspace(*p)) p++;
480
481                 smbdes_lmpwdhash(p, buffer);
482                 fr_bin2hex(out, buffer, LM_DIGEST_LENGTH);
483                 out[32] = '\0';
484                 RDEBUG("LM-Hash of %s = %s", p, out);
485                 return 32;
486         } else {
487                 REDEBUG("Unknown expansion string '%s'", fmt);
488                 return -1;
489         }
490
491         if (outlen == 0) return 0; /* nowhere to go, don't do anything */
492
493         /*
494          *      Didn't set anything: this is bad.
495          */
496         if (!data) {
497                 RWDEBUG2("Failed to do anything intelligent");
498                 return 0;
499         }
500
501         /*
502          *      Check the output length.
503          */
504         if (outlen < ((data_len * 2) + 1)) {
505                 data_len = (outlen - 1) / 2;
506         }
507
508         /*
509          *
510          */
511         for (i = 0; i < data_len; i++) {
512                 sprintf(out + (2 * i), "%02x", data[i]);
513         }
514         out[data_len * 2] = '\0';
515
516         return data_len * 2;
517 }
518
519
520 static const CONF_PARSER passchange_config[] = {
521         { "ntlm_auth", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_mschap_t, ntlm_cpw), NULL },
522         { "ntlm_auth_username", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_mschap_t, ntlm_cpw_username), NULL },
523         { "ntlm_auth_domain", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_mschap_t, ntlm_cpw_domain), NULL },
524         { "local_cpw", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_mschap_t, local_cpw), NULL },
525         { NULL, -1, 0, NULL, NULL }             /* end the list */
526 };
527 static const CONF_PARSER module_config[] = {
528         /*
529          *      Cache the password by default.
530          */
531         { "use_mppe", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_mschap_t, use_mppe), "yes" },
532         { "require_encryption", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_mschap_t, require_encryption), "no" },
533         { "require_strong", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_mschap_t, require_strong), "no" },
534         { "with_ntdomain_hack", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_mschap_t, with_ntdomain_hack), "yes" },
535         { "ntlm_auth", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_mschap_t, ntlm_auth), NULL },
536         { "ntlm_auth_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_mschap_t, ntlm_auth_timeout), NULL },
537         { "passchange", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) passchange_config },
538         { "allow_retry", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_mschap_t, allow_retry), "yes" },
539         { "retry_msg", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_mschap_t, retry_msg), NULL },
540 #ifdef WITH_OPEN_DIRECTORY
541         { "use_open_directory", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_mschap_t, open_directory), "yes" },
542 #endif
543
544         { NULL, -1, 0, NULL, NULL }             /* end the list */
545 };
546
547
548 /*
549  *      Create instance for our module. Allocate space for
550  *      instance structure and read configuration parameters
551  */
552 static int mod_instantiate(CONF_SECTION *conf, void *instance)
553 {
554         char const *name;
555         rlm_mschap_t *inst = instance;
556
557         /*
558          *      Create the dynamic translation.
559          */
560         name = cf_section_name2(conf);
561         if (!name) name = cf_section_name1(conf);
562         inst->xlat_name = name;
563         xlat_register(inst->xlat_name, mschap_xlat, NULL, inst);
564
565         /*
566          *      For backwards compatibility
567          */
568         if (!dict_valbyname(PW_AUTH_TYPE, 0, inst->xlat_name)) {
569                 inst->auth_type = "MS-CHAP";
570         } else {
571                 inst->auth_type = inst->xlat_name;
572         }
573
574         /*
575          *      Check ntlm_auth_timeout is sane
576          */
577         if (!inst->ntlm_auth_timeout) {
578                 inst->ntlm_auth_timeout = EXEC_TIMEOUT;
579         }
580         if (inst->ntlm_auth_timeout < 1) {
581                 cf_log_err_cs(conf, "ntml_auth_timeout '%d' is too small (minimum: 1)",
582                               inst->ntlm_auth_timeout);
583                 return -1;
584         }
585         if (inst->ntlm_auth_timeout > 10) {
586                 cf_log_err_cs(conf, "ntlm_auth_timeout '%d' is too large (maximum: 10)",
587                               inst->ntlm_auth_timeout);
588                 return -1;
589         }
590
591         return 0;
592 }
593
594 /*
595  *      add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
596  *      attribute to reply packet
597  */
598 void mschap_add_reply(REQUEST *request, unsigned char ident,
599                       char const *name, char const *value, size_t len)
600 {
601         VALUE_PAIR *vp;
602
603         vp = pairmake_reply(name, NULL, T_OP_EQ);
604         if (!vp) {
605                 REDEBUG("Failed to create attribute %s: %s", name, fr_strerror());
606                 return;
607         }
608
609         /* Account for the ident byte */
610         vp->length = len + 1;
611         if (vp->da->type == PW_TYPE_STRING) {
612                 char *p;
613
614                 vp->vp_strvalue = p = talloc_array(vp, char, vp->length + 1);
615                 p[vp->length] = '\0';   /* Always \0 terminate */
616                 p[0] = ident;
617                 memcpy(p + 1, value, len);
618         } else {
619                 uint8_t *p;
620
621                 vp->vp_octets = p = talloc_array(vp, uint8_t, vp->length);
622                 p[0] = ident;
623                 memcpy(p + 1, value, len);
624         }
625 }
626
627 /*
628  *      Add MPPE attributes to the reply.
629  */
630 static void mppe_add_reply(REQUEST *request, char const* name, uint8_t const * value, size_t len)
631 {
632        VALUE_PAIR *vp;
633
634        vp = pairmake_reply(name, NULL, T_OP_EQ);
635        if (!vp) {
636                REDEBUG("mppe_add_reply failed to create attribute %s: %s", name, fr_strerror());
637                return;
638        }
639
640        pairmemcpy(vp, value, len);
641 }
642
643 static int write_all(int fd, char const *buf, int len) {
644         int rv,done=0;
645
646         while (done < len) {
647                 rv = write(fd, buf+done, len-done);
648                 if (rv <= 0)
649                         break;
650                 done += rv;
651         }
652         return done;
653 }
654
655 /*
656  * Perform an MS-CHAP2 password change
657  */
658
659 static int CC_HINT(nonnull (1, 2, 4, 5)) do_mschap_cpw(rlm_mschap_t *inst,
660                                                        REQUEST *request,
661 #ifdef HAVE_OPENSSL_CRYPTO_H
662                                                        VALUE_PAIR *nt_password,
663 #else
664                                                        UNUSED VALUE_PAIR *nt_password,
665 #endif
666                                                        uint8_t *new_nt_password,
667                                                        uint8_t *old_nt_hash,
668                                                        bool do_ntlm_auth)
669 {
670         if (inst->ntlm_cpw && do_ntlm_auth) {
671                 /*
672                  * we're going to run ntlm_auth in helper-mode
673                  * we're expecting to use the ntlm-change-password-1 protocol
674                  * which needs the following on stdin:
675                  *
676                  * username: %{mschap:User-Name}
677                  * nt-domain: %{mschap:NT-Domain}
678                  * new-nt-password-blob: bin2hex(new_nt_password) - 1032 bytes encoded
679                  * old-nt-hash-blob: bin2hex(old_nt_hash) - 32 bytes encoded
680                  * new-lm-password-blob: 00000...0000 - 1032 bytes null
681                  * old-lm-hash-blob: 000....000 - 32 bytes null
682                  * .\n
683                  *
684                  * ...and it should then print out
685                  *
686                  * Password-Change: Yes
687                  *
688                  * or
689                  *
690                  * Password-Change: No
691                  * Password-Change-Error: blah
692                  */
693
694                 int to_child=-1;
695                 int from_child=-1;
696                 pid_t pid, child_pid;
697                 int status, len;
698                 char buf[2048];
699                 char *pmsg;
700                 char const *emsg;
701
702                 RDEBUG("Doing MS-CHAPv2 password change via ntlm_auth helper");
703
704                 /*
705                  * Start up ntlm_auth with a pipe on stdin and stdout
706                  */
707
708                 pid = radius_start_program(inst->ntlm_cpw, request, true, &to_child, &from_child, NULL, false);
709                 if (pid < 0) {
710                         REDEBUG("could not exec ntlm_auth cpw command");
711                         return -1;
712                 }
713
714                 /*
715                  * write the stuff to the client
716                  */
717
718                 if (inst->ntlm_cpw_username) {
719                         len = radius_xlat(buf, sizeof(buf) - 2, request, inst->ntlm_cpw_username, NULL, NULL);
720                         if (len < 0) {
721                                 goto ntlm_auth_err;
722                         }
723
724                         buf[len++] = '\n';
725                         buf[len] = '\0';
726
727                         if (write_all(to_child, buf, len) != len) {
728                                 REDEBUG("Failed to write username to child");
729                                 goto ntlm_auth_err;
730                         }
731                 } else {
732                         RWDEBUG2("No ntlm_auth username set, passchange will definitely fail!");
733                 }
734
735                 if (inst->ntlm_cpw_domain) {
736                         len = radius_xlat(buf, sizeof(buf) - 2, request, inst->ntlm_cpw_domain, NULL, NULL);
737                         if (len < 0) {
738                                 goto ntlm_auth_err;
739                         }
740
741                         buf[len++] = '\n';
742                         buf[len] = '\0';
743
744                         if (write_all(to_child, buf, len) != len) {
745                                 REDEBUG("Failed to write domain to child");
746                                 goto ntlm_auth_err;
747                         }
748                 } else {
749                         RWDEBUG2("No ntlm_auth domain set, username must be full-username to work");
750                 }
751
752                 /* now the password blobs */
753                 len = sprintf(buf, "new-nt-password-blob: ");
754                 fr_bin2hex(buf+len, new_nt_password, 516);
755                 buf[len+1032] = '\n';
756                 buf[len+1033] = '\0';
757                 len = strlen(buf);
758                 if (write_all(to_child, buf, len) != len) {
759                         RDEBUG2("failed to write new password blob to child");
760                         goto ntlm_auth_err;
761                 }
762
763                 len = sprintf(buf, "old-nt-hash-blob: ");
764                 fr_bin2hex(buf+len, old_nt_hash, NT_DIGEST_LENGTH);
765                 buf[len+32] = '\n';
766                 buf[len+33] = '\0';
767                 len = strlen(buf);
768                 if (write_all(to_child, buf, len) != len) {
769                         REDEBUG("Failed to write old hash blob to child");
770                         goto ntlm_auth_err;
771                 }
772
773                 /*
774                  *  In current samba versions, failure to supply empty LM password/hash
775                  *  blobs causes the change to fail.
776                  */
777                 len = sprintf(buf, "new-lm-password-blob: %01032i\n", 0);
778                 if (write_all(to_child, buf, len) != len) {
779                         REDEBUG("Failed to write dummy LM password to child");
780                         goto ntlm_auth_err;
781                 }
782                 len = sprintf(buf, "old-lm-hash-blob: %032i\n", 0);
783                 if (write_all(to_child, buf, len) != len) {
784                         REDEBUG("Failed to write dummy LM hash to child");
785                         goto ntlm_auth_err;
786                 }
787                 if (write_all(to_child, ".\n", 2) != 2) {
788                         REDEBUG("Failed to send finish to child");
789                         goto ntlm_auth_err;
790                 }
791                 close(to_child);
792                 to_child = -1;
793
794                 /*
795                  *  Read from the child
796                  */
797                 len = radius_readfrom_program(from_child, pid, 10, buf, sizeof(buf));
798                 if (len < 0) {
799                         /* radius_readfrom_program will have closed from_child for us */
800                         REDEBUG("Failure reading from child");
801                         return -1;
802                 }
803                 close(from_child);
804                 from_child = -1;
805
806                 buf[len] = 0;
807                 RDEBUG2("ntlm_auth said: %s", buf);
808
809                 child_pid = rad_waitpid(pid, &status);
810                 if (child_pid == 0) {
811                         REDEBUG("Timeout waiting for child");
812                         return -1;
813                 }
814                 if (child_pid != pid) {
815                         REDEBUG("Abnormal exit status: %s", fr_syserror(errno));
816                         return -1;
817                 }
818
819                 if (strstr(buf, "Password-Change: Yes")) {
820                         RDEBUG2("ntlm_auth password change succeeded");
821                         return 0;
822                 }
823
824                 pmsg = strstr(buf, "Password-Change-Error: ");
825                 if (pmsg) {
826                         emsg = strsep(&pmsg, "\n");
827                 } else {
828                         emsg = "could not find error";
829                 }
830                 REDEBUG("ntlm auth password change failed: %s", emsg);
831
832 ntlm_auth_err:
833                 /* safe because these either need closing or are == -1 */
834                 close(to_child);
835                 close(from_child);
836
837                 return -1;
838
839         } else if (inst->local_cpw) {
840 #ifdef HAVE_OPENSSL_CRYPTO_H
841                 /*
842                  *  Decrypt the new password blob, add it as a temporary request
843                  *  variable, xlat the local_cpw string, then remove it
844                  *
845                  *  this allows is to write e..g
846                  *
847                  *  %{sql:insert into ...}
848                  *
849                  *  ...or...
850                  *
851                  *  %{exec:/path/to %{mschap:User-Name} %{MS-CHAP-New-Password}}"
852                  *
853                  */
854                 VALUE_PAIR *new_pass, *new_hash;
855                 uint8_t *p, *q;
856                 char *x;
857                 size_t i;
858                 size_t passlen;
859                 ssize_t result_len;
860                 char result[253];
861                 uint8_t nt_pass_decrypted[516], old_nt_hash_expected[NT_DIGEST_LENGTH];
862                 RC4_KEY key;
863
864                 if (!nt_password) {
865                         RDEBUG("Local MS-CHAPv2 password change requires NT-Password attribute");
866                         return -1;
867                 } else {
868                         RDEBUG("Doing MS-CHAPv2 password change locally");
869                 }
870
871                 /*
872                  *  Decrypt the blob
873                  */
874                 RC4_set_key(&key, nt_password->length, nt_password->vp_octets);
875                 RC4(&key, 516, new_nt_password, nt_pass_decrypted);
876
877                 /*
878                  *  pwblock is
879                  *  512-N bytes random pad
880                  *  N bytes password as utf-16-le
881                  *  4 bytes - N as big-endian int
882                  */
883                 passlen = nt_pass_decrypted[512];
884                 passlen += nt_pass_decrypted[513] << 8;
885                 if ((nt_pass_decrypted[514] != 0) ||
886                     (nt_pass_decrypted[515] != 0)) {
887                         REDEBUG("Decrypted new password blob claims length > 65536, "
888                                 "probably an invalid NT-Password");
889                         return -1;
890                 }
891
892                 /*
893                  *  Sanity check - passlen positive and <= 512 if not, crypto has probably gone wrong
894                  */
895                 if (passlen > 512) {
896                         REDEBUG("Decrypted new password blob claims length %zu > 512, "
897                                 "probably an invalid NT-Password", passlen);
898                         return -1;
899                 }
900
901                 p = nt_pass_decrypted + 512 - passlen;
902
903                 /*
904                  *  The new NT hash - this should be preferred over the
905                  *  cleartext password as it avoids unicode hassles.
906                  */
907                 new_hash = pairmake_packet("MS-CHAP-New-NT-Password", NULL, T_OP_EQ);
908                 new_hash->length = NT_DIGEST_LENGTH;
909                 new_hash->vp_octets = q = talloc_array(new_hash, uint8_t, new_hash->length);
910                 fr_md4_calc(q, p, passlen);
911
912                 /*
913                  *  Check that nt_password encrypted with new_hash
914                  *  matches the old_hash value from the client.
915                  */
916                 smbhash(old_nt_hash_expected, nt_password->vp_octets, q);
917                 smbhash(old_nt_hash_expected+8, nt_password->vp_octets+8, q + 7);
918                 if (memcmp(old_nt_hash_expected, old_nt_hash, NT_DIGEST_LENGTH)!=0) {
919                         REDEBUG("Old NT hash value from client does not match our value");
920                         return -1;
921                 }
922
923                 /*
924                  *  The new cleartext password, which is utf-16 do some unpleasant vileness
925                  *  to turn it into utf8 without pulling in libraries like iconv.
926                  *
927                  *  First pass: get the length of the converted string.
928                  */
929                 new_pass = pairmake_packet("MS-CHAP-New-Cleartext-Password", NULL, T_OP_EQ);
930                 new_pass->length = 0;
931
932                 i = 0;
933                 while (i < passlen) {
934                         int c;
935
936                         c = p[i++];
937                         c += p[i++] << 8;
938
939                         /*
940                          *  Gah. nasty. maybe we should just pull in iconv?
941                          */
942                         if (c < 0x7f) {
943                                 new_pass->length++;
944                         } else if (c < 0x7ff) {
945                                 new_pass->length += 2;
946                         } else {
947                                 new_pass->length += 3;
948                         }
949                 }
950
951                 new_pass->vp_strvalue = x = talloc_array(new_pass, char, new_pass->length + 1);
952
953                 /*
954                  *      Second pass: convert the characters from UTF-16 to UTF-8.
955                  */
956                 i = 0;
957                 while (i < passlen) {
958                         int c;
959
960                         c = p[i++];
961                         c += p[i++] << 8;
962
963                         /*
964                          *  Gah. nasty. maybe we should just pull in iconv?
965                          */
966                         if (c < 0x7f) {
967                                 *x++ = c;
968
969                         } else if (c < 0x7ff) {
970                                 *x++ = 0xc0 + (c >> 6);
971                                 *x++ = 0x80 + (c & 0x3f);
972
973                         } else {
974                                 *x++ = 0xe0 + (c >> 12);
975                                 *x++ = 0x80 + ((c>>6) & 0x3f);
976                                 *x++ = 0x80 + (c & 0x3f);
977                         }
978                 }
979
980                 *x = '\0';
981
982                 /* Perform the xlat */
983                 result_len = radius_xlat(result, sizeof(result), request, inst->local_cpw, NULL, NULL);
984                 if (result_len < 0){
985                         return -1;
986                 } else if (result_len == 0) {
987                         REDEBUG("Local MS-CHAPv2 password change - xlat didn't give any result, assuming failure");
988                         return -1;
989                 }
990
991                 RDEBUG("MS-CHAPv2 password change succeeded: %s", result);
992
993                 /*
994                  *  Update the NT-Password attribute with the new hash this lets us
995                  *  fall through to the authentication code using the new hash,
996                  *  not the old one.
997                  */
998                 pairmemcpy(nt_password, new_hash->vp_octets, new_hash->length);
999
1000                 /*
1001                  *  Rock on! password change succeeded.
1002                  */
1003                 return 0;
1004 #else
1005                 REDEBUG("Local MS-CHAPv2 password changes require OpenSSL support");
1006                 return -1;
1007 #endif
1008         } else {
1009                 REDEBUG("MS-CHAPv2 password change not configured");
1010         }
1011
1012         return -1;
1013 }
1014
1015 /*
1016  *      Do the MS-CHAP stuff.
1017  *
1018  *      This function is here so that all of the MS-CHAP related
1019  *      authentication is in one place, and we can perhaps later replace
1020  *      it with code to call winbindd, or something similar.
1021  */
1022 static int CC_HINT(nonnull (1, 2, 4, 5 ,6)) do_mschap(rlm_mschap_t *inst, REQUEST *request, VALUE_PAIR *password,
1023                                                       uint8_t const *challenge, uint8_t const *response,
1024                                                       uint8_t nthashhash[NT_DIGEST_LENGTH], bool do_ntlm_auth)
1025 {
1026         uint8_t calculated[24];
1027
1028         memset(nthashhash, 0, NT_DIGEST_LENGTH);
1029         /*
1030          *      Do normal authentication.
1031          */
1032         if (!do_ntlm_auth) {
1033                 /*
1034                  *      No password: can't do authentication.
1035                  */
1036                 if (!password) {
1037                         REDEBUG("FAILED: No NT/LM-Password.  Cannot perform authentication");
1038                         return -1;
1039                 }
1040
1041                 smbdes_mschap(password->vp_octets, challenge, calculated);
1042                 if (rad_digest_cmp(response, calculated, 24) != 0) {
1043                         return -1;
1044                 }
1045
1046                 /*
1047                  *      If the password exists, and is an NT-Password,
1048                  *      then calculate the hash of the NT hash.  Doing this
1049                  *      here minimizes work for later.
1050                  */
1051                 if (password && !password->da->vendor &&
1052                     (password->da->attr == PW_NT_PASSWORD)) {
1053                         fr_md4_calc(nthashhash, password->vp_octets, MD4_DIGEST_LENGTH);
1054                 }
1055         } else {                /* run ntlm_auth */
1056                 int     result;
1057                 char    buffer[256];
1058                 size_t  len;
1059
1060                 /*
1061                  *      Run the program, and expect that we get 16
1062                  */
1063                 result = radius_exec_program(buffer, sizeof(buffer), NULL, request, inst->ntlm_auth, NULL,
1064                                              true, true, inst->ntlm_auth_timeout);
1065                 if (result != 0) {
1066                         char *p;
1067
1068                         /*
1069                          *      look for "Password expired", or "Must change password".
1070                          */
1071                         if (strcasestr(buffer, "Password expired") ||
1072                             strcasestr(buffer, "Must change password")) {
1073                                 REDEBUG2("%s", buffer);
1074                                 return -648;
1075                         }
1076
1077                         RDEBUG2("External script failed");
1078                         p = strchr(buffer, '\n');
1079                         if (p) *p = '\0';
1080
1081                         REDEBUG("External script says: %s", buffer);
1082                         return -1;
1083                 }
1084
1085                 /*
1086                  *      Parse the answer as an nthashhash.
1087                  *
1088                  *      ntlm_auth currently returns:
1089                  *      NT_KEY: 000102030405060708090a0b0c0d0e0f
1090                  */
1091                 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
1092                         REDEBUG("Invalid output from ntlm_auth: expecting 'NT_KEY: ' prefix");
1093                         return -1;
1094                 }
1095
1096                 /*
1097                  *      Check the length.  It should be at least 32, with an LF at the end.
1098                  */
1099                 len = strlen(buffer + 8);
1100                 if (len < 32) {
1101                         REDEBUG2("Invalid output from ntlm_auth: NT_KEY too short, expected 32 bytes got %zu bytes",
1102                                  len);
1103
1104                         return -1;
1105                 }
1106
1107                 /*
1108                  *      Update the NT hash hash, from the NT key.
1109                  */
1110                 if (fr_hex2bin(nthashhash, NT_DIGEST_LENGTH, buffer + 8, len) != NT_DIGEST_LENGTH) {
1111                         REDEBUG("Invalid output from ntlm_auth: NT_KEY has non-hex values");
1112                         return -1;
1113                 }
1114         }
1115
1116         return 0;
1117 }
1118
1119
1120 /*
1121  *      Data for the hashes.
1122  */
1123 static const uint8_t SHSpad1[40] =
1124                { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1125                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1126                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1127                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
1128
1129 static const uint8_t SHSpad2[40] =
1130                { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1131                  0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1132                  0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1133                  0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
1134
1135 static const uint8_t magic1[27] =
1136                { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
1137                  0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
1138                  0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
1139
1140 static const uint8_t magic2[84] =
1141                { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
1142                  0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
1143                  0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1144                  0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
1145                  0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
1146                  0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
1147                  0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1148                  0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
1149                  0x6b, 0x65, 0x79, 0x2e };
1150
1151 static const uint8_t magic3[84] =
1152                { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
1153                  0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
1154                  0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1155                  0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
1156                  0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
1157                  0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
1158                  0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
1159                  0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
1160                  0x6b, 0x65, 0x79, 0x2e };
1161
1162
1163 static void mppe_GetMasterKey(uint8_t const *nt_hashhash,uint8_t const *nt_response,
1164                               uint8_t *masterkey)
1165 {
1166        uint8_t digest[20];
1167        fr_SHA1_CTX Context;
1168
1169        fr_sha1_init(&Context);
1170        fr_sha1_update(&Context,nt_hashhash,NT_DIGEST_LENGTH);
1171        fr_sha1_update(&Context,nt_response,24);
1172        fr_sha1_update(&Context,magic1,27);
1173        fr_sha1_final(digest,&Context);
1174
1175        memcpy(masterkey,digest,16);
1176 }
1177
1178
1179 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
1180                                        int keylen,int issend)
1181 {
1182        uint8_t digest[20];
1183        const uint8_t *s;
1184        fr_SHA1_CTX Context;
1185
1186        memset(digest,0,20);
1187
1188        if(issend) {
1189                s = magic3;
1190        } else {
1191                s = magic2;
1192        }
1193
1194        fr_sha1_init(&Context);
1195        fr_sha1_update(&Context,masterkey,16);
1196        fr_sha1_update(&Context,SHSpad1,40);
1197        fr_sha1_update(&Context,s,84);
1198        fr_sha1_update(&Context,SHSpad2,40);
1199        fr_sha1_final(digest,&Context);
1200
1201        memcpy(sesskey,digest,keylen);
1202 }
1203
1204
1205 static void mppe_chap2_get_keys128(uint8_t const *nt_hashhash,uint8_t const *nt_response,
1206                                    uint8_t *sendkey,uint8_t *recvkey)
1207 {
1208        uint8_t masterkey[16];
1209
1210        mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
1211
1212        mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
1213        mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
1214 }
1215
1216 /*
1217  *      Generate MPPE keys.
1218  */
1219 static void mppe_chap2_gen_keys128(uint8_t const *nt_hashhash,uint8_t const *response,
1220                                    uint8_t *sendkey,uint8_t *recvkey)
1221 {
1222         uint8_t enckey1[16];
1223         uint8_t enckey2[16];
1224
1225         mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
1226
1227         /*
1228          *      dictionary.microsoft defines these attributes as
1229          *      'encrypt=2'.  The functions in src/lib/radius.c will
1230          *      take care of encrypting/decrypting them as appropriate,
1231          *      so that we don't have to.
1232          */
1233         memcpy (sendkey, enckey1, 16);
1234         memcpy (recvkey, enckey2, 16);
1235 }
1236
1237
1238 /*
1239  *      mod_authorize() - authorize user if we can authenticate
1240  *      it later. Add Auth-Type attribute if present in module
1241  *      configuration (usually Auth-Type must be "MS-CHAP")
1242  */
1243 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void * instance, REQUEST *request)
1244 {
1245         rlm_mschap_t *inst = instance;
1246         VALUE_PAIR *challenge = NULL;
1247
1248         challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
1249         if (!challenge) {
1250                 return RLM_MODULE_NOOP;
1251         }
1252
1253         if (!pairfind(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY) &&
1254             !pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY) &&
1255             !pairfind(request->packet->vps, PW_MSCHAP2_CPW, VENDORPEC_MICROSOFT, TAG_ANY)) {
1256                 RDEBUG2("Found MS-CHAP-Challenge, but no MS-CHAP response or change-password");
1257                 return RLM_MODULE_NOOP;
1258         }
1259
1260         if (pairfind(request->config_items, PW_AUTH_TYPE, 0, TAG_ANY)) {
1261                 RWDEBUG2("Auth-Type already set.  Not setting to MS-CHAP");
1262                 return RLM_MODULE_NOOP;
1263         }
1264
1265         RDEBUG2("Found MS-CHAP attributes.  Setting 'Auth-Type  = %s'", inst->xlat_name);
1266
1267         /*
1268          *      Set Auth-Type to MS-CHAP.  The authentication code
1269          *      will take care of turning cleartext passwords into
1270          *      NT/LM passwords.
1271          */
1272         if (!pairmake_config("Auth-Type", inst->auth_type, T_OP_EQ)) {
1273                 return RLM_MODULE_FAIL;
1274         }
1275
1276         return RLM_MODULE_OK;
1277 }
1278
1279 /*
1280  *      mod_authenticate() - authenticate user based on given
1281  *      attributes and configuration.
1282  *      We will try to find out password in configuration
1283  *      or in configured passwd file.
1284  *      If one is found we will check paraneters given by NAS.
1285  *
1286  *      If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
1287  *      one of:
1288  *              PAP:      PW_USER_PASSWORD or
1289  *              MS-CHAP:  PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
1290  *              MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
1291  *      In case of password mismatch or locked account we MAY return
1292  *      PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
1293  *      If MS-CHAP2 succeeds we MUST return
1294  *      PW_MSCHAP2_SUCCESS
1295  */
1296 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void * instance, REQUEST *request)
1297 {
1298 #define inst ((rlm_mschap_t *)instance)
1299         VALUE_PAIR *challenge = NULL;
1300         VALUE_PAIR *response = NULL;
1301         VALUE_PAIR *cpw = NULL;
1302         VALUE_PAIR *password = NULL;
1303         VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
1304         VALUE_PAIR *username;
1305         uint8_t nthashhash[NT_DIGEST_LENGTH];
1306         char msch2resp[42];
1307         uint8_t *p;
1308         char const *username_string;
1309         int chap = 0;
1310         bool do_ntlm_auth;
1311
1312         /*
1313          *      If we have ntlm_auth configured, use it unless told
1314          *      otherwise
1315          */
1316         do_ntlm_auth = (inst->ntlm_auth != NULL);
1317
1318         /*
1319          *      If we have an ntlm_auth configuration, then we may
1320          *      want to suppress it.
1321          */
1322         if (do_ntlm_auth) {
1323                 VALUE_PAIR *vp = pairfind(request->config_items, PW_MS_CHAP_USE_NTLM_AUTH, 0, TAG_ANY);
1324                 if (vp) do_ntlm_auth = (vp->vp_integer > 0);
1325         }
1326
1327         /*
1328          *      Find the SMB-Account-Ctrl attribute, or the
1329          *      SMB-Account-Ctrl-Text attribute.
1330          */
1331         smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL, 0, TAG_ANY);
1332         if (!smb_ctrl) {
1333                 password = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL_TEXT, 0, TAG_ANY);
1334                 if (password) {
1335                         smb_ctrl = pairmake_config("SMB-Account-CTRL", "0", T_OP_SET);
1336                         if (smb_ctrl) {
1337                                 smb_ctrl->vp_integer = pdb_decode_acct_ctrl(password->vp_strvalue);
1338                         }
1339                 }
1340         }
1341
1342         /*
1343          *      We're configured to do MS-CHAP authentication.
1344          *      and account control information exists.  Enforce it.
1345          */
1346         if (smb_ctrl) {
1347                 /*
1348                  *      Password is not required.
1349                  */
1350                 if ((smb_ctrl->vp_integer & ACB_PWNOTREQ) != 0) {
1351                         RDEBUG2("SMB-Account-Ctrl says no password is required");
1352                         return RLM_MODULE_OK;
1353                 }
1354         }
1355
1356         /*
1357          *      Decide how to get the passwords.
1358          */
1359         password = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY);
1360
1361         /*
1362          *      We need an LM-Password.
1363          */
1364         lm_password = pairfind(request->config_items, PW_LM_PASSWORD, 0, TAG_ANY);
1365         if (lm_password) {
1366                 VERIFY_VP(lm_password);
1367
1368                 switch (lm_password->length) {
1369                 case LM_DIGEST_LENGTH:
1370                         RDEBUG2("Found LM-Password");
1371                         break;
1372
1373                 /* 0x */
1374                 case 34:
1375                 case 32:
1376                         RWDEBUG("LM-Password has not been normalized by the 'pap' module (likely still in hex format).  "
1377                                 "Authentication may fail");
1378                         lm_password = NULL;
1379                         break;
1380
1381                 default:
1382                         RWDEBUG("LM-Password found but incorrect length, expected " STRINGIFY(LM_DIGEST_LENGTH)
1383                                 " bytes got %zu bytes.  Authentication may fail", lm_password->length);
1384                         lm_password = NULL;
1385                         break;
1386                 }
1387         }
1388         /*
1389          *      ... or a Cleartext-Password, which we now transform into an LM-Password
1390          */
1391         if (!lm_password && password) {
1392                 RDEBUG2("Found Cleartext-Password, hashing to create LM-Password");
1393                 lm_password = pairmake_config("LM-Password", NULL, T_OP_EQ);
1394                 if (!lm_password) {
1395                         RERROR("No memory");
1396                 } else {
1397                         lm_password->length = LM_DIGEST_LENGTH;
1398                         lm_password->vp_octets = p = talloc_array(lm_password, uint8_t, lm_password->length);
1399                         smbdes_lmpwdhash(password->vp_strvalue, p);
1400                 }
1401         } else if (!do_ntlm_auth) {
1402                 RWDEBUG2("No Cleartext-Password configured.  Cannot create LM-Password");
1403         }
1404
1405         /*
1406          *      or we need an NT-Password.
1407          */
1408         nt_password = pairfind(request->config_items, PW_NT_PASSWORD, 0, TAG_ANY);
1409         if (nt_password) {
1410                 VERIFY_VP(nt_password);
1411
1412                 switch (nt_password->length) {
1413                 case NT_DIGEST_LENGTH:
1414                         RDEBUG2("Found NT-Password");
1415                         break;
1416
1417                 /* 0x */
1418                 case 34:
1419                 case 32:
1420                         RWDEBUG("NT-Password has not been normalized by the 'pap' module (likely still in hex format).  "
1421                                 "Authentication may fail");
1422                         nt_password = NULL;
1423                         break;
1424
1425                 default:
1426                         RWDEBUG("NT-Password found but incorrect length, expected " STRINGIFY(NT_DIGEST_LENGTH)
1427                                 " bytes got %zu bytes.  Authentication may fail", nt_password->length);
1428                         nt_password = NULL;
1429                         break;
1430                 }
1431         }
1432
1433         /*
1434          *      ... or a Cleartext-Password, which we now transform into an NT-Password
1435          */
1436         if (!nt_password && password) {
1437                 RDEBUG2("Found Cleartext-Password, hashing to create NT-Password");
1438                 nt_password = pairmake_config("NT-Password", NULL, T_OP_EQ);
1439                 if (!nt_password) {
1440                         RERROR("No memory");
1441                         return RLM_MODULE_FAIL;
1442                 }
1443                 nt_password->length = NT_DIGEST_LENGTH;
1444                 nt_password->vp_octets = p = talloc_array(nt_password, uint8_t, nt_password->length);
1445
1446                 if (mschap_ntpwdhash(p, password->vp_strvalue) < 0) {
1447                         RERROR("Failed generating NT-Password");
1448                         return RLM_MODULE_FAIL;
1449                 }
1450         } else if (!do_ntlm_auth) {
1451                 RWDEBUG2("No Cleartext-Password configured.  Cannot create NT-Password");
1452         }
1453
1454         cpw = pairfind(request->packet->vps, PW_MSCHAP2_CPW, VENDORPEC_MICROSOFT, TAG_ANY);
1455         if (cpw) {
1456                 /*
1457                  * mschap2 password change request
1458                  * we cheat - first decode and execute the passchange
1459                  * we then extract the response, add it into the request
1460                  * then jump into mschap2 auth with the chal/resp
1461                  */
1462                 uint8_t new_nt_encrypted[516], old_nt_encrypted[NT_DIGEST_LENGTH];
1463                 VALUE_PAIR *nt_enc=NULL;
1464                 int seq, new_nt_enc_len=0;
1465
1466                 RDEBUG("MS-CHAPv2 password change request received");
1467
1468                 if (!nt_password) {
1469                         REDEBUG("No valid NT-Password attribute found, can't change password");
1470                         return RLM_MODULE_INVALID;
1471                 }
1472
1473                 if (cpw->length != 68) {
1474                         REDEBUG("MS-CHAP2-CPW has the wrong format: length %zu != 68", cpw->length);
1475                         return RLM_MODULE_INVALID;
1476                 } else if (cpw->vp_octets[0]!=7) {
1477                         REDEBUG("MS-CHAP2-CPW has the wrong format: code %d != 7", cpw->vp_octets[0]);
1478                         return RLM_MODULE_INVALID;
1479                 }
1480
1481                 /*
1482                  *  look for the new (encrypted) password
1483                  *  bah stupid composite attributes
1484                  *  we're expecting 3 attributes with the leading bytes
1485                  *  06:<mschapid>:00:01:<1st chunk>
1486                  *  06:<mschapid>:00:02:<2nd chunk>
1487                  *  06:<mschapid>:00:03:<3rd chunk>
1488                  */
1489                 for (seq = 1; seq < 4; seq++) {
1490                         vp_cursor_t cursor;
1491                         int found = 0;
1492
1493                         for (nt_enc = fr_cursor_init(&cursor, &request->packet->vps);
1494                              nt_enc;
1495                              nt_enc = fr_cursor_next(&cursor)) {
1496                                 if (nt_enc->da->vendor != VENDORPEC_MICROSOFT)
1497                                         continue;
1498
1499                                 if (nt_enc->da->attr != PW_MSCHAP_NT_ENC_PW)
1500                                         continue;
1501
1502                                 if (nt_enc->vp_octets[0] != 6) {
1503                                         REDEBUG("MS-CHAP-NT-Enc-PW with invalid format");
1504                                         return RLM_MODULE_INVALID;
1505                                 }
1506                                 if (nt_enc->vp_octets[2]==0 && nt_enc->vp_octets[3]==seq) {
1507                                         found = 1;
1508                                         break;
1509                                 }
1510                         }
1511
1512                         if (!found) {
1513                                 REDEBUG("Could not find MS-CHAP-NT-Enc-PW w/ sequence number %d", seq);
1514                                 return RLM_MODULE_INVALID;
1515                         }
1516
1517                         /*
1518                          * copy the data into the buffer
1519                          */
1520                         memcpy(new_nt_encrypted + new_nt_enc_len, nt_enc->vp_octets + 4, nt_enc->length - 4);
1521                         new_nt_enc_len += nt_enc->length - 4;
1522                 }
1523                 if (new_nt_enc_len != 516) {
1524                         REDEBUG("Unpacked MS-CHAP-NT-Enc-PW length != 516");
1525                         return RLM_MODULE_INVALID;
1526                 }
1527
1528                 /*
1529                  * RFC 2548 is confusing here
1530                  * it claims:
1531                  *
1532                  * 1 byte code
1533                  * 1 byte ident
1534                  * 16 octets - old hash encrypted with new hash
1535                  * 24 octets - peer challenge
1536                  *   this is actually:
1537                  *   16 octets - peer challenge
1538                  *    8 octets - reserved
1539                  * 24 octets - nt response
1540                  * 2 octets  - flags (ignored)
1541                  */
1542
1543                 memcpy(old_nt_encrypted, cpw->vp_octets + 2, sizeof(old_nt_encrypted));
1544
1545                 RDEBUG2("Password change payload valid");
1546
1547                 /* perform the actual password change */
1548                 rad_assert(nt_password);
1549                 if (do_mschap_cpw(inst, request, nt_password, new_nt_encrypted, old_nt_encrypted, do_ntlm_auth) < 0) {
1550                         char buffer[128];
1551
1552                         REDEBUG("Password change failed");
1553
1554                         snprintf(buffer, sizeof(buffer), "E=709 R=0 M=Password change failed");
1555                         mschap_add_reply(request, cpw->vp_octets[1], "MS-CHAP-Error", buffer, strlen(buffer));
1556
1557                         return RLM_MODULE_REJECT;
1558                 }
1559                 RDEBUG("Password change successful");
1560
1561                 /*
1562                  *  Clear any expiry bit so the user can now login;
1563                  *  obviously the password change action will need
1564                  *  to have cleared this bit in the config/SQL/wherever
1565                  */
1566                 if (smb_ctrl && smb_ctrl->vp_integer & ACB_PW_EXPIRED) {
1567                         RDEBUG("Clearing expiry bit in SMB-Acct-Ctrl to allow authentication");
1568                         smb_ctrl->vp_integer &= ~ACB_PW_EXPIRED;
1569                 }
1570
1571                 /*
1572                  *  Extract the challenge & response from the end of the password
1573                  *  change, add them into the request and then continue with
1574                  *  the authentication
1575                  */
1576
1577                 response = radius_paircreate(request->packet, &request->packet->vps,
1578                                              PW_MSCHAP2_RESPONSE,
1579                                              VENDORPEC_MICROSOFT);
1580                 response->length = 50;
1581                 response->vp_octets = p = talloc_array(response, uint8_t, response->length);
1582
1583                 /* ident & flags */
1584                 p[0] = cpw->vp_octets[1];
1585                 p[1] = 0;
1586                 /* peer challenge and client NT response */
1587                 memcpy(p + 2, cpw->vp_octets + 18, 48);
1588         }
1589
1590         challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
1591         if (!challenge) {
1592                 REDEBUG("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1593                 return RLM_MODULE_REJECT;
1594         }
1595
1596         /*
1597          *      We also require an MS-CHAP-Response.
1598          */
1599         response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
1600
1601         /*
1602          *      MS-CHAP-Response, means MS-CHAPv1
1603          */
1604         if (response) {
1605                 int offset;
1606
1607                 /*
1608                  *      MS-CHAPv1 challenges are 8 octets.
1609                  */
1610                 if (challenge->length < 8) {
1611                         REDEBUG("MS-CHAP-Challenge has the wrong format");
1612                         return RLM_MODULE_INVALID;
1613                 }
1614
1615                 /*
1616                  *      Responses are 50 octets.
1617                  */
1618                 if (response->length < 50) {
1619                         REDEBUG("MS-CHAP-Response has the wrong format");
1620                         return RLM_MODULE_INVALID;
1621                 }
1622
1623                 /*
1624                  *      We are doing MS-CHAP.  Calculate the MS-CHAP
1625                  *      response
1626                  */
1627                 if (response->vp_octets[1] & 0x01) {
1628                         RDEBUG2("Client is using MS-CHAPv1 with NT-Password");
1629                         password = nt_password;
1630                         offset = 26;
1631                 } else {
1632                         RDEBUG2("Client is using MS-CHAPv1 with LM-Password");
1633                         password = lm_password;
1634                         offset = 2;
1635                 }
1636
1637                 /*
1638                  *      Do the MS-CHAP authentication.
1639                  */
1640                 if (do_mschap(inst, request, password, challenge->vp_octets, response->vp_octets + offset, nthashhash,
1641                               do_ntlm_auth) < 0) {
1642                         REDEBUG("MS-CHAP-Response is incorrect");
1643                         goto do_error;
1644                 }
1645
1646                 chap = 1;
1647
1648         } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY)) != NULL) {
1649                 int mschap_result;
1650                 uint8_t mschapv1_challenge[16];
1651                 VALUE_PAIR *name_attr, *response_name;
1652
1653                 /*
1654                  *      MS-CHAPv2 challenges are 16 octets.
1655                  */
1656                 if (challenge->length < 16) {
1657                         REDEBUG("MS-CHAP-Challenge has the wrong format");
1658                         return RLM_MODULE_INVALID;
1659                 }
1660
1661                 /*
1662                  *      Responses are 50 octets.
1663                  */
1664                 if (response->length < 50) {
1665                         REDEBUG("MS-CHAP-Response has the wrong format");
1666                         return RLM_MODULE_INVALID;
1667                 }
1668
1669                 /*
1670                  *      We also require a User-Name
1671                  */
1672                 username = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
1673                 if (!username) {
1674                         REDEBUG("We require a User-Name for MS-CHAPv2");
1675                         return RLM_MODULE_INVALID;
1676                 }
1677
1678                 /*
1679                  *      Check for MS-CHAP-User-Name and if found, use it
1680                  *      to construct the MSCHAPv1 challenge.  This is
1681                  *      set by rlm_eap_mschap to the MS-CHAP Response
1682                  *      packet Name field.
1683                  *
1684                  *      We prefer this to the User-Name in the
1685                  *      packet.
1686                  */
1687                 response_name = pairfind(request->packet->vps, PW_MS_CHAP_USER_NAME, 0, TAG_ANY);
1688                 if (response_name) {
1689                         name_attr = response_name;
1690                 } else {
1691                         name_attr = username;
1692                 }
1693
1694                 /*
1695                  *      with_ntdomain_hack moved here, too.
1696                  */
1697                 if ((username_string = strchr(name_attr->vp_strvalue, '\\')) != NULL) {
1698                         if (inst->with_ntdomain_hack) {
1699                                 username_string++;
1700                         } else {
1701                                 RWDEBUG2("NT Domain delimeter found, should with_ntdomain_hack of been enabled?");
1702                                 username_string = name_attr->vp_strvalue;
1703                         }
1704                 } else {
1705                         username_string = name_attr->vp_strvalue;
1706                 }
1707
1708                 if (response_name && ((username->length != response_name->length) ||
1709                     (strncasecmp(username->vp_strvalue, response_name->vp_strvalue, username->length) != 0))) {
1710                         RWDEBUG("User-Name (%s) is not the same as MS-CHAP Name (%s) from EAP-MSCHAPv2",
1711                                 username->vp_strvalue, response_name->vp_strvalue);
1712                 }
1713
1714 #ifdef WITH_OPEN_DIRECTORY
1715                 /*
1716                  *  No "known good" NT-Password attribute.  Try to do
1717                  *  OpenDirectory authentication.
1718                  *
1719                  *  If OD determines the user is an AD user it will return noop, which
1720                  *  indicates the auth process should continue directly to AD.
1721                  *  Otherwise OD will determine auth success/fail.
1722                  */
1723                 if (!nt_password && inst->open_directory) {
1724                         RDEBUG2("No NT-Password configured. Trying OpenDirectory Authentication");
1725                         int odStatus = od_mschap_auth(request, challenge, username);
1726                         if (odStatus != RLM_MODULE_NOOP) {
1727                                 return odStatus;
1728                         }
1729                 }
1730 #endif
1731                 /*
1732                  *      The old "mschapv2" function has been moved to
1733                  *      here.
1734                  *
1735                  *      MS-CHAPv2 takes some additional data to create an
1736                  *      MS-CHAPv1 challenge, and then does MS-CHAPv1.
1737                  */
1738                 RDEBUG2("Creating challenge hash with username: %s", username_string);
1739                 mschap_challenge_hash(response->vp_octets + 2,  /* peer challenge */
1740                                       challenge->vp_octets,     /* our challenge */
1741                                       username_string,          /* user name */
1742                                       mschapv1_challenge);      /* resulting challenge */
1743
1744                 RDEBUG2("Client is using MS-CHAPv2");
1745
1746                 mschap_result = do_mschap(inst, request, nt_password, mschapv1_challenge,
1747                                           response->vp_octets + 26, nthashhash, do_ntlm_auth);
1748                 if (mschap_result == -648)
1749                         goto password_expired;
1750
1751                 if (mschap_result < 0) {
1752                         int i;
1753                         char buffer[128];
1754
1755                         REDEBUG("MS-CHAP2-Response is incorrect");
1756
1757                 do_error:
1758                         snprintf(buffer, sizeof(buffer), "E=691 R=%d", inst->allow_retry);
1759
1760                         if (inst->retry_msg) {
1761                                 snprintf(buffer + 9, sizeof(buffer) - 9, " C=");
1762                                 for (i = 0; i < 16; i++) {
1763                                         snprintf(buffer + 12 + (i * 2),
1764                                                  sizeof(buffer) - 12 - (i * 2), "%02x",
1765                                                  fr_rand() & 0xff);
1766                                 }
1767                                 snprintf(buffer + 45, sizeof(buffer) - 45, " V=3 M=%s", inst->retry_msg);
1768                         }
1769                         mschap_add_reply(request, *response->vp_octets, "MS-CHAP-Error", buffer, strlen(buffer));
1770                         return RLM_MODULE_REJECT;
1771                 }
1772
1773                 /*
1774                  *      If the password is correct and it has expired
1775                  *      we can permit password changes (only in MS-CHAPv2)
1776                  */
1777                 if (smb_ctrl && smb_ctrl->vp_integer & ACB_PW_EXPIRED) {
1778
1779                         char newchal[33], buffer[128];
1780                         int i;
1781                 password_expired:
1782
1783                         for (i = 0; i < 16; i++) {
1784                                 snprintf(newchal + (i * 2), 3, "%02x", fr_rand() & 0xff);
1785                         }
1786
1787                         snprintf(buffer, sizeof(buffer), "E=648 R=0 C=%s V=3 M=Password Expired", newchal);
1788
1789                         mschap_add_reply(request, *response->vp_octets, "MS-CHAP-Error", buffer, strlen(buffer));
1790                         return RLM_MODULE_REJECT;
1791                 }
1792
1793                 mschap_auth_response(username_string,           /* without the domain */
1794                                      nthashhash,                /* nt-hash-hash */
1795                                      response->vp_octets + 26,  /* peer response */
1796                                      response->vp_octets + 2,   /* peer challenge */
1797                                      challenge->vp_octets,      /* our challenge */
1798                                      msch2resp);                /* calculated MPPE key */
1799                 mschap_add_reply(request, *response->vp_octets, "MS-CHAP2-Success", msch2resp, 42);
1800                 chap = 2;
1801
1802         } else {                /* Neither CHAPv1 or CHAPv2 response: die */
1803                 REDEBUG("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1804                 return RLM_MODULE_INVALID;
1805         }
1806
1807         /*
1808          *      We have a CHAP response, but the account may be
1809          *      disabled.  Reject the user with the same error code
1810          *      we use when their password is invalid.
1811          */
1812         if (smb_ctrl) {
1813                 /*
1814                  *      Account is disabled.
1815                  *
1816                  *      They're found, but they don't exist, so we
1817                  *      return 'not found'.
1818                  */
1819                 if (((smb_ctrl->vp_integer & ACB_DISABLED) != 0) ||
1820                     ((smb_ctrl->vp_integer & (ACB_NORMAL|ACB_WSTRUST)) == 0)) {
1821                         REDEBUG("SMB-Account-Ctrl says that the account is disabled, or is not a normal "
1822                                 "or workstation trust account");
1823                         mschap_add_reply(request, *response->vp_octets, "MS-CHAP-Error", "E=691 R=1", 9);
1824                         return RLM_MODULE_NOTFOUND;
1825                 }
1826
1827                 /*
1828                  *      User is locked out.
1829                  */
1830                 if ((smb_ctrl->vp_integer & ACB_AUTOLOCK) != 0) {
1831                         REDEBUG("SMB-Account-Ctrl says that the account is locked out");
1832                         mschap_add_reply(request, *response->vp_octets, "MS-CHAP-Error", "E=647 R=0", 9);
1833                         return RLM_MODULE_USERLOCK;
1834                 }
1835         }
1836
1837         /* now create MPPE attributes */
1838         if (inst->use_mppe) {
1839                 uint8_t mppe_sendkey[34];
1840                 uint8_t mppe_recvkey[34];
1841
1842                 if (chap == 1){
1843                         RDEBUG2("adding MS-CHAPv1 MPPE keys");
1844                         memset(mppe_sendkey, 0, 32);
1845                         if (lm_password) {
1846                                 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1847                         }
1848
1849                         /*
1850                          *      According to RFC 2548 we
1851                          *      should send NT hash.  But in
1852                          *      practice it doesn't work.
1853                          *      Instead, we should send nthashhash
1854                          *
1855                          *      This is an error on RFC 2548.
1856                          */
1857                         /*
1858                          *      do_mschap cares to zero nthashhash if NT hash
1859                          *      is not available.
1860                          */
1861                         memcpy(mppe_sendkey + 8, nthashhash, NT_DIGEST_LENGTH);
1862                         mppe_add_reply(request, "MS-CHAP-MPPE-Keys", mppe_sendkey, 32);
1863                 } else if (chap == 2) {
1864                         RDEBUG2("Adding MS-CHAPv2 MPPE keys");
1865                         mppe_chap2_gen_keys128(nthashhash, response->vp_octets + 26, mppe_sendkey, mppe_recvkey);
1866
1867                         mppe_add_reply(request, "MS-MPPE-Recv-Key", mppe_recvkey, 16);
1868                         mppe_add_reply(request, "MS-MPPE-Send-Key", mppe_sendkey, 16);
1869
1870                 }
1871                 pairmake_reply("MS-MPPE-Encryption-Policy",
1872                                (inst->require_encryption) ? "0x00000002":"0x00000001", T_OP_EQ);
1873                 pairmake_reply("MS-MPPE-Encryption-Types",
1874                                (inst->require_strong) ? "0x00000004":"0x00000006", T_OP_EQ);
1875         } /* else we weren't asked to use MPPE */
1876
1877         return RLM_MODULE_OK;
1878 #undef inst
1879 }
1880
1881 module_t rlm_mschap = {
1882         RLM_MODULE_INIT,
1883         "MS-CHAP",
1884         RLM_TYPE_THREAD_SAFE | RLM_TYPE_HUP_SAFE,       /* type */
1885         sizeof(rlm_mschap_t),
1886         module_config,
1887         mod_instantiate,                /* instantiation */
1888         NULL,                           /* detach */
1889         {
1890                 mod_authenticate,       /* authenticate */
1891                 mod_authorize,          /* authorize */
1892                 NULL,                   /* pre-accounting */
1893                 NULL,                   /* accounting */
1894                 NULL,                   /* checksimul */
1895                 NULL,                   /* pre-proxy */
1896                 NULL,                   /* post-proxy */
1897                 NULL                    /* post-auth */
1898         },
1899 };