Formatting
[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 /*
1384  *      mod_authenticate() - authenticate user based on given
1385  *      attributes and configuration.
1386  *      We will try to find out password in configuration
1387  *      or in configured passwd file.
1388  *      If one is found we will check paraneters given by NAS.
1389  *
1390  *      If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
1391  *      one of:
1392  *              PAP:      PW_USER_PASSWORD or
1393  *              MS-CHAP:  PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
1394  *              MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
1395  *      In case of password mismatch or locked account we MAY return
1396  *      PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
1397  *      If MS-CHAP2 succeeds we MUST return
1398  *      PW_MSCHAP2_SUCCESS
1399  */
1400 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void * instance, REQUEST *request)
1401 {
1402 #define inst ((rlm_mschap_t *)instance)
1403         VALUE_PAIR *challenge = NULL;
1404         VALUE_PAIR *response = NULL;
1405         VALUE_PAIR *cpw = NULL;
1406         VALUE_PAIR *password = NULL;
1407         VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
1408         VALUE_PAIR *username;
1409         uint8_t nthashhash[NT_DIGEST_LENGTH];
1410         char msch2resp[42];
1411         uint8_t *p;
1412         char const *username_string;
1413         int chap = 0;
1414         MSCHAP_AUTH_METHOD auth_method;
1415
1416         /*
1417          *      If we have ntlm_auth configured, use it unless told
1418          *      otherwise
1419          */
1420         auth_method = inst->method;
1421
1422         /*
1423          *      If we have an ntlm_auth configuration, then we may
1424          *      want to suppress it.
1425          */
1426         if (auth_method != AUTH_INTERNAL) {
1427                 VALUE_PAIR *vp = fr_pair_find_by_num(request->config, PW_MS_CHAP_USE_NTLM_AUTH, 0, TAG_ANY);
1428                 if (vp && vp->vp_integer == 0) auth_method = AUTH_INTERNAL;
1429         }
1430
1431         /*
1432          *      Find the SMB-Account-Ctrl attribute, or the
1433          *      SMB-Account-Ctrl-Text attribute.
1434          */
1435         smb_ctrl = fr_pair_find_by_num(request->config, PW_SMB_ACCOUNT_CTRL, 0, TAG_ANY);
1436         if (!smb_ctrl) {
1437                 password = fr_pair_find_by_num(request->config, PW_SMB_ACCOUNT_CTRL_TEXT, 0, TAG_ANY);
1438                 if (password) {
1439                         smb_ctrl = pair_make_config("SMB-Account-CTRL", "0", T_OP_SET);
1440                         if (smb_ctrl) {
1441                                 smb_ctrl->vp_integer = pdb_decode_acct_ctrl(password->vp_strvalue);
1442                         }
1443                 }
1444         }
1445
1446         /*
1447          *      We're configured to do MS-CHAP authentication.
1448          *      and account control information exists.  Enforce it.
1449          */
1450         if (smb_ctrl) {
1451                 /*
1452                  *      Password is not required.
1453                  */
1454                 if ((smb_ctrl->vp_integer & ACB_PWNOTREQ) != 0) {
1455                         RDEBUG2("SMB-Account-Ctrl says no password is required");
1456                         return RLM_MODULE_OK;
1457                 }
1458         }
1459
1460         /*
1461          *      Decide how to get the passwords.
1462          */
1463         password = fr_pair_find_by_num(request->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY);
1464
1465         /*
1466          *      We need an NT-Password.
1467          */
1468         nt_password = fr_pair_find_by_num(request->config, PW_NT_PASSWORD, 0, TAG_ANY);
1469         if (nt_password) {
1470                 VERIFY_VP(nt_password);
1471
1472                 switch (nt_password->vp_length) {
1473                 case NT_DIGEST_LENGTH:
1474                         RDEBUG2("Found NT-Password");
1475                         break;
1476
1477                 /* 0x */
1478                 case 34:
1479                 case 32:
1480                         RWDEBUG("NT-Password has not been normalized by the 'pap' module (likely still in hex format).  "
1481                                 "Authentication may fail");
1482                         nt_password = NULL;
1483                         break;
1484
1485                 default:
1486                         RWDEBUG("NT-Password found but incorrect length, expected " STRINGIFY(NT_DIGEST_LENGTH)
1487                                 " bytes got %zu bytes.  Authentication may fail", nt_password->vp_length);
1488                         nt_password = NULL;
1489                         break;
1490                 }
1491         }
1492
1493         /*
1494          *      ... or a Cleartext-Password, which we now transform into an NT-Password
1495          */
1496         if (!nt_password) {
1497                 if (password) {
1498                         RDEBUG2("Found Cleartext-Password, hashing to create NT-Password");
1499                         nt_password = pair_make_config("NT-Password", NULL, T_OP_EQ);
1500                         if (!nt_password) {
1501                                 RERROR("No memory");
1502                                 return RLM_MODULE_FAIL;
1503                         }
1504                         nt_password->vp_length = NT_DIGEST_LENGTH;
1505                         nt_password->vp_octets = p = talloc_array(nt_password, uint8_t, nt_password->vp_length);
1506
1507                         if (mschap_ntpwdhash(p, password->vp_strvalue) < 0) {
1508                                 RERROR("Failed generating NT-Password");
1509                                 return RLM_MODULE_FAIL;
1510                         }
1511                 } else if (auth_method == AUTH_INTERNAL) {
1512                         RWDEBUG2("No Cleartext-Password configured.  Cannot create NT-Password");
1513                 }
1514         }
1515
1516         /*
1517          *      Or an LM-Password.
1518          */
1519         lm_password = fr_pair_find_by_num(request->config, PW_LM_PASSWORD, 0, TAG_ANY);
1520         if (lm_password) {
1521                 VERIFY_VP(lm_password);
1522
1523                 switch (lm_password->vp_length) {
1524                 case LM_DIGEST_LENGTH:
1525                         RDEBUG2("Found LM-Password");
1526                         break;
1527
1528                 /* 0x */
1529                 case 34:
1530                 case 32:
1531                         RWDEBUG("LM-Password has not been normalized by the 'pap' module (likely still in hex format).  "
1532                                 "Authentication may fail");
1533                         lm_password = NULL;
1534                         break;
1535
1536                 default:
1537                         RWDEBUG("LM-Password found but incorrect length, expected " STRINGIFY(LM_DIGEST_LENGTH)
1538                                 " bytes got %zu bytes.  Authentication may fail", lm_password->vp_length);
1539                         lm_password = NULL;
1540                         break;
1541                 }
1542         }
1543         /*
1544          *      ... or a Cleartext-Password, which we now transform into an LM-Password
1545          */
1546         if (!lm_password) {
1547                 if (password) {
1548                         RDEBUG2("Found Cleartext-Password, hashing to create LM-Password");
1549                         lm_password = pair_make_config("LM-Password", NULL, T_OP_EQ);
1550                         if (!lm_password) {
1551                                 RERROR("No memory");
1552                         } else {
1553                                 lm_password->vp_length = LM_DIGEST_LENGTH;
1554                                 lm_password->vp_octets = p = talloc_array(lm_password, uint8_t, lm_password->vp_length);
1555                                 smbdes_lmpwdhash(password->vp_strvalue, p);
1556                         }
1557                 /*
1558                  *      Only complain if we don't have NT-Password
1559                  */
1560                 } else if ((auth_method == AUTH_INTERNAL) && !nt_password) {
1561                         RWDEBUG2("No Cleartext-Password configured.  Cannot create LM-Password");
1562                 }
1563         }
1564
1565         cpw = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_CPW, VENDORPEC_MICROSOFT, TAG_ANY);
1566         if (cpw) {
1567                 /*
1568                  * mschap2 password change request
1569                  * we cheat - first decode and execute the passchange
1570                  * we then extract the response, add it into the request
1571                  * then jump into mschap2 auth with the chal/resp
1572                  */
1573                 uint8_t         new_nt_encrypted[516], old_nt_encrypted[NT_DIGEST_LENGTH];
1574                 VALUE_PAIR      *nt_enc=NULL;
1575                 int             seq, new_nt_enc_len=0;
1576
1577                 RDEBUG("MS-CHAPv2 password change request received");
1578
1579                 if (cpw->vp_length != 68) {
1580                         REDEBUG("MS-CHAP2-CPW has the wrong format: length %zu != 68", cpw->vp_length);
1581                         return RLM_MODULE_INVALID;
1582                 } else if (cpw->vp_octets[0]!=7) {
1583                         REDEBUG("MS-CHAP2-CPW has the wrong format: code %d != 7", cpw->vp_octets[0]);
1584                         return RLM_MODULE_INVALID;
1585                 }
1586
1587                 /*
1588                  *  look for the new (encrypted) password
1589                  *  bah stupid composite attributes
1590                  *  we're expecting 3 attributes with the leading bytes
1591                  *  06:<mschapid>:00:01:<1st chunk>
1592                  *  06:<mschapid>:00:02:<2nd chunk>
1593                  *  06:<mschapid>:00:03:<3rd chunk>
1594                  */
1595                 for (seq = 1; seq < 4; seq++) {
1596                         vp_cursor_t cursor;
1597                         int found = 0;
1598
1599                         for (nt_enc = fr_cursor_init(&cursor, &request->packet->vps);
1600                              nt_enc;
1601                              nt_enc = fr_cursor_next(&cursor)) {
1602                                 if (nt_enc->da->vendor != VENDORPEC_MICROSOFT)
1603                                         continue;
1604
1605                                 if (nt_enc->da->attr != PW_MSCHAP_NT_ENC_PW)
1606                                         continue;
1607
1608                                 if (nt_enc->vp_octets[0] != 6) {
1609                                         REDEBUG("MS-CHAP-NT-Enc-PW with invalid format");
1610                                         return RLM_MODULE_INVALID;
1611                                 }
1612                                 if (nt_enc->vp_octets[2]==0 && nt_enc->vp_octets[3]==seq) {
1613                                         found = 1;
1614                                         break;
1615                                 }
1616                         }
1617
1618                         if (!found) {
1619                                 REDEBUG("Could not find MS-CHAP-NT-Enc-PW w/ sequence number %d", seq);
1620                                 return RLM_MODULE_INVALID;
1621                         }
1622
1623                         /*
1624                          * copy the data into the buffer
1625                          */
1626                         memcpy(new_nt_encrypted + new_nt_enc_len, nt_enc->vp_octets + 4, nt_enc->vp_length - 4);
1627                         new_nt_enc_len += nt_enc->vp_length - 4;
1628                 }
1629                 if (new_nt_enc_len != 516) {
1630                         REDEBUG("Unpacked MS-CHAP-NT-Enc-PW length != 516");
1631                         return RLM_MODULE_INVALID;
1632                 }
1633
1634                 /*
1635                  * RFC 2548 is confusing here
1636                  * it claims:
1637                  *
1638                  * 1 byte code
1639                  * 1 byte ident
1640                  * 16 octets - old hash encrypted with new hash
1641                  * 24 octets - peer challenge
1642                  *   this is actually:
1643                  *   16 octets - peer challenge
1644                  *    8 octets - reserved
1645                  * 24 octets - nt response
1646                  * 2 octets  - flags (ignored)
1647                  */
1648
1649                 memcpy(old_nt_encrypted, cpw->vp_octets + 2, sizeof(old_nt_encrypted));
1650
1651                 RDEBUG2("Password change payload valid");
1652
1653                 /* perform the actual password change */
1654                 rad_assert(nt_password);
1655                 if (do_mschap_cpw(inst, request, nt_password, new_nt_encrypted, old_nt_encrypted, auth_method) < 0) {
1656                         char buffer[128];
1657
1658                         REDEBUG("Password change failed");
1659
1660                         snprintf(buffer, sizeof(buffer), "E=709 R=0 M=Password change failed");
1661                         mschap_add_reply(request, cpw->vp_octets[1], "MS-CHAP-Error", buffer, strlen(buffer));
1662
1663                         return RLM_MODULE_REJECT;
1664                 }
1665                 RDEBUG("Password change successful");
1666
1667                 /*
1668                  *  Clear any expiry bit so the user can now login;
1669                  *  obviously the password change action will need
1670                  *  to have cleared this bit in the config/SQL/wherever
1671                  */
1672                 if (smb_ctrl && smb_ctrl->vp_integer & ACB_PW_EXPIRED) {
1673                         RDEBUG("Clearing expiry bit in SMB-Acct-Ctrl to allow authentication");
1674                         smb_ctrl->vp_integer &= ~ACB_PW_EXPIRED;
1675                 }
1676
1677                 /*
1678                  *  Extract the challenge & response from the end of the password
1679                  *  change, add them into the request and then continue with
1680                  *  the authentication
1681                  */
1682
1683                 response = radius_pair_create(request->packet, &request->packet->vps,
1684                                              PW_MSCHAP2_RESPONSE,
1685                                              VENDORPEC_MICROSOFT);
1686                 response->vp_length = 50;
1687                 response->vp_octets = p = talloc_array(response, uint8_t, response->vp_length);
1688
1689                 /* ident & flags */
1690                 p[0] = cpw->vp_octets[1];
1691                 p[1] = 0;
1692                 /* peer challenge and client NT response */
1693                 memcpy(p + 2, cpw->vp_octets + 18, 48);
1694         }
1695
1696         challenge = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
1697         if (!challenge) {
1698                 REDEBUG("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1699                 return RLM_MODULE_REJECT;
1700         }
1701
1702         /*
1703          *      We also require an MS-CHAP-Response.
1704          */
1705         response = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
1706
1707         /*
1708          *      MS-CHAP-Response, means MS-CHAPv1
1709          */
1710         if (response) {
1711                 int offset;
1712
1713                 /*
1714                  *      MS-CHAPv1 challenges are 8 octets.
1715                  */
1716                 if (challenge->vp_length < 8) {
1717                         REDEBUG("MS-CHAP-Challenge has the wrong format");
1718                         return RLM_MODULE_INVALID;
1719                 }
1720
1721                 /*
1722                  *      Responses are 50 octets.
1723                  */
1724                 if (response->vp_length < 50) {
1725                         REDEBUG("MS-CHAP-Response has the wrong format");
1726                         return RLM_MODULE_INVALID;
1727                 }
1728
1729                 /*
1730                  *      We are doing MS-CHAP.  Calculate the MS-CHAP
1731                  *      response
1732                  */
1733                 if (response->vp_octets[1] & 0x01) {
1734                         RDEBUG2("Client is using MS-CHAPv1 with NT-Password");
1735                         password = nt_password;
1736                         offset = 26;
1737                 } else {
1738                         RDEBUG2("Client is using MS-CHAPv1 with LM-Password");
1739                         password = lm_password;
1740                         offset = 2;
1741                 }
1742
1743                 /*
1744                  *      Do the MS-CHAP authentication.
1745                  */
1746                 if (do_mschap(inst, request, password, challenge->vp_octets, response->vp_octets + offset, nthashhash,
1747                               auth_method) < 0) {
1748                         REDEBUG("MS-CHAP-Response is incorrect");
1749                         goto do_error;
1750                 }
1751
1752                 chap = 1;
1753
1754         } else if ((response = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_RESPONSE,
1755                                                    VENDORPEC_MICROSOFT, TAG_ANY)) != NULL) {
1756                 int mschap_result;
1757                 uint8_t mschapv1_challenge[16];
1758                 VALUE_PAIR *name_attr, *response_name;
1759
1760                 /*
1761                  *      MS-CHAPv2 challenges are 16 octets.
1762                  */
1763                 if (challenge->vp_length < 16) {
1764                         REDEBUG("MS-CHAP-Challenge has the wrong format");
1765                         return RLM_MODULE_INVALID;
1766                 }
1767
1768                 /*
1769                  *      Responses are 50 octets.
1770                  */
1771                 if (response->vp_length < 50) {
1772                         REDEBUG("MS-CHAP-Response has the wrong format");
1773                         return RLM_MODULE_INVALID;
1774                 }
1775
1776                 /*
1777                  *      We also require a User-Name
1778                  */
1779                 username = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
1780                 if (!username) {
1781                         REDEBUG("We require a User-Name for MS-CHAPv2");
1782                         return RLM_MODULE_INVALID;
1783                 }
1784
1785                 /*
1786                  *      Check for MS-CHAP-User-Name and if found, use it
1787                  *      to construct the MSCHAPv1 challenge.  This is
1788                  *      set by rlm_eap_mschap to the MS-CHAP Response
1789                  *      packet Name field.
1790                  *
1791                  *      We prefer this to the User-Name in the
1792                  *      packet.
1793                  */
1794                 response_name = fr_pair_find_by_num(request->packet->vps, PW_MS_CHAP_USER_NAME, 0, TAG_ANY);
1795                 if (response_name) {
1796                         name_attr = response_name;
1797                 } else {
1798                         name_attr = username;
1799                 }
1800
1801                 /*
1802                  *      with_ntdomain_hack moved here, too.
1803                  */
1804                 if ((username_string = strchr(name_attr->vp_strvalue, '\\')) != NULL) {
1805                         if (inst->with_ntdomain_hack) {
1806                                 username_string++;
1807                         } else {
1808                                 RWDEBUG2("NT Domain delimeter found, should with_ntdomain_hack of been enabled?");
1809                                 username_string = name_attr->vp_strvalue;
1810                         }
1811                 } else {
1812                         username_string = name_attr->vp_strvalue;
1813                 }
1814
1815                 if (response_name && ((username->vp_length != response_name->vp_length) ||
1816                     (strncasecmp(username->vp_strvalue, response_name->vp_strvalue, username->vp_length) != 0))) {
1817                         RWDEBUG("User-Name (%s) is not the same as MS-CHAP Name (%s) from EAP-MSCHAPv2",
1818                                 username->vp_strvalue, response_name->vp_strvalue);
1819                 }
1820
1821 #ifdef __APPLE__
1822                 /*
1823                  *  No "known good" NT-Password attribute.  Try to do
1824                  *  OpenDirectory authentication.
1825                  *
1826                  *  If OD determines the user is an AD user it will return noop, which
1827                  *  indicates the auth process should continue directly to AD.
1828                  *  Otherwise OD will determine auth success/fail.
1829                  */
1830                 if (!nt_password && inst->open_directory) {
1831                         RDEBUG2("No NT-Password configured. Trying OpenDirectory Authentication");
1832                         int odStatus = od_mschap_auth(request, challenge, username);
1833                         if (odStatus != RLM_MODULE_NOOP) {
1834                                 return odStatus;
1835                         }
1836                 }
1837 #endif
1838                 /*
1839                  *      The old "mschapv2" function has been moved to
1840                  *      here.
1841                  *
1842                  *      MS-CHAPv2 takes some additional data to create an
1843                  *      MS-CHAPv1 challenge, and then does MS-CHAPv1.
1844                  */
1845                 RDEBUG2("Creating challenge hash with username: %s", username_string);
1846                 mschap_challenge_hash(response->vp_octets + 2,  /* peer challenge */
1847                                       challenge->vp_octets,     /* our challenge */
1848                                       username_string,          /* user name */
1849                                       mschapv1_challenge);      /* resulting challenge */
1850
1851                 RDEBUG2("Client is using MS-CHAPv2");
1852
1853                 mschap_result = do_mschap(inst, request, nt_password, mschapv1_challenge,
1854                                           response->vp_octets + 26, nthashhash, auth_method);
1855                 if (mschap_result == -648) goto password_expired;
1856
1857                 if (mschap_result < 0) {
1858                         int             i;
1859                         char            buffer[128];
1860
1861                         REDEBUG("MS-CHAP2-Response is incorrect");
1862
1863                 do_error:
1864                         snprintf(buffer, sizeof(buffer), "E=691 R=%d", inst->allow_retry);
1865
1866                         if (inst->retry_msg) {
1867                                 snprintf(buffer + 9, sizeof(buffer) - 9, " C=");
1868                                 for (i = 0; i < 16; i++) {
1869                                         snprintf(buffer + 12 + (i * 2),
1870                                                  sizeof(buffer) - 12 - (i * 2), "%02x",
1871                                                  fr_rand() & 0xff);
1872                                 }
1873                                 /* E=691 R=d (9) + " C=" (3) + 32 hexits = 44 */
1874                                 snprintf(buffer + 44, sizeof(buffer) - 44, " V=3 M=%s", inst->retry_msg);
1875                         }
1876                         mschap_add_reply(request, *response->vp_octets, "MS-CHAP-Error", buffer, strlen(buffer));
1877                         return RLM_MODULE_REJECT;
1878                 }
1879
1880                 /*
1881                  *      If the password is correct and it has expired
1882                  *      we can permit password changes (only in MS-CHAPv2)
1883                  */
1884                 if (smb_ctrl && smb_ctrl->vp_integer & ACB_PW_EXPIRED) {
1885
1886                         char newchal[33], buffer[128];
1887                         int i;
1888                 password_expired:
1889
1890                         for (i = 0; i < 16; i++) {
1891                                 snprintf(newchal + (i * 2), 3, "%02x", fr_rand() & 0xff);
1892                         }
1893
1894                         snprintf(buffer, sizeof(buffer), "E=648 R=%d C=%s V=3 M=Password Expired",
1895                                  inst->allow_retry, newchal);
1896
1897                         RDEBUG("Password has expired.  The user should retry authentication");
1898                         mschap_add_reply(request, *response->vp_octets, "MS-CHAP-Error", buffer, strlen(buffer));
1899                         return RLM_MODULE_REJECT;
1900                 }
1901
1902                 mschap_auth_response(username_string,           /* without the domain */
1903                                      nthashhash,                /* nt-hash-hash */
1904                                      response->vp_octets + 26,  /* peer response */
1905                                      response->vp_octets + 2,   /* peer challenge */
1906                                      challenge->vp_octets,      /* our challenge */
1907                                      msch2resp);                /* calculated MPPE key */
1908                 mschap_add_reply(request, *response->vp_octets, "MS-CHAP2-Success", msch2resp, 42);
1909                 chap = 2;
1910
1911         } else {                /* Neither CHAPv1 or CHAPv2 response: die */
1912                 REDEBUG("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1913                 return RLM_MODULE_INVALID;
1914         }
1915
1916         /*
1917          *      We have a CHAP response, but the account may be
1918          *      disabled.  Reject the user with the same error code
1919          *      we use when their password is invalid.
1920          */
1921         if (smb_ctrl) {
1922                 /*
1923                  *      Account is disabled.
1924                  *
1925                  *      They're found, but they don't exist, so we
1926                  *      return 'not found'.
1927                  */
1928                 if (((smb_ctrl->vp_integer & ACB_DISABLED) != 0) ||
1929                     ((smb_ctrl->vp_integer & (ACB_NORMAL|ACB_WSTRUST)) == 0)) {
1930                         REDEBUG("SMB-Account-Ctrl says that the account is disabled, or is not a normal "
1931                                 "or workstation trust account");
1932                         mschap_add_reply(request, *response->vp_octets, "MS-CHAP-Error", "E=691 R=1", 9);
1933                         return RLM_MODULE_NOTFOUND;
1934                 }
1935
1936                 /*
1937                  *      User is locked out.
1938                  */
1939                 if ((smb_ctrl->vp_integer & ACB_AUTOLOCK) != 0) {
1940                         REDEBUG("SMB-Account-Ctrl says that the account is locked out");
1941                         mschap_add_reply(request, *response->vp_octets, "MS-CHAP-Error", "E=647 R=0", 9);
1942                         return RLM_MODULE_USERLOCK;
1943                 }
1944         }
1945
1946         /* now create MPPE attributes */
1947         if (inst->use_mppe) {
1948                 uint8_t mppe_sendkey[34];
1949                 uint8_t mppe_recvkey[34];
1950
1951                 if (chap == 1){
1952                         RDEBUG2("adding MS-CHAPv1 MPPE keys");
1953                         memset(mppe_sendkey, 0, 32);
1954                         if (lm_password) {
1955                                 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1956                         }
1957
1958                         /*
1959                          *      According to RFC 2548 we
1960                          *      should send NT hash.  But in
1961                          *      practice it doesn't work.
1962                          *      Instead, we should send nthashhash
1963                          *
1964                          *      This is an error in RFC 2548.
1965                          */
1966                         /*
1967                          *      do_mschap cares to zero nthashhash if NT hash
1968                          *      is not available.
1969                          */
1970                         memcpy(mppe_sendkey + 8, nthashhash, NT_DIGEST_LENGTH);
1971                         mppe_add_reply(request, "MS-CHAP-MPPE-Keys", mppe_sendkey, 24);
1972                 } else if (chap == 2) {
1973                         RDEBUG2("Adding MS-CHAPv2 MPPE keys");
1974                         mppe_chap2_gen_keys128(nthashhash, response->vp_octets + 26, mppe_sendkey, mppe_recvkey);
1975
1976                         mppe_add_reply(request, "MS-MPPE-Recv-Key", mppe_recvkey, 16);
1977                         mppe_add_reply(request, "MS-MPPE-Send-Key", mppe_sendkey, 16);
1978
1979                 }
1980                 pair_make_reply("MS-MPPE-Encryption-Policy",
1981                                (inst->require_encryption) ? "0x00000002":"0x00000001", T_OP_EQ);
1982                 pair_make_reply("MS-MPPE-Encryption-Types",
1983                                (inst->require_strong) ? "0x00000004":"0x00000006", T_OP_EQ);
1984         } /* else we weren't asked to use MPPE */
1985
1986         return RLM_MODULE_OK;
1987 #undef inst
1988 }
1989
1990 extern module_t rlm_mschap;
1991 module_t rlm_mschap = {
1992         .magic          = RLM_MODULE_INIT,
1993         .name           = "mschap",
1994         .type           = 0,
1995         .inst_size      = sizeof(rlm_mschap_t),
1996         .config         = module_config,
1997         .bootstrap      = mod_bootstrap,
1998         .instantiate    = mod_instantiate,
1999         .detach         = mod_detach,
2000         .methods = {
2001                 [MOD_AUTHENTICATE]      = mod_authenticate,
2002                 [MOD_AUTHORIZE]         = mod_authorize
2003         },
2004 };