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.
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.
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
20 * @brief Implemented mschap authentication.
22 * @copyright 2000,2001,2006 The FreeRADIUS server project
25 /* MPPE support from Takahiro Wagatsuma <waga@sic.shibaura-it.ac.jp> */
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>
36 #include "rlm_mschap.h"
40 #ifdef WITH_AUTH_WINBIND
41 #include "auth_wbclient.h"
44 #ifdef HAVE_OPENSSL_CRYPTO_H
45 USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
46 # include <openssl/rc4.h>
50 int od_mschap_auth(REQUEST *request, VALUE_PAIR *challenge, VALUE_PAIR * usernamepair);
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.
67 static int pdb_decode_acct_ctrl(char const *p)
73 * Check if the account type bits have been encoded after the
74 * NT password (in the form [NDHTUWSLXI]).
77 if (*p != '[') return 0;
79 for (p++; *p && !done; p++) {
81 case 'N': /* 'N'o password. */
82 acct_ctrl |= ACB_PWNOTREQ;
85 case 'D': /* 'D'isabled. */
86 acct_ctrl |= ACB_DISABLED ;
89 case 'H': /* 'H'omedir required. */
90 acct_ctrl |= ACB_HOMDIRREQ;
93 case 'T': /* 'T'emp account. */
94 acct_ctrl |= ACB_TEMPDUP;
97 case 'U': /* 'U'ser account (normal). */
98 acct_ctrl |= ACB_NORMAL;
101 case 'M': /* 'M'NS logon user account. What is this? */
102 acct_ctrl |= ACB_MNS;
105 case 'W': /* 'W'orkstation account. */
106 acct_ctrl |= ACB_WSTRUST;
109 case 'S': /* 'S'erver account. */
110 acct_ctrl |= ACB_SVRTRUST;
113 case 'L': /* 'L'ocked account. */
114 acct_ctrl |= ACB_AUTOLOCK;
117 case 'X': /* No 'X'piry on password */
118 acct_ctrl |= ACB_PWNOEXP;
121 case 'I': /* 'I'nterdomain trust account. */
122 acct_ctrl |= ACB_DOMTRUST;
125 case 'e': /* 'e'xpired, the password has */
126 acct_ctrl |= ACB_PW_EXPIRED;
129 case ' ': /* ignore spaces */
147 * Does dynamic translation of strings.
149 * Pulls NT-Response, LM-Response, or Challenge from MSCHAP
152 static ssize_t mschap_xlat(void *instance, REQUEST *request,
153 char const *fmt, char *out, size_t outlen)
156 uint8_t const *data = NULL;
158 VALUE_PAIR *user_name;
159 VALUE_PAIR *chap_challenge, *response;
160 rlm_mschap_t *inst = instance;
165 * Challenge means MS-CHAPv1 challenge, or
166 * hash of MS-CHAPv2 challenge, and peer challenge.
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");
176 * MS-CHAP-Challenges are 8 octets,
179 if (chap_challenge->vp_length == 8) {
180 RDEBUG2("mschap1: %02x", chap_challenge->vp_octets[0]);
181 data = chap_challenge->vp_octets;
185 * MS-CHAP-Challenges are 16 octets,
188 } else if (chap_challenge->vp_length == 16) {
189 VALUE_PAIR *name_attr, *response_name;
190 char const *username_string;
192 response = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
194 REDEBUG("MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge");
199 * FIXME: Much of this is copied from
200 * below. We should put it into a
205 * Responses are 50 octets.
207 if (response->vp_length < 50) {
208 REDEBUG("MS-CHAP-Response has the wrong format");
212 user_name = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
214 REDEBUG("User-Name is required to calculate MS-CHAPv1 Challenge");
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
224 * We prefer this to the User-Name in the
227 response_name = fr_pair_find_by_num(request->packet->vps, PW_MS_CHAP_USER_NAME, 0, TAG_ANY);
229 name_attr = response_name;
231 name_attr = user_name;
235 * with_ntdomain_hack moved here, too.
237 if ((username_string = strchr(name_attr->vp_strvalue, '\\')) != NULL) {
238 if (inst->with_ntdomain_hack) {
241 RWDEBUG2("NT Domain delimiter found, should we have enabled with_ntdomain_hack?");
242 username_string = name_attr->vp_strvalue;
245 username_string = name_attr->vp_strvalue;
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);
257 * Get the MS-CHAPv1 challenge
258 * from the MS-CHAPv2 peer challenge,
259 * our challenge, and the user name.
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);
268 REDEBUG("Invalid MS-CHAP challenge length");
273 * Get the MS-CHAPv1 response, or the MS-CHAPv2
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);
280 REDEBUG("No MS-CHAP-Response or MS-CHAP2-Response was found in the request");
285 * For MS-CHAPv1, the NT-Response exists only
286 * if the second octet says so.
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");
296 * MS-CHAP-Response and MS-CHAP2-Response have
297 * the NT-Response at the same offset, and are
300 data = response->vp_octets + 26;
304 * LM-Response is deprecated, and exists only
305 * in MS-CHAPv1, and not often there.
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);
310 REDEBUG("No MS-CHAP-Response was found in the request");
315 * For MS-CHAPv1, the LM-Response exists only
316 * if the second octet says so.
318 if ((response->vp_octets[1] & 0x01) != 0) {
319 REDEBUG("No LM-Response in MS-CHAP-Response");
322 data = response->vp_octets + 2;
326 * Pull the NT-Domain out of the User-Name, if it exists.
328 } else if (strncasecmp(fmt, "NT-Domain", 9) == 0) {
331 user_name = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
333 REDEBUG("No User-Name was found in the request");
338 * First check to see if this is a host/ style User-Name
339 * (a la Kerberos host principal)
341 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
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
348 p = strchr(user_name->vp_strvalue, '.');
350 RDEBUG2("setting NT-Domain to same as machine name");
351 strlcpy(out, user_name->vp_strvalue + 5, outlen);
353 p++; /* skip the period */
356 * use the same hack as below
357 * only if another period was found
360 strlcpy(out, p, outlen);
364 p = strchr(user_name->vp_strvalue, '\\');
366 REDEBUG("No NT-Domain was found in the User-Name");
371 * Hack. This is simpler than the alternatives.
374 strlcpy(out, user_name->vp_strvalue, outlen);
381 * Pull the User-Name out of the User-Name...
383 } else if (strncasecmp(fmt, "User-Name", 9) == 0) {
386 user_name = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
388 REDEBUG("No User-Name was found in the request");
393 * First check to see if this is a host/ style User-Name
394 * (a la Kerberos host principal)
396 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
397 p = user_name->vp_strvalue + 5;
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
411 * use the same hack as above
412 * only if a period was found
415 snprintf(out, outlen, "%.*s$",
418 snprintf(out, outlen, "%s$", p);
421 p = strchr(user_name->vp_strvalue, '\\');
423 p++; /* skip the backslash */
425 p = user_name->vp_strvalue; /* use the whole User-Name */
427 strlcpy(out, p, outlen);
433 * Return the NT-Hash of the passed string
435 } else if (strncasecmp(fmt, "NT-Hash ", 8) == 0) {
438 p = fmt + 8; /* 7 is the length of 'NT-Hash' */
439 if ((p == '\0') || (outlen <= 32))
442 while (isspace(*p)) p++;
444 if (mschap_ntpwdhash(buffer, p) < 0) {
445 REDEBUG("Failed generating NT-Password");
450 fr_bin2hex(out, buffer, NT_DIGEST_LENGTH);
452 RDEBUG("NT-Hash of \"known-good\" password: %s", out);
456 * Return the LM-Hash of the passed string
458 } else if (strncasecmp(fmt, "LM-Hash ", 8) == 0) {
461 p = fmt + 8; /* 7 is the length of 'LM-Hash' */
462 if ((p == '\0') || (outlen <= 32))
465 while (isspace(*p)) p++;
467 smbdes_lmpwdhash(p, buffer);
468 fr_bin2hex(out, buffer, LM_DIGEST_LENGTH);
470 RDEBUG("LM-Hash of %s = %s", p, out);
473 REDEBUG("Unknown expansion string '%s'", fmt);
477 if (outlen == 0) return 0; /* nowhere to go, don't do anything */
480 * Didn't set anything: this is bad.
483 RWDEBUG2("Failed to do anything intelligent");
488 * Check the output length.
490 if (outlen < ((data_len * 2) + 1)) {
491 data_len = (outlen - 1) / 2;
497 for (i = 0; i < data_len; i++) {
498 sprintf(out + (2 * i), "%02x", data[i]);
500 out[data_len * 2] = '\0';
506 #ifdef WITH_AUTH_WINBIND
508 * Free connection pool winbind context
510 static int _mod_conn_free(struct wbcContext **wb_ctx)
518 * Create connection pool winbind context
520 static void *mod_conn_create(TALLOC_CTX *ctx, UNUSED void *instance)
522 struct wbcContext **wb_ctx;
524 wb_ctx = talloc_zero(ctx, struct wbcContext *);
525 *wb_ctx = wbcCtxCreate();
527 if (*wb_ctx == NULL) {
528 ERROR("failed to create winbind context");
533 talloc_set_destructor(wb_ctx, _mod_conn_free);
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
548 static const CONF_PARSER module_config[] = {
550 * Cache the password by default.
552 { "use_mppe", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_mschap_t, use_mppe), "yes" },
553 { "require_encryption", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_mschap_t, require_encryption), "no" },
554 { "require_strong", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_mschap_t, require_strong), "no" },
555 { "with_ntdomain_hack", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_mschap_t, with_ntdomain_hack), "yes" },
556 { "ntlm_auth", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_mschap_t, ntlm_auth), NULL },
557 { "ntlm_auth_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_mschap_t, ntlm_auth_timeout), NULL },
558 { "passchange", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) passchange_config },
559 { "allow_retry", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_mschap_t, allow_retry), "yes" },
560 { "retry_msg", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_mschap_t, retry_msg), NULL },
561 { "winbind_username", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_TMPL, rlm_mschap_t, wb_username), NULL },
562 { "winbind_domain", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_TMPL, rlm_mschap_t, wb_domain), NULL },
563 { "winbind_retry_with_normalised_username", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_mschap_t, wb_retry_with_normalised_username), "no" },
565 { "use_open_directory", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_mschap_t, open_directory), "yes" },
567 CONF_PARSER_TERMINATOR
571 static int mod_bootstrap(CONF_SECTION *conf, void *instance)
574 rlm_mschap_t *inst = instance;
577 * Create the dynamic translation.
579 name = cf_section_name2(conf);
580 if (!name) name = cf_section_name1(conf);
581 inst->xlat_name = name;
582 xlat_register(inst->xlat_name, mschap_xlat, NULL, inst);
588 * Create instance for our module. Allocate space for
589 * instance structure and read configuration parameters
591 static int mod_instantiate(CONF_SECTION *conf, void *instance)
593 rlm_mschap_t *inst = instance;
596 * For backwards compatibility
598 if (!dict_valbyname(PW_AUTH_TYPE, 0, inst->xlat_name)) {
599 inst->auth_type = "MS-CHAP";
601 inst->auth_type = inst->xlat_name;
607 inst->method = AUTH_INTERNAL;
609 if (inst->wb_username) {
610 #ifdef WITH_AUTH_WINBIND
611 inst->method = AUTH_WBCLIENT;
613 inst->wb_pool = fr_connection_pool_module_init(conf, inst, mod_conn_create, NULL, NULL);
614 if (!inst->wb_pool) {
615 cf_log_err_cs(conf, "Unable to initialise winbind connection pool");
619 cf_log_err_cs(conf, "'winbind' auth not enabled at compiled time");
624 /* preserve existing behaviour: this option overrides all */
625 if (inst->ntlm_auth) {
626 inst->method = AUTH_NTLMAUTH_EXEC;
629 switch (inst->method) {
631 DEBUG("rlm_mschap (%s): using internal authentication", inst->xlat_name);
633 case AUTH_NTLMAUTH_EXEC:
634 DEBUG("rlm_mschap (%s): authenticating by calling 'ntlm_auth'", inst->xlat_name);
636 #ifdef WITH_AUTH_WINBIND
638 DEBUG("rlm_mschap (%s): authenticating directly to winbind", inst->xlat_name);
644 * Check ntlm_auth_timeout is sane
646 if (!inst->ntlm_auth_timeout) {
647 inst->ntlm_auth_timeout = EXEC_TIMEOUT;
649 if (inst->ntlm_auth_timeout < 1) {
650 cf_log_err_cs(conf, "ntml_auth_timeout '%d' is too small (minimum: 1)",
651 inst->ntlm_auth_timeout);
654 if (inst->ntlm_auth_timeout > 10) {
655 cf_log_err_cs(conf, "ntlm_auth_timeout '%d' is too large (maximum: 10)",
656 inst->ntlm_auth_timeout);
666 static int mod_detach(UNUSED void *instance)
668 #ifdef WITH_AUTH_WINBIND
669 rlm_mschap_t *inst = instance;
671 fr_connection_pool_free(inst->wb_pool);
678 * add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
679 * attribute to reply packet
681 void mschap_add_reply(REQUEST *request, unsigned char ident,
682 char const *name, char const *value, size_t len)
686 vp = pair_make_reply(name, NULL, T_OP_EQ);
688 REDEBUG("Failed to create attribute %s: %s", name, fr_strerror());
692 /* Account for the ident byte */
693 vp->vp_length = len + 1;
694 if (vp->da->type == PW_TYPE_STRING) {
697 vp->vp_strvalue = p = talloc_array(vp, char, vp->vp_length + 1);
698 p[vp->vp_length] = '\0'; /* Always \0 terminate */
700 memcpy(p + 1, value, len);
704 vp->vp_octets = p = talloc_array(vp, uint8_t, vp->vp_length);
706 memcpy(p + 1, value, len);
711 * Add MPPE attributes to the reply.
713 static void mppe_add_reply(REQUEST *request, char const* name, uint8_t const * value, size_t len)
717 vp = pair_make_reply(name, NULL, T_OP_EQ);
719 REDEBUG("mppe_add_reply failed to create attribute %s: %s", name, fr_strerror());
723 fr_pair_value_memcpy(vp, value, len);
726 static int write_all(int fd, char const *buf, int len) {
730 rv = write(fd, buf+done, len-done);
739 * Perform an MS-CHAP2 password change
742 static int CC_HINT(nonnull (1, 2, 4, 5)) do_mschap_cpw(rlm_mschap_t *inst,
744 #ifdef HAVE_OPENSSL_CRYPTO_H
745 VALUE_PAIR *nt_password,
747 UNUSED VALUE_PAIR *nt_password,
749 uint8_t *new_nt_password,
750 uint8_t *old_nt_hash,
751 MSCHAP_AUTH_METHOD method)
753 if (inst->ntlm_cpw && method != AUTH_INTERNAL) {
755 * we're going to run ntlm_auth in helper-mode
756 * we're expecting to use the ntlm-change-password-1 protocol
757 * which needs the following on stdin:
759 * username: %{mschap:User-Name}
760 * nt-domain: %{mschap:NT-Domain}
761 * new-nt-password-blob: bin2hex(new_nt_password) - 1032 bytes encoded
762 * old-nt-hash-blob: bin2hex(old_nt_hash) - 32 bytes encoded
763 * new-lm-password-blob: 00000...0000 - 1032 bytes null
764 * old-lm-hash-blob: 000....000 - 32 bytes null
767 * ...and it should then print out
769 * Password-Change: Yes
773 * Password-Change: No
774 * Password-Change-Error: blah
779 pid_t pid, child_pid;
785 RDEBUG("Doing MS-CHAPv2 password change via ntlm_auth helper");
788 * Start up ntlm_auth with a pipe on stdin and stdout
791 pid = radius_start_program(inst->ntlm_cpw, request, true, &to_child, &from_child, NULL, false);
793 REDEBUG("could not exec ntlm_auth cpw command");
798 * write the stuff to the client
801 if (inst->ntlm_cpw_username) {
802 len = radius_xlat(buf, sizeof(buf) - 2, request, inst->ntlm_cpw_username, NULL, NULL);
810 if (write_all(to_child, buf, len) != len) {
811 REDEBUG("Failed to write username to child");
815 RWDEBUG2("No ntlm_auth username set, passchange will definitely fail!");
818 if (inst->ntlm_cpw_domain) {
819 len = radius_xlat(buf, sizeof(buf) - 2, request, inst->ntlm_cpw_domain, NULL, NULL);
827 if (write_all(to_child, buf, len) != len) {
828 REDEBUG("Failed to write domain to child");
832 RWDEBUG2("No ntlm_auth domain set, username must be full-username to work");
835 /* now the password blobs */
836 len = sprintf(buf, "new-nt-password-blob: ");
837 fr_bin2hex(buf+len, new_nt_password, 516);
838 buf[len+1032] = '\n';
839 buf[len+1033] = '\0';
841 if (write_all(to_child, buf, len) != len) {
842 RDEBUG2("failed to write new password blob to child");
846 len = sprintf(buf, "old-nt-hash-blob: ");
847 fr_bin2hex(buf+len, old_nt_hash, NT_DIGEST_LENGTH);
851 if (write_all(to_child, buf, len) != len) {
852 REDEBUG("Failed to write old hash blob to child");
857 * In current samba versions, failure to supply empty LM password/hash
858 * blobs causes the change to fail.
860 len = sprintf(buf, "new-lm-password-blob: %01032i\n", 0);
861 if (write_all(to_child, buf, len) != len) {
862 REDEBUG("Failed to write dummy LM password to child");
865 len = sprintf(buf, "old-lm-hash-blob: %032i\n", 0);
866 if (write_all(to_child, buf, len) != len) {
867 REDEBUG("Failed to write dummy LM hash to child");
870 if (write_all(to_child, ".\n", 2) != 2) {
871 REDEBUG("Failed to send finish to child");
878 * Read from the child
880 len = radius_readfrom_program(from_child, pid, 10, buf, sizeof(buf));
882 /* radius_readfrom_program will have closed from_child for us */
883 REDEBUG("Failure reading from child");
890 RDEBUG2("ntlm_auth said: %s", buf);
892 child_pid = rad_waitpid(pid, &status);
893 if (child_pid == 0) {
894 REDEBUG("Timeout waiting for child");
897 if (child_pid != pid) {
898 REDEBUG("Abnormal exit status: %s", fr_syserror(errno));
902 if (strstr(buf, "Password-Change: Yes")) {
903 RDEBUG2("ntlm_auth password change succeeded");
907 pmsg = strstr(buf, "Password-Change-Error: ");
909 emsg = strsep(&pmsg, "\n");
911 emsg = "could not find error";
913 REDEBUG("ntlm auth password change failed: %s", emsg);
916 /* safe because these either need closing or are == -1 */
922 } else if (inst->local_cpw) {
923 #ifdef HAVE_OPENSSL_CRYPTO_H
925 * Decrypt the new password blob, add it as a temporary request
926 * variable, xlat the local_cpw string, then remove it
928 * this allows is to write e..g
930 * %{sql:insert into ...}
934 * %{exec:/path/to %{mschap:User-Name} %{MS-CHAP-New-Password}}"
937 VALUE_PAIR *new_pass, *new_hash;
944 uint8_t nt_pass_decrypted[516], old_nt_hash_expected[NT_DIGEST_LENGTH];
948 RDEBUG("Local MS-CHAPv2 password change requires NT-Password attribute");
951 RDEBUG("Doing MS-CHAPv2 password change locally");
957 RC4_set_key(&key, nt_password->vp_length, nt_password->vp_octets);
958 RC4(&key, 516, new_nt_password, nt_pass_decrypted);
962 * 512-N bytes random pad
963 * N bytes password as utf-16-le
964 * 4 bytes - N as big-endian int
966 passlen = nt_pass_decrypted[512];
967 passlen += nt_pass_decrypted[513] << 8;
968 if ((nt_pass_decrypted[514] != 0) ||
969 (nt_pass_decrypted[515] != 0)) {
970 REDEBUG("Decrypted new password blob claims length > 65536, "
971 "probably an invalid NT-Password");
976 * Sanity check - passlen positive and <= 512 if not, crypto has probably gone wrong
979 REDEBUG("Decrypted new password blob claims length %zu > 512, "
980 "probably an invalid NT-Password", passlen);
984 p = nt_pass_decrypted + 512 - passlen;
987 * The new NT hash - this should be preferred over the
988 * cleartext password as it avoids unicode hassles.
990 new_hash = pair_make_request("MS-CHAP-New-NT-Password", NULL, T_OP_EQ);
991 new_hash->vp_length = NT_DIGEST_LENGTH;
992 new_hash->vp_octets = q = talloc_array(new_hash, uint8_t, new_hash->vp_length);
993 fr_md4_calc(q, p, passlen);
996 * Check that nt_password encrypted with new_hash
997 * matches the old_hash value from the client.
999 smbhash(old_nt_hash_expected, nt_password->vp_octets, q);
1000 smbhash(old_nt_hash_expected+8, nt_password->vp_octets+8, q + 7);
1001 if (memcmp(old_nt_hash_expected, old_nt_hash, NT_DIGEST_LENGTH)!=0) {
1002 REDEBUG("Old NT hash value from client does not match our value");
1007 * The new cleartext password, which is utf-16 do some unpleasant vileness
1008 * to turn it into utf8 without pulling in libraries like iconv.
1010 * First pass: get the length of the converted string.
1012 new_pass = pair_make_request("MS-CHAP-New-Cleartext-Password", NULL, T_OP_EQ);
1013 new_pass->vp_length = 0;
1016 while (i < passlen) {
1023 * Gah. nasty. maybe we should just pull in iconv?
1026 new_pass->vp_length++;
1027 } else if (c < 0x7ff) {
1028 new_pass->vp_length += 2;
1030 new_pass->vp_length += 3;
1034 new_pass->vp_strvalue = x = talloc_array(new_pass, char, new_pass->vp_length + 1);
1037 * Second pass: convert the characters from UTF-16 to UTF-8.
1040 while (i < passlen) {
1047 * Gah. nasty. maybe we should just pull in iconv?
1052 } else if (c < 0x7ff) {
1053 *x++ = 0xc0 + (c >> 6);
1054 *x++ = 0x80 + (c & 0x3f);
1057 *x++ = 0xe0 + (c >> 12);
1058 *x++ = 0x80 + ((c>>6) & 0x3f);
1059 *x++ = 0x80 + (c & 0x3f);
1065 /* Perform the xlat */
1066 result_len = radius_xlat(result, sizeof(result), request, inst->local_cpw, NULL, NULL);
1067 if (result_len < 0){
1069 } else if (result_len == 0) {
1070 REDEBUG("Local MS-CHAPv2 password change - xlat didn't give any result, assuming failure");
1074 RDEBUG("MS-CHAPv2 password change succeeded: %s", result);
1077 * Update the NT-Password attribute with the new hash this lets us
1078 * fall through to the authentication code using the new hash,
1081 fr_pair_value_memcpy(nt_password, new_hash->vp_octets, new_hash->vp_length);
1084 * Rock on! password change succeeded.
1088 REDEBUG("Local MS-CHAPv2 password changes require OpenSSL support");
1092 REDEBUG("MS-CHAPv2 password change not configured");
1099 * Do the MS-CHAP stuff.
1101 * This function is here so that all of the MS-CHAP related
1102 * authentication is in one place, and we can perhaps later replace
1103 * it with code to call winbindd, or something similar.
1105 static int CC_HINT(nonnull (1, 2, 4, 5 ,6)) do_mschap(rlm_mschap_t *inst, REQUEST *request, VALUE_PAIR *password,
1106 uint8_t const *challenge, uint8_t const *response,
1107 uint8_t nthashhash[NT_DIGEST_LENGTH], MSCHAP_AUTH_METHOD method)
1109 uint8_t calculated[24];
1111 memset(nthashhash, 0, NT_DIGEST_LENGTH);
1115 * Do normal authentication.
1119 * No password: can't do authentication.
1122 REDEBUG("FAILED: No NT/LM-Password. Cannot perform authentication");
1126 smbdes_mschap(password->vp_octets, challenge, calculated);
1127 if (rad_digest_cmp(response, calculated, 24) != 0) {
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.
1136 if (!password->da->vendor &&
1137 (password->da->attr == PW_NT_PASSWORD)) {
1138 fr_md4_calc(nthashhash, password->vp_octets, MD4_DIGEST_LENGTH);
1145 case AUTH_NTLMAUTH_EXEC: {
1151 * Run the program, and expect that we get 16
1153 result = radius_exec_program(request, buffer, sizeof(buffer), NULL, request, inst->ntlm_auth, NULL,
1154 true, true, inst->ntlm_auth_timeout);
1159 * look for "Password expired", or "Must change password".
1161 if (strcasestr(buffer, "Password expired") ||
1162 strcasestr(buffer, "Must change password")) {
1163 REDEBUG2("%s", buffer);
1167 if (strcasestr(buffer, "Account locked out") ||
1168 strcasestr(buffer, "0xC0000234")) {
1169 REDEBUG2("%s", buffer);
1173 if (strcasestr(buffer, "Account disabled") ||
1174 strcasestr(buffer, "0xC0000072")) {
1175 REDEBUG2("%s", buffer);
1179 RDEBUG2("External script failed");
1180 p = strchr(buffer, '\n');
1183 REDEBUG("External script says: %s", buffer);
1188 * Parse the answer as an nthashhash.
1190 * ntlm_auth currently returns:
1191 * NT_KEY: 000102030405060708090a0b0c0d0e0f
1193 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
1194 REDEBUG("Invalid output from ntlm_auth: expecting 'NT_KEY: ' prefix");
1199 * Check the length. It should be at least 32, with an LF at the end.
1201 len = strlen(buffer + 8);
1203 REDEBUG2("Invalid output from ntlm_auth: NT_KEY too short, expected 32 bytes got %zu bytes",
1210 * Update the NT hash hash, from the NT key.
1212 if (fr_hex2bin(nthashhash, NT_DIGEST_LENGTH, buffer + 8, len) != NT_DIGEST_LENGTH) {
1213 REDEBUG("Invalid output from ntlm_auth: NT_KEY has non-hex values");
1219 #ifdef WITH_AUTH_WINBIND
1221 * Process auth via the wbclient library
1224 return do_auth_wbclient(inst, request, challenge, response, nthashhash);
1227 /* We should never reach this line */
1229 RERROR("Internal error: Unknown mschap auth method (%d)", method);
1238 * Data for the hashes.
1240 static const uint8_t SHSpad1[40] =
1241 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1242 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1243 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1244 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
1246 static const uint8_t SHSpad2[40] =
1247 { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1248 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1249 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1250 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
1252 static const uint8_t magic1[27] =
1253 { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
1254 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
1255 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
1257 static const uint8_t magic2[84] =
1258 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
1259 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
1260 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1261 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
1262 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
1263 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
1264 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1265 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
1266 0x6b, 0x65, 0x79, 0x2e };
1268 static const uint8_t magic3[84] =
1269 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
1270 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
1271 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1272 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
1273 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
1274 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
1275 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
1276 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
1277 0x6b, 0x65, 0x79, 0x2e };
1280 static void mppe_GetMasterKey(uint8_t const *nt_hashhash,uint8_t const *nt_response,
1284 fr_sha1_ctx Context;
1286 fr_sha1_init(&Context);
1287 fr_sha1_update(&Context,nt_hashhash,NT_DIGEST_LENGTH);
1288 fr_sha1_update(&Context,nt_response,24);
1289 fr_sha1_update(&Context,magic1,27);
1290 fr_sha1_final(digest,&Context);
1292 memcpy(masterkey,digest,16);
1296 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
1297 int keylen,int issend)
1301 fr_sha1_ctx Context;
1303 memset(digest,0,20);
1311 fr_sha1_init(&Context);
1312 fr_sha1_update(&Context,masterkey,16);
1313 fr_sha1_update(&Context,SHSpad1,40);
1314 fr_sha1_update(&Context,s,84);
1315 fr_sha1_update(&Context,SHSpad2,40);
1316 fr_sha1_final(digest,&Context);
1318 memcpy(sesskey,digest,keylen);
1322 static void mppe_chap2_get_keys128(uint8_t const *nt_hashhash,uint8_t const *nt_response,
1323 uint8_t *sendkey,uint8_t *recvkey)
1325 uint8_t masterkey[16];
1327 mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
1329 mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
1330 mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
1334 * Generate MPPE keys.
1336 static void mppe_chap2_gen_keys128(uint8_t const *nt_hashhash,uint8_t const *response,
1337 uint8_t *sendkey,uint8_t *recvkey)
1339 uint8_t enckey1[16];
1340 uint8_t enckey2[16];
1342 mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
1345 * dictionary.microsoft defines these attributes as
1346 * 'encrypt=2'. The functions in src/lib/radius.c will
1347 * take care of encrypting/decrypting them as appropriate,
1348 * so that we don't have to.
1350 memcpy (sendkey, enckey1, 16);
1351 memcpy (recvkey, enckey2, 16);
1356 * mod_authorize() - authorize user if we can authenticate
1357 * it later. Add Auth-Type attribute if present in module
1358 * configuration (usually Auth-Type must be "MS-CHAP")
1360 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void * instance, REQUEST *request)
1362 rlm_mschap_t *inst = instance;
1363 VALUE_PAIR *challenge = NULL;
1365 challenge = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
1367 return RLM_MODULE_NOOP;
1370 if (!fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY) &&
1371 !fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY) &&
1372 !fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_CPW, VENDORPEC_MICROSOFT, TAG_ANY)) {
1373 RDEBUG2("Found MS-CHAP-Challenge, but no MS-CHAP response or change-password");
1374 return RLM_MODULE_NOOP;
1377 if (fr_pair_find_by_num(request->config, PW_AUTH_TYPE, 0, TAG_ANY)) {
1378 RWDEBUG2("Auth-Type already set. Not setting to MS-CHAP");
1379 return RLM_MODULE_NOOP;
1382 RDEBUG2("Found MS-CHAP attributes. Setting 'Auth-Type = %s'", inst->xlat_name);
1385 * Set Auth-Type to MS-CHAP. The authentication code
1386 * will take care of turning cleartext passwords into
1389 if (!pair_make_config("Auth-Type", inst->auth_type, T_OP_EQ)) {
1390 return RLM_MODULE_FAIL;
1393 return RLM_MODULE_OK;
1396 static rlm_rcode_t mschap_error(rlm_mschap_t *inst, REQUEST *request, unsigned char ident,
1397 int mschap_result, int mschap_version, VALUE_PAIR *smb_ctrl)
1399 rlm_rcode_t rcode = RLM_MODULE_OK;
1402 char const *message = NULL;
1405 char new_challenge[33], buffer[128];
1408 if ((mschap_result == -648) ||
1409 ((mschap_result == 0) &&
1410 (smb_ctrl && ((smb_ctrl->vp_integer & ACB_PW_EXPIRED) != 0)))) {
1411 REDEBUG("Password has expired. User should retry authentication");
1415 * A password change is NOT a retry! We MUST have retry=0 here.
1418 message = "Password expired";
1419 rcode = RLM_MODULE_REJECT;
1422 * Account is disabled.
1424 * They're found, but they don't exist, so we
1425 * return 'not found'.
1427 } else if ((mschap_result == -691) ||
1428 (smb_ctrl && (((smb_ctrl->vp_integer & ACB_DISABLED) != 0) ||
1429 ((smb_ctrl->vp_integer & (ACB_NORMAL|ACB_WSTRUST)) == 0)))) {
1430 REDEBUG("SMB-Account-Ctrl (or ntlm_auth) "
1431 "says that the account is disabled, "
1432 "or is not a normal or workstation trust account");
1435 message = "Account disabled";
1436 rcode = RLM_MODULE_NOTFOUND;
1439 * User is locked out.
1441 } else if ((mschap_result == -647) ||
1442 (smb_ctrl && ((smb_ctrl->vp_integer & ACB_AUTOLOCK) != 0))) {
1443 REDEBUG("SMB-Account-Ctrl (or ntlm_auth) "
1444 "says that the account is locked out");
1447 message = "Account locked out";
1448 rcode = RLM_MODULE_USERLOCK;
1450 } else if (mschap_result < 0) {
1451 REDEBUG("MS-CHAP2-Response is incorrect");
1453 retry = inst->allow_retry;
1454 message = "Authentication failed";
1455 rcode = RLM_MODULE_REJECT;
1458 if (rcode == RLM_MODULE_OK) return RLM_MODULE_OK;
1460 switch (mschap_version) {
1462 for (p = new_challenge, i = 0; i < 2; i++) p += snprintf(p, 9, "%08x", fr_rand());
1463 snprintf(buffer, sizeof(buffer), "E=%i R=%i C=%s V=2",
1464 error, retry, new_challenge);
1468 for (p = new_challenge, i = 0; i < 4; i++) p += snprintf(p, 9, "%08x", fr_rand());
1469 snprintf(buffer, sizeof(buffer), "E=%i R=%i C=%s V=3 M=%s",
1470 error, retry, new_challenge, message);
1476 mschap_add_reply(request, ident, "MS-CHAP-Error", buffer, strlen(buffer));
1482 * mod_authenticate() - authenticate user based on given
1483 * attributes and configuration.
1484 * We will try to find out password in configuration
1485 * or in configured passwd file.
1486 * If one is found we will check paraneters given by NAS.
1488 * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
1490 * PAP: PW_USER_PASSWORD or
1491 * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
1492 * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
1493 * In case of password mismatch or locked account we MAY return
1494 * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
1495 * If MS-CHAP2 succeeds we MUST return
1496 * PW_MSCHAP2_SUCCESS
1498 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
1500 rlm_mschap_t *inst = instance;
1501 VALUE_PAIR *challenge = NULL;
1502 VALUE_PAIR *response = NULL;
1503 VALUE_PAIR *cpw = NULL;
1504 VALUE_PAIR *password = NULL;
1505 VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
1506 VALUE_PAIR *username;
1507 uint8_t nthashhash[NT_DIGEST_LENGTH];
1509 char const *username_string;
1510 int mschap_version = 0;
1512 MSCHAP_AUTH_METHOD auth_method;
1515 * If we have ntlm_auth configured, use it unless told
1518 auth_method = inst->method;
1521 * If we have an ntlm_auth configuration, then we may
1522 * want to suppress it.
1524 if (auth_method != AUTH_INTERNAL) {
1525 VALUE_PAIR *vp = fr_pair_find_by_num(request->config, PW_MS_CHAP_USE_NTLM_AUTH, 0, TAG_ANY);
1526 if (vp && vp->vp_integer == 0) auth_method = AUTH_INTERNAL;
1530 * Find the SMB-Account-Ctrl attribute, or the
1531 * SMB-Account-Ctrl-Text attribute.
1533 smb_ctrl = fr_pair_find_by_num(request->config, PW_SMB_ACCOUNT_CTRL, 0, TAG_ANY);
1535 password = fr_pair_find_by_num(request->config, PW_SMB_ACCOUNT_CTRL_TEXT, 0, TAG_ANY);
1537 smb_ctrl = pair_make_config("SMB-Account-CTRL", "0", T_OP_SET);
1539 smb_ctrl->vp_integer = pdb_decode_acct_ctrl(password->vp_strvalue);
1545 * We're configured to do MS-CHAP authentication.
1546 * and account control information exists. Enforce it.
1550 * Password is not required.
1552 if ((smb_ctrl->vp_integer & ACB_PWNOTREQ) != 0) {
1553 RDEBUG2("SMB-Account-Ctrl says no password is required");
1554 return RLM_MODULE_OK;
1559 * Decide how to get the passwords.
1561 password = fr_pair_find_by_num(request->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY);
1564 * We need an NT-Password.
1566 nt_password = fr_pair_find_by_num(request->config, PW_NT_PASSWORD, 0, TAG_ANY);
1568 VERIFY_VP(nt_password);
1570 switch (nt_password->vp_length) {
1571 case NT_DIGEST_LENGTH:
1572 RDEBUG2("Found NT-Password");
1578 RWDEBUG("NT-Password has not been normalized by the 'pap' module (likely still in hex format). "
1579 "Authentication may fail");
1584 RWDEBUG("NT-Password found but incorrect length, expected " STRINGIFY(NT_DIGEST_LENGTH)
1585 " bytes got %zu bytes. Authentication may fail", nt_password->vp_length);
1592 * ... or a Cleartext-Password, which we now transform into an NT-Password
1598 RDEBUG2("Found Cleartext-Password, hashing to create NT-Password");
1599 nt_password = pair_make_config("NT-Password", NULL, T_OP_EQ);
1601 RERROR("No memory");
1602 return RLM_MODULE_FAIL;
1604 nt_password->vp_length = NT_DIGEST_LENGTH;
1605 nt_password->vp_octets = p = talloc_array(nt_password, uint8_t, nt_password->vp_length);
1607 if (mschap_ntpwdhash(p, password->vp_strvalue) < 0) {
1608 RERROR("Failed generating NT-Password");
1609 return RLM_MODULE_FAIL;
1611 } else if (auth_method == AUTH_INTERNAL) {
1612 RWDEBUG2("No Cleartext-Password configured. Cannot create NT-Password");
1617 * Or an LM-Password.
1619 lm_password = fr_pair_find_by_num(request->config, PW_LM_PASSWORD, 0, TAG_ANY);
1621 VERIFY_VP(lm_password);
1623 switch (lm_password->vp_length) {
1624 case LM_DIGEST_LENGTH:
1625 RDEBUG2("Found LM-Password");
1631 RWDEBUG("LM-Password has not been normalized by the 'pap' module (likely still in hex format). "
1632 "Authentication may fail");
1637 RWDEBUG("LM-Password found but incorrect length, expected " STRINGIFY(LM_DIGEST_LENGTH)
1638 " bytes got %zu bytes. Authentication may fail", lm_password->vp_length);
1644 * ... or a Cleartext-Password, which we now transform into an LM-Password
1648 RDEBUG2("Found Cleartext-Password, hashing to create LM-Password");
1649 lm_password = pair_make_config("LM-Password", NULL, T_OP_EQ);
1651 RERROR("No memory");
1655 lm_password->vp_length = LM_DIGEST_LENGTH;
1656 lm_password->vp_octets = p = talloc_array(lm_password, uint8_t, lm_password->vp_length);
1657 smbdes_lmpwdhash(password->vp_strvalue, p);
1660 * Only complain if we don't have NT-Password
1662 } else if ((auth_method == AUTH_INTERNAL) && !nt_password) {
1663 RWDEBUG2("No Cleartext-Password configured. Cannot create LM-Password");
1667 cpw = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_CPW, VENDORPEC_MICROSOFT, TAG_ANY);
1670 * mschap2 password change request
1671 * we cheat - first decode and execute the passchange
1672 * we then extract the response, add it into the request
1673 * then jump into mschap2 auth with the chal/resp
1675 uint8_t new_nt_encrypted[516], old_nt_encrypted[NT_DIGEST_LENGTH];
1676 VALUE_PAIR *nt_enc=NULL;
1677 int seq, new_nt_enc_len;
1680 RDEBUG("MS-CHAPv2 password change request received");
1682 if (cpw->vp_length != 68) {
1683 REDEBUG("MS-CHAP2-CPW has the wrong format: length %zu != 68", cpw->vp_length);
1684 return RLM_MODULE_INVALID;
1687 if (cpw->vp_octets[0] != 7) {
1688 REDEBUG("MS-CHAP2-CPW has the wrong format: code %d != 7", cpw->vp_octets[0]);
1689 return RLM_MODULE_INVALID;
1693 * look for the new (encrypted) password
1694 * bah stupid composite attributes
1695 * we're expecting 3 attributes with the leading bytes
1696 * 06:<mschapid>:00:01:<1st chunk>
1697 * 06:<mschapid>:00:02:<2nd chunk>
1698 * 06:<mschapid>:00:03:<3rd chunk>
1701 for (seq = 1; seq < 4; seq++) {
1705 for (nt_enc = fr_cursor_init(&cursor, &request->packet->vps);
1707 nt_enc = fr_cursor_next(&cursor)) {
1708 if (nt_enc->da->vendor != VENDORPEC_MICROSOFT)
1711 if (nt_enc->da->attr != PW_MSCHAP_NT_ENC_PW)
1714 if (nt_enc->vp_length < 4) {
1715 REDEBUG("MS-CHAP-NT-Enc-PW with invalid format");
1716 return RLM_MODULE_INVALID;
1719 if (nt_enc->vp_octets[0] != 6) {
1720 REDEBUG("MS-CHAP-NT-Enc-PW with invalid format");
1721 return RLM_MODULE_INVALID;
1724 if ((nt_enc->vp_octets[2] == 0) && (nt_enc->vp_octets[3] == seq)) {
1731 REDEBUG("Could not find MS-CHAP-NT-Enc-PW w/ sequence number %d", seq);
1732 return RLM_MODULE_INVALID;
1735 if ((new_nt_enc_len + nt_enc->vp_length - 4) > sizeof(new_nt_encrypted)) {
1736 REDEBUG("Unpacked MS-CHAP-NT-Enc-PW length > 516");
1737 return RLM_MODULE_INVALID;
1740 memcpy(new_nt_encrypted + new_nt_enc_len, nt_enc->vp_octets + 4, nt_enc->vp_length - 4);
1741 new_nt_enc_len += nt_enc->vp_length - 4;
1744 if (new_nt_enc_len != 516) {
1745 REDEBUG("Unpacked MS-CHAP-NT-Enc-PW length != 516");
1746 return RLM_MODULE_INVALID;
1750 * RFC 2548 is confusing here
1755 * 16 octets - old hash encrypted with new hash
1756 * 24 octets - peer challenge
1758 * 16 octets - peer challenge
1759 * 8 octets - reserved
1760 * 24 octets - nt response
1761 * 2 octets - flags (ignored)
1764 memcpy(old_nt_encrypted, cpw->vp_octets + 2, sizeof(old_nt_encrypted));
1766 RDEBUG2("Password change payload valid");
1768 /* perform the actual password change */
1769 if (do_mschap_cpw(inst, request, nt_password, new_nt_encrypted, old_nt_encrypted, auth_method) < 0) {
1772 REDEBUG("Password change failed");
1774 snprintf(buffer, sizeof(buffer), "E=709 R=0 M=Password change failed");
1775 mschap_add_reply(request, cpw->vp_octets[1], "MS-CHAP-Error", buffer, strlen(buffer));
1777 return RLM_MODULE_REJECT;
1779 RDEBUG("Password change successful");
1782 * Clear any expiry bit so the user can now login;
1783 * obviously the password change action will need
1784 * to have cleared this bit in the config/SQL/wherever
1786 if (smb_ctrl && smb_ctrl->vp_integer & ACB_PW_EXPIRED) {
1787 RDEBUG("Clearing expiry bit in SMB-Acct-Ctrl to allow authentication");
1788 smb_ctrl->vp_integer &= ~ACB_PW_EXPIRED;
1792 * Extract the challenge & response from the end of the password
1793 * change, add them into the request and then continue with
1794 * the authentication
1796 response = radius_pair_create(request->packet, &request->packet->vps,
1797 PW_MSCHAP2_RESPONSE,
1798 VENDORPEC_MICROSOFT);
1799 response->vp_length = 50;
1800 response->vp_octets = p = talloc_array(response, uint8_t, response->vp_length);
1803 p[0] = cpw->vp_octets[1];
1805 /* peer challenge and client NT response */
1806 memcpy(p + 2, cpw->vp_octets + 18, 48);
1809 challenge = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
1811 REDEBUG("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1812 return RLM_MODULE_REJECT;
1816 * We also require an MS-CHAP-Response.
1818 response = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
1821 * MS-CHAP-Response, means MS-CHAPv1
1829 * MS-CHAPv1 challenges are 8 octets.
1831 if (challenge->vp_length < 8) {
1832 REDEBUG("MS-CHAP-Challenge has the wrong format");
1833 return RLM_MODULE_INVALID;
1837 * Responses are 50 octets.
1839 if (response->vp_length < 50) {
1840 REDEBUG("MS-CHAP-Response has the wrong format");
1841 return RLM_MODULE_INVALID;
1845 * We are doing MS-CHAP. Calculate the MS-CHAP
1848 if (response->vp_octets[1] & 0x01) {
1849 RDEBUG2("Client is using MS-CHAPv1 with NT-Password");
1850 password = nt_password;
1853 RDEBUG2("Client is using MS-CHAPv1 with LM-Password");
1854 password = lm_password;
1859 * Do the MS-CHAP authentication.
1861 mschap_result = do_mschap(inst, request, password, challenge->vp_octets,
1862 response->vp_octets + offset, nthashhash, auth_method);
1864 * Check for errors, and add MSCHAP-Error if necessary.
1866 rcode = mschap_error(inst, request, *response->vp_octets,
1867 mschap_result, mschap_version, smb_ctrl);
1868 if (rcode != RLM_MODULE_OK) return rcode;
1869 } else if ((response = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_RESPONSE,
1870 VENDORPEC_MICROSOFT, TAG_ANY)) != NULL) {
1871 uint8_t mschapv1_challenge[16];
1872 VALUE_PAIR *name_attr, *response_name;
1878 * MS-CHAPv2 challenges are 16 octets.
1880 if (challenge->vp_length < 16) {
1881 REDEBUG("MS-CHAP-Challenge has the wrong format");
1882 return RLM_MODULE_INVALID;
1886 * Responses are 50 octets.
1888 if (response->vp_length < 50) {
1889 REDEBUG("MS-CHAP-Response has the wrong format");
1890 return RLM_MODULE_INVALID;
1894 * We also require a User-Name
1896 username = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
1898 REDEBUG("We require a User-Name for MS-CHAPv2");
1899 return RLM_MODULE_INVALID;
1903 * Check for MS-CHAP-User-Name and if found, use it
1904 * to construct the MSCHAPv1 challenge. This is
1905 * set by rlm_eap_mschap to the MS-CHAP Response
1906 * packet Name field.
1908 * We prefer this to the User-Name in the
1911 response_name = fr_pair_find_by_num(request->packet->vps, PW_MS_CHAP_USER_NAME, 0, TAG_ANY);
1912 if (response_name) {
1913 name_attr = response_name;
1915 name_attr = username;
1919 * with_ntdomain_hack moved here, too.
1921 if ((username_string = strchr(name_attr->vp_strvalue, '\\')) != NULL) {
1922 if (inst->with_ntdomain_hack) {
1925 RWDEBUG2("NT Domain delimeter found, should with_ntdomain_hack of been enabled?");
1926 username_string = name_attr->vp_strvalue;
1929 username_string = name_attr->vp_strvalue;
1932 if (response_name && ((username->vp_length != response_name->vp_length) ||
1933 (strncasecmp(username->vp_strvalue, response_name->vp_strvalue, username->vp_length) != 0))) {
1934 RWDEBUG("User-Name (%s) is not the same as MS-CHAP Name (%s) from EAP-MSCHAPv2",
1935 username->vp_strvalue, response_name->vp_strvalue);
1940 * No "known good" NT-Password attribute. Try to do
1941 * OpenDirectory authentication.
1943 * If OD determines the user is an AD user it will return noop, which
1944 * indicates the auth process should continue directly to AD.
1945 * Otherwise OD will determine auth success/fail.
1947 if (!nt_password && inst->open_directory) {
1948 RDEBUG2("No NT-Password configured. Trying OpenDirectory Authentication");
1949 int odStatus = od_mschap_auth(request, challenge, username);
1950 if (odStatus != RLM_MODULE_NOOP) {
1956 * The old "mschapv2" function has been moved to
1959 * MS-CHAPv2 takes some additional data to create an
1960 * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1962 RDEBUG2("Creating challenge hash with username: %s", username_string);
1963 mschap_challenge_hash(response->vp_octets + 2, /* peer challenge */
1964 challenge->vp_octets, /* our challenge */
1965 username_string, /* user name */
1966 mschapv1_challenge); /* resulting challenge */
1968 RDEBUG2("Client is using MS-CHAPv2");
1969 mschap_result = do_mschap(inst, request, nt_password, mschapv1_challenge,
1970 response->vp_octets + 26, nthashhash, auth_method);
1971 rcode = mschap_error(inst, request, *response->vp_octets,
1972 mschap_result, mschap_version, smb_ctrl);
1973 if (rcode != RLM_MODULE_OK) return rcode;
1975 #ifdef WITH_AUTH_WINBIND
1976 if (inst->wb_retry_with_normalised_username) {
1977 if ((response_name = fr_pair_find_by_num(request->packet->vps, PW_MS_CHAP_USER_NAME, 0, TAG_ANY))) {
1978 if (strcmp(username_string, response_name->vp_strvalue)) {
1979 RDEBUG2("Changing username %s to %s", username_string, response_name->vp_strvalue);
1980 username_string = response_name->vp_strvalue;
1986 mschap_auth_response(username_string, /* without the domain */
1987 nthashhash, /* nt-hash-hash */
1988 response->vp_octets + 26, /* peer response */
1989 response->vp_octets + 2, /* peer challenge */
1990 challenge->vp_octets, /* our challenge */
1991 msch2resp); /* calculated MPPE key */
1992 mschap_add_reply(request, *response->vp_octets, "MS-CHAP2-Success", msch2resp, 42);
1994 } else { /* Neither CHAPv1 or CHAPv2 response: die */
1995 REDEBUG("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1996 return RLM_MODULE_INVALID;
1999 /* now create MPPE attributes */
2000 if (inst->use_mppe) {
2001 uint8_t mppe_sendkey[34];
2002 uint8_t mppe_recvkey[34];
2004 if (mschap_version == 1) {
2005 RDEBUG2("adding MS-CHAPv1 MPPE keys");
2006 memset(mppe_sendkey, 0, 32);
2008 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
2012 * According to RFC 2548 we
2013 * should send NT hash. But in
2014 * practice it doesn't work.
2015 * Instead, we should send nthashhash
2017 * This is an error in RFC 2548.
2020 * do_mschap cares to zero nthashhash if NT hash
2023 memcpy(mppe_sendkey + 8, nthashhash, NT_DIGEST_LENGTH);
2024 mppe_add_reply(request, "MS-CHAP-MPPE-Keys", mppe_sendkey, 24);
2026 } else if (mschap_version == 2) {
2027 RDEBUG2("Adding MS-CHAPv2 MPPE keys");
2028 mppe_chap2_gen_keys128(nthashhash, response->vp_octets + 26, mppe_sendkey, mppe_recvkey);
2030 mppe_add_reply(request, "MS-MPPE-Recv-Key", mppe_recvkey, 16);
2031 mppe_add_reply(request, "MS-MPPE-Send-Key", mppe_sendkey, 16);
2034 pair_make_reply("MS-MPPE-Encryption-Policy",
2035 (inst->require_encryption) ? "0x00000002":"0x00000001", T_OP_EQ);
2036 pair_make_reply("MS-MPPE-Encryption-Types",
2037 (inst->require_strong) ? "0x00000004":"0x00000006", T_OP_EQ);
2038 } /* else we weren't asked to use MPPE */
2040 return RLM_MODULE_OK;
2044 extern module_t rlm_mschap;
2045 module_t rlm_mschap = {
2046 .magic = RLM_MODULE_INIT,
2049 .inst_size = sizeof(rlm_mschap_t),
2050 .config = module_config,
2051 .bootstrap = mod_bootstrap,
2052 .instantiate = mod_instantiate,
2053 .detach = mod_detach,
2055 [MOD_AUTHENTICATE] = mod_authenticate,
2056 [MOD_AUTHORIZE] = mod_authorize