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 if (strcasestr(buffer, "Account locked out") ||
1167 strcasestr(buffer, "0xC0000234")) {
1168 REDEBUG2("%s", buffer);
1172 if (strcasestr(buffer, "Account disabled") ||
1173 strcasestr(buffer, "0xC0000072")) {
1174 REDEBUG2("%s", buffer);
1178 RDEBUG2("External script failed");
1179 p = strchr(buffer, '\n');
1182 REDEBUG("External script says: %s", buffer);
1187 * Parse the answer as an nthashhash.
1189 * ntlm_auth currently returns:
1190 * NT_KEY: 000102030405060708090a0b0c0d0e0f
1192 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
1193 REDEBUG("Invalid output from ntlm_auth: expecting 'NT_KEY: ' prefix");
1198 * Check the length. It should be at least 32, with an LF at the end.
1200 len = strlen(buffer + 8);
1202 REDEBUG2("Invalid output from ntlm_auth: NT_KEY too short, expected 32 bytes got %zu bytes",
1209 * Update the NT hash hash, from the NT key.
1211 if (fr_hex2bin(nthashhash, NT_DIGEST_LENGTH, buffer + 8, len) != NT_DIGEST_LENGTH) {
1212 REDEBUG("Invalid output from ntlm_auth: NT_KEY has non-hex values");
1218 #ifdef WITH_AUTH_WINBIND
1220 * Process auth via the wbclient library
1223 return do_auth_wbclient(inst, request, challenge, response, nthashhash);
1226 /* We should never reach this line */
1228 RERROR("Internal error: Unknown mschap auth method (%d)", method);
1237 * Data for the hashes.
1239 static const uint8_t SHSpad1[40] =
1240 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1241 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1242 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1243 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
1245 static const uint8_t SHSpad2[40] =
1246 { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1247 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1248 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1249 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
1251 static const uint8_t magic1[27] =
1252 { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
1253 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
1254 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
1256 static const uint8_t magic2[84] =
1257 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
1258 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
1259 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1260 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
1261 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
1262 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
1263 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1264 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
1265 0x6b, 0x65, 0x79, 0x2e };
1267 static const uint8_t magic3[84] =
1268 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
1269 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
1270 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1271 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
1272 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
1273 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
1274 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
1275 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
1276 0x6b, 0x65, 0x79, 0x2e };
1279 static void mppe_GetMasterKey(uint8_t const *nt_hashhash,uint8_t const *nt_response,
1283 fr_sha1_ctx Context;
1285 fr_sha1_init(&Context);
1286 fr_sha1_update(&Context,nt_hashhash,NT_DIGEST_LENGTH);
1287 fr_sha1_update(&Context,nt_response,24);
1288 fr_sha1_update(&Context,magic1,27);
1289 fr_sha1_final(digest,&Context);
1291 memcpy(masterkey,digest,16);
1295 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
1296 int keylen,int issend)
1300 fr_sha1_ctx Context;
1302 memset(digest,0,20);
1310 fr_sha1_init(&Context);
1311 fr_sha1_update(&Context,masterkey,16);
1312 fr_sha1_update(&Context,SHSpad1,40);
1313 fr_sha1_update(&Context,s,84);
1314 fr_sha1_update(&Context,SHSpad2,40);
1315 fr_sha1_final(digest,&Context);
1317 memcpy(sesskey,digest,keylen);
1321 static void mppe_chap2_get_keys128(uint8_t const *nt_hashhash,uint8_t const *nt_response,
1322 uint8_t *sendkey,uint8_t *recvkey)
1324 uint8_t masterkey[16];
1326 mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
1328 mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
1329 mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
1333 * Generate MPPE keys.
1335 static void mppe_chap2_gen_keys128(uint8_t const *nt_hashhash,uint8_t const *response,
1336 uint8_t *sendkey,uint8_t *recvkey)
1338 uint8_t enckey1[16];
1339 uint8_t enckey2[16];
1341 mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
1344 * dictionary.microsoft defines these attributes as
1345 * 'encrypt=2'. The functions in src/lib/radius.c will
1346 * take care of encrypting/decrypting them as appropriate,
1347 * so that we don't have to.
1349 memcpy (sendkey, enckey1, 16);
1350 memcpy (recvkey, enckey2, 16);
1355 * mod_authorize() - authorize user if we can authenticate
1356 * it later. Add Auth-Type attribute if present in module
1357 * configuration (usually Auth-Type must be "MS-CHAP")
1359 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void * instance, REQUEST *request)
1361 rlm_mschap_t *inst = instance;
1362 VALUE_PAIR *challenge = NULL;
1364 challenge = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
1366 return RLM_MODULE_NOOP;
1369 if (!fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY) &&
1370 !fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY) &&
1371 !fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_CPW, VENDORPEC_MICROSOFT, TAG_ANY)) {
1372 RDEBUG2("Found MS-CHAP-Challenge, but no MS-CHAP response or change-password");
1373 return RLM_MODULE_NOOP;
1376 if (fr_pair_find_by_num(request->config, PW_AUTH_TYPE, 0, TAG_ANY)) {
1377 RWDEBUG2("Auth-Type already set. Not setting to MS-CHAP");
1378 return RLM_MODULE_NOOP;
1381 RDEBUG2("Found MS-CHAP attributes. Setting 'Auth-Type = %s'", inst->xlat_name);
1384 * Set Auth-Type to MS-CHAP. The authentication code
1385 * will take care of turning cleartext passwords into
1388 if (!pair_make_config("Auth-Type", inst->auth_type, T_OP_EQ)) {
1389 return RLM_MODULE_FAIL;
1392 return RLM_MODULE_OK;
1395 static rlm_rcode_t mschap_error(rlm_mschap_t *inst, REQUEST *request, unsigned char ident,
1396 int mschap_result, int mschap_version, VALUE_PAIR *smb_ctrl)
1398 rlm_rcode_t rcode = RLM_MODULE_OK;
1401 char const *message = NULL;
1404 char new_challenge[33], buffer[128];
1407 if ((mschap_result == -648) ||
1408 ((mschap_result == 0) &&
1409 (smb_ctrl && ((smb_ctrl->vp_integer & ACB_PW_EXPIRED) != 0)))) {
1410 REDEBUG("Password has expired. User should retry authentication");
1414 * A password change is NOT a retry! We MUST have retry=0 here.
1417 message = "Password expired";
1418 rcode = RLM_MODULE_REJECT;
1421 * Account is disabled.
1423 * They're found, but they don't exist, so we
1424 * return 'not found'.
1426 } else if ((mschap_result == -691) ||
1427 (smb_ctrl && (((smb_ctrl->vp_integer & ACB_DISABLED) != 0) ||
1428 ((smb_ctrl->vp_integer & (ACB_NORMAL|ACB_WSTRUST)) == 0)))) {
1429 REDEBUG("SMB-Account-Ctrl (or ntlm_auth) "
1430 "says that the account is disabled, "
1431 "or is not a normal or workstation trust account");
1434 message = "Account disabled";
1435 rcode = RLM_MODULE_NOTFOUND;
1438 * User is locked out.
1440 } else if ((mschap_result == -647) ||
1441 (smb_ctrl && ((smb_ctrl->vp_integer & ACB_AUTOLOCK) != 0))) {
1442 REDEBUG("SMB-Account-Ctrl (or ntlm_auth) "
1443 "says that the account is locked out");
1446 message = "Account locked out";
1447 rcode = RLM_MODULE_USERLOCK;
1449 } else if (mschap_result < 0) {
1450 REDEBUG("MS-CHAP2-Response is incorrect");
1452 retry = inst->allow_retry;
1453 message = "Authentication failed";
1454 rcode = RLM_MODULE_REJECT;
1457 if (rcode == RLM_MODULE_OK) return RLM_MODULE_OK;
1459 switch (mschap_version) {
1461 for (p = new_challenge, i = 0; i < 2; i++) p += snprintf(p, 9, "%08x", fr_rand());
1462 snprintf(buffer, sizeof(buffer), "E=%i R=%i C=%s V=2",
1463 error, retry, new_challenge);
1467 for (p = new_challenge, i = 0; i < 4; i++) p += snprintf(p, 9, "%08x", fr_rand());
1468 snprintf(buffer, sizeof(buffer), "E=%i R=%i C=%s V=3 M=%s",
1469 error, retry, new_challenge, message);
1475 mschap_add_reply(request, ident, "MS-CHAP-Error", buffer, strlen(buffer));
1481 * mod_authenticate() - authenticate user based on given
1482 * attributes and configuration.
1483 * We will try to find out password in configuration
1484 * or in configured passwd file.
1485 * If one is found we will check paraneters given by NAS.
1487 * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
1489 * PAP: PW_USER_PASSWORD or
1490 * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
1491 * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
1492 * In case of password mismatch or locked account we MAY return
1493 * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
1494 * If MS-CHAP2 succeeds we MUST return
1495 * PW_MSCHAP2_SUCCESS
1497 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
1499 rlm_mschap_t *inst = instance;
1500 VALUE_PAIR *challenge = NULL;
1501 VALUE_PAIR *response = NULL;
1502 VALUE_PAIR *cpw = NULL;
1503 VALUE_PAIR *password = NULL;
1504 VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
1505 VALUE_PAIR *username;
1506 uint8_t nthashhash[NT_DIGEST_LENGTH];
1508 char const *username_string;
1509 int mschap_version = 0;
1511 MSCHAP_AUTH_METHOD auth_method;
1514 * If we have ntlm_auth configured, use it unless told
1517 auth_method = inst->method;
1520 * If we have an ntlm_auth configuration, then we may
1521 * want to suppress it.
1523 if (auth_method != AUTH_INTERNAL) {
1524 VALUE_PAIR *vp = fr_pair_find_by_num(request->config, PW_MS_CHAP_USE_NTLM_AUTH, 0, TAG_ANY);
1525 if (vp && vp->vp_integer == 0) auth_method = AUTH_INTERNAL;
1529 * Find the SMB-Account-Ctrl attribute, or the
1530 * SMB-Account-Ctrl-Text attribute.
1532 smb_ctrl = fr_pair_find_by_num(request->config, PW_SMB_ACCOUNT_CTRL, 0, TAG_ANY);
1534 password = fr_pair_find_by_num(request->config, PW_SMB_ACCOUNT_CTRL_TEXT, 0, TAG_ANY);
1536 smb_ctrl = pair_make_config("SMB-Account-CTRL", "0", T_OP_SET);
1538 smb_ctrl->vp_integer = pdb_decode_acct_ctrl(password->vp_strvalue);
1544 * We're configured to do MS-CHAP authentication.
1545 * and account control information exists. Enforce it.
1549 * Password is not required.
1551 if ((smb_ctrl->vp_integer & ACB_PWNOTREQ) != 0) {
1552 RDEBUG2("SMB-Account-Ctrl says no password is required");
1553 return RLM_MODULE_OK;
1558 * Decide how to get the passwords.
1560 password = fr_pair_find_by_num(request->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY);
1563 * We need an NT-Password.
1565 nt_password = fr_pair_find_by_num(request->config, PW_NT_PASSWORD, 0, TAG_ANY);
1567 VERIFY_VP(nt_password);
1569 switch (nt_password->vp_length) {
1570 case NT_DIGEST_LENGTH:
1571 RDEBUG2("Found NT-Password");
1577 RWDEBUG("NT-Password has not been normalized by the 'pap' module (likely still in hex format). "
1578 "Authentication may fail");
1583 RWDEBUG("NT-Password found but incorrect length, expected " STRINGIFY(NT_DIGEST_LENGTH)
1584 " bytes got %zu bytes. Authentication may fail", nt_password->vp_length);
1591 * ... or a Cleartext-Password, which we now transform into an NT-Password
1597 RDEBUG2("Found Cleartext-Password, hashing to create NT-Password");
1598 nt_password = pair_make_config("NT-Password", NULL, T_OP_EQ);
1600 RERROR("No memory");
1601 return RLM_MODULE_FAIL;
1603 nt_password->vp_length = NT_DIGEST_LENGTH;
1604 nt_password->vp_octets = p = talloc_array(nt_password, uint8_t, nt_password->vp_length);
1606 if (mschap_ntpwdhash(p, password->vp_strvalue) < 0) {
1607 RERROR("Failed generating NT-Password");
1608 return RLM_MODULE_FAIL;
1610 } else if (auth_method == AUTH_INTERNAL) {
1611 RWDEBUG2("No Cleartext-Password configured. Cannot create NT-Password");
1616 * Or an LM-Password.
1618 lm_password = fr_pair_find_by_num(request->config, PW_LM_PASSWORD, 0, TAG_ANY);
1620 VERIFY_VP(lm_password);
1622 switch (lm_password->vp_length) {
1623 case LM_DIGEST_LENGTH:
1624 RDEBUG2("Found LM-Password");
1630 RWDEBUG("LM-Password has not been normalized by the 'pap' module (likely still in hex format). "
1631 "Authentication may fail");
1636 RWDEBUG("LM-Password found but incorrect length, expected " STRINGIFY(LM_DIGEST_LENGTH)
1637 " bytes got %zu bytes. Authentication may fail", lm_password->vp_length);
1643 * ... or a Cleartext-Password, which we now transform into an LM-Password
1647 RDEBUG2("Found Cleartext-Password, hashing to create LM-Password");
1648 lm_password = pair_make_config("LM-Password", NULL, T_OP_EQ);
1650 RERROR("No memory");
1654 lm_password->vp_length = LM_DIGEST_LENGTH;
1655 lm_password->vp_octets = p = talloc_array(lm_password, uint8_t, lm_password->vp_length);
1656 smbdes_lmpwdhash(password->vp_strvalue, p);
1659 * Only complain if we don't have NT-Password
1661 } else if ((auth_method == AUTH_INTERNAL) && !nt_password) {
1662 RWDEBUG2("No Cleartext-Password configured. Cannot create LM-Password");
1666 cpw = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_CPW, VENDORPEC_MICROSOFT, TAG_ANY);
1669 * mschap2 password change request
1670 * we cheat - first decode and execute the passchange
1671 * we then extract the response, add it into the request
1672 * then jump into mschap2 auth with the chal/resp
1674 uint8_t new_nt_encrypted[516], old_nt_encrypted[NT_DIGEST_LENGTH];
1675 VALUE_PAIR *nt_enc=NULL;
1676 int seq, new_nt_enc_len;
1679 RDEBUG("MS-CHAPv2 password change request received");
1681 if (cpw->vp_length != 68) {
1682 REDEBUG("MS-CHAP2-CPW has the wrong format: length %zu != 68", cpw->vp_length);
1683 return RLM_MODULE_INVALID;
1686 if (cpw->vp_octets[0] != 7) {
1687 REDEBUG("MS-CHAP2-CPW has the wrong format: code %d != 7", cpw->vp_octets[0]);
1688 return RLM_MODULE_INVALID;
1692 * look for the new (encrypted) password
1693 * bah stupid composite attributes
1694 * we're expecting 3 attributes with the leading bytes
1695 * 06:<mschapid>:00:01:<1st chunk>
1696 * 06:<mschapid>:00:02:<2nd chunk>
1697 * 06:<mschapid>:00:03:<3rd chunk>
1700 for (seq = 1; seq < 4; seq++) {
1704 for (nt_enc = fr_cursor_init(&cursor, &request->packet->vps);
1706 nt_enc = fr_cursor_next(&cursor)) {
1707 if (nt_enc->da->vendor != VENDORPEC_MICROSOFT)
1710 if (nt_enc->da->attr != PW_MSCHAP_NT_ENC_PW)
1713 if (nt_enc->vp_length < 4) {
1714 REDEBUG("MS-CHAP-NT-Enc-PW with invalid format");
1715 return RLM_MODULE_INVALID;
1718 if (nt_enc->vp_octets[0] != 6) {
1719 REDEBUG("MS-CHAP-NT-Enc-PW with invalid format");
1720 return RLM_MODULE_INVALID;
1723 if ((nt_enc->vp_octets[2] == 0) && (nt_enc->vp_octets[3] == seq)) {
1730 REDEBUG("Could not find MS-CHAP-NT-Enc-PW w/ sequence number %d", seq);
1731 return RLM_MODULE_INVALID;
1734 if ((new_nt_enc_len + nt_enc->vp_length - 4) > sizeof(new_nt_encrypted)) {
1735 REDEBUG("Unpacked MS-CHAP-NT-Enc-PW length > 516");
1736 return RLM_MODULE_INVALID;
1739 memcpy(new_nt_encrypted + new_nt_enc_len, nt_enc->vp_octets + 4, nt_enc->vp_length - 4);
1740 new_nt_enc_len += nt_enc->vp_length - 4;
1743 if (new_nt_enc_len != 516) {
1744 REDEBUG("Unpacked MS-CHAP-NT-Enc-PW length != 516");
1745 return RLM_MODULE_INVALID;
1749 * RFC 2548 is confusing here
1754 * 16 octets - old hash encrypted with new hash
1755 * 24 octets - peer challenge
1757 * 16 octets - peer challenge
1758 * 8 octets - reserved
1759 * 24 octets - nt response
1760 * 2 octets - flags (ignored)
1763 memcpy(old_nt_encrypted, cpw->vp_octets + 2, sizeof(old_nt_encrypted));
1765 RDEBUG2("Password change payload valid");
1767 /* perform the actual password change */
1768 if (do_mschap_cpw(inst, request, nt_password, new_nt_encrypted, old_nt_encrypted, auth_method) < 0) {
1771 REDEBUG("Password change failed");
1773 snprintf(buffer, sizeof(buffer), "E=709 R=0 M=Password change failed");
1774 mschap_add_reply(request, cpw->vp_octets[1], "MS-CHAP-Error", buffer, strlen(buffer));
1776 return RLM_MODULE_REJECT;
1778 RDEBUG("Password change successful");
1781 * Clear any expiry bit so the user can now login;
1782 * obviously the password change action will need
1783 * to have cleared this bit in the config/SQL/wherever
1785 if (smb_ctrl && smb_ctrl->vp_integer & ACB_PW_EXPIRED) {
1786 RDEBUG("Clearing expiry bit in SMB-Acct-Ctrl to allow authentication");
1787 smb_ctrl->vp_integer &= ~ACB_PW_EXPIRED;
1791 * Extract the challenge & response from the end of the password
1792 * change, add them into the request and then continue with
1793 * the authentication
1795 response = radius_pair_create(request->packet, &request->packet->vps,
1796 PW_MSCHAP2_RESPONSE,
1797 VENDORPEC_MICROSOFT);
1798 response->vp_length = 50;
1799 response->vp_octets = p = talloc_array(response, uint8_t, response->vp_length);
1802 p[0] = cpw->vp_octets[1];
1804 /* peer challenge and client NT response */
1805 memcpy(p + 2, cpw->vp_octets + 18, 48);
1808 challenge = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
1810 REDEBUG("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1811 return RLM_MODULE_REJECT;
1815 * We also require an MS-CHAP-Response.
1817 response = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
1820 * MS-CHAP-Response, means MS-CHAPv1
1828 * MS-CHAPv1 challenges are 8 octets.
1830 if (challenge->vp_length < 8) {
1831 REDEBUG("MS-CHAP-Challenge has the wrong format");
1832 return RLM_MODULE_INVALID;
1836 * Responses are 50 octets.
1838 if (response->vp_length < 50) {
1839 REDEBUG("MS-CHAP-Response has the wrong format");
1840 return RLM_MODULE_INVALID;
1844 * We are doing MS-CHAP. Calculate the MS-CHAP
1847 if (response->vp_octets[1] & 0x01) {
1848 RDEBUG2("Client is using MS-CHAPv1 with NT-Password");
1849 password = nt_password;
1852 RDEBUG2("Client is using MS-CHAPv1 with LM-Password");
1853 password = lm_password;
1858 * Do the MS-CHAP authentication.
1860 mschap_result = do_mschap(inst, request, password, challenge->vp_octets,
1861 response->vp_octets + offset, nthashhash, auth_method);
1863 * Check for errors, and add MSCHAP-Error if necessary.
1865 rcode = mschap_error(inst, request, *response->vp_octets,
1866 mschap_result, mschap_version, smb_ctrl);
1867 if (rcode != RLM_MODULE_OK) return rcode;
1868 } else if ((response = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_RESPONSE,
1869 VENDORPEC_MICROSOFT, TAG_ANY)) != NULL) {
1870 uint8_t mschapv1_challenge[16];
1871 VALUE_PAIR *name_attr, *response_name;
1877 * MS-CHAPv2 challenges are 16 octets.
1879 if (challenge->vp_length < 16) {
1880 REDEBUG("MS-CHAP-Challenge has the wrong format");
1881 return RLM_MODULE_INVALID;
1885 * Responses are 50 octets.
1887 if (response->vp_length < 50) {
1888 REDEBUG("MS-CHAP-Response has the wrong format");
1889 return RLM_MODULE_INVALID;
1893 * We also require a User-Name
1895 username = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
1897 REDEBUG("We require a User-Name for MS-CHAPv2");
1898 return RLM_MODULE_INVALID;
1902 * Check for MS-CHAP-User-Name and if found, use it
1903 * to construct the MSCHAPv1 challenge. This is
1904 * set by rlm_eap_mschap to the MS-CHAP Response
1905 * packet Name field.
1907 * We prefer this to the User-Name in the
1910 response_name = fr_pair_find_by_num(request->packet->vps, PW_MS_CHAP_USER_NAME, 0, TAG_ANY);
1911 if (response_name) {
1912 name_attr = response_name;
1914 name_attr = username;
1918 * with_ntdomain_hack moved here, too.
1920 if ((username_string = strchr(name_attr->vp_strvalue, '\\')) != NULL) {
1921 if (inst->with_ntdomain_hack) {
1924 RWDEBUG2("NT Domain delimeter found, should with_ntdomain_hack of been enabled?");
1925 username_string = name_attr->vp_strvalue;
1928 username_string = name_attr->vp_strvalue;
1931 if (response_name && ((username->vp_length != response_name->vp_length) ||
1932 (strncasecmp(username->vp_strvalue, response_name->vp_strvalue, username->vp_length) != 0))) {
1933 RWDEBUG("User-Name (%s) is not the same as MS-CHAP Name (%s) from EAP-MSCHAPv2",
1934 username->vp_strvalue, response_name->vp_strvalue);
1939 * No "known good" NT-Password attribute. Try to do
1940 * OpenDirectory authentication.
1942 * If OD determines the user is an AD user it will return noop, which
1943 * indicates the auth process should continue directly to AD.
1944 * Otherwise OD will determine auth success/fail.
1946 if (!nt_password && inst->open_directory) {
1947 RDEBUG2("No NT-Password configured. Trying OpenDirectory Authentication");
1948 int odStatus = od_mschap_auth(request, challenge, username);
1949 if (odStatus != RLM_MODULE_NOOP) {
1955 * The old "mschapv2" function has been moved to
1958 * MS-CHAPv2 takes some additional data to create an
1959 * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1961 RDEBUG2("Creating challenge hash with username: %s", username_string);
1962 mschap_challenge_hash(response->vp_octets + 2, /* peer challenge */
1963 challenge->vp_octets, /* our challenge */
1964 username_string, /* user name */
1965 mschapv1_challenge); /* resulting challenge */
1967 RDEBUG2("Client is using MS-CHAPv2");
1968 mschap_result = do_mschap(inst, request, nt_password, mschapv1_challenge,
1969 response->vp_octets + 26, nthashhash, auth_method);
1970 rcode = mschap_error(inst, request, *response->vp_octets,
1971 mschap_result, mschap_version, smb_ctrl);
1972 if (rcode != RLM_MODULE_OK) return rcode;
1974 mschap_auth_response(username_string, /* without the domain */
1975 nthashhash, /* nt-hash-hash */
1976 response->vp_octets + 26, /* peer response */
1977 response->vp_octets + 2, /* peer challenge */
1978 challenge->vp_octets, /* our challenge */
1979 msch2resp); /* calculated MPPE key */
1980 mschap_add_reply(request, *response->vp_octets, "MS-CHAP2-Success", msch2resp, 42);
1982 } else { /* Neither CHAPv1 or CHAPv2 response: die */
1983 REDEBUG("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1984 return RLM_MODULE_INVALID;
1987 /* now create MPPE attributes */
1988 if (inst->use_mppe) {
1989 uint8_t mppe_sendkey[34];
1990 uint8_t mppe_recvkey[34];
1992 if (mschap_version == 1) {
1993 RDEBUG2("adding MS-CHAPv1 MPPE keys");
1994 memset(mppe_sendkey, 0, 32);
1996 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
2000 * According to RFC 2548 we
2001 * should send NT hash. But in
2002 * practice it doesn't work.
2003 * Instead, we should send nthashhash
2005 * This is an error in RFC 2548.
2008 * do_mschap cares to zero nthashhash if NT hash
2011 memcpy(mppe_sendkey + 8, nthashhash, NT_DIGEST_LENGTH);
2012 mppe_add_reply(request, "MS-CHAP-MPPE-Keys", mppe_sendkey, 24);
2014 } else if (mschap_version == 2) {
2015 RDEBUG2("Adding MS-CHAPv2 MPPE keys");
2016 mppe_chap2_gen_keys128(nthashhash, response->vp_octets + 26, mppe_sendkey, mppe_recvkey);
2018 mppe_add_reply(request, "MS-MPPE-Recv-Key", mppe_recvkey, 16);
2019 mppe_add_reply(request, "MS-MPPE-Send-Key", mppe_sendkey, 16);
2022 pair_make_reply("MS-MPPE-Encryption-Policy",
2023 (inst->require_encryption) ? "0x00000002":"0x00000001", T_OP_EQ);
2024 pair_make_reply("MS-MPPE-Encryption-Types",
2025 (inst->require_strong) ? "0x00000004":"0x00000006", T_OP_EQ);
2026 } /* else we weren't asked to use MPPE */
2028 return RLM_MODULE_OK;
2032 extern module_t rlm_mschap;
2033 module_t rlm_mschap = {
2034 .magic = RLM_MODULE_INIT,
2037 .inst_size = sizeof(rlm_mschap_t),
2038 .config = module_config,
2039 .bootstrap = mod_bootstrap,
2040 .instantiate = mod_instantiate,
2041 .detach = mod_detach,
2043 [MOD_AUTHENTICATE] = mod_authenticate,
2044 [MOD_AUTHORIZE] = mod_authorize