Allow authentication retry in winbind
[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         { "winbind_retry_with_normalised_username", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_mschap_t, wb_retry_with_normalised_username), "no" },
564 #ifdef __APPLE__
565         { "use_open_directory", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_mschap_t, open_directory), "yes" },
566 #endif
567         CONF_PARSER_TERMINATOR
568 };
569
570
571 static int mod_bootstrap(CONF_SECTION *conf, void *instance)
572 {
573         char const *name;
574         rlm_mschap_t *inst = instance;
575
576         /*
577          *      Create the dynamic translation.
578          */
579         name = cf_section_name2(conf);
580         if (!name) name = cf_section_name1(conf);
581         inst->xlat_name = name;
582         xlat_register(inst->xlat_name, mschap_xlat, NULL, inst);
583
584         return 0;
585 }
586
587 /*
588  *      Create instance for our module. Allocate space for
589  *      instance structure and read configuration parameters
590  */
591 static int mod_instantiate(CONF_SECTION *conf, void *instance)
592 {
593         rlm_mschap_t *inst = instance;
594
595         /*
596          *      For backwards compatibility
597          */
598         if (!dict_valbyname(PW_AUTH_TYPE, 0, inst->xlat_name)) {
599                 inst->auth_type = "MS-CHAP";
600         } else {
601                 inst->auth_type = inst->xlat_name;
602         }
603
604         /*
605          *      Set auth method
606          */
607         inst->method = AUTH_INTERNAL;
608
609         if (inst->wb_username) {
610 #ifdef WITH_AUTH_WINBIND
611                 inst->method = AUTH_WBCLIENT;
612
613                 inst->wb_pool = fr_connection_pool_module_init(conf, inst, mod_conn_create, NULL, NULL);
614                 if (!inst->wb_pool) {
615                         cf_log_err_cs(conf, "Unable to initialise winbind connection pool");
616                         return -1;
617                 }
618 #else
619                 cf_log_err_cs(conf, "'winbind' auth not enabled at compiled time");
620                 return -1;
621 #endif
622         }
623
624         /* preserve existing behaviour: this option overrides all */
625         if (inst->ntlm_auth) {
626                 inst->method = AUTH_NTLMAUTH_EXEC;
627         }
628
629         switch (inst->method) {
630         case AUTH_INTERNAL:
631                 DEBUG("rlm_mschap (%s): using internal authentication", inst->xlat_name);
632                 break;
633         case AUTH_NTLMAUTH_EXEC:
634                 DEBUG("rlm_mschap (%s): authenticating by calling 'ntlm_auth'", inst->xlat_name);
635                 break;
636 #ifdef WITH_AUTH_WINBIND
637         case AUTH_WBCLIENT:
638                 DEBUG("rlm_mschap (%s): authenticating directly to winbind", inst->xlat_name);
639                 break;
640 #endif
641         }
642
643         /*
644          *      Check ntlm_auth_timeout is sane
645          */
646         if (!inst->ntlm_auth_timeout) {
647                 inst->ntlm_auth_timeout = EXEC_TIMEOUT;
648         }
649         if (inst->ntlm_auth_timeout < 1) {
650                 cf_log_err_cs(conf, "ntml_auth_timeout '%d' is too small (minimum: 1)",
651                               inst->ntlm_auth_timeout);
652                 return -1;
653         }
654         if (inst->ntlm_auth_timeout > 10) {
655                 cf_log_err_cs(conf, "ntlm_auth_timeout '%d' is too large (maximum: 10)",
656                               inst->ntlm_auth_timeout);
657                 return -1;
658         }
659
660         return 0;
661 }
662
663 /*
664  *      Tidy up instance
665  */
666 static int mod_detach(UNUSED void *instance)
667 {
668 #ifdef WITH_AUTH_WINBIND
669         rlm_mschap_t *inst = instance;
670
671         fr_connection_pool_free(inst->wb_pool);
672 #endif
673
674         return 0;
675 }
676
677 /*
678  *      add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
679  *      attribute to reply packet
680  */
681 void mschap_add_reply(REQUEST *request, unsigned char ident,
682                       char const *name, char const *value, size_t len)
683 {
684         VALUE_PAIR *vp;
685
686         vp = pair_make_reply(name, NULL, T_OP_EQ);
687         if (!vp) {
688                 REDEBUG("Failed to create attribute %s: %s", name, fr_strerror());
689                 return;
690         }
691
692         /* Account for the ident byte */
693         vp->vp_length = len + 1;
694         if (vp->da->type == PW_TYPE_STRING) {
695                 char *p;
696
697                 vp->vp_strvalue = p = talloc_array(vp, char, vp->vp_length + 1);
698                 p[vp->vp_length] = '\0';        /* Always \0 terminate */
699                 p[0] = ident;
700                 memcpy(p + 1, value, len);
701         } else {
702                 uint8_t *p;
703
704                 vp->vp_octets = p = talloc_array(vp, uint8_t, vp->vp_length);
705                 p[0] = ident;
706                 memcpy(p + 1, value, len);
707         }
708 }
709
710 /*
711  *      Add MPPE attributes to the reply.
712  */
713 static void mppe_add_reply(REQUEST *request, char const* name, uint8_t const * value, size_t len)
714 {
715        VALUE_PAIR *vp;
716
717        vp = pair_make_reply(name, NULL, T_OP_EQ);
718        if (!vp) {
719                REDEBUG("mppe_add_reply failed to create attribute %s: %s", name, fr_strerror());
720                return;
721        }
722
723        fr_pair_value_memcpy(vp, value, len);
724 }
725
726 static int write_all(int fd, char const *buf, int len) {
727         int rv,done=0;
728
729         while (done < len) {
730                 rv = write(fd, buf+done, len-done);
731                 if (rv <= 0)
732                         break;
733                 done += rv;
734         }
735         return done;
736 }
737
738 /*
739  * Perform an MS-CHAP2 password change
740  */
741
742 static int CC_HINT(nonnull (1, 2, 4, 5)) do_mschap_cpw(rlm_mschap_t *inst,
743                                                        REQUEST *request,
744 #ifdef HAVE_OPENSSL_CRYPTO_H
745                                                        VALUE_PAIR *nt_password,
746 #else
747                                                        UNUSED VALUE_PAIR *nt_password,
748 #endif
749                                                        uint8_t *new_nt_password,
750                                                        uint8_t *old_nt_hash,
751                                                        MSCHAP_AUTH_METHOD method)
752 {
753         if (inst->ntlm_cpw && method != AUTH_INTERNAL) {
754                 /*
755                  * we're going to run ntlm_auth in helper-mode
756                  * we're expecting to use the ntlm-change-password-1 protocol
757                  * which needs the following on stdin:
758                  *
759                  * username: %{mschap:User-Name}
760                  * nt-domain: %{mschap:NT-Domain}
761                  * new-nt-password-blob: bin2hex(new_nt_password) - 1032 bytes encoded
762                  * old-nt-hash-blob: bin2hex(old_nt_hash) - 32 bytes encoded
763                  * new-lm-password-blob: 00000...0000 - 1032 bytes null
764                  * old-lm-hash-blob: 000....000 - 32 bytes null
765                  * .\n
766                  *
767                  * ...and it should then print out
768                  *
769                  * Password-Change: Yes
770                  *
771                  * or
772                  *
773                  * Password-Change: No
774                  * Password-Change-Error: blah
775                  */
776
777                 int to_child=-1;
778                 int from_child=-1;
779                 pid_t pid, child_pid;
780                 int status, len;
781                 char buf[2048];
782                 char *pmsg;
783                 char const *emsg;
784
785                 RDEBUG("Doing MS-CHAPv2 password change via ntlm_auth helper");
786
787                 /*
788                  * Start up ntlm_auth with a pipe on stdin and stdout
789                  */
790
791                 pid = radius_start_program(inst->ntlm_cpw, request, true, &to_child, &from_child, NULL, false);
792                 if (pid < 0) {
793                         REDEBUG("could not exec ntlm_auth cpw command");
794                         return -1;
795                 }
796
797                 /*
798                  * write the stuff to the client
799                  */
800
801                 if (inst->ntlm_cpw_username) {
802                         len = radius_xlat(buf, sizeof(buf) - 2, request, inst->ntlm_cpw_username, NULL, NULL);
803                         if (len < 0) {
804                                 goto ntlm_auth_err;
805                         }
806
807                         buf[len++] = '\n';
808                         buf[len] = '\0';
809
810                         if (write_all(to_child, buf, len) != len) {
811                                 REDEBUG("Failed to write username to child");
812                                 goto ntlm_auth_err;
813                         }
814                 } else {
815                         RWDEBUG2("No ntlm_auth username set, passchange will definitely fail!");
816                 }
817
818                 if (inst->ntlm_cpw_domain) {
819                         len = radius_xlat(buf, sizeof(buf) - 2, request, inst->ntlm_cpw_domain, NULL, NULL);
820                         if (len < 0) {
821                                 goto ntlm_auth_err;
822                         }
823
824                         buf[len++] = '\n';
825                         buf[len] = '\0';
826
827                         if (write_all(to_child, buf, len) != len) {
828                                 REDEBUG("Failed to write domain to child");
829                                 goto ntlm_auth_err;
830                         }
831                 } else {
832                         RWDEBUG2("No ntlm_auth domain set, username must be full-username to work");
833                 }
834
835                 /* now the password blobs */
836                 len = sprintf(buf, "new-nt-password-blob: ");
837                 fr_bin2hex(buf+len, new_nt_password, 516);
838                 buf[len+1032] = '\n';
839                 buf[len+1033] = '\0';
840                 len = strlen(buf);
841                 if (write_all(to_child, buf, len) != len) {
842                         RDEBUG2("failed to write new password blob to child");
843                         goto ntlm_auth_err;
844                 }
845
846                 len = sprintf(buf, "old-nt-hash-blob: ");
847                 fr_bin2hex(buf+len, old_nt_hash, NT_DIGEST_LENGTH);
848                 buf[len+32] = '\n';
849                 buf[len+33] = '\0';
850                 len = strlen(buf);
851                 if (write_all(to_child, buf, len) != len) {
852                         REDEBUG("Failed to write old hash blob to child");
853                         goto ntlm_auth_err;
854                 }
855
856                 /*
857                  *  In current samba versions, failure to supply empty LM password/hash
858                  *  blobs causes the change to fail.
859                  */
860                 len = sprintf(buf, "new-lm-password-blob: %01032i\n", 0);
861                 if (write_all(to_child, buf, len) != len) {
862                         REDEBUG("Failed to write dummy LM password to child");
863                         goto ntlm_auth_err;
864                 }
865                 len = sprintf(buf, "old-lm-hash-blob: %032i\n", 0);
866                 if (write_all(to_child, buf, len) != len) {
867                         REDEBUG("Failed to write dummy LM hash to child");
868                         goto ntlm_auth_err;
869                 }
870                 if (write_all(to_child, ".\n", 2) != 2) {
871                         REDEBUG("Failed to send finish to child");
872                         goto ntlm_auth_err;
873                 }
874                 close(to_child);
875                 to_child = -1;
876
877                 /*
878                  *  Read from the child
879                  */
880                 len = radius_readfrom_program(from_child, pid, 10, buf, sizeof(buf));
881                 if (len < 0) {
882                         /* radius_readfrom_program will have closed from_child for us */
883                         REDEBUG("Failure reading from child");
884                         return -1;
885                 }
886                 close(from_child);
887                 from_child = -1;
888
889                 buf[len] = 0;
890                 RDEBUG2("ntlm_auth said: %s", buf);
891
892                 child_pid = rad_waitpid(pid, &status);
893                 if (child_pid == 0) {
894                         REDEBUG("Timeout waiting for child");
895                         return -1;
896                 }
897                 if (child_pid != pid) {
898                         REDEBUG("Abnormal exit status: %s", fr_syserror(errno));
899                         return -1;
900                 }
901
902                 if (strstr(buf, "Password-Change: Yes")) {
903                         RDEBUG2("ntlm_auth password change succeeded");
904                         return 0;
905                 }
906
907                 pmsg = strstr(buf, "Password-Change-Error: ");
908                 if (pmsg) {
909                         emsg = strsep(&pmsg, "\n");
910                 } else {
911                         emsg = "could not find error";
912                 }
913                 REDEBUG("ntlm auth password change failed: %s", emsg);
914
915 ntlm_auth_err:
916                 /* safe because these either need closing or are == -1 */
917                 close(to_child);
918                 close(from_child);
919
920                 return -1;
921
922         } else if (inst->local_cpw) {
923 #ifdef HAVE_OPENSSL_CRYPTO_H
924                 /*
925                  *  Decrypt the new password blob, add it as a temporary request
926                  *  variable, xlat the local_cpw string, then remove it
927                  *
928                  *  this allows is to write e..g
929                  *
930                  *  %{sql:insert into ...}
931                  *
932                  *  ...or...
933                  *
934                  *  %{exec:/path/to %{mschap:User-Name} %{MS-CHAP-New-Password}}"
935                  *
936                  */
937                 VALUE_PAIR *new_pass, *new_hash;
938                 uint8_t *p, *q;
939                 char *x;
940                 size_t i;
941                 size_t passlen;
942                 ssize_t result_len;
943                 char result[253];
944                 uint8_t nt_pass_decrypted[516], old_nt_hash_expected[NT_DIGEST_LENGTH];
945                 RC4_KEY key;
946
947                 if (!nt_password) {
948                         RDEBUG("Local MS-CHAPv2 password change requires NT-Password attribute");
949                         return -1;
950                 } else {
951                         RDEBUG("Doing MS-CHAPv2 password change locally");
952                 }
953
954                 /*
955                  *  Decrypt the blob
956                  */
957                 RC4_set_key(&key, nt_password->vp_length, nt_password->vp_octets);
958                 RC4(&key, 516, new_nt_password, nt_pass_decrypted);
959
960                 /*
961                  *  pwblock is
962                  *  512-N bytes random pad
963                  *  N bytes password as utf-16-le
964                  *  4 bytes - N as big-endian int
965                  */
966                 passlen = nt_pass_decrypted[512];
967                 passlen += nt_pass_decrypted[513] << 8;
968                 if ((nt_pass_decrypted[514] != 0) ||
969                     (nt_pass_decrypted[515] != 0)) {
970                         REDEBUG("Decrypted new password blob claims length > 65536, "
971                                 "probably an invalid NT-Password");
972                         return -1;
973                 }
974
975                 /*
976                  *  Sanity check - passlen positive and <= 512 if not, crypto has probably gone wrong
977                  */
978                 if (passlen > 512) {
979                         REDEBUG("Decrypted new password blob claims length %zu > 512, "
980                                 "probably an invalid NT-Password", passlen);
981                         return -1;
982                 }
983
984                 p = nt_pass_decrypted + 512 - passlen;
985
986                 /*
987                  *  The new NT hash - this should be preferred over the
988                  *  cleartext password as it avoids unicode hassles.
989                  */
990                 new_hash = pair_make_request("MS-CHAP-New-NT-Password", NULL, T_OP_EQ);
991                 new_hash->vp_length = NT_DIGEST_LENGTH;
992                 new_hash->vp_octets = q = talloc_array(new_hash, uint8_t, new_hash->vp_length);
993                 fr_md4_calc(q, p, passlen);
994
995                 /*
996                  *  Check that nt_password encrypted with new_hash
997                  *  matches the old_hash value from the client.
998                  */
999                 smbhash(old_nt_hash_expected, nt_password->vp_octets, q);
1000                 smbhash(old_nt_hash_expected+8, nt_password->vp_octets+8, q + 7);
1001                 if (memcmp(old_nt_hash_expected, old_nt_hash, NT_DIGEST_LENGTH)!=0) {
1002                         REDEBUG("Old NT hash value from client does not match our value");
1003                         return -1;
1004                 }
1005
1006                 /*
1007                  *  The new cleartext password, which is utf-16 do some unpleasant vileness
1008                  *  to turn it into utf8 without pulling in libraries like iconv.
1009                  *
1010                  *  First pass: get the length of the converted string.
1011                  */
1012                 new_pass = pair_make_request("MS-CHAP-New-Cleartext-Password", NULL, T_OP_EQ);
1013                 new_pass->vp_length = 0;
1014
1015                 i = 0;
1016                 while (i < passlen) {
1017                         int c;
1018
1019                         c = p[i++];
1020                         c += p[i++] << 8;
1021
1022                         /*
1023                          *  Gah. nasty. maybe we should just pull in iconv?
1024                          */
1025                         if (c < 0x7f) {
1026                                 new_pass->vp_length++;
1027                         } else if (c < 0x7ff) {
1028                                 new_pass->vp_length += 2;
1029                         } else {
1030                                 new_pass->vp_length += 3;
1031                         }
1032                 }
1033
1034                 new_pass->vp_strvalue = x = talloc_array(new_pass, char, new_pass->vp_length + 1);
1035
1036                 /*
1037                  *      Second pass: convert the characters from UTF-16 to UTF-8.
1038                  */
1039                 i = 0;
1040                 while (i < passlen) {
1041                         int c;
1042
1043                         c = p[i++];
1044                         c += p[i++] << 8;
1045
1046                         /*
1047                          *  Gah. nasty. maybe we should just pull in iconv?
1048                          */
1049                         if (c < 0x7f) {
1050                                 *x++ = c;
1051
1052                         } else if (c < 0x7ff) {
1053                                 *x++ = 0xc0 + (c >> 6);
1054                                 *x++ = 0x80 + (c & 0x3f);
1055
1056                         } else {
1057                                 *x++ = 0xe0 + (c >> 12);
1058                                 *x++ = 0x80 + ((c>>6) & 0x3f);
1059                                 *x++ = 0x80 + (c & 0x3f);
1060                         }
1061                 }
1062
1063                 *x = '\0';
1064
1065                 /* Perform the xlat */
1066                 result_len = radius_xlat(result, sizeof(result), request, inst->local_cpw, NULL, NULL);
1067                 if (result_len < 0){
1068                         return -1;
1069                 } else if (result_len == 0) {
1070                         REDEBUG("Local MS-CHAPv2 password change - xlat didn't give any result, assuming failure");
1071                         return -1;
1072                 }
1073
1074                 RDEBUG("MS-CHAPv2 password change succeeded: %s", result);
1075
1076                 /*
1077                  *  Update the NT-Password attribute with the new hash this lets us
1078                  *  fall through to the authentication code using the new hash,
1079                  *  not the old one.
1080                  */
1081                 fr_pair_value_memcpy(nt_password, new_hash->vp_octets, new_hash->vp_length);
1082
1083                 /*
1084                  *  Rock on! password change succeeded.
1085                  */
1086                 return 0;
1087 #else
1088                 REDEBUG("Local MS-CHAPv2 password changes require OpenSSL support");
1089                 return -1;
1090 #endif
1091         } else {
1092                 REDEBUG("MS-CHAPv2 password change not configured");
1093         }
1094
1095         return -1;
1096 }
1097
1098 /*
1099  *      Do the MS-CHAP stuff.
1100  *
1101  *      This function is here so that all of the MS-CHAP related
1102  *      authentication is in one place, and we can perhaps later replace
1103  *      it with code to call winbindd, or something similar.
1104  */
1105 static int CC_HINT(nonnull (1, 2, 4, 5 ,6)) do_mschap(rlm_mschap_t *inst, REQUEST *request, VALUE_PAIR *password,
1106                                                       uint8_t const *challenge, uint8_t const *response,
1107                                                       uint8_t nthashhash[NT_DIGEST_LENGTH], MSCHAP_AUTH_METHOD method)
1108 {
1109         uint8_t calculated[24];
1110
1111         memset(nthashhash, 0, NT_DIGEST_LENGTH);
1112
1113         switch (method) {
1114                 /*
1115                  *      Do normal authentication.
1116                  */
1117         case AUTH_INTERNAL:
1118                 /*
1119                  *      No password: can't do authentication.
1120                  */
1121                 if (!password) {
1122                         REDEBUG("FAILED: No NT/LM-Password.  Cannot perform authentication");
1123                         return -1;
1124                 }
1125
1126                 smbdes_mschap(password->vp_octets, challenge, calculated);
1127                 if (rad_digest_cmp(response, calculated, 24) != 0) {
1128                         return -1;
1129                 }
1130
1131                 /*
1132                  *      If the password exists, and is an NT-Password,
1133                  *      then calculate the hash of the NT hash.  Doing this
1134                  *      here minimizes work for later.
1135                  */
1136                 if (!password->da->vendor &&
1137                     (password->da->attr == PW_NT_PASSWORD)) {
1138                         fr_md4_calc(nthashhash, password->vp_octets, MD4_DIGEST_LENGTH);
1139                 }
1140                 break;
1141
1142                 /*
1143                  *      Run ntlm_auth
1144                  */
1145         case AUTH_NTLMAUTH_EXEC: {
1146                 int     result;
1147                 char    buffer[256];
1148                 size_t  len;
1149
1150                 /*
1151                  *      Run the program, and expect that we get 16
1152                  */
1153                 result = radius_exec_program(request, buffer, sizeof(buffer), NULL, request, inst->ntlm_auth, NULL,
1154                                              true, true, inst->ntlm_auth_timeout);
1155                 if (result != 0) {
1156                         char *p;
1157
1158                         /*
1159                          *      look for "Password expired", or "Must change password".
1160                          */
1161                         if (strcasestr(buffer, "Password expired") ||
1162                             strcasestr(buffer, "Must change password")) {
1163                                 REDEBUG2("%s", buffer);
1164                                 return -648;
1165                         }
1166
1167                         if (strcasestr(buffer, "Account locked out") ||
1168                             strcasestr(buffer, "0xC0000234")) {
1169                                 REDEBUG2("%s", buffer);
1170                                 return -647;
1171                         }
1172
1173                         if (strcasestr(buffer, "Account disabled") ||
1174                             strcasestr(buffer, "0xC0000072")) {
1175                                 REDEBUG2("%s", buffer);
1176                                 return -691;
1177                         }
1178
1179                         RDEBUG2("External script failed");
1180                         p = strchr(buffer, '\n');
1181                         if (p) *p = '\0';
1182
1183                         REDEBUG("External script says: %s", buffer);
1184                         return -1;
1185                 }
1186
1187                 /*
1188                  *      Parse the answer as an nthashhash.
1189                  *
1190                  *      ntlm_auth currently returns:
1191                  *      NT_KEY: 000102030405060708090a0b0c0d0e0f
1192                  */
1193                 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
1194                         REDEBUG("Invalid output from ntlm_auth: expecting 'NT_KEY: ' prefix");
1195                         return -1;
1196                 }
1197
1198                 /*
1199                  *      Check the length.  It should be at least 32, with an LF at the end.
1200                  */
1201                 len = strlen(buffer + 8);
1202                 if (len < 32) {
1203                         REDEBUG2("Invalid output from ntlm_auth: NT_KEY too short, expected 32 bytes got %zu bytes",
1204                                  len);
1205
1206                         return -1;
1207                 }
1208
1209                 /*
1210                  *      Update the NT hash hash, from the NT key.
1211                  */
1212                 if (fr_hex2bin(nthashhash, NT_DIGEST_LENGTH, buffer + 8, len) != NT_DIGEST_LENGTH) {
1213                         REDEBUG("Invalid output from ntlm_auth: NT_KEY has non-hex values");
1214                         return -1;
1215                 }
1216                 break;
1217         }
1218
1219 #ifdef WITH_AUTH_WINBIND
1220                 /*
1221                  *      Process auth via the wbclient library
1222                  */
1223         case AUTH_WBCLIENT:
1224                 return do_auth_wbclient(inst, request, challenge, response, nthashhash);
1225 #endif
1226
1227                 /* We should never reach this line */
1228         default:
1229                 RERROR("Internal error: Unknown mschap auth method (%d)", method);
1230                 return -1;
1231         }
1232
1233         return 0;
1234 }
1235
1236
1237 /*
1238  *      Data for the hashes.
1239  */
1240 static const uint8_t SHSpad1[40] =
1241                { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1242                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1243                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1244                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
1245
1246 static const uint8_t SHSpad2[40] =
1247                { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1248                  0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1249                  0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1250                  0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
1251
1252 static const uint8_t magic1[27] =
1253                { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
1254                  0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
1255                  0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
1256
1257 static const uint8_t magic2[84] =
1258                { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
1259                  0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
1260                  0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1261                  0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
1262                  0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
1263                  0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
1264                  0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1265                  0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
1266                  0x6b, 0x65, 0x79, 0x2e };
1267
1268 static const uint8_t magic3[84] =
1269                { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
1270                  0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
1271                  0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1272                  0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
1273                  0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
1274                  0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
1275                  0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
1276                  0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
1277                  0x6b, 0x65, 0x79, 0x2e };
1278
1279
1280 static void mppe_GetMasterKey(uint8_t const *nt_hashhash,uint8_t const *nt_response,
1281                               uint8_t *masterkey)
1282 {
1283        uint8_t digest[20];
1284        fr_sha1_ctx Context;
1285
1286        fr_sha1_init(&Context);
1287        fr_sha1_update(&Context,nt_hashhash,NT_DIGEST_LENGTH);
1288        fr_sha1_update(&Context,nt_response,24);
1289        fr_sha1_update(&Context,magic1,27);
1290        fr_sha1_final(digest,&Context);
1291
1292        memcpy(masterkey,digest,16);
1293 }
1294
1295
1296 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
1297                                        int keylen,int issend)
1298 {
1299        uint8_t digest[20];
1300        const uint8_t *s;
1301        fr_sha1_ctx Context;
1302
1303        memset(digest,0,20);
1304
1305        if(issend) {
1306                s = magic3;
1307        } else {
1308                s = magic2;
1309        }
1310
1311        fr_sha1_init(&Context);
1312        fr_sha1_update(&Context,masterkey,16);
1313        fr_sha1_update(&Context,SHSpad1,40);
1314        fr_sha1_update(&Context,s,84);
1315        fr_sha1_update(&Context,SHSpad2,40);
1316        fr_sha1_final(digest,&Context);
1317
1318        memcpy(sesskey,digest,keylen);
1319 }
1320
1321
1322 static void mppe_chap2_get_keys128(uint8_t const *nt_hashhash,uint8_t const *nt_response,
1323                                    uint8_t *sendkey,uint8_t *recvkey)
1324 {
1325        uint8_t masterkey[16];
1326
1327        mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
1328
1329        mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
1330        mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
1331 }
1332
1333 /*
1334  *      Generate MPPE keys.
1335  */
1336 static void mppe_chap2_gen_keys128(uint8_t const *nt_hashhash,uint8_t const *response,
1337                                    uint8_t *sendkey,uint8_t *recvkey)
1338 {
1339         uint8_t enckey1[16];
1340         uint8_t enckey2[16];
1341
1342         mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
1343
1344         /*
1345          *      dictionary.microsoft defines these attributes as
1346          *      'encrypt=2'.  The functions in src/lib/radius.c will
1347          *      take care of encrypting/decrypting them as appropriate,
1348          *      so that we don't have to.
1349          */
1350         memcpy (sendkey, enckey1, 16);
1351         memcpy (recvkey, enckey2, 16);
1352 }
1353
1354
1355 /*
1356  *      mod_authorize() - authorize user if we can authenticate
1357  *      it later. Add Auth-Type attribute if present in module
1358  *      configuration (usually Auth-Type must be "MS-CHAP")
1359  */
1360 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void * instance, REQUEST *request)
1361 {
1362         rlm_mschap_t *inst = instance;
1363         VALUE_PAIR *challenge = NULL;
1364
1365         challenge = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
1366         if (!challenge) {
1367                 return RLM_MODULE_NOOP;
1368         }
1369
1370         if (!fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY) &&
1371             !fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY) &&
1372             !fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_CPW, VENDORPEC_MICROSOFT, TAG_ANY)) {
1373                 RDEBUG2("Found MS-CHAP-Challenge, but no MS-CHAP response or change-password");
1374                 return RLM_MODULE_NOOP;
1375         }
1376
1377         if (fr_pair_find_by_num(request->config, PW_AUTH_TYPE, 0, TAG_ANY)) {
1378                 RWDEBUG2("Auth-Type already set.  Not setting to MS-CHAP");
1379                 return RLM_MODULE_NOOP;
1380         }
1381
1382         RDEBUG2("Found MS-CHAP attributes.  Setting 'Auth-Type  = %s'", inst->xlat_name);
1383
1384         /*
1385          *      Set Auth-Type to MS-CHAP.  The authentication code
1386          *      will take care of turning cleartext passwords into
1387          *      NT/LM passwords.
1388          */
1389         if (!pair_make_config("Auth-Type", inst->auth_type, T_OP_EQ)) {
1390                 return RLM_MODULE_FAIL;
1391         }
1392
1393         return RLM_MODULE_OK;
1394 }
1395
1396 static rlm_rcode_t mschap_error(rlm_mschap_t *inst, REQUEST *request, unsigned char ident,
1397                                 int mschap_result, int mschap_version, VALUE_PAIR *smb_ctrl)
1398 {
1399         rlm_rcode_t     rcode = RLM_MODULE_OK;
1400         int             error = 0;
1401         int             retry = 0;
1402         char const      *message = NULL;
1403
1404         int             i;
1405         char            new_challenge[33], buffer[128];
1406         char            *p;
1407
1408         if ((mschap_result == -648) ||
1409             ((mschap_result == 0) &&
1410              (smb_ctrl && ((smb_ctrl->vp_integer & ACB_PW_EXPIRED) != 0)))) {
1411                 REDEBUG("Password has expired.  User should retry authentication");
1412                 error = 648;
1413
1414                 /*
1415                  *      A password change is NOT a retry!  We MUST have retry=0 here.
1416                  */
1417                 retry = 0;
1418                 message = "Password expired";
1419                 rcode = RLM_MODULE_REJECT;
1420
1421                 /*
1422                  *      Account is disabled.
1423                  *
1424                  *      They're found, but they don't exist, so we
1425                  *      return 'not found'.
1426                  */
1427         } else if ((mschap_result == -691) ||
1428                    (smb_ctrl && (((smb_ctrl->vp_integer & ACB_DISABLED) != 0) ||
1429                                  ((smb_ctrl->vp_integer & (ACB_NORMAL|ACB_WSTRUST)) == 0)))) {
1430                 REDEBUG("SMB-Account-Ctrl (or ntlm_auth) "
1431                         "says that the account is disabled, "
1432                         "or is not a normal or workstation trust account");
1433                 error = 691;
1434                 retry = 0;
1435                 message = "Account disabled";
1436                 rcode = RLM_MODULE_NOTFOUND;
1437
1438                 /*
1439                  *      User is locked out.
1440                  */
1441         } else if ((mschap_result == -647) ||
1442                    (smb_ctrl && ((smb_ctrl->vp_integer & ACB_AUTOLOCK) != 0))) {
1443                 REDEBUG("SMB-Account-Ctrl (or ntlm_auth) "
1444                         "says that the account is locked out");
1445                 error = 647;
1446                 retry = 0;
1447                 message = "Account locked out";
1448                 rcode = RLM_MODULE_USERLOCK;
1449
1450         } else if (mschap_result < 0) {
1451                 REDEBUG("MS-CHAP2-Response is incorrect");
1452                 error = 691;
1453                 retry = inst->allow_retry;
1454                 message = "Authentication failed";
1455                 rcode = RLM_MODULE_REJECT;
1456         }
1457
1458         if (rcode == RLM_MODULE_OK) return RLM_MODULE_OK;
1459
1460         switch (mschap_version) {
1461         case 1:
1462                 for (p = new_challenge, i = 0; i < 2; i++) p += snprintf(p, 9, "%08x", fr_rand());
1463                 snprintf(buffer, sizeof(buffer), "E=%i R=%i C=%s V=2",
1464                          error, retry, new_challenge);
1465                 break;
1466
1467         case 2:
1468                 for (p = new_challenge, i = 0; i < 4; i++) p += snprintf(p, 9, "%08x", fr_rand());
1469                 snprintf(buffer, sizeof(buffer), "E=%i R=%i C=%s V=3 M=%s",
1470                          error, retry, new_challenge, message);
1471                 break;
1472
1473         default:
1474                 rad_assert(0);
1475         }
1476         mschap_add_reply(request, ident, "MS-CHAP-Error", buffer, strlen(buffer));
1477
1478         return rcode;
1479 }
1480
1481 /*
1482  *      mod_authenticate() - authenticate user based on given
1483  *      attributes and configuration.
1484  *      We will try to find out password in configuration
1485  *      or in configured passwd file.
1486  *      If one is found we will check paraneters given by NAS.
1487  *
1488  *      If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
1489  *      one of:
1490  *              PAP:      PW_USER_PASSWORD or
1491  *              MS-CHAP:  PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
1492  *              MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
1493  *      In case of password mismatch or locked account we MAY return
1494  *      PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
1495  *      If MS-CHAP2 succeeds we MUST return
1496  *      PW_MSCHAP2_SUCCESS
1497  */
1498 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
1499 {
1500         rlm_mschap_t *inst = instance;
1501         VALUE_PAIR *challenge = NULL;
1502         VALUE_PAIR *response = NULL;
1503         VALUE_PAIR *cpw = NULL;
1504         VALUE_PAIR *password = NULL;
1505         VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
1506         VALUE_PAIR *username;
1507         uint8_t nthashhash[NT_DIGEST_LENGTH];
1508         char msch2resp[42];
1509         char const *username_string;
1510         int mschap_version = 0;
1511         int mschap_result;
1512         MSCHAP_AUTH_METHOD auth_method;
1513
1514         /*
1515          *      If we have ntlm_auth configured, use it unless told
1516          *      otherwise
1517          */
1518         auth_method = inst->method;
1519
1520         /*
1521          *      If we have an ntlm_auth configuration, then we may
1522          *      want to suppress it.
1523          */
1524         if (auth_method != AUTH_INTERNAL) {
1525                 VALUE_PAIR *vp = fr_pair_find_by_num(request->config, PW_MS_CHAP_USE_NTLM_AUTH, 0, TAG_ANY);
1526                 if (vp && vp->vp_integer == 0) auth_method = AUTH_INTERNAL;
1527         }
1528
1529         /*
1530          *      Find the SMB-Account-Ctrl attribute, or the
1531          *      SMB-Account-Ctrl-Text attribute.
1532          */
1533         smb_ctrl = fr_pair_find_by_num(request->config, PW_SMB_ACCOUNT_CTRL, 0, TAG_ANY);
1534         if (!smb_ctrl) {
1535                 password = fr_pair_find_by_num(request->config, PW_SMB_ACCOUNT_CTRL_TEXT, 0, TAG_ANY);
1536                 if (password) {
1537                         smb_ctrl = pair_make_config("SMB-Account-CTRL", "0", T_OP_SET);
1538                         if (smb_ctrl) {
1539                                 smb_ctrl->vp_integer = pdb_decode_acct_ctrl(password->vp_strvalue);
1540                         }
1541                 }
1542         }
1543
1544         /*
1545          *      We're configured to do MS-CHAP authentication.
1546          *      and account control information exists.  Enforce it.
1547          */
1548         if (smb_ctrl) {
1549                 /*
1550                  *      Password is not required.
1551                  */
1552                 if ((smb_ctrl->vp_integer & ACB_PWNOTREQ) != 0) {
1553                         RDEBUG2("SMB-Account-Ctrl says no password is required");
1554                         return RLM_MODULE_OK;
1555                 }
1556         }
1557
1558         /*
1559          *      Decide how to get the passwords.
1560          */
1561         password = fr_pair_find_by_num(request->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY);
1562
1563         /*
1564          *      We need an NT-Password.
1565          */
1566         nt_password = fr_pair_find_by_num(request->config, PW_NT_PASSWORD, 0, TAG_ANY);
1567         if (nt_password) {
1568                 VERIFY_VP(nt_password);
1569
1570                 switch (nt_password->vp_length) {
1571                 case NT_DIGEST_LENGTH:
1572                         RDEBUG2("Found NT-Password");
1573                         break;
1574
1575                 /* 0x */
1576                 case 34:
1577                 case 32:
1578                         RWDEBUG("NT-Password has not been normalized by the 'pap' module (likely still in hex format).  "
1579                                 "Authentication may fail");
1580                         nt_password = NULL;
1581                         break;
1582
1583                 default:
1584                         RWDEBUG("NT-Password found but incorrect length, expected " STRINGIFY(NT_DIGEST_LENGTH)
1585                                 " bytes got %zu bytes.  Authentication may fail", nt_password->vp_length);
1586                         nt_password = NULL;
1587                         break;
1588                 }
1589         }
1590
1591         /*
1592          *      ... or a Cleartext-Password, which we now transform into an NT-Password
1593          */
1594         if (!nt_password) {
1595                 uint8_t *p;
1596
1597                 if (password) {
1598                         RDEBUG2("Found Cleartext-Password, hashing to create NT-Password");
1599                         nt_password = pair_make_config("NT-Password", NULL, T_OP_EQ);
1600                         if (!nt_password) {
1601                                 RERROR("No memory");
1602                                 return RLM_MODULE_FAIL;
1603                         }
1604                         nt_password->vp_length = NT_DIGEST_LENGTH;
1605                         nt_password->vp_octets = p = talloc_array(nt_password, uint8_t, nt_password->vp_length);
1606
1607                         if (mschap_ntpwdhash(p, password->vp_strvalue) < 0) {
1608                                 RERROR("Failed generating NT-Password");
1609                                 return RLM_MODULE_FAIL;
1610                         }
1611                 } else if (auth_method == AUTH_INTERNAL) {
1612                         RWDEBUG2("No Cleartext-Password configured.  Cannot create NT-Password");
1613                 }
1614         }
1615
1616         /*
1617          *      Or an LM-Password.
1618          */
1619         lm_password = fr_pair_find_by_num(request->config, PW_LM_PASSWORD, 0, TAG_ANY);
1620         if (lm_password) {
1621                 VERIFY_VP(lm_password);
1622
1623                 switch (lm_password->vp_length) {
1624                 case LM_DIGEST_LENGTH:
1625                         RDEBUG2("Found LM-Password");
1626                         break;
1627
1628                 /* 0x */
1629                 case 34:
1630                 case 32:
1631                         RWDEBUG("LM-Password has not been normalized by the 'pap' module (likely still in hex format).  "
1632                                 "Authentication may fail");
1633                         lm_password = NULL;
1634                         break;
1635
1636                 default:
1637                         RWDEBUG("LM-Password found but incorrect length, expected " STRINGIFY(LM_DIGEST_LENGTH)
1638                                 " bytes got %zu bytes.  Authentication may fail", lm_password->vp_length);
1639                         lm_password = NULL;
1640                         break;
1641                 }
1642         }
1643         /*
1644          *      ... or a Cleartext-Password, which we now transform into an LM-Password
1645          */
1646         if (!lm_password) {
1647                 if (password) {
1648                         RDEBUG2("Found Cleartext-Password, hashing to create LM-Password");
1649                         lm_password = pair_make_config("LM-Password", NULL, T_OP_EQ);
1650                         if (!lm_password) {
1651                                 RERROR("No memory");
1652                         } else {
1653                                 uint8_t *p;
1654
1655                                 lm_password->vp_length = LM_DIGEST_LENGTH;
1656                                 lm_password->vp_octets = p = talloc_array(lm_password, uint8_t, lm_password->vp_length);
1657                                 smbdes_lmpwdhash(password->vp_strvalue, p);
1658                         }
1659                 /*
1660                  *      Only complain if we don't have NT-Password
1661                  */
1662                 } else if ((auth_method == AUTH_INTERNAL) && !nt_password) {
1663                         RWDEBUG2("No Cleartext-Password configured.  Cannot create LM-Password");
1664                 }
1665         }
1666
1667         cpw = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_CPW, VENDORPEC_MICROSOFT, TAG_ANY);
1668         if (cpw) {
1669                 /*
1670                  * mschap2 password change request
1671                  * we cheat - first decode and execute the passchange
1672                  * we then extract the response, add it into the request
1673                  * then jump into mschap2 auth with the chal/resp
1674                  */
1675                 uint8_t         new_nt_encrypted[516], old_nt_encrypted[NT_DIGEST_LENGTH];
1676                 VALUE_PAIR      *nt_enc=NULL;
1677                 int             seq, new_nt_enc_len;
1678                 uint8_t         *p;
1679
1680                 RDEBUG("MS-CHAPv2 password change request received");
1681
1682                 if (cpw->vp_length != 68) {
1683                         REDEBUG("MS-CHAP2-CPW has the wrong format: length %zu != 68", cpw->vp_length);
1684                         return RLM_MODULE_INVALID;
1685                 }
1686
1687                 if (cpw->vp_octets[0] != 7) {
1688                         REDEBUG("MS-CHAP2-CPW has the wrong format: code %d != 7", cpw->vp_octets[0]);
1689                         return RLM_MODULE_INVALID;
1690                 }
1691
1692                 /*
1693                  *  look for the new (encrypted) password
1694                  *  bah stupid composite attributes
1695                  *  we're expecting 3 attributes with the leading bytes
1696                  *  06:<mschapid>:00:01:<1st chunk>
1697                  *  06:<mschapid>:00:02:<2nd chunk>
1698                  *  06:<mschapid>:00:03:<3rd chunk>
1699                  */
1700                 new_nt_enc_len = 0;
1701                 for (seq = 1; seq < 4; seq++) {
1702                         vp_cursor_t cursor;
1703                         int found = 0;
1704
1705                         for (nt_enc = fr_cursor_init(&cursor, &request->packet->vps);
1706                              nt_enc;
1707                              nt_enc = fr_cursor_next(&cursor)) {
1708                                 if (nt_enc->da->vendor != VENDORPEC_MICROSOFT)
1709                                         continue;
1710
1711                                 if (nt_enc->da->attr != PW_MSCHAP_NT_ENC_PW)
1712                                         continue;
1713
1714                                 if (nt_enc->vp_length < 4) {
1715                                         REDEBUG("MS-CHAP-NT-Enc-PW with invalid format");
1716                                         return RLM_MODULE_INVALID;
1717                                 }
1718
1719                                 if (nt_enc->vp_octets[0] != 6) {
1720                                         REDEBUG("MS-CHAP-NT-Enc-PW with invalid format");
1721                                         return RLM_MODULE_INVALID;
1722                                 }
1723
1724                                 if ((nt_enc->vp_octets[2] == 0) && (nt_enc->vp_octets[3] == seq)) {
1725                                         found = 1;
1726                                         break;
1727                                 }
1728                         }
1729
1730                         if (!found) {
1731                                 REDEBUG("Could not find MS-CHAP-NT-Enc-PW w/ sequence number %d", seq);
1732                                 return RLM_MODULE_INVALID;
1733                         }
1734
1735                         if ((new_nt_enc_len + nt_enc->vp_length - 4) > sizeof(new_nt_encrypted)) {
1736                                 REDEBUG("Unpacked MS-CHAP-NT-Enc-PW length > 516");
1737                                 return RLM_MODULE_INVALID;
1738                         }
1739
1740                         memcpy(new_nt_encrypted + new_nt_enc_len, nt_enc->vp_octets + 4, nt_enc->vp_length - 4);
1741                         new_nt_enc_len += nt_enc->vp_length - 4;
1742                 }
1743
1744                 if (new_nt_enc_len != 516) {
1745                         REDEBUG("Unpacked MS-CHAP-NT-Enc-PW length != 516");
1746                         return RLM_MODULE_INVALID;
1747                 }
1748
1749                 /*
1750                  * RFC 2548 is confusing here
1751                  * it claims:
1752                  *
1753                  * 1 byte code
1754                  * 1 byte ident
1755                  * 16 octets - old hash encrypted with new hash
1756                  * 24 octets - peer challenge
1757                  *   this is actually:
1758                  *   16 octets - peer challenge
1759                  *    8 octets - reserved
1760                  * 24 octets - nt response
1761                  * 2 octets  - flags (ignored)
1762                  */
1763
1764                 memcpy(old_nt_encrypted, cpw->vp_octets + 2, sizeof(old_nt_encrypted));
1765
1766                 RDEBUG2("Password change payload valid");
1767
1768                 /* perform the actual password change */
1769                 if (do_mschap_cpw(inst, request, nt_password, new_nt_encrypted, old_nt_encrypted, auth_method) < 0) {
1770                         char buffer[128];
1771
1772                         REDEBUG("Password change failed");
1773
1774                         snprintf(buffer, sizeof(buffer), "E=709 R=0 M=Password change failed");
1775                         mschap_add_reply(request, cpw->vp_octets[1], "MS-CHAP-Error", buffer, strlen(buffer));
1776
1777                         return RLM_MODULE_REJECT;
1778                 }
1779                 RDEBUG("Password change successful");
1780
1781                 /*
1782                  *  Clear any expiry bit so the user can now login;
1783                  *  obviously the password change action will need
1784                  *  to have cleared this bit in the config/SQL/wherever
1785                  */
1786                 if (smb_ctrl && smb_ctrl->vp_integer & ACB_PW_EXPIRED) {
1787                         RDEBUG("Clearing expiry bit in SMB-Acct-Ctrl to allow authentication");
1788                         smb_ctrl->vp_integer &= ~ACB_PW_EXPIRED;
1789                 }
1790
1791                 /*
1792                  *  Extract the challenge & response from the end of the password
1793                  *  change, add them into the request and then continue with
1794                  *  the authentication
1795                  */
1796                 response = radius_pair_create(request->packet, &request->packet->vps,
1797                                              PW_MSCHAP2_RESPONSE,
1798                                              VENDORPEC_MICROSOFT);
1799                 response->vp_length = 50;
1800                 response->vp_octets = p = talloc_array(response, uint8_t, response->vp_length);
1801
1802                 /* ident & flags */
1803                 p[0] = cpw->vp_octets[1];
1804                 p[1] = 0;
1805                 /* peer challenge and client NT response */
1806                 memcpy(p + 2, cpw->vp_octets + 18, 48);
1807         }
1808
1809         challenge = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
1810         if (!challenge) {
1811                 REDEBUG("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1812                 return RLM_MODULE_REJECT;
1813         }
1814
1815         /*
1816          *      We also require an MS-CHAP-Response.
1817          */
1818         response = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
1819
1820         /*
1821          *      MS-CHAP-Response, means MS-CHAPv1
1822          */
1823         if (response) {
1824                 int             offset;
1825                 rlm_rcode_t     rcode;
1826                 mschap_version = 1;
1827
1828                 /*
1829                  *      MS-CHAPv1 challenges are 8 octets.
1830                  */
1831                 if (challenge->vp_length < 8) {
1832                         REDEBUG("MS-CHAP-Challenge has the wrong format");
1833                         return RLM_MODULE_INVALID;
1834                 }
1835
1836                 /*
1837                  *      Responses are 50 octets.
1838                  */
1839                 if (response->vp_length < 50) {
1840                         REDEBUG("MS-CHAP-Response has the wrong format");
1841                         return RLM_MODULE_INVALID;
1842                 }
1843
1844                 /*
1845                  *      We are doing MS-CHAP.  Calculate the MS-CHAP
1846                  *      response
1847                  */
1848                 if (response->vp_octets[1] & 0x01) {
1849                         RDEBUG2("Client is using MS-CHAPv1 with NT-Password");
1850                         password = nt_password;
1851                         offset = 26;
1852                 } else {
1853                         RDEBUG2("Client is using MS-CHAPv1 with LM-Password");
1854                         password = lm_password;
1855                         offset = 2;
1856                 }
1857
1858                 /*
1859                  *      Do the MS-CHAP authentication.
1860                  */
1861                 mschap_result = do_mschap(inst, request, password, challenge->vp_octets,
1862                                           response->vp_octets + offset, nthashhash, auth_method);
1863                 /*
1864                  *      Check for errors, and add MSCHAP-Error if necessary.
1865                  */
1866                 rcode = mschap_error(inst, request, *response->vp_octets,
1867                                      mschap_result, mschap_version, smb_ctrl);
1868                 if (rcode != RLM_MODULE_OK) return rcode;
1869         } else if ((response = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_RESPONSE,
1870                                                    VENDORPEC_MICROSOFT, TAG_ANY)) != NULL) {
1871                 uint8_t         mschapv1_challenge[16];
1872                 VALUE_PAIR      *name_attr, *response_name;
1873                 rlm_rcode_t     rcode;
1874
1875                 mschap_version = 2;
1876
1877                 /*
1878                  *      MS-CHAPv2 challenges are 16 octets.
1879                  */
1880                 if (challenge->vp_length < 16) {
1881                         REDEBUG("MS-CHAP-Challenge has the wrong format");
1882                         return RLM_MODULE_INVALID;
1883                 }
1884
1885                 /*
1886                  *      Responses are 50 octets.
1887                  */
1888                 if (response->vp_length < 50) {
1889                         REDEBUG("MS-CHAP-Response has the wrong format");
1890                         return RLM_MODULE_INVALID;
1891                 }
1892
1893                 /*
1894                  *      We also require a User-Name
1895                  */
1896                 username = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
1897                 if (!username) {
1898                         REDEBUG("We require a User-Name for MS-CHAPv2");
1899                         return RLM_MODULE_INVALID;
1900                 }
1901
1902                 /*
1903                  *      Check for MS-CHAP-User-Name and if found, use it
1904                  *      to construct the MSCHAPv1 challenge.  This is
1905                  *      set by rlm_eap_mschap to the MS-CHAP Response
1906                  *      packet Name field.
1907                  *
1908                  *      We prefer this to the User-Name in the
1909                  *      packet.
1910                  */
1911                 response_name = fr_pair_find_by_num(request->packet->vps, PW_MS_CHAP_USER_NAME, 0, TAG_ANY);
1912                 if (response_name) {
1913                         name_attr = response_name;
1914                 } else {
1915                         name_attr = username;
1916                 }
1917
1918                 /*
1919                  *      with_ntdomain_hack moved here, too.
1920                  */
1921                 if ((username_string = strchr(name_attr->vp_strvalue, '\\')) != NULL) {
1922                         if (inst->with_ntdomain_hack) {
1923                                 username_string++;
1924                         } else {
1925                                 RWDEBUG2("NT Domain delimeter found, should with_ntdomain_hack of been enabled?");
1926                                 username_string = name_attr->vp_strvalue;
1927                         }
1928                 } else {
1929                         username_string = name_attr->vp_strvalue;
1930                 }
1931
1932                 if (response_name && ((username->vp_length != response_name->vp_length) ||
1933                     (strncasecmp(username->vp_strvalue, response_name->vp_strvalue, username->vp_length) != 0))) {
1934                         RWDEBUG("User-Name (%s) is not the same as MS-CHAP Name (%s) from EAP-MSCHAPv2",
1935                                 username->vp_strvalue, response_name->vp_strvalue);
1936                 }
1937
1938 #ifdef __APPLE__
1939                 /*
1940                  *  No "known good" NT-Password attribute.  Try to do
1941                  *  OpenDirectory authentication.
1942                  *
1943                  *  If OD determines the user is an AD user it will return noop, which
1944                  *  indicates the auth process should continue directly to AD.
1945                  *  Otherwise OD will determine auth success/fail.
1946                  */
1947                 if (!nt_password && inst->open_directory) {
1948                         RDEBUG2("No NT-Password configured. Trying OpenDirectory Authentication");
1949                         int odStatus = od_mschap_auth(request, challenge, username);
1950                         if (odStatus != RLM_MODULE_NOOP) {
1951                                 return odStatus;
1952                         }
1953                 }
1954 #endif
1955                 /*
1956                  *      The old "mschapv2" function has been moved to
1957                  *      here.
1958                  *
1959                  *      MS-CHAPv2 takes some additional data to create an
1960                  *      MS-CHAPv1 challenge, and then does MS-CHAPv1.
1961                  */
1962                 RDEBUG2("Creating challenge hash with username: %s", username_string);
1963                 mschap_challenge_hash(response->vp_octets + 2,  /* peer challenge */
1964                                       challenge->vp_octets,     /* our challenge */
1965                                       username_string,          /* user name */
1966                                       mschapv1_challenge);      /* resulting challenge */
1967
1968                 RDEBUG2("Client is using MS-CHAPv2");
1969                 mschap_result = do_mschap(inst, request, nt_password, mschapv1_challenge,
1970                                           response->vp_octets + 26, nthashhash, auth_method);
1971                 rcode = mschap_error(inst, request, *response->vp_octets,
1972                                      mschap_result, mschap_version, smb_ctrl);
1973                 if (rcode != RLM_MODULE_OK) return rcode;
1974
1975 #ifdef WITH_AUTH_WINBIND
1976                 if (inst->wb_retry_with_normalised_username) {
1977                         if ((response_name = fr_pair_find_by_num(request->packet->vps, PW_MS_CHAP_USER_NAME, 0, TAG_ANY))) {
1978                                 if (strcmp(username_string, response_name->vp_strvalue)) {
1979                                         RDEBUG2("Changing username %s to %s", username_string, response_name->vp_strvalue);
1980                                         username_string = response_name->vp_strvalue;
1981                                 }
1982                         }
1983                 }
1984 #endif
1985
1986                 mschap_auth_response(username_string,           /* without the domain */
1987                                      nthashhash,                /* nt-hash-hash */
1988                                      response->vp_octets + 26,  /* peer response */
1989                                      response->vp_octets + 2,   /* peer challenge */
1990                                      challenge->vp_octets,      /* our challenge */
1991                                      msch2resp);                /* calculated MPPE key */
1992                 mschap_add_reply(request, *response->vp_octets, "MS-CHAP2-Success", msch2resp, 42);
1993
1994         } else {                /* Neither CHAPv1 or CHAPv2 response: die */
1995                 REDEBUG("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1996                 return RLM_MODULE_INVALID;
1997         }
1998
1999         /* now create MPPE attributes */
2000         if (inst->use_mppe) {
2001                 uint8_t mppe_sendkey[34];
2002                 uint8_t mppe_recvkey[34];
2003
2004                 if (mschap_version == 1) {
2005                         RDEBUG2("adding MS-CHAPv1 MPPE keys");
2006                         memset(mppe_sendkey, 0, 32);
2007                         if (lm_password) {
2008                                 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
2009                         }
2010
2011                         /*
2012                          *      According to RFC 2548 we
2013                          *      should send NT hash.  But in
2014                          *      practice it doesn't work.
2015                          *      Instead, we should send nthashhash
2016                          *
2017                          *      This is an error in RFC 2548.
2018                          */
2019                         /*
2020                          *      do_mschap cares to zero nthashhash if NT hash
2021                          *      is not available.
2022                          */
2023                         memcpy(mppe_sendkey + 8, nthashhash, NT_DIGEST_LENGTH);
2024                         mppe_add_reply(request, "MS-CHAP-MPPE-Keys", mppe_sendkey, 24);
2025
2026                 } else if (mschap_version == 2) {
2027                         RDEBUG2("Adding MS-CHAPv2 MPPE keys");
2028                         mppe_chap2_gen_keys128(nthashhash, response->vp_octets + 26, mppe_sendkey, mppe_recvkey);
2029
2030                         mppe_add_reply(request, "MS-MPPE-Recv-Key", mppe_recvkey, 16);
2031                         mppe_add_reply(request, "MS-MPPE-Send-Key", mppe_sendkey, 16);
2032
2033                 }
2034                 pair_make_reply("MS-MPPE-Encryption-Policy",
2035                                (inst->require_encryption) ? "0x00000002":"0x00000001", T_OP_EQ);
2036                 pair_make_reply("MS-MPPE-Encryption-Types",
2037                                (inst->require_strong) ? "0x00000004":"0x00000006", T_OP_EQ);
2038         } /* else we weren't asked to use MPPE */
2039
2040         return RLM_MODULE_OK;
2041 #undef inst
2042 }
2043
2044 extern module_t rlm_mschap;
2045 module_t rlm_mschap = {
2046         .magic          = RLM_MODULE_INIT,
2047         .name           = "mschap",
2048         .type           = 0,
2049         .inst_size      = sizeof(rlm_mschap_t),
2050         .config         = module_config,
2051         .bootstrap      = mod_bootstrap,
2052         .instantiate    = mod_instantiate,
2053         .detach         = mod_detach,
2054         .methods = {
2055                 [MOD_AUTHENTICATE]      = mod_authenticate,
2056                 [MOD_AUTHORIZE]         = mod_authorize
2057         },
2058 };