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 },
564 { "use_open_directory", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_mschap_t, open_directory), "yes" },
566 CONF_PARSER_TERMINATOR
570 static int mod_bootstrap(CONF_SECTION *conf, void *instance)
573 rlm_mschap_t *inst = instance;
576 * Create the dynamic translation.
578 name = cf_section_name2(conf);
579 if (!name) name = cf_section_name1(conf);
580 inst->xlat_name = name;
581 xlat_register(inst->xlat_name, mschap_xlat, NULL, inst);
587 * Create instance for our module. Allocate space for
588 * instance structure and read configuration parameters
590 static int mod_instantiate(CONF_SECTION *conf, void *instance)
592 rlm_mschap_t *inst = instance;
595 * For backwards compatibility
597 if (!dict_valbyname(PW_AUTH_TYPE, 0, inst->xlat_name)) {
598 inst->auth_type = "MS-CHAP";
600 inst->auth_type = inst->xlat_name;
606 inst->method = AUTH_INTERNAL;
608 if (inst->wb_username) {
609 #ifdef WITH_AUTH_WINBIND
610 inst->method = AUTH_WBCLIENT;
612 inst->wb_pool = fr_connection_pool_module_init(conf, inst, mod_conn_create, NULL, NULL);
613 if (!inst->wb_pool) {
614 cf_log_err_cs(conf, "Unable to initialise winbind connection pool");
618 cf_log_err_cs(conf, "'winbind' auth not enabled at compiled time");
623 /* preserve existing behaviour: this option overrides all */
624 if (inst->ntlm_auth) {
625 inst->method = AUTH_NTLMAUTH_EXEC;
628 switch (inst->method) {
630 DEBUG("rlm_mschap (%s): using internal authentication", inst->xlat_name);
632 case AUTH_NTLMAUTH_EXEC:
633 DEBUG("rlm_mschap (%s): authenticating by calling 'ntlm_auth'", inst->xlat_name);
635 #ifdef WITH_AUTH_WINBIND
637 DEBUG("rlm_mschap (%s): authenticating directly to winbind", inst->xlat_name);
643 * Check ntlm_auth_timeout is sane
645 if (!inst->ntlm_auth_timeout) {
646 inst->ntlm_auth_timeout = EXEC_TIMEOUT;
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);
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);
665 static int mod_detach(UNUSED void *instance)
667 #ifdef WITH_AUTH_WINBIND
668 rlm_mschap_t *inst = instance;
670 fr_connection_pool_free(inst->wb_pool);
677 * add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
678 * attribute to reply packet
680 void mschap_add_reply(REQUEST *request, unsigned char ident,
681 char const *name, char const *value, size_t len)
685 vp = pair_make_reply(name, NULL, T_OP_EQ);
687 REDEBUG("Failed to create attribute %s: %s", name, fr_strerror());
691 /* Account for the ident byte */
692 vp->vp_length = len + 1;
693 if (vp->da->type == PW_TYPE_STRING) {
696 vp->vp_strvalue = p = talloc_array(vp, char, vp->vp_length + 1);
697 p[vp->vp_length] = '\0'; /* Always \0 terminate */
699 memcpy(p + 1, value, len);
703 vp->vp_octets = p = talloc_array(vp, uint8_t, vp->vp_length);
705 memcpy(p + 1, value, len);
710 * Add MPPE attributes to the reply.
712 static void mppe_add_reply(REQUEST *request, char const* name, uint8_t const * value, size_t len)
716 vp = pair_make_reply(name, NULL, T_OP_EQ);
718 REDEBUG("mppe_add_reply failed to create attribute %s: %s", name, fr_strerror());
722 fr_pair_value_memcpy(vp, value, len);
725 static int write_all(int fd, char const *buf, int len) {
729 rv = write(fd, buf+done, len-done);
738 * Perform an MS-CHAP2 password change
741 static int CC_HINT(nonnull (1, 2, 4, 5)) do_mschap_cpw(rlm_mschap_t *inst,
743 #ifdef HAVE_OPENSSL_CRYPTO_H
744 VALUE_PAIR *nt_password,
746 UNUSED VALUE_PAIR *nt_password,
748 uint8_t *new_nt_password,
749 uint8_t *old_nt_hash,
750 MSCHAP_AUTH_METHOD method)
752 if (inst->ntlm_cpw && method != AUTH_INTERNAL) {
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:
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
766 * ...and it should then print out
768 * Password-Change: Yes
772 * Password-Change: No
773 * Password-Change-Error: blah
778 pid_t pid, child_pid;
784 RDEBUG("Doing MS-CHAPv2 password change via ntlm_auth helper");
787 * Start up ntlm_auth with a pipe on stdin and stdout
790 pid = radius_start_program(inst->ntlm_cpw, request, true, &to_child, &from_child, NULL, false);
792 REDEBUG("could not exec ntlm_auth cpw command");
797 * write the stuff to the client
800 if (inst->ntlm_cpw_username) {
801 len = radius_xlat(buf, sizeof(buf) - 2, request, inst->ntlm_cpw_username, NULL, NULL);
809 if (write_all(to_child, buf, len) != len) {
810 REDEBUG("Failed to write username to child");
814 RWDEBUG2("No ntlm_auth username set, passchange will definitely fail!");
817 if (inst->ntlm_cpw_domain) {
818 len = radius_xlat(buf, sizeof(buf) - 2, request, inst->ntlm_cpw_domain, NULL, NULL);
826 if (write_all(to_child, buf, len) != len) {
827 REDEBUG("Failed to write domain to child");
831 RWDEBUG2("No ntlm_auth domain set, username must be full-username to work");
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';
840 if (write_all(to_child, buf, len) != len) {
841 RDEBUG2("failed to write new password blob to child");
845 len = sprintf(buf, "old-nt-hash-blob: ");
846 fr_bin2hex(buf+len, old_nt_hash, NT_DIGEST_LENGTH);
850 if (write_all(to_child, buf, len) != len) {
851 REDEBUG("Failed to write old hash blob to child");
856 * In current samba versions, failure to supply empty LM password/hash
857 * blobs causes the change to fail.
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");
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");
869 if (write_all(to_child, ".\n", 2) != 2) {
870 REDEBUG("Failed to send finish to child");
877 * Read from the child
879 len = radius_readfrom_program(from_child, pid, 10, buf, sizeof(buf));
881 /* radius_readfrom_program will have closed from_child for us */
882 REDEBUG("Failure reading from child");
889 RDEBUG2("ntlm_auth said: %s", buf);
891 child_pid = rad_waitpid(pid, &status);
892 if (child_pid == 0) {
893 REDEBUG("Timeout waiting for child");
896 if (child_pid != pid) {
897 REDEBUG("Abnormal exit status: %s", fr_syserror(errno));
901 if (strstr(buf, "Password-Change: Yes")) {
902 RDEBUG2("ntlm_auth password change succeeded");
906 pmsg = strstr(buf, "Password-Change-Error: ");
908 emsg = strsep(&pmsg, "\n");
910 emsg = "could not find error";
912 REDEBUG("ntlm auth password change failed: %s", emsg);
915 /* safe because these either need closing or are == -1 */
921 } else if (inst->local_cpw) {
922 #ifdef HAVE_OPENSSL_CRYPTO_H
924 * Decrypt the new password blob, add it as a temporary request
925 * variable, xlat the local_cpw string, then remove it
927 * this allows is to write e..g
929 * %{sql:insert into ...}
933 * %{exec:/path/to %{mschap:User-Name} %{MS-CHAP-New-Password}}"
936 VALUE_PAIR *new_pass, *new_hash;
943 uint8_t nt_pass_decrypted[516], old_nt_hash_expected[NT_DIGEST_LENGTH];
947 RDEBUG("Local MS-CHAPv2 password change requires NT-Password attribute");
950 RDEBUG("Doing MS-CHAPv2 password change locally");
956 RC4_set_key(&key, nt_password->vp_length, nt_password->vp_octets);
957 RC4(&key, 516, new_nt_password, nt_pass_decrypted);
961 * 512-N bytes random pad
962 * N bytes password as utf-16-le
963 * 4 bytes - N as big-endian int
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");
975 * Sanity check - passlen positive and <= 512 if not, crypto has probably gone wrong
978 REDEBUG("Decrypted new password blob claims length %zu > 512, "
979 "probably an invalid NT-Password", passlen);
983 p = nt_pass_decrypted + 512 - passlen;
986 * The new NT hash - this should be preferred over the
987 * cleartext password as it avoids unicode hassles.
989 new_hash = pair_make_request("MS-CHAP-New-NT-Password", NULL, T_OP_EQ);
990 new_hash->vp_length = NT_DIGEST_LENGTH;
991 new_hash->vp_octets = q = talloc_array(new_hash, uint8_t, new_hash->vp_length);
992 fr_md4_calc(q, p, passlen);
995 * Check that nt_password encrypted with new_hash
996 * matches the old_hash value from the client.
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");
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.
1009 * First pass: get the length of the converted string.
1011 new_pass = pair_make_request("MS-CHAP-New-Cleartext-Password", NULL, T_OP_EQ);
1012 new_pass->vp_length = 0;
1015 while (i < passlen) {
1022 * Gah. nasty. maybe we should just pull in iconv?
1025 new_pass->vp_length++;
1026 } else if (c < 0x7ff) {
1027 new_pass->vp_length += 2;
1029 new_pass->vp_length += 3;
1033 new_pass->vp_strvalue = x = talloc_array(new_pass, char, new_pass->vp_length + 1);
1036 * Second pass: convert the characters from UTF-16 to UTF-8.
1039 while (i < passlen) {
1046 * Gah. nasty. maybe we should just pull in iconv?
1051 } else if (c < 0x7ff) {
1052 *x++ = 0xc0 + (c >> 6);
1053 *x++ = 0x80 + (c & 0x3f);
1056 *x++ = 0xe0 + (c >> 12);
1057 *x++ = 0x80 + ((c>>6) & 0x3f);
1058 *x++ = 0x80 + (c & 0x3f);
1064 /* Perform the xlat */
1065 result_len = radius_xlat(result, sizeof(result), request, inst->local_cpw, NULL, NULL);
1066 if (result_len < 0){
1068 } else if (result_len == 0) {
1069 REDEBUG("Local MS-CHAPv2 password change - xlat didn't give any result, assuming failure");
1073 RDEBUG("MS-CHAPv2 password change succeeded: %s", result);
1076 * Update the NT-Password attribute with the new hash this lets us
1077 * fall through to the authentication code using the new hash,
1080 fr_pair_value_memcpy(nt_password, new_hash->vp_octets, new_hash->vp_length);
1083 * Rock on! password change succeeded.
1087 REDEBUG("Local MS-CHAPv2 password changes require OpenSSL support");
1091 REDEBUG("MS-CHAPv2 password change not configured");
1098 * Do the MS-CHAP stuff.
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.
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)
1108 uint8_t calculated[24];
1110 memset(nthashhash, 0, NT_DIGEST_LENGTH);
1114 * Do normal authentication.
1118 * No password: can't do authentication.
1121 REDEBUG("FAILED: No NT/LM-Password. Cannot perform authentication");
1125 smbdes_mschap(password->vp_octets, challenge, calculated);
1126 if (rad_digest_cmp(response, calculated, 24) != 0) {
1131 * If the password exists, and is an NT-Password,
1132 * then calculate the hash of the NT hash. Doing this
1133 * here minimizes work for later.
1135 if (!password->da->vendor &&
1136 (password->da->attr == PW_NT_PASSWORD)) {
1137 fr_md4_calc(nthashhash, password->vp_octets, MD4_DIGEST_LENGTH);
1144 case AUTH_NTLMAUTH_EXEC: {
1150 * Run the program, and expect that we get 16
1152 result = radius_exec_program(request, buffer, sizeof(buffer), NULL, request, inst->ntlm_auth, NULL,
1153 true, true, inst->ntlm_auth_timeout);
1158 * look for "Password expired", or "Must change password".
1160 if (strcasestr(buffer, "Password expired") ||
1161 strcasestr(buffer, "Must change password")) {
1162 REDEBUG2("%s", buffer);
1166 RDEBUG2("External script failed");
1167 p = strchr(buffer, '\n');
1170 REDEBUG("External script says: %s", buffer);
1175 * Parse the answer as an nthashhash.
1177 * ntlm_auth currently returns:
1178 * NT_KEY: 000102030405060708090a0b0c0d0e0f
1180 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
1181 REDEBUG("Invalid output from ntlm_auth: expecting 'NT_KEY: ' prefix");
1186 * Check the length. It should be at least 32, with an LF at the end.
1188 len = strlen(buffer + 8);
1190 REDEBUG2("Invalid output from ntlm_auth: NT_KEY too short, expected 32 bytes got %zu bytes",
1197 * Update the NT hash hash, from the NT key.
1199 if (fr_hex2bin(nthashhash, NT_DIGEST_LENGTH, buffer + 8, len) != NT_DIGEST_LENGTH) {
1200 REDEBUG("Invalid output from ntlm_auth: NT_KEY has non-hex values");
1206 #ifdef WITH_AUTH_WINBIND
1208 * Process auth via the wbclient library
1211 return do_auth_wbclient(inst, request, challenge, response, nthashhash);
1214 /* We should never reach this line */
1216 RERROR("Internal error: Unknown mschap auth method (%d)", method);
1225 * Data for the hashes.
1227 static const uint8_t SHSpad1[40] =
1228 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1229 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1230 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1231 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
1233 static const uint8_t SHSpad2[40] =
1234 { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1235 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1236 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1237 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
1239 static const uint8_t magic1[27] =
1240 { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
1241 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
1242 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
1244 static const uint8_t magic2[84] =
1245 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
1246 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
1247 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1248 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
1249 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
1250 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
1251 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1252 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
1253 0x6b, 0x65, 0x79, 0x2e };
1255 static const uint8_t magic3[84] =
1256 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
1257 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
1258 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1259 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
1260 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
1261 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
1262 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
1263 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
1264 0x6b, 0x65, 0x79, 0x2e };
1267 static void mppe_GetMasterKey(uint8_t const *nt_hashhash,uint8_t const *nt_response,
1271 fr_sha1_ctx Context;
1273 fr_sha1_init(&Context);
1274 fr_sha1_update(&Context,nt_hashhash,NT_DIGEST_LENGTH);
1275 fr_sha1_update(&Context,nt_response,24);
1276 fr_sha1_update(&Context,magic1,27);
1277 fr_sha1_final(digest,&Context);
1279 memcpy(masterkey,digest,16);
1283 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
1284 int keylen,int issend)
1288 fr_sha1_ctx Context;
1290 memset(digest,0,20);
1298 fr_sha1_init(&Context);
1299 fr_sha1_update(&Context,masterkey,16);
1300 fr_sha1_update(&Context,SHSpad1,40);
1301 fr_sha1_update(&Context,s,84);
1302 fr_sha1_update(&Context,SHSpad2,40);
1303 fr_sha1_final(digest,&Context);
1305 memcpy(sesskey,digest,keylen);
1309 static void mppe_chap2_get_keys128(uint8_t const *nt_hashhash,uint8_t const *nt_response,
1310 uint8_t *sendkey,uint8_t *recvkey)
1312 uint8_t masterkey[16];
1314 mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
1316 mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
1317 mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
1321 * Generate MPPE keys.
1323 static void mppe_chap2_gen_keys128(uint8_t const *nt_hashhash,uint8_t const *response,
1324 uint8_t *sendkey,uint8_t *recvkey)
1326 uint8_t enckey1[16];
1327 uint8_t enckey2[16];
1329 mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
1332 * dictionary.microsoft defines these attributes as
1333 * 'encrypt=2'. The functions in src/lib/radius.c will
1334 * take care of encrypting/decrypting them as appropriate,
1335 * so that we don't have to.
1337 memcpy (sendkey, enckey1, 16);
1338 memcpy (recvkey, enckey2, 16);
1343 * mod_authorize() - authorize user if we can authenticate
1344 * it later. Add Auth-Type attribute if present in module
1345 * configuration (usually Auth-Type must be "MS-CHAP")
1347 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void * instance, REQUEST *request)
1349 rlm_mschap_t *inst = instance;
1350 VALUE_PAIR *challenge = NULL;
1352 challenge = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
1354 return RLM_MODULE_NOOP;
1357 if (!fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY) &&
1358 !fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY) &&
1359 !fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_CPW, VENDORPEC_MICROSOFT, TAG_ANY)) {
1360 RDEBUG2("Found MS-CHAP-Challenge, but no MS-CHAP response or change-password");
1361 return RLM_MODULE_NOOP;
1364 if (fr_pair_find_by_num(request->config, PW_AUTH_TYPE, 0, TAG_ANY)) {
1365 RWDEBUG2("Auth-Type already set. Not setting to MS-CHAP");
1366 return RLM_MODULE_NOOP;
1369 RDEBUG2("Found MS-CHAP attributes. Setting 'Auth-Type = %s'", inst->xlat_name);
1372 * Set Auth-Type to MS-CHAP. The authentication code
1373 * will take care of turning cleartext passwords into
1376 if (!pair_make_config("Auth-Type", inst->auth_type, T_OP_EQ)) {
1377 return RLM_MODULE_FAIL;
1380 return RLM_MODULE_OK;
1384 * mod_authenticate() - authenticate user based on given
1385 * attributes and configuration.
1386 * We will try to find out password in configuration
1387 * or in configured passwd file.
1388 * If one is found we will check paraneters given by NAS.
1390 * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
1392 * PAP: PW_USER_PASSWORD or
1393 * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
1394 * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
1395 * In case of password mismatch or locked account we MAY return
1396 * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
1397 * If MS-CHAP2 succeeds we MUST return
1398 * PW_MSCHAP2_SUCCESS
1400 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void * instance, REQUEST *request)
1402 #define inst ((rlm_mschap_t *)instance)
1403 VALUE_PAIR *challenge = NULL;
1404 VALUE_PAIR *response = NULL;
1405 VALUE_PAIR *cpw = NULL;
1406 VALUE_PAIR *password = NULL;
1407 VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
1408 VALUE_PAIR *username;
1409 uint8_t nthashhash[NT_DIGEST_LENGTH];
1412 char const *username_string;
1414 MSCHAP_AUTH_METHOD auth_method;
1417 * If we have ntlm_auth configured, use it unless told
1420 auth_method = inst->method;
1423 * If we have an ntlm_auth configuration, then we may
1424 * want to suppress it.
1426 if (auth_method != AUTH_INTERNAL) {
1427 VALUE_PAIR *vp = fr_pair_find_by_num(request->config, PW_MS_CHAP_USE_NTLM_AUTH, 0, TAG_ANY);
1428 if (vp && vp->vp_integer == 0) auth_method = AUTH_INTERNAL;
1432 * Find the SMB-Account-Ctrl attribute, or the
1433 * SMB-Account-Ctrl-Text attribute.
1435 smb_ctrl = fr_pair_find_by_num(request->config, PW_SMB_ACCOUNT_CTRL, 0, TAG_ANY);
1437 password = fr_pair_find_by_num(request->config, PW_SMB_ACCOUNT_CTRL_TEXT, 0, TAG_ANY);
1439 smb_ctrl = pair_make_config("SMB-Account-CTRL", "0", T_OP_SET);
1441 smb_ctrl->vp_integer = pdb_decode_acct_ctrl(password->vp_strvalue);
1447 * We're configured to do MS-CHAP authentication.
1448 * and account control information exists. Enforce it.
1452 * Password is not required.
1454 if ((smb_ctrl->vp_integer & ACB_PWNOTREQ) != 0) {
1455 RDEBUG2("SMB-Account-Ctrl says no password is required");
1456 return RLM_MODULE_OK;
1461 * Decide how to get the passwords.
1463 password = fr_pair_find_by_num(request->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY);
1466 * We need an NT-Password.
1468 nt_password = fr_pair_find_by_num(request->config, PW_NT_PASSWORD, 0, TAG_ANY);
1470 VERIFY_VP(nt_password);
1472 switch (nt_password->vp_length) {
1473 case NT_DIGEST_LENGTH:
1474 RDEBUG2("Found NT-Password");
1480 RWDEBUG("NT-Password has not been normalized by the 'pap' module (likely still in hex format). "
1481 "Authentication may fail");
1486 RWDEBUG("NT-Password found but incorrect length, expected " STRINGIFY(NT_DIGEST_LENGTH)
1487 " bytes got %zu bytes. Authentication may fail", nt_password->vp_length);
1494 * ... or a Cleartext-Password, which we now transform into an NT-Password
1498 RDEBUG2("Found Cleartext-Password, hashing to create NT-Password");
1499 nt_password = pair_make_config("NT-Password", NULL, T_OP_EQ);
1501 RERROR("No memory");
1502 return RLM_MODULE_FAIL;
1504 nt_password->vp_length = NT_DIGEST_LENGTH;
1505 nt_password->vp_octets = p = talloc_array(nt_password, uint8_t, nt_password->vp_length);
1507 if (mschap_ntpwdhash(p, password->vp_strvalue) < 0) {
1508 RERROR("Failed generating NT-Password");
1509 return RLM_MODULE_FAIL;
1511 } else if (auth_method == AUTH_INTERNAL) {
1512 RWDEBUG2("No Cleartext-Password configured. Cannot create NT-Password");
1517 * Or an LM-Password.
1519 lm_password = fr_pair_find_by_num(request->config, PW_LM_PASSWORD, 0, TAG_ANY);
1521 VERIFY_VP(lm_password);
1523 switch (lm_password->vp_length) {
1524 case LM_DIGEST_LENGTH:
1525 RDEBUG2("Found LM-Password");
1531 RWDEBUG("LM-Password has not been normalized by the 'pap' module (likely still in hex format). "
1532 "Authentication may fail");
1537 RWDEBUG("LM-Password found but incorrect length, expected " STRINGIFY(LM_DIGEST_LENGTH)
1538 " bytes got %zu bytes. Authentication may fail", lm_password->vp_length);
1544 * ... or a Cleartext-Password, which we now transform into an LM-Password
1548 RDEBUG2("Found Cleartext-Password, hashing to create LM-Password");
1549 lm_password = pair_make_config("LM-Password", NULL, T_OP_EQ);
1551 RERROR("No memory");
1553 lm_password->vp_length = LM_DIGEST_LENGTH;
1554 lm_password->vp_octets = p = talloc_array(lm_password, uint8_t, lm_password->vp_length);
1555 smbdes_lmpwdhash(password->vp_strvalue, p);
1558 * Only complain if we don't have NT-Password
1560 } else if ((auth_method == AUTH_INTERNAL) && !nt_password) {
1561 RWDEBUG2("No Cleartext-Password configured. Cannot create LM-Password");
1565 cpw = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_CPW, VENDORPEC_MICROSOFT, TAG_ANY);
1568 * mschap2 password change request
1569 * we cheat - first decode and execute the passchange
1570 * we then extract the response, add it into the request
1571 * then jump into mschap2 auth with the chal/resp
1573 uint8_t new_nt_encrypted[516], old_nt_encrypted[NT_DIGEST_LENGTH];
1574 VALUE_PAIR *nt_enc=NULL;
1575 int seq, new_nt_enc_len=0;
1577 RDEBUG("MS-CHAPv2 password change request received");
1579 if (cpw->vp_length != 68) {
1580 REDEBUG("MS-CHAP2-CPW has the wrong format: length %zu != 68", cpw->vp_length);
1581 return RLM_MODULE_INVALID;
1582 } else if (cpw->vp_octets[0]!=7) {
1583 REDEBUG("MS-CHAP2-CPW has the wrong format: code %d != 7", cpw->vp_octets[0]);
1584 return RLM_MODULE_INVALID;
1588 * look for the new (encrypted) password
1589 * bah stupid composite attributes
1590 * we're expecting 3 attributes with the leading bytes
1591 * 06:<mschapid>:00:01:<1st chunk>
1592 * 06:<mschapid>:00:02:<2nd chunk>
1593 * 06:<mschapid>:00:03:<3rd chunk>
1595 for (seq = 1; seq < 4; seq++) {
1599 for (nt_enc = fr_cursor_init(&cursor, &request->packet->vps);
1601 nt_enc = fr_cursor_next(&cursor)) {
1602 if (nt_enc->da->vendor != VENDORPEC_MICROSOFT)
1605 if (nt_enc->da->attr != PW_MSCHAP_NT_ENC_PW)
1608 if (nt_enc->vp_octets[0] != 6) {
1609 REDEBUG("MS-CHAP-NT-Enc-PW with invalid format");
1610 return RLM_MODULE_INVALID;
1612 if (nt_enc->vp_octets[2]==0 && nt_enc->vp_octets[3]==seq) {
1619 REDEBUG("Could not find MS-CHAP-NT-Enc-PW w/ sequence number %d", seq);
1620 return RLM_MODULE_INVALID;
1624 * copy the data into the buffer
1626 memcpy(new_nt_encrypted + new_nt_enc_len, nt_enc->vp_octets + 4, nt_enc->vp_length - 4);
1627 new_nt_enc_len += nt_enc->vp_length - 4;
1629 if (new_nt_enc_len != 516) {
1630 REDEBUG("Unpacked MS-CHAP-NT-Enc-PW length != 516");
1631 return RLM_MODULE_INVALID;
1635 * RFC 2548 is confusing here
1640 * 16 octets - old hash encrypted with new hash
1641 * 24 octets - peer challenge
1643 * 16 octets - peer challenge
1644 * 8 octets - reserved
1645 * 24 octets - nt response
1646 * 2 octets - flags (ignored)
1649 memcpy(old_nt_encrypted, cpw->vp_octets + 2, sizeof(old_nt_encrypted));
1651 RDEBUG2("Password change payload valid");
1653 /* perform the actual password change */
1654 rad_assert(nt_password);
1655 if (do_mschap_cpw(inst, request, nt_password, new_nt_encrypted, old_nt_encrypted, auth_method) < 0) {
1658 REDEBUG("Password change failed");
1660 snprintf(buffer, sizeof(buffer), "E=709 R=0 M=Password change failed");
1661 mschap_add_reply(request, cpw->vp_octets[1], "MS-CHAP-Error", buffer, strlen(buffer));
1663 return RLM_MODULE_REJECT;
1665 RDEBUG("Password change successful");
1668 * Clear any expiry bit so the user can now login;
1669 * obviously the password change action will need
1670 * to have cleared this bit in the config/SQL/wherever
1672 if (smb_ctrl && smb_ctrl->vp_integer & ACB_PW_EXPIRED) {
1673 RDEBUG("Clearing expiry bit in SMB-Acct-Ctrl to allow authentication");
1674 smb_ctrl->vp_integer &= ~ACB_PW_EXPIRED;
1678 * Extract the challenge & response from the end of the password
1679 * change, add them into the request and then continue with
1680 * the authentication
1683 response = radius_pair_create(request->packet, &request->packet->vps,
1684 PW_MSCHAP2_RESPONSE,
1685 VENDORPEC_MICROSOFT);
1686 response->vp_length = 50;
1687 response->vp_octets = p = talloc_array(response, uint8_t, response->vp_length);
1690 p[0] = cpw->vp_octets[1];
1692 /* peer challenge and client NT response */
1693 memcpy(p + 2, cpw->vp_octets + 18, 48);
1696 challenge = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
1698 REDEBUG("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1699 return RLM_MODULE_REJECT;
1703 * We also require an MS-CHAP-Response.
1705 response = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
1708 * MS-CHAP-Response, means MS-CHAPv1
1714 * MS-CHAPv1 challenges are 8 octets.
1716 if (challenge->vp_length < 8) {
1717 REDEBUG("MS-CHAP-Challenge has the wrong format");
1718 return RLM_MODULE_INVALID;
1722 * Responses are 50 octets.
1724 if (response->vp_length < 50) {
1725 REDEBUG("MS-CHAP-Response has the wrong format");
1726 return RLM_MODULE_INVALID;
1730 * We are doing MS-CHAP. Calculate the MS-CHAP
1733 if (response->vp_octets[1] & 0x01) {
1734 RDEBUG2("Client is using MS-CHAPv1 with NT-Password");
1735 password = nt_password;
1738 RDEBUG2("Client is using MS-CHAPv1 with LM-Password");
1739 password = lm_password;
1744 * Do the MS-CHAP authentication.
1746 if (do_mschap(inst, request, password, challenge->vp_octets, response->vp_octets + offset, nthashhash,
1748 REDEBUG("MS-CHAP-Response is incorrect");
1754 } else if ((response = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_RESPONSE,
1755 VENDORPEC_MICROSOFT, TAG_ANY)) != NULL) {
1757 uint8_t mschapv1_challenge[16];
1758 VALUE_PAIR *name_attr, *response_name;
1761 * MS-CHAPv2 challenges are 16 octets.
1763 if (challenge->vp_length < 16) {
1764 REDEBUG("MS-CHAP-Challenge has the wrong format");
1765 return RLM_MODULE_INVALID;
1769 * Responses are 50 octets.
1771 if (response->vp_length < 50) {
1772 REDEBUG("MS-CHAP-Response has the wrong format");
1773 return RLM_MODULE_INVALID;
1777 * We also require a User-Name
1779 username = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
1781 REDEBUG("We require a User-Name for MS-CHAPv2");
1782 return RLM_MODULE_INVALID;
1786 * Check for MS-CHAP-User-Name and if found, use it
1787 * to construct the MSCHAPv1 challenge. This is
1788 * set by rlm_eap_mschap to the MS-CHAP Response
1789 * packet Name field.
1791 * We prefer this to the User-Name in the
1794 response_name = fr_pair_find_by_num(request->packet->vps, PW_MS_CHAP_USER_NAME, 0, TAG_ANY);
1795 if (response_name) {
1796 name_attr = response_name;
1798 name_attr = username;
1802 * with_ntdomain_hack moved here, too.
1804 if ((username_string = strchr(name_attr->vp_strvalue, '\\')) != NULL) {
1805 if (inst->with_ntdomain_hack) {
1808 RWDEBUG2("NT Domain delimeter found, should with_ntdomain_hack of been enabled?");
1809 username_string = name_attr->vp_strvalue;
1812 username_string = name_attr->vp_strvalue;
1815 if (response_name && ((username->vp_length != response_name->vp_length) ||
1816 (strncasecmp(username->vp_strvalue, response_name->vp_strvalue, username->vp_length) != 0))) {
1817 RWDEBUG("User-Name (%s) is not the same as MS-CHAP Name (%s) from EAP-MSCHAPv2",
1818 username->vp_strvalue, response_name->vp_strvalue);
1823 * No "known good" NT-Password attribute. Try to do
1824 * OpenDirectory authentication.
1826 * If OD determines the user is an AD user it will return noop, which
1827 * indicates the auth process should continue directly to AD.
1828 * Otherwise OD will determine auth success/fail.
1830 if (!nt_password && inst->open_directory) {
1831 RDEBUG2("No NT-Password configured. Trying OpenDirectory Authentication");
1832 int odStatus = od_mschap_auth(request, challenge, username);
1833 if (odStatus != RLM_MODULE_NOOP) {
1839 * The old "mschapv2" function has been moved to
1842 * MS-CHAPv2 takes some additional data to create an
1843 * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1845 RDEBUG2("Creating challenge hash with username: %s", username_string);
1846 mschap_challenge_hash(response->vp_octets + 2, /* peer challenge */
1847 challenge->vp_octets, /* our challenge */
1848 username_string, /* user name */
1849 mschapv1_challenge); /* resulting challenge */
1851 RDEBUG2("Client is using MS-CHAPv2");
1853 mschap_result = do_mschap(inst, request, nt_password, mschapv1_challenge,
1854 response->vp_octets + 26, nthashhash, auth_method);
1855 if (mschap_result == -648) goto password_expired;
1857 if (mschap_result < 0) {
1861 REDEBUG("MS-CHAP2-Response is incorrect");
1864 snprintf(buffer, sizeof(buffer), "E=691 R=%d", inst->allow_retry);
1866 if (inst->retry_msg) {
1867 snprintf(buffer + 9, sizeof(buffer) - 9, " C=");
1868 for (i = 0; i < 16; i++) {
1869 snprintf(buffer + 12 + (i * 2),
1870 sizeof(buffer) - 12 - (i * 2), "%02x",
1873 /* E=691 R=d (9) + " C=" (3) + 32 hexits = 44 */
1874 snprintf(buffer + 44, sizeof(buffer) - 44, " V=3 M=%s", inst->retry_msg);
1876 mschap_add_reply(request, *response->vp_octets, "MS-CHAP-Error", buffer, strlen(buffer));
1877 return RLM_MODULE_REJECT;
1881 * If the password is correct and it has expired
1882 * we can permit password changes (only in MS-CHAPv2)
1884 if (smb_ctrl && smb_ctrl->vp_integer & ACB_PW_EXPIRED) {
1886 char newchal[33], buffer[128];
1890 for (i = 0; i < 16; i++) {
1891 snprintf(newchal + (i * 2), 3, "%02x", fr_rand() & 0xff);
1894 snprintf(buffer, sizeof(buffer), "E=648 R=%d C=%s V=3 M=Password Expired",
1895 inst->allow_retry, newchal);
1897 RDEBUG("Password has expired. The user should retry authentication");
1898 mschap_add_reply(request, *response->vp_octets, "MS-CHAP-Error", buffer, strlen(buffer));
1899 return RLM_MODULE_REJECT;
1902 mschap_auth_response(username_string, /* without the domain */
1903 nthashhash, /* nt-hash-hash */
1904 response->vp_octets + 26, /* peer response */
1905 response->vp_octets + 2, /* peer challenge */
1906 challenge->vp_octets, /* our challenge */
1907 msch2resp); /* calculated MPPE key */
1908 mschap_add_reply(request, *response->vp_octets, "MS-CHAP2-Success", msch2resp, 42);
1911 } else { /* Neither CHAPv1 or CHAPv2 response: die */
1912 REDEBUG("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1913 return RLM_MODULE_INVALID;
1917 * We have a CHAP response, but the account may be
1918 * disabled. Reject the user with the same error code
1919 * we use when their password is invalid.
1923 * Account is disabled.
1925 * They're found, but they don't exist, so we
1926 * return 'not found'.
1928 if (((smb_ctrl->vp_integer & ACB_DISABLED) != 0) ||
1929 ((smb_ctrl->vp_integer & (ACB_NORMAL|ACB_WSTRUST)) == 0)) {
1930 REDEBUG("SMB-Account-Ctrl says that the account is disabled, or is not a normal "
1931 "or workstation trust account");
1932 mschap_add_reply(request, *response->vp_octets, "MS-CHAP-Error", "E=691 R=1", 9);
1933 return RLM_MODULE_NOTFOUND;
1937 * User is locked out.
1939 if ((smb_ctrl->vp_integer & ACB_AUTOLOCK) != 0) {
1940 REDEBUG("SMB-Account-Ctrl says that the account is locked out");
1941 mschap_add_reply(request, *response->vp_octets, "MS-CHAP-Error", "E=647 R=0", 9);
1942 return RLM_MODULE_USERLOCK;
1946 /* now create MPPE attributes */
1947 if (inst->use_mppe) {
1948 uint8_t mppe_sendkey[34];
1949 uint8_t mppe_recvkey[34];
1952 RDEBUG2("adding MS-CHAPv1 MPPE keys");
1953 memset(mppe_sendkey, 0, 32);
1955 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1959 * According to RFC 2548 we
1960 * should send NT hash. But in
1961 * practice it doesn't work.
1962 * Instead, we should send nthashhash
1964 * This is an error in RFC 2548.
1967 * do_mschap cares to zero nthashhash if NT hash
1970 memcpy(mppe_sendkey + 8, nthashhash, NT_DIGEST_LENGTH);
1971 mppe_add_reply(request, "MS-CHAP-MPPE-Keys", mppe_sendkey, 24);
1972 } else if (chap == 2) {
1973 RDEBUG2("Adding MS-CHAPv2 MPPE keys");
1974 mppe_chap2_gen_keys128(nthashhash, response->vp_octets + 26, mppe_sendkey, mppe_recvkey);
1976 mppe_add_reply(request, "MS-MPPE-Recv-Key", mppe_recvkey, 16);
1977 mppe_add_reply(request, "MS-MPPE-Send-Key", mppe_sendkey, 16);
1980 pair_make_reply("MS-MPPE-Encryption-Policy",
1981 (inst->require_encryption) ? "0x00000002":"0x00000001", T_OP_EQ);
1982 pair_make_reply("MS-MPPE-Encryption-Types",
1983 (inst->require_strong) ? "0x00000004":"0x00000006", T_OP_EQ);
1984 } /* else we weren't asked to use MPPE */
1986 return RLM_MODULE_OK;
1990 extern module_t rlm_mschap;
1991 module_t rlm_mschap = {
1992 .magic = RLM_MODULE_INIT,
1995 .inst_size = sizeof(rlm_mschap_t),
1996 .config = module_config,
1997 .bootstrap = mod_bootstrap,
1998 .instantiate = mod_instantiate,
1999 .detach = mod_detach,
2001 [MOD_AUTHENTICATE] = mod_authenticate,
2002 [MOD_AUTHORIZE] = mod_authorize