Indentation fix in rlm_mschap.c
[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                         if (strcasestr(buffer, "Account locked out") ||
1167                             strcasestr(buffer, "0xC0000234")) {
1168                                 REDEBUG2("%s", buffer);
1169                                 return -647;
1170                         }
1171
1172                         if (strcasestr(buffer, "Account disabled") ||
1173                             strcasestr(buffer, "0xC0000072")) {
1174                                 REDEBUG2("%s", buffer);
1175                                 return -691;
1176                         }
1177
1178                         RDEBUG2("External script failed");
1179                         p = strchr(buffer, '\n');
1180                         if (p) *p = '\0';
1181
1182                         REDEBUG("External script says: %s", buffer);
1183                         return -1;
1184                 }
1185
1186                 /*
1187                  *      Parse the answer as an nthashhash.
1188                  *
1189                  *      ntlm_auth currently returns:
1190                  *      NT_KEY: 000102030405060708090a0b0c0d0e0f
1191                  */
1192                 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
1193                         REDEBUG("Invalid output from ntlm_auth: expecting 'NT_KEY: ' prefix");
1194                         return -1;
1195                 }
1196
1197                 /*
1198                  *      Check the length.  It should be at least 32, with an LF at the end.
1199                  */
1200                 len = strlen(buffer + 8);
1201                 if (len < 32) {
1202                         REDEBUG2("Invalid output from ntlm_auth: NT_KEY too short, expected 32 bytes got %zu bytes",
1203                                  len);
1204
1205                         return -1;
1206                 }
1207
1208                 /*
1209                  *      Update the NT hash hash, from the NT key.
1210                  */
1211                 if (fr_hex2bin(nthashhash, NT_DIGEST_LENGTH, buffer + 8, len) != NT_DIGEST_LENGTH) {
1212                         REDEBUG("Invalid output from ntlm_auth: NT_KEY has non-hex values");
1213                         return -1;
1214                 }
1215                 break;
1216         }
1217
1218 #ifdef WITH_AUTH_WINBIND
1219                 /*
1220                  *      Process auth via the wbclient library
1221                  */
1222         case AUTH_WBCLIENT:
1223                 return do_auth_wbclient(inst, request, challenge, response, nthashhash);
1224 #endif
1225
1226                 /* We should never reach this line */
1227         default:
1228                 RERROR("Internal error: Unknown mschap auth method (%d)", method);
1229                 return -1;
1230         }
1231
1232         return 0;
1233 }
1234
1235
1236 /*
1237  *      Data for the hashes.
1238  */
1239 static const uint8_t SHSpad1[40] =
1240                { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
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
1245 static const uint8_t SHSpad2[40] =
1246                { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
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
1251 static const uint8_t magic1[27] =
1252                { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
1253                  0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
1254                  0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
1255
1256 static const uint8_t magic2[84] =
1257                { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
1258                  0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
1259                  0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1260                  0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
1261                  0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
1262                  0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
1263                  0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1264                  0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
1265                  0x6b, 0x65, 0x79, 0x2e };
1266
1267 static const uint8_t magic3[84] =
1268                { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
1269                  0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
1270                  0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1271                  0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
1272                  0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
1273                  0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
1274                  0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
1275                  0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
1276                  0x6b, 0x65, 0x79, 0x2e };
1277
1278
1279 static void mppe_GetMasterKey(uint8_t const *nt_hashhash,uint8_t const *nt_response,
1280                               uint8_t *masterkey)
1281 {
1282        uint8_t digest[20];
1283        fr_sha1_ctx Context;
1284
1285        fr_sha1_init(&Context);
1286        fr_sha1_update(&Context,nt_hashhash,NT_DIGEST_LENGTH);
1287        fr_sha1_update(&Context,nt_response,24);
1288        fr_sha1_update(&Context,magic1,27);
1289        fr_sha1_final(digest,&Context);
1290
1291        memcpy(masterkey,digest,16);
1292 }
1293
1294
1295 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
1296                                        int keylen,int issend)
1297 {
1298        uint8_t digest[20];
1299        const uint8_t *s;
1300        fr_sha1_ctx Context;
1301
1302        memset(digest,0,20);
1303
1304        if(issend) {
1305                s = magic3;
1306        } else {
1307                s = magic2;
1308        }
1309
1310        fr_sha1_init(&Context);
1311        fr_sha1_update(&Context,masterkey,16);
1312        fr_sha1_update(&Context,SHSpad1,40);
1313        fr_sha1_update(&Context,s,84);
1314        fr_sha1_update(&Context,SHSpad2,40);
1315        fr_sha1_final(digest,&Context);
1316
1317        memcpy(sesskey,digest,keylen);
1318 }
1319
1320
1321 static void mppe_chap2_get_keys128(uint8_t const *nt_hashhash,uint8_t const *nt_response,
1322                                    uint8_t *sendkey,uint8_t *recvkey)
1323 {
1324        uint8_t masterkey[16];
1325
1326        mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
1327
1328        mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
1329        mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
1330 }
1331
1332 /*
1333  *      Generate MPPE keys.
1334  */
1335 static void mppe_chap2_gen_keys128(uint8_t const *nt_hashhash,uint8_t const *response,
1336                                    uint8_t *sendkey,uint8_t *recvkey)
1337 {
1338         uint8_t enckey1[16];
1339         uint8_t enckey2[16];
1340
1341         mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
1342
1343         /*
1344          *      dictionary.microsoft defines these attributes as
1345          *      'encrypt=2'.  The functions in src/lib/radius.c will
1346          *      take care of encrypting/decrypting them as appropriate,
1347          *      so that we don't have to.
1348          */
1349         memcpy (sendkey, enckey1, 16);
1350         memcpy (recvkey, enckey2, 16);
1351 }
1352
1353
1354 /*
1355  *      mod_authorize() - authorize user if we can authenticate
1356  *      it later. Add Auth-Type attribute if present in module
1357  *      configuration (usually Auth-Type must be "MS-CHAP")
1358  */
1359 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void * instance, REQUEST *request)
1360 {
1361         rlm_mschap_t *inst = instance;
1362         VALUE_PAIR *challenge = NULL;
1363
1364         challenge = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
1365         if (!challenge) {
1366                 return RLM_MODULE_NOOP;
1367         }
1368
1369         if (!fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY) &&
1370             !fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY) &&
1371             !fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_CPW, VENDORPEC_MICROSOFT, TAG_ANY)) {
1372                 RDEBUG2("Found MS-CHAP-Challenge, but no MS-CHAP response or change-password");
1373                 return RLM_MODULE_NOOP;
1374         }
1375
1376         if (fr_pair_find_by_num(request->config, PW_AUTH_TYPE, 0, TAG_ANY)) {
1377                 RWDEBUG2("Auth-Type already set.  Not setting to MS-CHAP");
1378                 return RLM_MODULE_NOOP;
1379         }
1380
1381         RDEBUG2("Found MS-CHAP attributes.  Setting 'Auth-Type  = %s'", inst->xlat_name);
1382
1383         /*
1384          *      Set Auth-Type to MS-CHAP.  The authentication code
1385          *      will take care of turning cleartext passwords into
1386          *      NT/LM passwords.
1387          */
1388         if (!pair_make_config("Auth-Type", inst->auth_type, T_OP_EQ)) {
1389                 return RLM_MODULE_FAIL;
1390         }
1391
1392         return RLM_MODULE_OK;
1393 }
1394
1395 static rlm_rcode_t mschap_error(rlm_mschap_t *inst, REQUEST *request, unsigned char ident,
1396                                 int mschap_result, int mschap_version, VALUE_PAIR *smb_ctrl)
1397 {
1398         rlm_rcode_t     rcode = RLM_MODULE_OK;
1399         int             error = 0;
1400         int             retry = 0;
1401         char const      *message = NULL;
1402
1403         int             i;
1404         char            new_challenge[33], buffer[128];
1405         char            *p;
1406
1407         if ((mschap_result == -648) ||
1408             ((mschap_result == 0) &&
1409              (smb_ctrl && ((smb_ctrl->vp_integer & ACB_PW_EXPIRED) != 0)))) {
1410                 REDEBUG("Password has expired.  User should retry authentication");
1411                 error = 648;
1412
1413                 /*
1414                  *      A password change is NOT a retry!  We MUST have retry=0 here.
1415                  */
1416                 retry = 0;
1417                 message = "Password expired";
1418                 rcode = RLM_MODULE_REJECT;
1419
1420                 /*
1421                  *      Account is disabled.
1422                  *
1423                  *      They're found, but they don't exist, so we
1424                  *      return 'not found'.
1425                  */
1426         } else if ((mschap_result == -691) ||
1427                    (smb_ctrl && (((smb_ctrl->vp_integer & ACB_DISABLED) != 0) ||
1428                                  ((smb_ctrl->vp_integer & (ACB_NORMAL|ACB_WSTRUST)) == 0)))) {
1429                 REDEBUG("SMB-Account-Ctrl (or ntlm_auth) "
1430                         "says that the account is disabled, "
1431                         "or is not a normal or workstation trust account");
1432                 error = 691;
1433                 retry = 0;
1434                 message = "Account disabled";
1435                 rcode = RLM_MODULE_NOTFOUND;
1436
1437                 /*
1438                  *      User is locked out.
1439                  */
1440         } else if ((mschap_result == -647) ||
1441                    (smb_ctrl && ((smb_ctrl->vp_integer & ACB_AUTOLOCK) != 0))) {
1442                 REDEBUG("SMB-Account-Ctrl (or ntlm_auth) "
1443                         "says that the account is locked out");
1444                 error = 647;
1445                 retry = 0;
1446                 message = "Account locked out";
1447                 rcode = RLM_MODULE_USERLOCK;
1448
1449         } else if (mschap_result < 0) {
1450                 REDEBUG("MS-CHAP2-Response is incorrect");
1451                 error = 691;
1452                 retry = inst->allow_retry;
1453                 message = "Authentication failed";
1454                 rcode = RLM_MODULE_REJECT;
1455         }
1456
1457         if (rcode == RLM_MODULE_OK) return RLM_MODULE_OK;
1458
1459         switch (mschap_version) {
1460         case 1:
1461                 for (p = new_challenge, i = 0; i < 2; i++) p += snprintf(p, 9, "%08x", fr_rand());
1462                 snprintf(buffer, sizeof(buffer), "E=%i R=%i C=%s V=2",
1463                          error, retry, new_challenge);
1464                 break;
1465
1466         case 2:
1467                 for (p = new_challenge, i = 0; i < 4; i++) p += snprintf(p, 9, "%08x", fr_rand());
1468                 snprintf(buffer, sizeof(buffer), "E=%i R=%i C=%s V=3 M=%s",
1469                          error, retry, new_challenge, message);
1470                 break;
1471
1472         default:
1473                 rad_assert(0);
1474         }
1475         mschap_add_reply(request, ident, "MS-CHAP-Error", buffer, strlen(buffer));
1476
1477         return rcode;
1478 }
1479
1480 /*
1481  *      mod_authenticate() - authenticate user based on given
1482  *      attributes and configuration.
1483  *      We will try to find out password in configuration
1484  *      or in configured passwd file.
1485  *      If one is found we will check paraneters given by NAS.
1486  *
1487  *      If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
1488  *      one of:
1489  *              PAP:      PW_USER_PASSWORD or
1490  *              MS-CHAP:  PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
1491  *              MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
1492  *      In case of password mismatch or locked account we MAY return
1493  *      PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
1494  *      If MS-CHAP2 succeeds we MUST return
1495  *      PW_MSCHAP2_SUCCESS
1496  */
1497 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
1498 {
1499         rlm_mschap_t *inst = instance;
1500         VALUE_PAIR *challenge = NULL;
1501         VALUE_PAIR *response = NULL;
1502         VALUE_PAIR *cpw = NULL;
1503         VALUE_PAIR *password = NULL;
1504         VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
1505         VALUE_PAIR *username;
1506         uint8_t nthashhash[NT_DIGEST_LENGTH];
1507         char msch2resp[42];
1508         char const *username_string;
1509         int mschap_version = 0;
1510         int mschap_result;
1511         MSCHAP_AUTH_METHOD auth_method;
1512
1513         /*
1514          *      If we have ntlm_auth configured, use it unless told
1515          *      otherwise
1516          */
1517         auth_method = inst->method;
1518
1519         /*
1520          *      If we have an ntlm_auth configuration, then we may
1521          *      want to suppress it.
1522          */
1523         if (auth_method != AUTH_INTERNAL) {
1524                 VALUE_PAIR *vp = fr_pair_find_by_num(request->config, PW_MS_CHAP_USE_NTLM_AUTH, 0, TAG_ANY);
1525                 if (vp && vp->vp_integer == 0) auth_method = AUTH_INTERNAL;
1526         }
1527
1528         /*
1529          *      Find the SMB-Account-Ctrl attribute, or the
1530          *      SMB-Account-Ctrl-Text attribute.
1531          */
1532         smb_ctrl = fr_pair_find_by_num(request->config, PW_SMB_ACCOUNT_CTRL, 0, TAG_ANY);
1533         if (!smb_ctrl) {
1534                 password = fr_pair_find_by_num(request->config, PW_SMB_ACCOUNT_CTRL_TEXT, 0, TAG_ANY);
1535                 if (password) {
1536                         smb_ctrl = pair_make_config("SMB-Account-CTRL", "0", T_OP_SET);
1537                         if (smb_ctrl) {
1538                                 smb_ctrl->vp_integer = pdb_decode_acct_ctrl(password->vp_strvalue);
1539                         }
1540                 }
1541         }
1542
1543         /*
1544          *      We're configured to do MS-CHAP authentication.
1545          *      and account control information exists.  Enforce it.
1546          */
1547         if (smb_ctrl) {
1548                 /*
1549                  *      Password is not required.
1550                  */
1551                 if ((smb_ctrl->vp_integer & ACB_PWNOTREQ) != 0) {
1552                         RDEBUG2("SMB-Account-Ctrl says no password is required");
1553                         return RLM_MODULE_OK;
1554                 }
1555         }
1556
1557         /*
1558          *      Decide how to get the passwords.
1559          */
1560         password = fr_pair_find_by_num(request->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY);
1561
1562         /*
1563          *      We need an NT-Password.
1564          */
1565         nt_password = fr_pair_find_by_num(request->config, PW_NT_PASSWORD, 0, TAG_ANY);
1566         if (nt_password) {
1567                 VERIFY_VP(nt_password);
1568
1569                 switch (nt_password->vp_length) {
1570                 case NT_DIGEST_LENGTH:
1571                         RDEBUG2("Found NT-Password");
1572                         break;
1573
1574                 /* 0x */
1575                 case 34:
1576                 case 32:
1577                         RWDEBUG("NT-Password has not been normalized by the 'pap' module (likely still in hex format).  "
1578                                 "Authentication may fail");
1579                         nt_password = NULL;
1580                         break;
1581
1582                 default:
1583                         RWDEBUG("NT-Password found but incorrect length, expected " STRINGIFY(NT_DIGEST_LENGTH)
1584                                 " bytes got %zu bytes.  Authentication may fail", nt_password->vp_length);
1585                         nt_password = NULL;
1586                         break;
1587                 }
1588         }
1589
1590         /*
1591          *      ... or a Cleartext-Password, which we now transform into an NT-Password
1592          */
1593         if (!nt_password) {
1594                 uint8_t *p;
1595
1596                 if (password) {
1597                         RDEBUG2("Found Cleartext-Password, hashing to create NT-Password");
1598                         nt_password = pair_make_config("NT-Password", NULL, T_OP_EQ);
1599                         if (!nt_password) {
1600                                 RERROR("No memory");
1601                                 return RLM_MODULE_FAIL;
1602                         }
1603                         nt_password->vp_length = NT_DIGEST_LENGTH;
1604                         nt_password->vp_octets = p = talloc_array(nt_password, uint8_t, nt_password->vp_length);
1605
1606                         if (mschap_ntpwdhash(p, password->vp_strvalue) < 0) {
1607                                 RERROR("Failed generating NT-Password");
1608                                 return RLM_MODULE_FAIL;
1609                         }
1610                 } else if (auth_method == AUTH_INTERNAL) {
1611                         RWDEBUG2("No Cleartext-Password configured.  Cannot create NT-Password");
1612                 }
1613         }
1614
1615         /*
1616          *      Or an LM-Password.
1617          */
1618         lm_password = fr_pair_find_by_num(request->config, PW_LM_PASSWORD, 0, TAG_ANY);
1619         if (lm_password) {
1620                 VERIFY_VP(lm_password);
1621
1622                 switch (lm_password->vp_length) {
1623                 case LM_DIGEST_LENGTH:
1624                         RDEBUG2("Found LM-Password");
1625                         break;
1626
1627                 /* 0x */
1628                 case 34:
1629                 case 32:
1630                         RWDEBUG("LM-Password has not been normalized by the 'pap' module (likely still in hex format).  "
1631                                 "Authentication may fail");
1632                         lm_password = NULL;
1633                         break;
1634
1635                 default:
1636                         RWDEBUG("LM-Password found but incorrect length, expected " STRINGIFY(LM_DIGEST_LENGTH)
1637                                 " bytes got %zu bytes.  Authentication may fail", lm_password->vp_length);
1638                         lm_password = NULL;
1639                         break;
1640                 }
1641         }
1642         /*
1643          *      ... or a Cleartext-Password, which we now transform into an LM-Password
1644          */
1645         if (!lm_password) {
1646                 if (password) {
1647                         RDEBUG2("Found Cleartext-Password, hashing to create LM-Password");
1648                         lm_password = pair_make_config("LM-Password", NULL, T_OP_EQ);
1649                         if (!lm_password) {
1650                                 RERROR("No memory");
1651                         } else {
1652                                 uint8_t *p;
1653
1654                                 lm_password->vp_length = LM_DIGEST_LENGTH;
1655                                 lm_password->vp_octets = p = talloc_array(lm_password, uint8_t, lm_password->vp_length);
1656                                 smbdes_lmpwdhash(password->vp_strvalue, p);
1657                         }
1658                 /*
1659                  *      Only complain if we don't have NT-Password
1660                  */
1661                 } else if ((auth_method == AUTH_INTERNAL) && !nt_password) {
1662                         RWDEBUG2("No Cleartext-Password configured.  Cannot create LM-Password");
1663                 }
1664         }
1665
1666         cpw = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_CPW, VENDORPEC_MICROSOFT, TAG_ANY);
1667         if (cpw) {
1668                 /*
1669                  * mschap2 password change request
1670                  * we cheat - first decode and execute the passchange
1671                  * we then extract the response, add it into the request
1672                  * then jump into mschap2 auth with the chal/resp
1673                  */
1674                 uint8_t         new_nt_encrypted[516], old_nt_encrypted[NT_DIGEST_LENGTH];
1675                 VALUE_PAIR      *nt_enc=NULL;
1676                 int             seq, new_nt_enc_len;
1677                 uint8_t         *p;
1678
1679                 RDEBUG("MS-CHAPv2 password change request received");
1680
1681                 if (cpw->vp_length != 68) {
1682                         REDEBUG("MS-CHAP2-CPW has the wrong format: length %zu != 68", cpw->vp_length);
1683                         return RLM_MODULE_INVALID;
1684                 }
1685
1686                 if (cpw->vp_octets[0] != 7) {
1687                         REDEBUG("MS-CHAP2-CPW has the wrong format: code %d != 7", cpw->vp_octets[0]);
1688                         return RLM_MODULE_INVALID;
1689                 }
1690
1691                 /*
1692                  *  look for the new (encrypted) password
1693                  *  bah stupid composite attributes
1694                  *  we're expecting 3 attributes with the leading bytes
1695                  *  06:<mschapid>:00:01:<1st chunk>
1696                  *  06:<mschapid>:00:02:<2nd chunk>
1697                  *  06:<mschapid>:00:03:<3rd chunk>
1698                  */
1699                 new_nt_enc_len = 0;
1700                 for (seq = 1; seq < 4; seq++) {
1701                         vp_cursor_t cursor;
1702                         int found = 0;
1703
1704                         for (nt_enc = fr_cursor_init(&cursor, &request->packet->vps);
1705                              nt_enc;
1706                              nt_enc = fr_cursor_next(&cursor)) {
1707                                 if (nt_enc->da->vendor != VENDORPEC_MICROSOFT)
1708                                         continue;
1709
1710                                 if (nt_enc->da->attr != PW_MSCHAP_NT_ENC_PW)
1711                                         continue;
1712
1713                                 if (nt_enc->vp_length < 4) {
1714                                         REDEBUG("MS-CHAP-NT-Enc-PW with invalid format");
1715                                         return RLM_MODULE_INVALID;
1716                                 }
1717
1718                                 if (nt_enc->vp_octets[0] != 6) {
1719                                         REDEBUG("MS-CHAP-NT-Enc-PW with invalid format");
1720                                         return RLM_MODULE_INVALID;
1721                                 }
1722
1723                                 if ((nt_enc->vp_octets[2] == 0) && (nt_enc->vp_octets[3] == seq)) {
1724                                         found = 1;
1725                                         break;
1726                                 }
1727                         }
1728
1729                         if (!found) {
1730                                 REDEBUG("Could not find MS-CHAP-NT-Enc-PW w/ sequence number %d", seq);
1731                                 return RLM_MODULE_INVALID;
1732                         }
1733
1734                         if ((new_nt_enc_len + nt_enc->vp_length - 4) > sizeof(new_nt_encrypted)) {
1735                                 REDEBUG("Unpacked MS-CHAP-NT-Enc-PW length > 516");
1736                                 return RLM_MODULE_INVALID;
1737                         }
1738
1739                         memcpy(new_nt_encrypted + new_nt_enc_len, nt_enc->vp_octets + 4, nt_enc->vp_length - 4);
1740                         new_nt_enc_len += nt_enc->vp_length - 4;
1741                 }
1742
1743                 if (new_nt_enc_len != 516) {
1744                         REDEBUG("Unpacked MS-CHAP-NT-Enc-PW length != 516");
1745                         return RLM_MODULE_INVALID;
1746                 }
1747
1748                 /*
1749                  * RFC 2548 is confusing here
1750                  * it claims:
1751                  *
1752                  * 1 byte code
1753                  * 1 byte ident
1754                  * 16 octets - old hash encrypted with new hash
1755                  * 24 octets - peer challenge
1756                  *   this is actually:
1757                  *   16 octets - peer challenge
1758                  *    8 octets - reserved
1759                  * 24 octets - nt response
1760                  * 2 octets  - flags (ignored)
1761                  */
1762
1763                 memcpy(old_nt_encrypted, cpw->vp_octets + 2, sizeof(old_nt_encrypted));
1764
1765                 RDEBUG2("Password change payload valid");
1766
1767                 /* perform the actual password change */
1768                 if (do_mschap_cpw(inst, request, nt_password, new_nt_encrypted, old_nt_encrypted, auth_method) < 0) {
1769                         char buffer[128];
1770
1771                         REDEBUG("Password change failed");
1772
1773                         snprintf(buffer, sizeof(buffer), "E=709 R=0 M=Password change failed");
1774                         mschap_add_reply(request, cpw->vp_octets[1], "MS-CHAP-Error", buffer, strlen(buffer));
1775
1776                         return RLM_MODULE_REJECT;
1777                 }
1778                 RDEBUG("Password change successful");
1779
1780                 /*
1781                  *  Clear any expiry bit so the user can now login;
1782                  *  obviously the password change action will need
1783                  *  to have cleared this bit in the config/SQL/wherever
1784                  */
1785                 if (smb_ctrl && smb_ctrl->vp_integer & ACB_PW_EXPIRED) {
1786                         RDEBUG("Clearing expiry bit in SMB-Acct-Ctrl to allow authentication");
1787                         smb_ctrl->vp_integer &= ~ACB_PW_EXPIRED;
1788                 }
1789
1790                 /*
1791                  *  Extract the challenge & response from the end of the password
1792                  *  change, add them into the request and then continue with
1793                  *  the authentication
1794                  */
1795                 response = radius_pair_create(request->packet, &request->packet->vps,
1796                                              PW_MSCHAP2_RESPONSE,
1797                                              VENDORPEC_MICROSOFT);
1798                 response->vp_length = 50;
1799                 response->vp_octets = p = talloc_array(response, uint8_t, response->vp_length);
1800
1801                 /* ident & flags */
1802                 p[0] = cpw->vp_octets[1];
1803                 p[1] = 0;
1804                 /* peer challenge and client NT response */
1805                 memcpy(p + 2, cpw->vp_octets + 18, 48);
1806         }
1807
1808         challenge = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
1809         if (!challenge) {
1810                 REDEBUG("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1811                 return RLM_MODULE_REJECT;
1812         }
1813
1814         /*
1815          *      We also require an MS-CHAP-Response.
1816          */
1817         response = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
1818
1819         /*
1820          *      MS-CHAP-Response, means MS-CHAPv1
1821          */
1822         if (response) {
1823                 int             offset;
1824                 rlm_rcode_t     rcode;
1825                 mschap_version = 1;
1826
1827                 /*
1828                  *      MS-CHAPv1 challenges are 8 octets.
1829                  */
1830                 if (challenge->vp_length < 8) {
1831                         REDEBUG("MS-CHAP-Challenge has the wrong format");
1832                         return RLM_MODULE_INVALID;
1833                 }
1834
1835                 /*
1836                  *      Responses are 50 octets.
1837                  */
1838                 if (response->vp_length < 50) {
1839                         REDEBUG("MS-CHAP-Response has the wrong format");
1840                         return RLM_MODULE_INVALID;
1841                 }
1842
1843                 /*
1844                  *      We are doing MS-CHAP.  Calculate the MS-CHAP
1845                  *      response
1846                  */
1847                 if (response->vp_octets[1] & 0x01) {
1848                         RDEBUG2("Client is using MS-CHAPv1 with NT-Password");
1849                         password = nt_password;
1850                         offset = 26;
1851                 } else {
1852                         RDEBUG2("Client is using MS-CHAPv1 with LM-Password");
1853                         password = lm_password;
1854                         offset = 2;
1855                 }
1856
1857                 /*
1858                  *      Do the MS-CHAP authentication.
1859                  */
1860                 mschap_result = do_mschap(inst, request, password, challenge->vp_octets,
1861                                           response->vp_octets + offset, nthashhash, auth_method);
1862                 /*
1863                  *      Check for errors, and add MSCHAP-Error if necessary.
1864                  */
1865                 rcode = mschap_error(inst, request, *response->vp_octets,
1866                                      mschap_result, mschap_version, smb_ctrl);
1867                 if (rcode != RLM_MODULE_OK) return rcode;
1868         } else if ((response = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_RESPONSE,
1869                                                    VENDORPEC_MICROSOFT, TAG_ANY)) != NULL) {
1870                 uint8_t         mschapv1_challenge[16];
1871                 VALUE_PAIR      *name_attr, *response_name;
1872                 rlm_rcode_t     rcode;
1873
1874                 mschap_version = 2;
1875
1876                 /*
1877                  *      MS-CHAPv2 challenges are 16 octets.
1878                  */
1879                 if (challenge->vp_length < 16) {
1880                         REDEBUG("MS-CHAP-Challenge has the wrong format");
1881                         return RLM_MODULE_INVALID;
1882                 }
1883
1884                 /*
1885                  *      Responses are 50 octets.
1886                  */
1887                 if (response->vp_length < 50) {
1888                         REDEBUG("MS-CHAP-Response has the wrong format");
1889                         return RLM_MODULE_INVALID;
1890                 }
1891
1892                 /*
1893                  *      We also require a User-Name
1894                  */
1895                 username = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
1896                 if (!username) {
1897                         REDEBUG("We require a User-Name for MS-CHAPv2");
1898                         return RLM_MODULE_INVALID;
1899                 }
1900
1901                 /*
1902                  *      Check for MS-CHAP-User-Name and if found, use it
1903                  *      to construct the MSCHAPv1 challenge.  This is
1904                  *      set by rlm_eap_mschap to the MS-CHAP Response
1905                  *      packet Name field.
1906                  *
1907                  *      We prefer this to the User-Name in the
1908                  *      packet.
1909                  */
1910                 response_name = fr_pair_find_by_num(request->packet->vps, PW_MS_CHAP_USER_NAME, 0, TAG_ANY);
1911                 if (response_name) {
1912                         name_attr = response_name;
1913                 } else {
1914                         name_attr = username;
1915                 }
1916
1917                 /*
1918                  *      with_ntdomain_hack moved here, too.
1919                  */
1920                 if ((username_string = strchr(name_attr->vp_strvalue, '\\')) != NULL) {
1921                         if (inst->with_ntdomain_hack) {
1922                                 username_string++;
1923                         } else {
1924                                 RWDEBUG2("NT Domain delimeter found, should with_ntdomain_hack of been enabled?");
1925                                 username_string = name_attr->vp_strvalue;
1926                         }
1927                 } else {
1928                         username_string = name_attr->vp_strvalue;
1929                 }
1930
1931                 if (response_name && ((username->vp_length != response_name->vp_length) ||
1932                     (strncasecmp(username->vp_strvalue, response_name->vp_strvalue, username->vp_length) != 0))) {
1933                         RWDEBUG("User-Name (%s) is not the same as MS-CHAP Name (%s) from EAP-MSCHAPv2",
1934                                 username->vp_strvalue, response_name->vp_strvalue);
1935                 }
1936
1937 #ifdef __APPLE__
1938                 /*
1939                  *  No "known good" NT-Password attribute.  Try to do
1940                  *  OpenDirectory authentication.
1941                  *
1942                  *  If OD determines the user is an AD user it will return noop, which
1943                  *  indicates the auth process should continue directly to AD.
1944                  *  Otherwise OD will determine auth success/fail.
1945                  */
1946                 if (!nt_password && inst->open_directory) {
1947                         RDEBUG2("No NT-Password configured. Trying OpenDirectory Authentication");
1948                         int odStatus = od_mschap_auth(request, challenge, username);
1949                         if (odStatus != RLM_MODULE_NOOP) {
1950                                 return odStatus;
1951                         }
1952                 }
1953 #endif
1954                 /*
1955                  *      The old "mschapv2" function has been moved to
1956                  *      here.
1957                  *
1958                  *      MS-CHAPv2 takes some additional data to create an
1959                  *      MS-CHAPv1 challenge, and then does MS-CHAPv1.
1960                  */
1961                 RDEBUG2("Creating challenge hash with username: %s", username_string);
1962                 mschap_challenge_hash(response->vp_octets + 2,  /* peer challenge */
1963                                       challenge->vp_octets,     /* our challenge */
1964                                       username_string,          /* user name */
1965                                       mschapv1_challenge);      /* resulting challenge */
1966
1967                 RDEBUG2("Client is using MS-CHAPv2");
1968                 mschap_result = do_mschap(inst, request, nt_password, mschapv1_challenge,
1969                                           response->vp_octets + 26, nthashhash, auth_method);
1970                 rcode = mschap_error(inst, request, *response->vp_octets,
1971                                      mschap_result, mschap_version, smb_ctrl);
1972                 if (rcode != RLM_MODULE_OK) return rcode;
1973
1974                 mschap_auth_response(username_string,           /* without the domain */
1975                                      nthashhash,                /* nt-hash-hash */
1976                                      response->vp_octets + 26,  /* peer response */
1977                                      response->vp_octets + 2,   /* peer challenge */
1978                                      challenge->vp_octets,      /* our challenge */
1979                                      msch2resp);                /* calculated MPPE key */
1980                 mschap_add_reply(request, *response->vp_octets, "MS-CHAP2-Success", msch2resp, 42);
1981
1982         } else {                /* Neither CHAPv1 or CHAPv2 response: die */
1983                 REDEBUG("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1984                 return RLM_MODULE_INVALID;
1985         }
1986
1987         /* now create MPPE attributes */
1988         if (inst->use_mppe) {
1989                 uint8_t mppe_sendkey[34];
1990                 uint8_t mppe_recvkey[34];
1991
1992                 if (mschap_version == 1) {
1993                         RDEBUG2("adding MS-CHAPv1 MPPE keys");
1994                         memset(mppe_sendkey, 0, 32);
1995                         if (lm_password) {
1996                                 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1997                         }
1998
1999                         /*
2000                          *      According to RFC 2548 we
2001                          *      should send NT hash.  But in
2002                          *      practice it doesn't work.
2003                          *      Instead, we should send nthashhash
2004                          *
2005                          *      This is an error in RFC 2548.
2006                          */
2007                         /*
2008                          *      do_mschap cares to zero nthashhash if NT hash
2009                          *      is not available.
2010                          */
2011                         memcpy(mppe_sendkey + 8, nthashhash, NT_DIGEST_LENGTH);
2012                         mppe_add_reply(request, "MS-CHAP-MPPE-Keys", mppe_sendkey, 24);
2013
2014                 } else if (mschap_version == 2) {
2015                         RDEBUG2("Adding MS-CHAPv2 MPPE keys");
2016                         mppe_chap2_gen_keys128(nthashhash, response->vp_octets + 26, mppe_sendkey, mppe_recvkey);
2017
2018                         mppe_add_reply(request, "MS-MPPE-Recv-Key", mppe_recvkey, 16);
2019                         mppe_add_reply(request, "MS-MPPE-Send-Key", mppe_sendkey, 16);
2020
2021                 }
2022                 pair_make_reply("MS-MPPE-Encryption-Policy",
2023                                (inst->require_encryption) ? "0x00000002":"0x00000001", T_OP_EQ);
2024                 pair_make_reply("MS-MPPE-Encryption-Types",
2025                                (inst->require_strong) ? "0x00000004":"0x00000006", T_OP_EQ);
2026         } /* else we weren't asked to use MPPE */
2027
2028         return RLM_MODULE_OK;
2029 #undef inst
2030 }
2031
2032 extern module_t rlm_mschap;
2033 module_t rlm_mschap = {
2034         .magic          = RLM_MODULE_INIT,
2035         .name           = "mschap",
2036         .type           = 0,
2037         .inst_size      = sizeof(rlm_mschap_t),
2038         .config         = module_config,
2039         .bootstrap      = mod_bootstrap,
2040         .instantiate    = mod_instantiate,
2041         .detach         = mod_detach,
2042         .methods = {
2043                 [MOD_AUTHENTICATE]      = mod_authenticate,
2044                 [MOD_AUTHORIZE]         = mod_authorize
2045         },
2046 };