All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
rlm_mschap.c
Go to the documentation of this file.
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: aa5683ce561bdfd9b0ecd15ac1898d8f1afd63e9 $
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: aa5683ce561bdfd9b0ecd15ac1898d8f1afd63e9 $")
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 #include "auth_wbclient.h"
40 
41 #ifdef HAVE_OPENSSL_CRYPTO_H
42 USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
43 # include <openssl/rc4.h>
44 #endif
45 
46 #ifdef __APPLE__
47 int od_mschap_auth(REQUEST *request, VALUE_PAIR *challenge, VALUE_PAIR * usernamepair);
48 #endif
49 
50 /* Allowable account control bits */
51 #define ACB_DISABLED 0x00010000 //!< User account disabled.
52 #define ACB_HOMDIRREQ 0x00020000 //!< Home directory required.
53 #define ACB_PWNOTREQ 0x00040000 //!< User password not required.
54 #define ACB_TEMPDUP 0x00080000 //!< Temporary duplicate account.
55 #define ACB_NORMAL 0x00100000 //!< Normal user account.
56 #define ACB_MNS 0x00200000 //!< MNS logon user account.
57 #define ACB_DOMTRUST 0x00400000 //!< Interdomain trust account.
58 #define ACB_WSTRUST 0x00800000 //!< Workstation trust account.
59 #define ACB_SVRTRUST 0x01000000 //!< Server trust account.
60 #define ACB_PWNOEXP 0x02000000 //!< User password does not expire.
61 #define ACB_AUTOLOCK 0x04000000 //!< Account auto locked.
62 #define ACB_PW_EXPIRED 0x00020000 //!< Password Expired.
63 
64 static int pdb_decode_acct_ctrl(char const *p)
65 {
66  int acct_ctrl = 0;
67  int done = 0;
68 
69  /*
70  * Check if the account type bits have been encoded after the
71  * NT password (in the form [NDHTUWSLXI]).
72  */
73 
74  if (*p != '[') return 0;
75 
76  for (p++; *p && !done; p++) {
77  switch (*p) {
78  case 'N': /* 'N'o password. */
79  acct_ctrl |= ACB_PWNOTREQ;
80  break;
81 
82  case 'D': /* 'D'isabled. */
83  acct_ctrl |= ACB_DISABLED ;
84  break;
85 
86  case 'H': /* 'H'omedir required. */
87  acct_ctrl |= ACB_HOMDIRREQ;
88  break;
89 
90  case 'T': /* 'T'emp account. */
91  acct_ctrl |= ACB_TEMPDUP;
92  break;
93 
94  case 'U': /* 'U'ser account (normal). */
95  acct_ctrl |= ACB_NORMAL;
96  break;
97 
98  case 'M': /* 'M'NS logon user account. What is this? */
99  acct_ctrl |= ACB_MNS;
100  break;
101 
102  case 'W': /* 'W'orkstation account. */
103  acct_ctrl |= ACB_WSTRUST;
104  break;
105 
106  case 'S': /* 'S'erver account. */
107  acct_ctrl |= ACB_SVRTRUST;
108  break;
109 
110  case 'L': /* 'L'ocked account. */
111  acct_ctrl |= ACB_AUTOLOCK;
112  break;
113 
114  case 'X': /* No 'X'piry on password */
115  acct_ctrl |= ACB_PWNOEXP;
116  break;
117 
118  case 'I': /* 'I'nterdomain trust account. */
119  acct_ctrl |= ACB_DOMTRUST;
120  break;
121 
122  case 'e': /* 'e'xpired, the password has */
123  acct_ctrl |= ACB_PW_EXPIRED;
124  break;
125 
126  case ' ': /* ignore spaces */
127  break;
128 
129  case ':':
130  case '\n':
131  case '\0':
132  case ']':
133  default:
134  done = 1;
135  break;
136  }
137  }
138 
139  return acct_ctrl;
140 }
141 
142 
143 /*
144  * Does dynamic translation of strings.
145  *
146  * Pulls NT-Response, LM-Response, or Challenge from MSCHAP
147  * attributes.
148  */
149 static ssize_t mschap_xlat(char **out, size_t outlen,
150  void const *mod_inst, UNUSED void const *xlat_inst,
151  REQUEST *request, char const *fmt)
152 {
153  size_t i, data_len;
154  uint8_t const *data = NULL;
155  uint8_t buffer[32];
156  VALUE_PAIR *user_name;
157  VALUE_PAIR *chap_challenge, *response;
158  rlm_mschap_t const *inst = mod_inst;
159 
160  response = NULL;
161 
162  /*
163  * Challenge means MS-CHAPv1 challenge, or
164  * hash of MS-CHAPv2 challenge, and peer challenge.
165  */
166  if (strncasecmp(fmt, "Challenge", 9) == 0) {
168  TAG_ANY);
169  if (!chap_challenge) {
170  REDEBUG("No MS-CHAP-Challenge in the request");
171  return -1;
172  }
173 
174  /*
175  * MS-CHAP-Challenges are 8 octets,
176  * for MS-CHAPv1
177  */
178  if (chap_challenge->vp_length == 8) {
179  RDEBUG2("mschap1: %02x", chap_challenge->vp_octets[0]);
180  data = chap_challenge->vp_octets;
181  data_len = 8;
182 
183  /*
184  * MS-CHAP-Challenges are 16 octets,
185  * for MS-CHAPv2.
186  */
187  } else if (chap_challenge->vp_length == 16) {
188  VALUE_PAIR *name_attr, *response_name;
189  char const *username_string;
190 
192  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, 0, PW_USER_NAME, 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, 0, PW_MS_CHAP_USER_NAME, 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) {
278  if (!response) response = fr_pair_find_by_num(request->packet->vps, VENDORPEC_MICROSOFT,
280  if (!response) {
281  REDEBUG("No MS-CHAP-Response or MS-CHAP2-Response was found in the request");
282  return -1;
283  }
284 
285  /*
286  * For MS-CHAPv1, the NT-Response exists only
287  * if the second octet says so.
288  */
289  if ((response->da->vendor == VENDORPEC_MICROSOFT) &&
290  (response->da->attr == PW_MSCHAP_RESPONSE) &&
291  ((response->vp_octets[1] & 0x01) == 0)) {
292  REDEBUG("No NT-Response in MS-CHAP-Response");
293  return -1;
294  }
295 
296  /*
297  * MS-CHAP-Response and MS-CHAP2-Response have
298  * the NT-Response at the same offset, and are
299  * the same length.
300  */
301  data = response->vp_octets + 26;
302  data_len = 24;
303 
304  /*
305  * LM-Response is deprecated, and exists only
306  * in MS-CHAPv1, and not often there.
307  */
308  } else if (strncasecmp(fmt, "LM-Response", 11) == 0) {
310  if (!response) {
311  REDEBUG("No MS-CHAP-Response was found in the request");
312  return -1;
313  }
314 
315  /*
316  * For MS-CHAPv1, the LM-Response exists only
317  * if the second octet says so.
318  */
319  if ((response->vp_octets[1] & 0x01) != 0) {
320  REDEBUG("No LM-Response in MS-CHAP-Response");
321  return -1;
322  }
323  data = response->vp_octets + 2;
324  data_len = 24;
325 
326  /*
327  * Pull the NT-Domain out of the User-Name, if it exists.
328  */
329  } else if (strncasecmp(fmt, "NT-Domain", 9) == 0) {
330  char *p, *q;
331 
332  user_name = fr_pair_find_by_num(request->packet->vps, 0, PW_USER_NAME, TAG_ANY);
333  if (!user_name) {
334  REDEBUG("No User-Name was found in the request");
335  return -1;
336  }
337 
338  /*
339  * First check to see if this is a host/ style User-Name
340  * (a la Kerberos host principal)
341  */
342  if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
343  /*
344  * If we're getting a User-Name formatted in this way,
345  * it's likely due to PEAP. The Windows Domain will be
346  * the first domain component following the hostname,
347  * or the machine name itself if only a hostname is supplied
348  */
349  p = strchr(user_name->vp_strvalue, '.');
350  if (!p) {
351  RDEBUG2("setting NT-Domain to same as machine name");
352  strlcpy(*out, user_name->vp_strvalue + 5, outlen);
353  } else {
354  p++; /* skip the period */
355  q = strchr(p, '.');
356  /*
357  * use the same hack as below
358  * only if another period was found
359  */
360  if (q) *q = '\0';
361  strlcpy(*out, p, outlen);
362  if (q) *q = '.';
363  }
364  } else {
365  p = strchr(user_name->vp_strvalue, '\\');
366  if (!p) {
367  REDEBUG("No NT-Domain was found in the User-Name");
368  return -1;
369  }
370 
371  /*
372  * Hack. This is simpler than the alternatives.
373  */
374  *p = '\0';
375  strlcpy(*out, user_name->vp_strvalue, outlen);
376  *p = '\\';
377  }
378 
379  return strlen(*out);
380 
381  /*
382  * Pull the User-Name out of the User-Name...
383  */
384  } else if (strncasecmp(fmt, "User-Name", 9) == 0) {
385  char const *p, *q;
386 
387  user_name = fr_pair_find_by_num(request->packet->vps, 0, PW_USER_NAME, TAG_ANY);
388  if (!user_name) {
389  REDEBUG("No User-Name was found in the request");
390  return -1;
391  }
392 
393  /*
394  * First check to see if this is a host/ style User-Name
395  * (a la Kerberos host principal)
396  */
397  if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
398  p = user_name->vp_strvalue + 5;
399  /*
400  * If we're getting a User-Name formatted in this way,
401  * it's likely due to PEAP. When authenticating this against
402  * a Domain, Windows will expect the User-Name to be in the
403  * format of hostname$, the SAM version of the name, so we
404  * have to convert it to that here. We do so by stripping
405  * off the first 5 characters (host/), and copying everything
406  * from that point to the first period into a string and appending
407  * a $ to the end.
408  */
409  q = strchr(p, '.');
410 
411  /*
412  * use the same hack as above
413  * only if a period was found
414  */
415  if (q) {
416  snprintf(*out, outlen, "%.*s$", (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, UNUSED struct timeval *timeout)
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  { FR_CONF_OFFSET("ntlm_auth", PW_TYPE_STRING | PW_TYPE_XLAT, rlm_mschap_t, ntlm_cpw) },
542  { FR_CONF_OFFSET("ntlm_auth_username", PW_TYPE_STRING | PW_TYPE_XLAT, rlm_mschap_t, ntlm_cpw_username) },
543  { FR_CONF_OFFSET("ntlm_auth_domain", PW_TYPE_STRING | PW_TYPE_XLAT, rlm_mschap_t, ntlm_cpw_domain) },
544  { FR_CONF_OFFSET("local_cpw", PW_TYPE_STRING | PW_TYPE_XLAT, rlm_mschap_t, local_cpw) },
546 };
547 
548 static const CONF_PARSER module_config[] = {
549  /*
550  * Cache the password by default.
551  */
552  { FR_CONF_OFFSET("use_mppe", PW_TYPE_BOOLEAN, rlm_mschap_t, use_mppe), .dflt = "yes" },
553  { FR_CONF_OFFSET("require_encryption", PW_TYPE_BOOLEAN, rlm_mschap_t, require_encryption), .dflt = "no" },
554  { FR_CONF_OFFSET("require_strong", PW_TYPE_BOOLEAN, rlm_mschap_t, require_strong), .dflt = "no" },
555  { FR_CONF_OFFSET("with_ntdomain_hack", PW_TYPE_BOOLEAN, rlm_mschap_t, with_ntdomain_hack), .dflt = "yes" },
556  { FR_CONF_OFFSET("ntlm_auth", PW_TYPE_STRING | PW_TYPE_XLAT, rlm_mschap_t, ntlm_auth) },
557  { FR_CONF_OFFSET("ntlm_auth_timeout", PW_TYPE_INTEGER, rlm_mschap_t, ntlm_auth_timeout) },
558  { FR_CONF_POINTER("passchange", PW_TYPE_SUBSECTION, NULL), .dflt = (void const *) passchange_config },
559  { FR_CONF_OFFSET("allow_retry", PW_TYPE_BOOLEAN, rlm_mschap_t, allow_retry), .dflt = "yes" },
560  { FR_CONF_OFFSET("retry_msg", PW_TYPE_STRING, rlm_mschap_t, retry_msg) },
561  { FR_CONF_OFFSET("winbind_username", PW_TYPE_TMPL, rlm_mschap_t, wb_username) },
562  { FR_CONF_OFFSET("winbind_domain", PW_TYPE_TMPL, rlm_mschap_t, wb_domain) },
563 #ifdef __APPLE__
564  { FR_CONF_OFFSET("use_open_directory", PW_TYPE_BOOLEAN, rlm_mschap_t, open_directory), .dflt = "yes" },
565 #endif
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, inst->xlat_name, mschap_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN);
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 (!fr_dict_enum_by_name(NULL, fr_dict_attr_by_num(NULL, 0, PW_AUTH_TYPE), 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 = module_connection_pool_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) {
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 
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  if (vp->da->type == PW_TYPE_STRING) {
692  char *p;
693 
694  p = talloc_array(vp, char, len + 1 + 1); /* Account for the ident byte */
695  p[vp->vp_length] = '\0'; /* Always \0 terminate */
696  p[0] = ident;
697  memcpy(p + 1, value, len);
698  fr_pair_value_strsteal(vp, p);
699  } else {
700  uint8_t *p;
701 
702  p = talloc_array(vp, uint8_t, len + 1); /* Account for the ident byte */
703  p[0] = ident;
704  memcpy(p + 1, value, len);
705  fr_pair_value_memsteal(vp, p);
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  q = talloc_array(new_hash, uint8_t, NT_DIGEST_LENGTH);
991  fr_pair_value_memsteal(new_hash, q);
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  case AUTH_INTERNAL:
1114  /*
1115  * Do normal authentication.
1116  */
1117  {
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 (fr_radius_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 
1141  break;
1142  }
1143  case AUTH_NTLMAUTH_EXEC:
1144  /*
1145  * Run ntlm_auth
1146  */
1147  {
1148  int result;
1149  char buffer[256];
1150  size_t len;
1151 
1152  /*
1153  * Run the program, and expect that we get 16
1154  */
1155  result = radius_exec_program(request, buffer, sizeof(buffer), NULL, request, inst->ntlm_auth, NULL,
1156  true, true, inst->ntlm_auth_timeout);
1157  if (result != 0) {
1158  char *p;
1159 
1160  /*
1161  * look for "Password expired", or "Must change password".
1162  */
1163  if (strcasestr(buffer, "Password expired") ||
1164  strcasestr(buffer, "Must change password")) {
1165  REDEBUG2("%s", buffer);
1166  return -648;
1167  }
1168 
1169  if (strcasestr(buffer, "Account locked out") ||
1170  strcasestr(buffer, "0xC0000234")) {
1171  REDEBUG2("%s", buffer);
1172  return -647;
1173  }
1174 
1175  if (strcasestr(buffer, "Account disabled") ||
1176  strcasestr(buffer, "0xC0000072")) {
1177  REDEBUG2("%s", buffer);
1178  return -691;
1179  }
1180 
1181  RDEBUG2("External script failed");
1182  p = strchr(buffer, '\n');
1183  if (p) *p = '\0';
1184 
1185  REDEBUG("External script says: %s", buffer);
1186  return -1;
1187  }
1188 
1189  /*
1190  * Parse the answer as an nthashhash.
1191  *
1192  * ntlm_auth currently returns:
1193  * NT_KEY: 000102030405060708090a0b0c0d0e0f
1194  */
1195  if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
1196  REDEBUG("Invalid output from ntlm_auth: expecting 'NT_KEY: ' prefix");
1197  return -1;
1198  }
1199 
1200  /*
1201  * Check the length. It should be at least 32, with an LF at the end.
1202  */
1203  len = strlen(buffer + 8);
1204  if (len < 32) {
1205  REDEBUG2("Invalid output from ntlm_auth: NT_KEY too short, expected 32 bytes got %zu bytes",
1206  len);
1207 
1208  return -1;
1209  }
1210 
1211  /*
1212  * Update the NT hash hash, from the NT key.
1213  */
1214  if (fr_hex2bin(nthashhash, NT_DIGEST_LENGTH, buffer + 8, len) != NT_DIGEST_LENGTH) {
1215  REDEBUG("Invalid output from ntlm_auth: NT_KEY has non-hex values");
1216  return -1;
1217  }
1218 
1219  break;
1220  }
1221 #ifdef WITH_AUTH_WINBIND
1222  case AUTH_WBCLIENT:
1223  /*
1224  * Process auth via the wbclient library
1225  */
1226  return do_auth_wbclient(inst, request, challenge, response, nthashhash);
1227 #endif
1228  default:
1229  /* We should never reach this line */
1230  RERROR("Internal error: Unknown mschap auth method (%d)", method);
1231  return -1;
1232  }
1233 
1234  return 0;
1235 }
1236 
1237 
1238 /*
1239  * Data for the hashes.
1240  */
1241 static const uint8_t SHSpad1[40] =
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  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
1246 
1247 static const uint8_t SHSpad2[40] =
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  0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
1252 
1253 static const uint8_t magic1[27] =
1254  { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
1255  0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
1256  0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
1257 
1258 static const uint8_t magic2[84] =
1259  { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
1260  0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
1261  0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1262  0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
1263  0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
1264  0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
1265  0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1266  0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
1267  0x6b, 0x65, 0x79, 0x2e };
1268 
1269 static const uint8_t magic3[84] =
1270  { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
1271  0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
1272  0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1273  0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
1274  0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
1275  0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
1276  0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
1277  0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
1278  0x6b, 0x65, 0x79, 0x2e };
1279 
1280 
1281 static void mppe_GetMasterKey(uint8_t const *nt_hashhash,uint8_t const *nt_response,
1282  uint8_t *masterkey)
1283 {
1284  uint8_t digest[20];
1285  fr_sha1_ctx Context;
1286 
1287  fr_sha1_init(&Context);
1288  fr_sha1_update(&Context,nt_hashhash,NT_DIGEST_LENGTH);
1289  fr_sha1_update(&Context,nt_response,24);
1290  fr_sha1_update(&Context,magic1,27);
1291  fr_sha1_final(digest,&Context);
1292 
1293  memcpy(masterkey,digest,16);
1294 }
1295 
1296 
1297 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
1298  int keylen,int issend)
1299 {
1300  uint8_t digest[20];
1301  const uint8_t *s;
1302  fr_sha1_ctx Context;
1303 
1304  memset(digest,0,20);
1305 
1306  if(issend) {
1307  s = magic3;
1308  } else {
1309  s = magic2;
1310  }
1311 
1312  fr_sha1_init(&Context);
1313  fr_sha1_update(&Context,masterkey,16);
1314  fr_sha1_update(&Context,SHSpad1,40);
1315  fr_sha1_update(&Context,s,84);
1316  fr_sha1_update(&Context,SHSpad2,40);
1317  fr_sha1_final(digest,&Context);
1318 
1319  memcpy(sesskey,digest,keylen);
1320 }
1321 
1322 
1323 static void mppe_chap2_get_keys128(uint8_t const *nt_hashhash,uint8_t const *nt_response,
1324  uint8_t *sendkey,uint8_t *recvkey)
1325 {
1326  uint8_t masterkey[16];
1327 
1328  mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
1329 
1330  mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
1331  mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
1332 }
1333 
1334 /*
1335  * Generate MPPE keys.
1336  */
1337 static void mppe_chap2_gen_keys128(uint8_t const *nt_hashhash,uint8_t const *response,
1338  uint8_t *sendkey,uint8_t *recvkey)
1339 {
1340  uint8_t enckey1[16];
1341  uint8_t enckey2[16];
1342 
1343  mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
1344 
1345  /*
1346  * dictionary.microsoft defines these attributes as
1347  * 'encrypt=2'. The functions in src/lib/radius.c will
1348  * take care of encrypting/decrypting them as appropriate,
1349  * so that we don't have to.
1350  */
1351  memcpy (sendkey, enckey1, 16);
1352  memcpy (recvkey, enckey2, 16);
1353 }
1354 
1355 
1356 /*
1357  * mod_authorize() - authorize user if we can authenticate
1358  * it later. Add Auth-Type attribute if present in module
1359  * configuration (usually Auth-Type must be "MS-CHAP")
1360  */
1361 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void * instance, REQUEST *request)
1362 {
1363  rlm_mschap_t *inst = instance;
1364  VALUE_PAIR *challenge = NULL;
1365 
1366  challenge = fr_pair_find_by_num(request->packet->vps, VENDORPEC_MICROSOFT, PW_MSCHAP_CHALLENGE, TAG_ANY);
1367  if (!challenge) {
1368  return RLM_MODULE_NOOP;
1369  }
1370 
1371  if (!fr_pair_find_by_num(request->packet->vps, VENDORPEC_MICROSOFT, PW_MSCHAP_RESPONSE, TAG_ANY) &&
1373  !fr_pair_find_by_num(request->packet->vps, VENDORPEC_MICROSOFT, PW_MSCHAP2_CPW, TAG_ANY)) {
1374  RDEBUG2("Found MS-CHAP-Challenge, but no MS-CHAP response or change-password");
1375  return RLM_MODULE_NOOP;
1376  }
1377 
1378  if (fr_pair_find_by_num(request->config, 0, PW_AUTH_TYPE, TAG_ANY)) {
1379  RWDEBUG2("Auth-Type already set. Not setting to MS-CHAP");
1380  return RLM_MODULE_NOOP;
1381  }
1382 
1383  RDEBUG2("Found MS-CHAP attributes. Setting 'Auth-Type = %s'", inst->xlat_name);
1384 
1385  /*
1386  * Set Auth-Type to MS-CHAP. The authentication code
1387  * will take care of turning cleartext passwords into
1388  * NT/LM passwords.
1389  */
1390  if (!pair_make_config("Auth-Type", inst->auth_type, T_OP_EQ)) {
1391  return RLM_MODULE_FAIL;
1392  }
1393 
1394  return RLM_MODULE_OK;
1395 }
1396 
1397 static rlm_rcode_t mschap_error(rlm_mschap_t *inst, REQUEST *request, unsigned char ident,
1398  int mschap_result, int mschap_version, VALUE_PAIR *smb_ctrl)
1399 {
1400  rlm_rcode_t rcode = RLM_MODULE_OK;
1401  int error = 0;
1402  int retry = 0;
1403  char const *message = NULL;
1404 
1405  int i;
1406  char new_challenge[33], buffer[128];
1407  char *p;
1408 
1409  if ((mschap_result == -648) ||
1410  (smb_ctrl && ((smb_ctrl->vp_integer & ACB_PW_EXPIRED) != 0))) {
1411  REDEBUG("Password has expired. User should retry authentication");
1412  error = 648;
1413  retry = inst->allow_retry;
1414  message = "Password expired";
1415  rcode = RLM_MODULE_REJECT;
1416 
1417  /*
1418  * Account is disabled.
1419  *
1420  * They're found, but they don't exist, so we
1421  * return 'not found'.
1422  */
1423  } else if ((mschap_result == -691) ||
1424  (smb_ctrl && (((smb_ctrl->vp_integer & ACB_DISABLED) != 0) ||
1425  ((smb_ctrl->vp_integer & (ACB_NORMAL|ACB_WSTRUST)) == 0)))) {
1426  REDEBUG("SMB-Account-Ctrl (or ntlm_auth) "
1427  "says that the account is disabled, "
1428  "or is not a normal or workstation trust account");
1429  error = 691;
1430  retry = 0;
1431  message = "Account disabled";
1432  rcode = RLM_MODULE_NOTFOUND;
1433 
1434  /*
1435  * User is locked out.
1436  */
1437  } else if ((mschap_result == -647) ||
1438  (smb_ctrl && ((smb_ctrl->vp_integer & ACB_AUTOLOCK) != 0))) {
1439  REDEBUG("SMB-Account-Ctrl (or ntlm_auth) "
1440  "says that the account is locked out");
1441  error = 647;
1442  retry = 0;
1443  message = "Account locked out";
1444  rcode = RLM_MODULE_USERLOCK;
1445 
1446  } else if (mschap_result < 0) {
1447  REDEBUG("MS-CHAP2-Response is incorrect");
1448  error = 691;
1449  retry = inst->allow_retry;
1450  message = "Authentication failed";
1451  rcode = RLM_MODULE_REJECT;
1452  }
1453 
1454  if (rcode == RLM_MODULE_OK) return RLM_MODULE_OK;
1455 
1456  switch (mschap_version) {
1457  case 1:
1458  for (p = new_challenge, i = 0; i < 2; i++) p += snprintf(p, 9, "%08x", fr_rand());
1459  snprintf(buffer, sizeof(buffer), "E=%i R=%i C=%s V=2",
1460  error, retry, new_challenge);
1461  break;
1462 
1463  case 2:
1464  for (p = new_challenge, i = 0; i < 4; i++) p += snprintf(p, 9, "%08x", fr_rand());
1465  snprintf(buffer, sizeof(buffer), "E=%i R=%i C=%s V=3 M=%s",
1466  error, retry, new_challenge, message);
1467  break;
1468 
1469  default:
1470  rad_assert(0);
1471  }
1472  mschap_add_reply(request, ident, "MS-CHAP-Error", buffer, strlen(buffer));
1473 
1474  return rcode;
1475 }
1476 
1477 /*
1478  * mod_authenticate() - authenticate user based on given
1479  * attributes and configuration.
1480  * We will try to find out password in configuration
1481  * or in configured passwd file.
1482  * If one is found we will check paraneters given by NAS.
1483  *
1484  * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
1485  * one of:
1486  * PAP: PW_USER_PASSWORD or
1487  * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
1488  * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
1489  * In case of password mismatch or locked account we MAY return
1490  * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
1491  * If MS-CHAP2 succeeds we MUST return
1492  * PW_MSCHAP2_SUCCESS
1493  */
1494 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
1495 {
1496  rlm_mschap_t *inst = instance;
1497  VALUE_PAIR *challenge = NULL;
1498  VALUE_PAIR *response = NULL;
1499  VALUE_PAIR *cpw = NULL;
1500  VALUE_PAIR *password = NULL;
1501  VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
1502  VALUE_PAIR *username;
1503  uint8_t nthashhash[NT_DIGEST_LENGTH];
1504  char msch2resp[42];
1505  char const *username_string;
1506  int mschap_version = 0;
1507  int mschap_result;
1508  MSCHAP_AUTH_METHOD auth_method;
1509 
1510  /*
1511  * If we have ntlm_auth configured, use it unless told
1512  * otherwise
1513  */
1514  auth_method = inst->method;
1515 
1516  /*
1517  * If we have an ntlm_auth configuration, then we may
1518  * want to suppress it.
1519  */
1520  if (auth_method != AUTH_INTERNAL) {
1521  VALUE_PAIR *vp = fr_pair_find_by_num(request->config, 0, PW_MS_CHAP_USE_NTLM_AUTH, TAG_ANY);
1522  if (vp && vp->vp_integer == 0) auth_method = AUTH_INTERNAL;
1523  }
1524 
1525  /*
1526  * Find the SMB-Account-Ctrl attribute, or the
1527  * SMB-Account-Ctrl-Text attribute.
1528  */
1529  smb_ctrl = fr_pair_find_by_num(request->config, 0, PW_SMB_ACCOUNT_CTRL, TAG_ANY);
1530  if (!smb_ctrl) {
1531  password = fr_pair_find_by_num(request->config, 0, PW_SMB_ACCOUNT_CTRL_TEXT, TAG_ANY);
1532  if (password) {
1533  smb_ctrl = pair_make_config("SMB-Account-CTRL", "0", T_OP_SET);
1534  if (smb_ctrl) {
1535  smb_ctrl->vp_integer = pdb_decode_acct_ctrl(password->vp_strvalue);
1536  }
1537  }
1538  }
1539 
1540  /*
1541  * We're configured to do MS-CHAP authentication.
1542  * and account control information exists. Enforce it.
1543  */
1544  if (smb_ctrl) {
1545  /*
1546  * Password is not required.
1547  */
1548  if ((smb_ctrl->vp_integer & ACB_PWNOTREQ) != 0) {
1549  RDEBUG2("SMB-Account-Ctrl says no password is required");
1550  return RLM_MODULE_OK;
1551  }
1552  }
1553 
1554  /*
1555  * Decide how to get the passwords.
1556  */
1557  password = fr_pair_find_by_num(request->config, 0, PW_CLEARTEXT_PASSWORD, TAG_ANY);
1558 
1559  /*
1560  * We need an NT-Password.
1561  */
1562  nt_password = fr_pair_find_by_num(request->config, 0, PW_NT_PASSWORD, TAG_ANY);
1563  if (nt_password) {
1564  VERIFY_VP(nt_password);
1565 
1566  switch (nt_password->vp_length) {
1567  case NT_DIGEST_LENGTH:
1568  RDEBUG2("Found NT-Password");
1569  break;
1570 
1571  /* 0x */
1572  case 34:
1573  case 32:
1574  RWDEBUG("NT-Password has not been normalized by the 'pap' module (likely still in hex format). "
1575  "Authentication may fail");
1576  nt_password = NULL;
1577  break;
1578 
1579  default:
1580  RWDEBUG("NT-Password found but incorrect length, expected " STRINGIFY(NT_DIGEST_LENGTH)
1581  " bytes got %zu bytes. Authentication may fail", nt_password->vp_length);
1582  nt_password = NULL;
1583  break;
1584  }
1585  }
1586 
1587  /*
1588  * ... or a Cleartext-Password, which we now transform into an NT-Password
1589  */
1590  if (!nt_password) {
1591  uint8_t *p;
1592 
1593  if (password) {
1594  RDEBUG2("Found Cleartext-Password, hashing to create NT-Password");
1595  nt_password = pair_make_config("NT-Password", NULL, T_OP_EQ);
1596  if (!nt_password) {
1597  RERROR("No memory");
1598  return RLM_MODULE_FAIL;
1599  }
1600  p = talloc_array(nt_password, uint8_t, NT_DIGEST_LENGTH);
1601  fr_pair_value_memsteal(nt_password, p);
1602 
1603  if (mschap_ntpwdhash(p, password->vp_strvalue) < 0) {
1604  RERROR("Failed generating NT-Password");
1605  return RLM_MODULE_FAIL;
1606  }
1607  } else if (auth_method == AUTH_INTERNAL) {
1608  RWDEBUG2("No Cleartext-Password configured. Cannot create NT-Password");
1609  }
1610  }
1611 
1612  /*
1613  * Or an LM-Password.
1614  */
1615  lm_password = fr_pair_find_by_num(request->config, 0, PW_LM_PASSWORD, TAG_ANY);
1616  if (lm_password) {
1617  VERIFY_VP(lm_password);
1618 
1619  switch (lm_password->vp_length) {
1620  case LM_DIGEST_LENGTH:
1621  RDEBUG2("Found LM-Password");
1622  break;
1623 
1624  /* 0x */
1625  case 34:
1626  case 32:
1627  RWDEBUG("LM-Password has not been normalized by the 'pap' module (likely still in hex format). "
1628  "Authentication may fail");
1629  lm_password = NULL;
1630  break;
1631 
1632  default:
1633  RWDEBUG("LM-Password found but incorrect length, expected " STRINGIFY(LM_DIGEST_LENGTH)
1634  " bytes got %zu bytes. Authentication may fail", lm_password->vp_length);
1635  lm_password = NULL;
1636  break;
1637  }
1638  }
1639  /*
1640  * ... or a Cleartext-Password, which we now transform into an LM-Password
1641  */
1642  if (!lm_password) {
1643  if (password) {
1644  RDEBUG2("Found Cleartext-Password, hashing to create LM-Password");
1645  lm_password = pair_make_config("LM-Password", NULL, T_OP_EQ);
1646  if (!lm_password) {
1647  RERROR("No memory");
1648  } else {
1649  uint8_t *p;
1650 
1651  p = talloc_array(lm_password, uint8_t, LM_DIGEST_LENGTH);
1652  fr_pair_value_memsteal(lm_password, p);
1653  smbdes_lmpwdhash(password->vp_strvalue, p);
1654  }
1655  /*
1656  * Only complain if we don't have NT-Password
1657  */
1658  } else if ((auth_method == AUTH_INTERNAL) && !nt_password) {
1659  RWDEBUG2("No Cleartext-Password configured. Cannot create LM-Password");
1660  }
1661  }
1662 
1663  cpw = fr_pair_find_by_num(request->packet->vps, VENDORPEC_MICROSOFT, PW_MSCHAP2_CPW, TAG_ANY);
1664  if (cpw) {
1665  /*
1666  * mschap2 password change request
1667  * we cheat - first decode and execute the passchange
1668  * we then extract the response, add it into the request
1669  * then jump into mschap2 auth with the chal/resp
1670  */
1671  uint8_t new_nt_encrypted[516], old_nt_encrypted[NT_DIGEST_LENGTH];
1672  VALUE_PAIR *nt_enc=NULL;
1673  int seq, new_nt_enc_len;
1674  uint8_t *p;
1675 
1676  RDEBUG("MS-CHAPv2 password change request received");
1677 
1678  if (cpw->vp_length != 68) {
1679  REDEBUG("MS-CHAP2-CPW has the wrong format: length %zu != 68", cpw->vp_length);
1680  return RLM_MODULE_INVALID;
1681  }
1682 
1683  if (cpw->vp_octets[0] != 7) {
1684  REDEBUG("MS-CHAP2-CPW has the wrong format: code %d != 7", cpw->vp_octets[0]);
1685  return RLM_MODULE_INVALID;
1686  }
1687 
1688  /*
1689  * look for the new (encrypted) password
1690  * bah stupid composite attributes
1691  * we're expecting 3 attributes with the leading bytes
1692  * 06:<mschapid>:00:01:<1st chunk>
1693  * 06:<mschapid>:00:02:<2nd chunk>
1694  * 06:<mschapid>:00:03:<3rd chunk>
1695  */
1696  new_nt_enc_len = 0;
1697  for (seq = 1; seq < 4; seq++) {
1698  vp_cursor_t cursor;
1699  int found = 0;
1700 
1701  for (nt_enc = fr_cursor_init(&cursor, &request->packet->vps);
1702  nt_enc;
1703  nt_enc = fr_cursor_next(&cursor)) {
1704  if (nt_enc->da->vendor != VENDORPEC_MICROSOFT)
1705  continue;
1706 
1707  if (nt_enc->da->attr != PW_MSCHAP_NT_ENC_PW)
1708  continue;
1709 
1710  if (nt_enc->vp_octets[0] != 6) {
1711  REDEBUG("MS-CHAP-NT-Enc-PW with invalid format");
1712  return RLM_MODULE_INVALID;
1713  }
1714 
1715  if ((nt_enc->vp_octets[2] == 0) && (nt_enc->vp_octets[3] == seq)) {
1716  found = 1;
1717  break;
1718  }
1719  }
1720 
1721  if (!found) {
1722  REDEBUG("Could not find MS-CHAP-NT-Enc-PW w/ sequence number %d", seq);
1723  return RLM_MODULE_INVALID;
1724  }
1725 
1726  if ((new_nt_enc_len + nt_enc->vp_length - 4) >= sizeof(new_nt_encrypted)) {
1727  REDEBUG("Unpacked MS-CHAP-NT-Enc-PW length > 516");
1728  return RLM_MODULE_INVALID;
1729  }
1730 
1731  memcpy(new_nt_encrypted + new_nt_enc_len, nt_enc->vp_octets + 4, nt_enc->vp_length - 4);
1732  new_nt_enc_len += nt_enc->vp_length - 4;
1733  }
1734 
1735  if (new_nt_enc_len != 516) {
1736  REDEBUG("Unpacked MS-CHAP-NT-Enc-PW length != 516");
1737  return RLM_MODULE_INVALID;
1738  }
1739 
1740  /*
1741  * RFC 2548 is confusing here
1742  * it claims:
1743  *
1744  * 1 byte code
1745  * 1 byte ident
1746  * 16 octets - old hash encrypted with new hash
1747  * 24 octets - peer challenge
1748  * this is actually:
1749  * 16 octets - peer challenge
1750  * 8 octets - reserved
1751  * 24 octets - nt response
1752  * 2 octets - flags (ignored)
1753  */
1754 
1755  memcpy(old_nt_encrypted, cpw->vp_octets + 2, sizeof(old_nt_encrypted));
1756 
1757  RDEBUG2("Password change payload valid");
1758 
1759  /* perform the actual password change */
1760  if (do_mschap_cpw(inst, request, nt_password, new_nt_encrypted, old_nt_encrypted, auth_method) < 0) {
1761  char buffer[128];
1762 
1763  REDEBUG("Password change failed");
1764 
1765  snprintf(buffer, sizeof(buffer), "E=709 R=0 M=Password change failed");
1766  mschap_add_reply(request, cpw->vp_octets[1], "MS-CHAP-Error", buffer, strlen(buffer));
1767 
1768  return RLM_MODULE_REJECT;
1769  }
1770  RDEBUG("Password change successful");
1771 
1772  /*
1773  * Clear any expiry bit so the user can now login;
1774  * obviously the password change action will need
1775  * to have cleared this bit in the config/SQL/wherever
1776  */
1777  if (smb_ctrl && smb_ctrl->vp_integer & ACB_PW_EXPIRED) {
1778  RDEBUG("Clearing expiry bit in SMB-Acct-Ctrl to allow authentication");
1779  smb_ctrl->vp_integer &= ~ACB_PW_EXPIRED;
1780  }
1781 
1782  /*
1783  * Extract the challenge & response from the end of the password
1784  * change, add them into the request and then continue with
1785  * the authentication
1786  */
1787  response = radius_pair_create(request->packet, &request->packet->vps,
1790  p = talloc_array(response, uint8_t, 50);
1791 
1792  /* ident & flags */
1793  p[0] = cpw->vp_octets[1];
1794  p[1] = 0;
1795  /* peer challenge and client NT response */
1796  memcpy(p + 2, cpw->vp_octets + 18, 48);
1797 
1798  fr_pair_value_memsteal(response, p);
1799  }
1800 
1801  challenge = fr_pair_find_by_num(request->packet->vps, VENDORPEC_MICROSOFT, PW_MSCHAP_CHALLENGE, TAG_ANY);
1802  if (!challenge) {
1803  REDEBUG("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1804  return RLM_MODULE_REJECT;
1805  }
1806 
1807  /*
1808  * We also require an MS-CHAP-Response.
1809  */
1810  response = fr_pair_find_by_num(request->packet->vps, VENDORPEC_MICROSOFT, PW_MSCHAP_RESPONSE, TAG_ANY);
1811 
1812  /*
1813  * MS-CHAP-Response, means MS-CHAPv1
1814  */
1815  if (response) {
1816  int offset;
1817  rlm_rcode_t rcode;
1818  mschap_version = 1;
1819 
1820  /*
1821  * MS-CHAPv1 challenges are 8 octets.
1822  */
1823  if (challenge->vp_length < 8) {
1824  REDEBUG("MS-CHAP-Challenge has the wrong format");
1825  return RLM_MODULE_INVALID;
1826  }
1827 
1828  /*
1829  * Responses are 50 octets.
1830  */
1831  if (response->vp_length < 50) {
1832  REDEBUG("MS-CHAP-Response has the wrong format");
1833  return RLM_MODULE_INVALID;
1834  }
1835 
1836  /*
1837  * We are doing MS-CHAP. Calculate the MS-CHAP
1838  * response
1839  */
1840  if (response->vp_octets[1] & 0x01) {
1841  RDEBUG2("Client is using MS-CHAPv1 with NT-Password");
1842  password = nt_password;
1843  offset = 26;
1844  } else {
1845  RDEBUG2("Client is using MS-CHAPv1 with LM-Password");
1846  password = lm_password;
1847  offset = 2;
1848  }
1849 
1850  /*
1851  * Do the MS-CHAP authentication.
1852  */
1853  mschap_result = do_mschap(inst, request, password, challenge->vp_octets,
1854  response->vp_octets + offset, nthashhash, auth_method);
1855  /*
1856  * Check for errors, and add MSCHAP-Error if necessary.
1857  */
1858  rcode = mschap_error(inst, request, *response->vp_octets,
1859  mschap_result, mschap_version, smb_ctrl);
1860  if (rcode != RLM_MODULE_OK) return rcode;
1861  } else if ((response = fr_pair_find_by_num(request->packet->vps, VENDORPEC_MICROSOFT, PW_MSCHAP2_RESPONSE,
1862  TAG_ANY)) != NULL) {
1863  uint8_t mschapv1_challenge[16];
1864  VALUE_PAIR *name_attr, *response_name;
1865  rlm_rcode_t rcode;
1866 
1867  mschap_version = 2;
1868 
1869  /*
1870  * MS-CHAPv2 challenges are 16 octets.
1871  */
1872  if (challenge->vp_length < 16) {
1873  REDEBUG("MS-CHAP-Challenge has the wrong format");
1874  return RLM_MODULE_INVALID;
1875  }
1876 
1877  /*
1878  * Responses are 50 octets.
1879  */
1880  if (response->vp_length < 50) {
1881  REDEBUG("MS-CHAP-Response has the wrong format");
1882  return RLM_MODULE_INVALID;
1883  }
1884 
1885  /*
1886  * We also require a User-Name
1887  */
1888  username = fr_pair_find_by_num(request->packet->vps, 0, PW_USER_NAME, TAG_ANY);
1889  if (!username) {
1890  REDEBUG("We require a User-Name for MS-CHAPv2");
1891  return RLM_MODULE_INVALID;
1892  }
1893 
1894  /*
1895  * Check for MS-CHAP-User-Name and if found, use it
1896  * to construct the MSCHAPv1 challenge. This is
1897  * set by rlm_eap_mschap to the MS-CHAP Response
1898  * packet Name field.
1899  *
1900  * We prefer this to the User-Name in the
1901  * packet.
1902  */
1903  response_name = fr_pair_find_by_num(request->packet->vps, 0, PW_MS_CHAP_USER_NAME, TAG_ANY);
1904  name_attr = response_name ? response_name : username;
1905 
1906  /*
1907  * with_ntdomain_hack moved here, too.
1908  */
1909  if ((username_string = strchr(name_attr->vp_strvalue, '\\')) != NULL) {
1910  if (inst->with_ntdomain_hack) {
1911  username_string++;
1912  } else {
1913  RWDEBUG2("NT Domain delimeter found, should with_ntdomain_hack of been enabled?");
1914  username_string = name_attr->vp_strvalue;
1915  }
1916  } else {
1917  username_string = name_attr->vp_strvalue;
1918  }
1919 
1920  if (response_name && ((username->vp_length != response_name->vp_length) ||
1921  (strncasecmp(username->vp_strvalue, response_name->vp_strvalue, username->vp_length) != 0))) {
1922  RWDEBUG("User-Name (%s) is not the same as MS-CHAP Name (%s) from EAP-MSCHAPv2",
1923  username->vp_strvalue, response_name->vp_strvalue);
1924  }
1925 
1926 #ifdef __APPLE__
1927  /*
1928  * No "known good" NT-Password attribute. Try to do
1929  * OpenDirectory authentication.
1930  *
1931  * If OD determines the user is an AD user it will return noop, which
1932  * indicates the auth process should continue directly to AD.
1933  * Otherwise OD will determine auth success/fail.
1934  */
1935  if (!nt_password && inst->open_directory) {
1936  RDEBUG2("No NT-Password configured. Trying OpenDirectory Authentication");
1937  int odStatus = od_mschap_auth(request, challenge, username);
1938  if (odStatus != RLM_MODULE_NOOP) {
1939  return odStatus;
1940  }
1941  }
1942 #endif
1943  /*
1944  * The old "mschapv2" function has been moved to
1945  * here.
1946  *
1947  * MS-CHAPv2 takes some additional data to create an
1948  * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1949  */
1950  RDEBUG2("Creating challenge hash with username: %s", username_string);
1951  mschap_challenge_hash(response->vp_octets + 2, /* peer challenge */
1952  challenge->vp_octets, /* our challenge */
1953  username_string, /* user name */
1954  mschapv1_challenge); /* resulting challenge */
1955 
1956  RDEBUG2("Client is using MS-CHAPv2");
1957  mschap_result = do_mschap(inst, request, nt_password, mschapv1_challenge,
1958  response->vp_octets + 26, nthashhash, auth_method);
1959 
1960  /*
1961  * Check for errors, and add MSCHAP-Error if necessary.
1962  */
1963  rcode = mschap_error(inst, request, *response->vp_octets,
1964  mschap_result, mschap_version, smb_ctrl);
1965  if (rcode != RLM_MODULE_OK) return rcode;
1966 
1967  mschap_auth_response(username_string, /* without the domain */
1968  nthashhash, /* nt-hash-hash */
1969  response->vp_octets + 26, /* peer response */
1970  response->vp_octets + 2, /* peer challenge */
1971  challenge->vp_octets, /* our challenge */
1972  msch2resp); /* calculated MPPE key */
1973  mschap_add_reply(request, *response->vp_octets, "MS-CHAP2-Success", msch2resp, 42);
1974 
1975 
1976  } else { /* Neither CHAPv1 or CHAPv2 response: die */
1977  REDEBUG("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1978  return RLM_MODULE_INVALID;
1979  }
1980 
1981  /* now create MPPE attributes */
1982  if (inst->use_mppe) {
1983  uint8_t mppe_sendkey[34];
1984  uint8_t mppe_recvkey[34];
1985 
1986  switch (mschap_version) {
1987  case 1:
1988  RDEBUG2("Adding MS-CHAPv1 MPPE keys");
1989  memset(mppe_sendkey, 0, 32);
1990  if (lm_password) memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1991 
1992  /*
1993  * According to RFC 2548 we
1994  * should send NT hash. But in
1995  * practice it doesn't work.
1996  * Instead, we should send nthashhash
1997  *
1998  * This is an error in RFC 2548.
1999  */
2000  /*
2001  * do_mschap cares to zero nthashhash if NT hash
2002  * is not available.
2003  */
2004  memcpy(mppe_sendkey + 8, nthashhash, NT_DIGEST_LENGTH);
2005  mppe_add_reply(request, "MS-CHAP-MPPE-Keys", mppe_sendkey, 24);
2006  break;
2007 
2008  case 2:
2009  RDEBUG2("Adding MS-CHAPv2 MPPE keys");
2010  mppe_chap2_gen_keys128(nthashhash, response->vp_octets + 26, mppe_sendkey, mppe_recvkey);
2011 
2012  mppe_add_reply(request, "MS-MPPE-Recv-Key", mppe_recvkey, 16);
2013  mppe_add_reply(request, "MS-MPPE-Send-Key", mppe_sendkey, 16);
2014  break;
2015 
2016  default:
2017  rad_assert(0);
2018  break;
2019  }
2020 
2021  pair_make_reply("MS-MPPE-Encryption-Policy",
2022  (inst->require_encryption) ? "0x00000002":"0x00000001", T_OP_EQ);
2023  pair_make_reply("MS-MPPE-Encryption-Types",
2024  (inst->require_strong) ? "0x00000004":"0x00000006", T_OP_EQ);
2025  } /* else we weren't asked to use MPPE */
2026 
2027  return RLM_MODULE_OK;
2028 #undef inst
2029 }
2030 
2031 extern module_t rlm_mschap;
2032 module_t rlm_mschap = {
2034  .name = "mschap",
2035  .type = 0,
2036  .inst_size = sizeof(rlm_mschap_t),
2037  .config = module_config,
2038  .bootstrap = mod_bootstrap,
2039  .instantiate = mod_instantiate,
2040  .detach = mod_detach,
2041  .methods = {
2044  },
2045 };
#define PW_MSCHAP_CHALLENGE
Definition: radius.h:213
void fr_sha1_update(fr_sha1_ctx *context, uint8_t const *data, size_t len)
Definition: sha1.c:106
#define PW_MSCHAP_NT_ENC_PW
Definition: radius.h:212
int radius_exec_program(TALLOC_CTX *ctx, char *out, size_t outlen, VALUE_PAIR **output_pairs, REQUEST *request, char const *cmd, VALUE_PAIR *input_pairs, bool exec_wait, bool shell_escape, int timeout) CC_HINT(nonnull(5
#define ACB_SVRTRUST
Server trust account.
Definition: rlm_mschap.c:59
fr_connection_pool_t * wb_pool
Definition: rlm_mschap.h:41
#define RERROR(fmt,...)
Definition: log.h:207
int xlat_register(void *mod_inst, char const *name, xlat_func_t func, xlat_escape_t escape, xlat_instantiate_t instantiate, size_t inst_size, size_t buf_len)
Register an xlat function.
Definition: xlat.c:717
The module is OK, continue.
Definition: radiusd.h:91
void smbdes_lmpwdhash(char const *password, uint8_t *lmhash)
Definition: smbdes.c:318
Metadata exported by the module.
Definition: modules.h:134
uint32_t fr_rand(void)
Return a 32-bit random number.
Definition: radius.c:1621
bool allow_retry
Definition: rlm_mschap.h:36
static void smbhash(unsigned char *out, unsigned char const *in, unsigned char *key)
Definition: smbdes.c:284
static char const * name
bool with_ntdomain_hack
Definition: rlm_mschap.h:27
struct rlm_mschap_t rlm_mschap_t
VALUE_PAIR * radius_pair_create(TALLOC_CTX *ctx, VALUE_PAIR **vps, unsigned int attribute, unsigned int vendor)
Create a VALUE_PAIR and add it to a list of VALUE_PAIR s.
Definition: pair.c:704
static rlm_rcode_t mod_authorize(void *instance, REQUEST *request)
Handle authorization requests using Couchbase document data.
static int mod_bootstrap(CONF_SECTION *conf, void *instance)
Definition: rlm_mschap.c:570
#define VERIFY_VP(_x)
Definition: pair.h:44
#define UNUSED
Definition: libradius.h:134
#define RLM_MODULE_INIT
Definition: modules.h:86
#define REDEBUG2(fmt,...)
Definition: log.h:255
VALUE_PAIR * vps
Result of decoding the packet into VALUE_PAIRs.
Definition: libradius.h:162
#define CONF_PARSER_TERMINATOR
Definition: conffile.h:289
#define NT_DIGEST_LENGTH
Definition: mschap.h:8
#define pair_make_request(_a, _b, _c)
Definition: radiusd.h:545
VALUE_PAIR * fr_cursor_init(vp_cursor_t *cursor, VALUE_PAIR *const *node)
Setup a cursor to iterate over attribute pairs.
Definition: cursor.c:60
bool use_mppe
Definition: rlm_mschap.h:24
PUBLIC int snprintf(char *string, size_t length, char *format, va_alist)
Definition: snprintf.c:686
void fr_md4_calc(uint8_t out[MD4_DIGEST_LENGTH], uint8_t const *in, size_t inlen)
Calculate the MD4 hash of the contents of a buffer.
Definition: md4.c:24
#define rad_waitpid(a, b)
Definition: radiusd.h:564
static float timeout
Definition: radclient.c:43
#define inst
Definition: token.h:46
The module considers the request invalid.
Definition: radiusd.h:93
#define XLAT_DEFAULT_BUF_LEN
Definition: xlat.h:89
void mschap_challenge_hash(uint8_t const *peer_challenge, uint8_t const *auth_challenge, char const *user_name, uint8_t *challenge)
Definition: mschap.c:72
static rlm_rcode_t mod_authenticate(void *instance, REQUEST *request) CC_HINT(nonnull)
#define PW_TYPE_SUBSECTION
Definition: conffile.h:188
static void mppe_GetMasterKey(uint8_t const *nt_hashhash, uint8_t const *nt_response, uint8_t *masterkey)
Definition: rlm_mschap.c:1281
Defines a CONF_PAIR to C data type mapping.
Definition: conffile.h:267
fr_dict_enum_t * fr_dict_enum_by_name(fr_dict_t *dict, fr_dict_attr_t const *da, char const *val)
Definition: dict.c:3703
Abstraction to allow iterating over different configurations of VALUE_PAIRs.
Definition: pair.h:144
void fr_sha1_init(fr_sha1_ctx *context)
Definition: sha1.c:94
#define ACB_TEMPDUP
Temporary duplicate account.
Definition: rlm_mschap.c:54
module_t rlm_mschap
Definition: rlm_mschap.c:2032
#define VENDORPEC_MICROSOFT
Definition: radius.h:200
static const CONF_PARSER passchange_config[]
Definition: rlm_mschap.c:540
void fr_pair_value_strsteal(VALUE_PAIR *vp, char const *src)
Reparent an allocated char buffer to a VALUE_PAIR.
Definition: pair.c:1955
#define rad_assert(expr)
Definition: rad_assert.h:38
Reject the request (user is locked out).
Definition: radiusd.h:94
static const uint8_t magic3[84]
Definition: rlm_mschap.c:1269
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition: log.c:238
fr_connection_pool_t * module_connection_pool_init(CONF_SECTION *module, void *opaque, fr_connection_create_t c, fr_connection_alive_t a, char const *prefix)
Initialise a module specific connection pool.
Definition: modules.c:1759
MSCHAP_AUTH_METHOD
Definition: rlm_mschap.h:15
#define DEBUG(fmt,...)
Definition: log.h:175
void mschap_auth_response(char const *username, uint8_t const *nt_hash_hash, uint8_t const *ntresponse, uint8_t const *peer_challenge, uint8_t const *auth_challenge, char *response)
Definition: mschap.c:93
void void MD4_DIGEST_LENGTH
Definition: md4.h:68
static void * mod_conn_create(TALLOC_CTX *ctx, void *instance, struct timeval const *timeout)
Create a new memcached handle.
int mschap_ntpwdhash(uint8_t *out, char const *password)
Converts Unicode password to 16-byte NT hash with MD4.
Definition: mschap.c:52
#define pair_make_config(_a, _b, _c)
Definition: radiusd.h:547
size_t fr_hex2bin(uint8_t *bin, size_t outlen, char const *hex, size_t inlen)
Convert hex strings to binary data.
Definition: misc.c:220
static int _mod_conn_free(rlm_cache_memcached_handle_t *mandle)
Free a connection handle.
#define PW_TYPE_XLAT
string will be dynamically expanded.
Definition: conffile.h:207
#define ACB_HOMDIRREQ
Home directory required.
Definition: rlm_mschap.c:52
#define STRINGIFY(x)
Definition: build.h:34
static void mppe_add_reply(REQUEST *request, char const *name, uint8_t const *value, size_t len)
Definition: rlm_mschap.c:712
unsigned int attr
Attribute number.
Definition: dict.h:79
static bool done
Definition: radclient.c:53
Immediately reject the request.
Definition: radiusd.h:89
static int CC_HINT(nonnull(1, 2, 4, 5))
Definition: rlm_mschap.c:741
unsigned int vendor
Vendor that defines this attribute.
Definition: dict.h:78
Stores an attribute, a value and various bits of other data.
Definition: pair.h:112
void mschap_add_reply(REQUEST *request, unsigned char ident, char const *name, char const *value, size_t len)
Definition: rlm_mschap.c:680
static int mod_instantiate(CONF_SECTION *conf, void *instance)
Definition: rlm_mschap.c:590
void fr_sha1_final(uint8_t digest[20], fr_sha1_ctx *context)
Definition: sha1.c:132
void void cf_log_err_cs(CONF_SECTION const *cs, char const *fmt,...) CC_HINT(format(printf
0 methods index for authenticate section.
Definition: modules.h:41
A truth value.
Definition: radius.h:56
Definition: token.h:45
32 Bit unsigned integer.
Definition: radius.h:34
enum rlm_rcodes rlm_rcode_t
Return codes indicating the result of the module call.
#define ACB_PWNOEXP
User password does not expire.
Definition: rlm_mschap.c:60
ssize_t radius_xlat(char *out, size_t outlen, REQUEST *request, char const *fmt, xlat_escape_t escape, void *escape_ctx) CC_HINT(nonnull(1
#define ACB_MNS
MNS logon user account.
Definition: rlm_mschap.c:56
bool require_strong
Definition: rlm_mschap.h:26
static rs_t * conf
Definition: radsniff.c:46
char const * fr_strerror(void)
Get the last library error.
Definition: log.c:212
#define RWDEBUG2(fmt,...)
Definition: log.h:252
#define ACB_PW_EXPIRED
Password Expired.
Definition: rlm_mschap.c:62
char const * cf_section_name1(CONF_SECTION const *cs)
Definition: conffile.c:3592
Module succeeded without doing anything.
Definition: radiusd.h:96
#define RDEBUG2(fmt,...)
Definition: log.h:244
static void mppe_chap2_gen_keys128(uint8_t const *nt_hashhash, uint8_t const *response, uint8_t *sendkey, uint8_t *recvkey)
Definition: rlm_mschap.c:1337
uint8_t data[]
Definition: eap_pwd.h:625
int radius_readfrom_program(int fd, pid_t pid, int timeout, char *answer, int left)
Read from the child process.
Definition: exec.c:386
bool require_encryption
Definition: rlm_mschap.h:25
static const CONF_PARSER module_config[]
Definition: rlm_mschap.c:548
uint64_t magic
Used to validate module struct.
Definition: modules.h:135
Module failed, don't reply.
Definition: radiusd.h:90
void fr_pair_value_memsteal(VALUE_PAIR *vp, uint8_t const *src)
Reparent an allocated octet buffer to a VALUE_PAIR.
Definition: pair.c:1933
#define TAG_ANY
Definition: pair.h:191
static const uint8_t magic2[84]
Definition: rlm_mschap.c:1258
#define FR_CONF_OFFSET(_n, _t, _s, _f)
Definition: conffile.h:168
static void mppe_GetAsymmetricStartKey(uint8_t *masterkey, uint8_t *sesskey, int keylen, int issend)
Definition: rlm_mschap.c:1297
VALUE_PAIR * fr_cursor_next(vp_cursor_t *cursor)
Advanced the cursor to the next VALUE_PAIR.
Definition: cursor.c:263
#define ACB_WSTRUST
Workstation trust account.
Definition: rlm_mschap.c:58
pid_t radius_start_program(char const *cmd, REQUEST *request, bool exec_wait, int *input_fd, int *output_fd, VALUE_PAIR *input_pairs, bool shell_escape)
Start a process.
Definition: exec.c:87
#define pair_make_reply(_a, _b, _c)
Definition: radiusd.h:546
MSCHAP_AUTH_METHOD method
Definition: rlm_mschap.h:38
int strncasecmp(char *s1, char *s2, int n)
Definition: missing.c:43
#define PW_MSCHAP_RESPONSE
Definition: radius.h:208
RADIUS_PACKET * packet
Incoming request.
Definition: radiusd.h:221
char const * auth_type
Definition: rlm_mschap.h:35
#define REDEBUG(fmt,...)
Definition: log.h:254
#define PW_MSCHAP2_CPW
Definition: radius.h:216
static int write_all(int fd, char const *buf, int len)
Definition: rlm_mschap.c:725
int fr_radius_digest_cmp(uint8_t const *a, uint8_t const *b, size_t length)
Do a comparison of two authentication digests by comparing the FULL digest.
Definition: radius.c:578
char const * xlat_name
Definition: rlm_mschap.h:28
#define EXEC_TIMEOUT
Definition: radiusd.h:329
VALUE_PAIR * fr_pair_find_by_num(VALUE_PAIR *head, unsigned int vendor, unsigned int attr, int8_t tag)
Find the pair with the matching attribute.
Definition: pair.c:639
static int pdb_decode_acct_ctrl(char const *p)
Definition: rlm_mschap.c:64
static ssize_t mschap_xlat(char **out, size_t outlen, void const *mod_inst, UNUSED void const *xlat_inst, REQUEST *request, char const *fmt)
Definition: rlm_mschap.c:149
#define ACB_AUTOLOCK
Account auto locked.
Definition: rlm_mschap.c:61
size_t strlcpy(char *dst, char const *src, size_t siz)
Definition: strlcpy.c:38
#define ACB_NORMAL
Normal user account.
Definition: rlm_mschap.c:55
#define PW_MSCHAP2_RESPONSE
Definition: radius.h:214
fr_dict_attr_t const * da
Dictionary attribute defines the attribute.
Definition: pair.h:113
char const * ntlm_auth
Definition: rlm_mschap.h:29
fr_dict_attr_t const * fr_dict_attr_by_num(fr_dict_t *dict, unsigned int vendor, unsigned int attr)
Lookup a fr_dict_attr_t by its vendor and attribute numbers.
Definition: dict.c:3519
String of printable characters.
Definition: radius.h:33
void smbdes_mschap(uint8_t const win_password[16], uint8_t const *challenge, uint8_t *response)
Definition: smbdes.c:338
#define FR_CONF_POINTER(_n, _t, _p)
Definition: conffile.h:172
#define LM_DIGEST_LENGTH
Definition: mschap.h:9
#define RWDEBUG(fmt,...)
Definition: log.h:251
#define ACB_DISABLED
User account disabled.
Definition: rlm_mschap.c:51
#define PW_TYPE_TMPL
CONF_PAIR should be parsed as a template.
Definition: conffile.h:208
PW_TYPE type
Value type.
Definition: dict.h:80
1 methods index for authorize section.
Definition: modules.h:42
vp_tmpl_t * wb_username
Definition: rlm_mschap.h:39
static const uint8_t SHSpad1[40]
Definition: rlm_mschap.c:1241
static const uint8_t SHSpad2[40]
Definition: rlm_mschap.c:1247
User not found.
Definition: radiusd.h:95
char * strsep(char **stringp, char const *delim)
Definition: missing.c:112
#define RCSID(id)
Definition: build.h:135
static void mppe_chap2_get_keys128(uint8_t const *nt_hashhash, uint8_t const *nt_response, uint8_t *sendkey, uint8_t *recvkey)
Definition: rlm_mschap.c:1323
static const uint8_t magic1[27]
Definition: rlm_mschap.c:1253
int do_auth_wbclient(rlm_mschap_t *inst, REQUEST *request, uint8_t const *challenge, uint8_t const *response, uint8_t nthashhash[NT_DIGEST_LENGTH])
Definition: auth_wbclient.c:48
#define RDEBUG(fmt,...)
Definition: log.h:243
size_t fr_bin2hex(char *hex, uint8_t const *bin, size_t inlen)
Convert binary data to a hex string.
Definition: misc.c:254
#define ERROR(fmt,...)
Definition: log.h:145
void fr_pair_value_memcpy(VALUE_PAIR *vp, uint8_t const *src, size_t len)
Copy data into an "octets" data type.
Definition: pair.c:1905
#define ACB_DOMTRUST
Interdomain trust account.
Definition: rlm_mschap.c:57
static rlm_rcode_t mschap_error(rlm_mschap_t *inst, REQUEST *request, unsigned char ident, int mschap_result, int mschap_version, VALUE_PAIR *smb_ctrl)
Definition: rlm_mschap.c:1397
#define ACB_PWNOTREQ
User password not required.
Definition: rlm_mschap.c:53
char const * cf_section_name2(CONF_SECTION const *cs)
Definition: conffile.c:3601
#define USES_APPLE_DEPRECATED_API
Definition: build.h:122
void fr_connection_pool_free(fr_connection_pool_t *pool)
Delete a connection pool.
Definition: connection.c:1226
static int mod_detach(UNUSED void *instance)
Definition: rlm_mschap.c:665
uint32_t ntlm_auth_timeout
Definition: rlm_mschap.h:30