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;
1383 static rlm_rcode_t mschap_error(rlm_mschap_t *inst, REQUEST *request, unsigned char ident,
1384 int mschap_result, int mschap_version, VALUE_PAIR *smb_ctrl)
1386 rlm_rcode_t rcode = RLM_MODULE_OK;
1389 char const *message = NULL;
1392 char new_challenge[33], buffer[128];
1395 if ((smb_ctrl && ((smb_ctrl->vp_integer & ACB_PW_EXPIRED) != 0)) || (mschap_result == -648)) {
1396 REDEBUG("Password has expired. User should retry authentication");
1398 retry = inst->allow_retry ? 1 : 0;
1399 message = "Password expired";
1400 rcode = RLM_MODULE_REJECT;
1402 } else if (mschap_result < 0) {
1403 REDEBUG("MS-CHAP2-Response is incorrect");
1405 retry = inst->allow_retry ? 1 : 0;
1406 message = "Authentication failed";
1407 rcode = RLM_MODULE_REJECT;
1409 * Account is disabled.
1411 * They're found, but they don't exist, so we
1412 * return 'not found'.
1414 } else if (smb_ctrl && (((smb_ctrl->vp_integer & ACB_DISABLED) != 0) ||
1415 ((smb_ctrl->vp_integer & (ACB_NORMAL|ACB_WSTRUST)) == 0))) {
1416 REDEBUG("SMB-Account-Ctrl says that the account is disabled, or is not a normal "
1417 "or workstation trust account");
1420 message = "Account disabled";
1421 rcode = RLM_MODULE_NOTFOUND;
1423 * User is locked out.
1425 } else if (smb_ctrl && ((smb_ctrl->vp_integer & ACB_AUTOLOCK) != 0)) {
1426 REDEBUG("SMB-Account-Ctrl says that the account is locked out");
1429 message = "Account locked out";
1430 rcode = RLM_MODULE_USERLOCK;
1433 if (rcode == RLM_MODULE_OK) return RLM_MODULE_OK;
1435 switch (mschap_version) {
1437 for (p = new_challenge, i = 0; i < 2; i++) p += snprintf(p, 9, "%08x", fr_rand());
1438 snprintf(buffer, sizeof(buffer), "E=%i R=%i C=%s V=2",
1439 error, retry, new_challenge);
1443 for (p = new_challenge, i = 0; i < 4; i++) p += snprintf(p, 9, "%08x", fr_rand());
1444 snprintf(buffer, sizeof(buffer), "E=%i R=%i C=%s V=3 M=%s",
1445 error, retry, new_challenge, message);
1451 mschap_add_reply(request, ident, "MS-CHAP-Error", buffer, strlen(buffer));
1457 * mod_authenticate() - authenticate user based on given
1458 * attributes and configuration.
1459 * We will try to find out password in configuration
1460 * or in configured passwd file.
1461 * If one is found we will check paraneters given by NAS.
1463 * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
1465 * PAP: PW_USER_PASSWORD or
1466 * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
1467 * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
1468 * In case of password mismatch or locked account we MAY return
1469 * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
1470 * If MS-CHAP2 succeeds we MUST return
1471 * PW_MSCHAP2_SUCCESS
1473 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
1475 rlm_mschap_t *inst = instance;
1476 VALUE_PAIR *challenge = NULL;
1477 VALUE_PAIR *response = NULL;
1478 VALUE_PAIR *cpw = NULL;
1479 VALUE_PAIR *password = NULL;
1480 VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
1481 VALUE_PAIR *username;
1482 uint8_t nthashhash[NT_DIGEST_LENGTH];
1484 char const *username_string;
1485 int mschap_version = 0;
1487 MSCHAP_AUTH_METHOD auth_method;
1490 * If we have ntlm_auth configured, use it unless told
1493 auth_method = inst->method;
1496 * If we have an ntlm_auth configuration, then we may
1497 * want to suppress it.
1499 if (auth_method != AUTH_INTERNAL) {
1500 VALUE_PAIR *vp = fr_pair_find_by_num(request->config, PW_MS_CHAP_USE_NTLM_AUTH, 0, TAG_ANY);
1501 if (vp && vp->vp_integer == 0) auth_method = AUTH_INTERNAL;
1505 * Find the SMB-Account-Ctrl attribute, or the
1506 * SMB-Account-Ctrl-Text attribute.
1508 smb_ctrl = fr_pair_find_by_num(request->config, PW_SMB_ACCOUNT_CTRL, 0, TAG_ANY);
1510 password = fr_pair_find_by_num(request->config, PW_SMB_ACCOUNT_CTRL_TEXT, 0, TAG_ANY);
1512 smb_ctrl = pair_make_config("SMB-Account-CTRL", "0", T_OP_SET);
1514 smb_ctrl->vp_integer = pdb_decode_acct_ctrl(password->vp_strvalue);
1520 * We're configured to do MS-CHAP authentication.
1521 * and account control information exists. Enforce it.
1525 * Password is not required.
1527 if ((smb_ctrl->vp_integer & ACB_PWNOTREQ) != 0) {
1528 RDEBUG2("SMB-Account-Ctrl says no password is required");
1529 return RLM_MODULE_OK;
1534 * Decide how to get the passwords.
1536 password = fr_pair_find_by_num(request->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY);
1539 * We need an NT-Password.
1541 nt_password = fr_pair_find_by_num(request->config, PW_NT_PASSWORD, 0, TAG_ANY);
1543 VERIFY_VP(nt_password);
1545 switch (nt_password->vp_length) {
1546 case NT_DIGEST_LENGTH:
1547 RDEBUG2("Found NT-Password");
1553 RWDEBUG("NT-Password has not been normalized by the 'pap' module (likely still in hex format). "
1554 "Authentication may fail");
1559 RWDEBUG("NT-Password found but incorrect length, expected " STRINGIFY(NT_DIGEST_LENGTH)
1560 " bytes got %zu bytes. Authentication may fail", nt_password->vp_length);
1567 * ... or a Cleartext-Password, which we now transform into an NT-Password
1573 RDEBUG2("Found Cleartext-Password, hashing to create NT-Password");
1574 nt_password = pair_make_config("NT-Password", NULL, T_OP_EQ);
1576 RERROR("No memory");
1577 return RLM_MODULE_FAIL;
1579 nt_password->vp_length = NT_DIGEST_LENGTH;
1580 nt_password->vp_octets = p = talloc_array(nt_password, uint8_t, nt_password->vp_length);
1582 if (mschap_ntpwdhash(p, password->vp_strvalue) < 0) {
1583 RERROR("Failed generating NT-Password");
1584 return RLM_MODULE_FAIL;
1586 } else if (auth_method == AUTH_INTERNAL) {
1587 RWDEBUG2("No Cleartext-Password configured. Cannot create NT-Password");
1592 * Or an LM-Password.
1594 lm_password = fr_pair_find_by_num(request->config, PW_LM_PASSWORD, 0, TAG_ANY);
1596 VERIFY_VP(lm_password);
1598 switch (lm_password->vp_length) {
1599 case LM_DIGEST_LENGTH:
1600 RDEBUG2("Found LM-Password");
1606 RWDEBUG("LM-Password has not been normalized by the 'pap' module (likely still in hex format). "
1607 "Authentication may fail");
1612 RWDEBUG("LM-Password found but incorrect length, expected " STRINGIFY(LM_DIGEST_LENGTH)
1613 " bytes got %zu bytes. Authentication may fail", lm_password->vp_length);
1619 * ... or a Cleartext-Password, which we now transform into an LM-Password
1623 RDEBUG2("Found Cleartext-Password, hashing to create LM-Password");
1624 lm_password = pair_make_config("LM-Password", NULL, T_OP_EQ);
1626 RERROR("No memory");
1630 lm_password->vp_length = LM_DIGEST_LENGTH;
1631 lm_password->vp_octets = p = talloc_array(lm_password, uint8_t, lm_password->vp_length);
1632 smbdes_lmpwdhash(password->vp_strvalue, p);
1635 * Only complain if we don't have NT-Password
1637 } else if ((auth_method == AUTH_INTERNAL) && !nt_password) {
1638 RWDEBUG2("No Cleartext-Password configured. Cannot create LM-Password");
1642 cpw = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_CPW, VENDORPEC_MICROSOFT, TAG_ANY);
1645 * mschap2 password change request
1646 * we cheat - first decode and execute the passchange
1647 * we then extract the response, add it into the request
1648 * then jump into mschap2 auth with the chal/resp
1650 uint8_t new_nt_encrypted[516], old_nt_encrypted[NT_DIGEST_LENGTH];
1651 VALUE_PAIR *nt_enc=NULL;
1652 int seq, new_nt_enc_len=0;
1655 RDEBUG("MS-CHAPv2 password change request received");
1657 if (cpw->vp_length != 68) {
1658 REDEBUG("MS-CHAP2-CPW has the wrong format: length %zu != 68", cpw->vp_length);
1659 return RLM_MODULE_INVALID;
1660 } else if (cpw->vp_octets[0]!=7) {
1661 REDEBUG("MS-CHAP2-CPW has the wrong format: code %d != 7", cpw->vp_octets[0]);
1662 return RLM_MODULE_INVALID;
1666 * look for the new (encrypted) password
1667 * bah stupid composite attributes
1668 * we're expecting 3 attributes with the leading bytes
1669 * 06:<mschapid>:00:01:<1st chunk>
1670 * 06:<mschapid>:00:02:<2nd chunk>
1671 * 06:<mschapid>:00:03:<3rd chunk>
1673 for (seq = 1; seq < 4; seq++) {
1677 for (nt_enc = fr_cursor_init(&cursor, &request->packet->vps);
1679 nt_enc = fr_cursor_next(&cursor)) {
1680 if (nt_enc->da->vendor != VENDORPEC_MICROSOFT)
1683 if (nt_enc->da->attr != PW_MSCHAP_NT_ENC_PW)
1686 if (nt_enc->vp_octets[0] != 6) {
1687 REDEBUG("MS-CHAP-NT-Enc-PW with invalid format");
1688 return RLM_MODULE_INVALID;
1690 if (nt_enc->vp_octets[2]==0 && nt_enc->vp_octets[3]==seq) {
1697 REDEBUG("Could not find MS-CHAP-NT-Enc-PW w/ sequence number %d", seq);
1698 return RLM_MODULE_INVALID;
1702 * copy the data into the buffer
1704 memcpy(new_nt_encrypted + new_nt_enc_len, nt_enc->vp_octets + 4, nt_enc->vp_length - 4);
1705 new_nt_enc_len += nt_enc->vp_length - 4;
1707 if (new_nt_enc_len != 516) {
1708 REDEBUG("Unpacked MS-CHAP-NT-Enc-PW length != 516");
1709 return RLM_MODULE_INVALID;
1713 * RFC 2548 is confusing here
1718 * 16 octets - old hash encrypted with new hash
1719 * 24 octets - peer challenge
1721 * 16 octets - peer challenge
1722 * 8 octets - reserved
1723 * 24 octets - nt response
1724 * 2 octets - flags (ignored)
1727 memcpy(old_nt_encrypted, cpw->vp_octets + 2, sizeof(old_nt_encrypted));
1729 RDEBUG2("Password change payload valid");
1731 /* perform the actual password change */
1732 rad_assert(nt_password);
1733 if (do_mschap_cpw(inst, request, nt_password, new_nt_encrypted, old_nt_encrypted, auth_method) < 0) {
1736 REDEBUG("Password change failed");
1738 snprintf(buffer, sizeof(buffer), "E=709 R=0 M=Password change failed");
1739 mschap_add_reply(request, cpw->vp_octets[1], "MS-CHAP-Error", buffer, strlen(buffer));
1741 return RLM_MODULE_REJECT;
1743 RDEBUG("Password change successful");
1746 * Clear any expiry bit so the user can now login;
1747 * obviously the password change action will need
1748 * to have cleared this bit in the config/SQL/wherever
1750 if (smb_ctrl && smb_ctrl->vp_integer & ACB_PW_EXPIRED) {
1751 RDEBUG("Clearing expiry bit in SMB-Acct-Ctrl to allow authentication");
1752 smb_ctrl->vp_integer &= ~ACB_PW_EXPIRED;
1756 * Extract the challenge & response from the end of the password
1757 * change, add them into the request and then continue with
1758 * the authentication
1760 response = radius_pair_create(request->packet, &request->packet->vps,
1761 PW_MSCHAP2_RESPONSE,
1762 VENDORPEC_MICROSOFT);
1763 response->vp_length = 50;
1764 response->vp_octets = p = talloc_array(response, uint8_t, response->vp_length);
1767 p[0] = cpw->vp_octets[1];
1769 /* peer challenge and client NT response */
1770 memcpy(p + 2, cpw->vp_octets + 18, 48);
1773 challenge = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
1775 REDEBUG("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1776 return RLM_MODULE_REJECT;
1780 * We also require an MS-CHAP-Response.
1782 response = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
1785 * MS-CHAP-Response, means MS-CHAPv1
1793 * MS-CHAPv1 challenges are 8 octets.
1795 if (challenge->vp_length < 8) {
1796 REDEBUG("MS-CHAP-Challenge has the wrong format");
1797 return RLM_MODULE_INVALID;
1801 * Responses are 50 octets.
1803 if (response->vp_length < 50) {
1804 REDEBUG("MS-CHAP-Response has the wrong format");
1805 return RLM_MODULE_INVALID;
1809 * We are doing MS-CHAP. Calculate the MS-CHAP
1812 if (response->vp_octets[1] & 0x01) {
1813 RDEBUG2("Client is using MS-CHAPv1 with NT-Password");
1814 password = nt_password;
1817 RDEBUG2("Client is using MS-CHAPv1 with LM-Password");
1818 password = lm_password;
1823 * Do the MS-CHAP authentication.
1825 mschap_result = do_mschap(inst, request, password, challenge->vp_octets,
1826 response->vp_octets + offset, nthashhash, auth_method);
1828 * Check for errors, and add MSCHAP-Error if necessary.
1830 rcode = mschap_error(inst, request, *response->vp_octets,
1831 mschap_result, mschap_version, smb_ctrl);
1832 if (rcode != RLM_MODULE_OK) return rcode;
1833 } else if ((response = fr_pair_find_by_num(request->packet->vps, PW_MSCHAP2_RESPONSE,
1834 VENDORPEC_MICROSOFT, TAG_ANY)) != NULL) {
1835 uint8_t mschapv1_challenge[16];
1836 VALUE_PAIR *name_attr, *response_name;
1842 * MS-CHAPv2 challenges are 16 octets.
1844 if (challenge->vp_length < 16) {
1845 REDEBUG("MS-CHAP-Challenge has the wrong format");
1846 return RLM_MODULE_INVALID;
1850 * Responses are 50 octets.
1852 if (response->vp_length < 50) {
1853 REDEBUG("MS-CHAP-Response has the wrong format");
1854 return RLM_MODULE_INVALID;
1858 * We also require a User-Name
1860 username = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
1862 REDEBUG("We require a User-Name for MS-CHAPv2");
1863 return RLM_MODULE_INVALID;
1867 * Check for MS-CHAP-User-Name and if found, use it
1868 * to construct the MSCHAPv1 challenge. This is
1869 * set by rlm_eap_mschap to the MS-CHAP Response
1870 * packet Name field.
1872 * We prefer this to the User-Name in the
1875 response_name = fr_pair_find_by_num(request->packet->vps, PW_MS_CHAP_USER_NAME, 0, TAG_ANY);
1876 if (response_name) {
1877 name_attr = response_name;
1879 name_attr = username;
1883 * with_ntdomain_hack moved here, too.
1885 if ((username_string = strchr(name_attr->vp_strvalue, '\\')) != NULL) {
1886 if (inst->with_ntdomain_hack) {
1889 RWDEBUG2("NT Domain delimeter found, should with_ntdomain_hack of been enabled?");
1890 username_string = name_attr->vp_strvalue;
1893 username_string = name_attr->vp_strvalue;
1896 if (response_name && ((username->vp_length != response_name->vp_length) ||
1897 (strncasecmp(username->vp_strvalue, response_name->vp_strvalue, username->vp_length) != 0))) {
1898 RWDEBUG("User-Name (%s) is not the same as MS-CHAP Name (%s) from EAP-MSCHAPv2",
1899 username->vp_strvalue, response_name->vp_strvalue);
1904 * No "known good" NT-Password attribute. Try to do
1905 * OpenDirectory authentication.
1907 * If OD determines the user is an AD user it will return noop, which
1908 * indicates the auth process should continue directly to AD.
1909 * Otherwise OD will determine auth success/fail.
1911 if (!nt_password && inst->open_directory) {
1912 RDEBUG2("No NT-Password configured. Trying OpenDirectory Authentication");
1913 int odStatus = od_mschap_auth(request, challenge, username);
1914 if (odStatus != RLM_MODULE_NOOP) {
1920 * The old "mschapv2" function has been moved to
1923 * MS-CHAPv2 takes some additional data to create an
1924 * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1926 RDEBUG2("Creating challenge hash with username: %s", username_string);
1927 mschap_challenge_hash(response->vp_octets + 2, /* peer challenge */
1928 challenge->vp_octets, /* our challenge */
1929 username_string, /* user name */
1930 mschapv1_challenge); /* resulting challenge */
1932 RDEBUG2("Client is using MS-CHAPv2");
1933 mschap_result = do_mschap(inst, request, nt_password, mschapv1_challenge,
1934 response->vp_octets + 26, nthashhash, auth_method);
1935 rcode = mschap_error(inst, request, *response->vp_octets,
1936 mschap_result, mschap_version, smb_ctrl);
1937 if (rcode != RLM_MODULE_OK) return rcode;
1939 mschap_auth_response(username_string, /* without the domain */
1940 nthashhash, /* nt-hash-hash */
1941 response->vp_octets + 26, /* peer response */
1942 response->vp_octets + 2, /* peer challenge */
1943 challenge->vp_octets, /* our challenge */
1944 msch2resp); /* calculated MPPE key */
1945 mschap_add_reply(request, *response->vp_octets, "MS-CHAP2-Success", msch2resp, 42);
1947 } else { /* Neither CHAPv1 or CHAPv2 response: die */
1948 REDEBUG("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1949 return RLM_MODULE_INVALID;
1952 /* now create MPPE attributes */
1953 if (inst->use_mppe) {
1954 uint8_t mppe_sendkey[34];
1955 uint8_t mppe_recvkey[34];
1957 if (mschap_version == 1) {
1958 RDEBUG2("adding MS-CHAPv1 MPPE keys");
1959 memset(mppe_sendkey, 0, 32);
1961 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1965 * According to RFC 2548 we
1966 * should send NT hash. But in
1967 * practice it doesn't work.
1968 * Instead, we should send nthashhash
1970 * This is an error in RFC 2548.
1973 * do_mschap cares to zero nthashhash if NT hash
1976 memcpy(mppe_sendkey + 8, nthashhash, NT_DIGEST_LENGTH);
1977 mppe_add_reply(request, "MS-CHAP-MPPE-Keys", mppe_sendkey, 24);
1979 } else if (mschap_version == 2) {
1980 RDEBUG2("Adding MS-CHAPv2 MPPE keys");
1981 mppe_chap2_gen_keys128(nthashhash, response->vp_octets + 26, mppe_sendkey, mppe_recvkey);
1983 mppe_add_reply(request, "MS-MPPE-Recv-Key", mppe_recvkey, 16);
1984 mppe_add_reply(request, "MS-MPPE-Send-Key", mppe_sendkey, 16);
1987 pair_make_reply("MS-MPPE-Encryption-Policy",
1988 (inst->require_encryption) ? "0x00000002":"0x00000001", T_OP_EQ);
1989 pair_make_reply("MS-MPPE-Encryption-Types",
1990 (inst->require_strong) ? "0x00000004":"0x00000006", T_OP_EQ);
1991 } /* else we weren't asked to use MPPE */
1993 return RLM_MODULE_OK;
1997 extern module_t rlm_mschap;
1998 module_t rlm_mschap = {
1999 .magic = RLM_MODULE_INIT,
2002 .inst_size = sizeof(rlm_mschap_t),
2003 .config = module_config,
2004 .bootstrap = mod_bootstrap,
2005 .instantiate = mod_instantiate,
2006 .detach = mod_detach,
2008 [MOD_AUTHENTICATE] = mod_authenticate,
2009 [MOD_AUTHORIZE] = mod_authorize