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