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, version 2 if the
4 * License as published by the Free Software Foundation.
6 * This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 * GNU General Public License for more details.
11 * You should have received a copy of the GNU General Public License
12 * along with this program; if not, write to the Free Software
13 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 * @brief Implemented mschap authentication.
21 * @copyright 2000,2001,2006 The FreeRADIUS server project
24 /* MPPE support from Takahiro Wagatsuma <waga@sic.shibaura-it.ac.jp> */
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/modules.h>
29 #include <freeradius-devel/rad_assert.h>
30 #include <freeradius-devel/md5.h>
31 #include <freeradius-devel/sha1.h>
38 #ifdef HAVE_OPENSSL_CRYPTO_H
39 USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
40 # include <openssl/rc4.h>
43 #ifdef WITH_OPEN_DIRECTORY
44 int od_mschap_auth(REQUEST *request, VALUE_PAIR *challenge, VALUE_PAIR * usernamepair);
47 /* Allowable account control bits */
48 #define ACB_DISABLED 0x00010000 //!< User account disabled.
49 #define ACB_HOMDIRREQ 0x00020000 //!< Home directory required.
50 #define ACB_PWNOTREQ 0x00040000 //!< User password not required.
51 #define ACB_TEMPDUP 0x00080000 //!< Temporary duplicate account.
52 #define ACB_NORMAL 0x00100000 //!< Normal user account.
53 #define ACB_MNS 0x00200000 //!< MNS logon user account.
54 #define ACB_DOMTRUST 0x00400000 //!< Interdomain trust account.
55 #define ACB_WSTRUST 0x00800000 //!< Workstation trust account.
56 #define ACB_SVRTRUST 0x01000000 //!< Server trust account.
57 #define ACB_PWNOEXP 0x02000000 //!< User password does not expire.
58 #define ACB_AUTOLOCK 0x04000000 //!< Account auto locked.
59 #define ACB_PW_EXPIRED 0x00020000 //!< Password Expired.
61 static int pdb_decode_acct_ctrl(char const *p)
67 * Check if the account type bits have been encoded after the
68 * NT password (in the form [NDHTUWSLXI]).
71 if (*p != '[') return 0;
73 for (p++; *p && !done; p++) {
75 case 'N': /* 'N'o password. */
76 acct_ctrl |= ACB_PWNOTREQ;
79 case 'D': /* 'D'isabled. */
80 acct_ctrl |= ACB_DISABLED ;
83 case 'H': /* 'H'omedir required. */
84 acct_ctrl |= ACB_HOMDIRREQ;
87 case 'T': /* 'T'emp account. */
88 acct_ctrl |= ACB_TEMPDUP;
91 case 'U': /* 'U'ser account (normal). */
92 acct_ctrl |= ACB_NORMAL;
95 case 'M': /* 'M'NS logon user account. What is this? */
99 case 'W': /* 'W'orkstation account. */
100 acct_ctrl |= ACB_WSTRUST;
103 case 'S': /* 'S'erver account. */
104 acct_ctrl |= ACB_SVRTRUST;
107 case 'L': /* 'L'ocked account. */
108 acct_ctrl |= ACB_AUTOLOCK;
111 case 'X': /* No 'X'piry on password */
112 acct_ctrl |= ACB_PWNOEXP;
115 case 'I': /* 'I'nterdomain trust account. */
116 acct_ctrl |= ACB_DOMTRUST;
119 case 'e': /* 'e'xpired, the password has */
120 acct_ctrl |= ACB_PW_EXPIRED;
123 case ' ': /* ignore spaces */
140 typedef struct rlm_mschap_t {
142 bool require_encryption;
144 bool with_ntdomain_hack; /* this should be in another module */
145 char const *xlat_name;
146 char const *ntlm_auth;
147 uint32_t ntlm_auth_timeout;
148 char const *ntlm_cpw;
149 char const *ntlm_cpw_username;
150 char const *ntlm_cpw_domain;
151 char const *local_cpw;
152 char const *auth_type;
154 char const *retry_msg;
155 #ifdef WITH_OPEN_DIRECTORY
162 * Does dynamic translation of strings.
164 * Pulls NT-Response, LM-Response, or Challenge from MSCHAP
167 static ssize_t mschap_xlat(void *instance, REQUEST *request,
168 char const *fmt, char *out, size_t outlen)
171 uint8_t const *data = NULL;
173 VALUE_PAIR *user_name;
174 VALUE_PAIR *chap_challenge, *response;
175 rlm_mschap_t *inst = instance;
180 * Challenge means MS-CHAPv1 challenge, or
181 * hash of MS-CHAPv2 challenge, and peer challenge.
183 if (strncasecmp(fmt, "Challenge", 9) == 0) {
184 chap_challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
185 if (!chap_challenge) {
186 REDEBUG("No MS-CHAP-Challenge in the request");
191 * MS-CHAP-Challenges are 8 octets,
194 if (chap_challenge->length == 8) {
195 RDEBUG2("mschap1: %02x", chap_challenge->vp_octets[0]);
196 data = chap_challenge->vp_octets;
200 * MS-CHAP-Challenges are 16 octets,
203 } else if (chap_challenge->length == 16) {
204 VALUE_PAIR *name_attr, *response_name;
205 char const *username_string;
207 response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
209 REDEBUG("MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge");
214 * FIXME: Much of this is copied from
215 * below. We should put it into a
220 * Responses are 50 octets.
222 if (response->length < 50) {
223 REDEBUG("MS-CHAP-Response has the wrong format");
227 user_name = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
229 REDEBUG("User-Name is required to calculate MS-CHAPv1 Challenge");
234 * Check for MS-CHAP-User-Name and if found, use it
235 * to construct the MSCHAPv1 challenge. This is
236 * set by rlm_eap_mschap to the MS-CHAP Response
239 * We prefer this to the User-Name in the
242 response_name = pairfind(request->packet->vps, PW_MS_CHAP_USER_NAME, 0, TAG_ANY);
244 name_attr = response_name;
246 name_attr = user_name;
250 * with_ntdomain_hack moved here, too.
252 if ((username_string = strchr(name_attr->vp_strvalue, '\\')) != NULL) {
253 if (inst->with_ntdomain_hack) {
256 RWDEBUG2("NT Domain delimiter found, should we have enabled with_ntdomain_hack?");
257 username_string = name_attr->vp_strvalue;
260 username_string = name_attr->vp_strvalue;
264 ((user_name->length != response_name->length) ||
265 (strncasecmp(user_name->vp_strvalue, response_name->vp_strvalue, user_name->length) != 0))) {
266 RWDEBUG2("User-Name (%s) is not the same as MS-CHAP Name (%s) from EAP-MSCHAPv2",
267 user_name->vp_strvalue, response_name->vp_strvalue);
271 * Get the MS-CHAPv1 challenge
272 * from the MS-CHAPv2 peer challenge,
273 * our challenge, and the user name.
275 RDEBUG2("Creating challenge hash with username: %s", username_string);
276 mschap_challenge_hash(response->vp_octets + 2,
277 chap_challenge->vp_octets,
278 username_string, buffer);
282 REDEBUG("Invalid MS-CHAP challenge length");
287 * Get the MS-CHAPv1 response, or the MS-CHAPv2
290 } else if (strncasecmp(fmt, "NT-Response", 11) == 0) {
291 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
292 if (!response) response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
294 REDEBUG("No MS-CHAP-Response or MS-CHAP2-Response was found in the request");
299 * For MS-CHAPv1, the NT-Response exists only
300 * if the second octet says so.
302 if ((response->da->vendor == VENDORPEC_MICROSOFT) &&
303 (response->da->attr == PW_MSCHAP_RESPONSE) &&
304 ((response->vp_octets[1] & 0x01) == 0)) {
305 REDEBUG("No NT-Response in MS-CHAP-Response");
310 * MS-CHAP-Response and MS-CHAP2-Response have
311 * the NT-Response at the same offset, and are
314 data = response->vp_octets + 26;
318 * LM-Response is deprecated, and exists only
319 * in MS-CHAPv1, and not often there.
321 } else if (strncasecmp(fmt, "LM-Response", 11) == 0) {
322 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
324 REDEBUG("No MS-CHAP-Response was found in the request");
329 * For MS-CHAPv1, the LM-Response exists only
330 * if the second octet says so.
332 if ((response->vp_octets[1] & 0x01) != 0) {
333 REDEBUG("No LM-Response in MS-CHAP-Response");
336 data = response->vp_octets + 2;
340 * Pull the NT-Domain out of the User-Name, if it exists.
342 } else if (strncasecmp(fmt, "NT-Domain", 9) == 0) {
345 user_name = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
347 REDEBUG("No User-Name was found in the request");
352 * First check to see if this is a host/ style User-Name
353 * (a la Kerberos host principal)
355 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
357 * If we're getting a User-Name formatted in this way,
358 * it's likely due to PEAP. The Windows Domain will be
359 * the first domain component following the hostname,
360 * or the machine name itself if only a hostname is supplied
362 p = strchr(user_name->vp_strvalue, '.');
364 RDEBUG2("setting NT-Domain to same as machine name");
365 strlcpy(out, user_name->vp_strvalue + 5, outlen);
367 p++; /* skip the period */
370 * use the same hack as below
371 * only if another period was found
374 strlcpy(out, p, outlen);
378 p = strchr(user_name->vp_strvalue, '\\');
380 REDEBUG("No NT-Domain was found in the User-Name");
385 * Hack. This is simpler than the alternatives.
388 strlcpy(out, user_name->vp_strvalue, outlen);
395 * Pull the User-Name out of the User-Name...
397 } else if (strncasecmp(fmt, "User-Name", 9) == 0) {
400 user_name = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
402 REDEBUG("No User-Name was found in the request");
407 * First check to see if this is a host/ style User-Name
408 * (a la Kerberos host principal)
410 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
411 p = user_name->vp_strvalue + 5;
413 * If we're getting a User-Name formatted in this way,
414 * it's likely due to PEAP. When authenticating this against
415 * a Domain, Windows will expect the User-Name to be in the
416 * format of hostname$, the SAM version of the name, so we
417 * have to convert it to that here. We do so by stripping
418 * off the first 5 characters (host/), and copying everything
419 * from that point to the first period into a string and appending
425 * use the same hack as above
426 * only if a period was found
429 snprintf(out, outlen, "%.*s$",
432 snprintf(out, outlen, "%s$", p);
435 p = strchr(user_name->vp_strvalue, '\\');
437 p++; /* skip the backslash */
439 p = user_name->vp_strvalue; /* use the whole User-Name */
441 strlcpy(out, p, outlen);
447 * Return the NT-Hash of the passed string
449 } else if (strncasecmp(fmt, "NT-Hash ", 8) == 0) {
452 p = fmt + 8; /* 7 is the length of 'NT-Hash' */
453 if ((p == '\0') || (outlen <= 32))
456 while (isspace(*p)) p++;
458 if (mschap_ntpwdhash(buffer, p) < 0) {
459 REDEBUG("Failed generating NT-Password");
464 fr_bin2hex(out, buffer, NT_DIGEST_LENGTH);
466 RDEBUG("NT-Hash of \"known-good\" password: %s", out);
470 * Return the LM-Hash of the passed string
472 } else if (strncasecmp(fmt, "LM-Hash ", 8) == 0) {
475 p = fmt + 8; /* 7 is the length of 'LM-Hash' */
476 if ((p == '\0') || (outlen <= 32))
479 while (isspace(*p)) p++;
481 smbdes_lmpwdhash(p, buffer);
482 fr_bin2hex(out, buffer, LM_DIGEST_LENGTH);
484 RDEBUG("LM-Hash of %s = %s", p, out);
487 REDEBUG("Unknown expansion string '%s'", fmt);
491 if (outlen == 0) return 0; /* nowhere to go, don't do anything */
494 * Didn't set anything: this is bad.
497 RWDEBUG2("Failed to do anything intelligent");
502 * Check the output length.
504 if (outlen < ((data_len * 2) + 1)) {
505 data_len = (outlen - 1) / 2;
511 for (i = 0; i < data_len; i++) {
512 sprintf(out + (2 * i), "%02x", data[i]);
514 out[data_len * 2] = '\0';
520 static const CONF_PARSER passchange_config[] = {
521 { "ntlm_auth", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_mschap_t, ntlm_cpw), NULL },
522 { "ntlm_auth_username", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_mschap_t, ntlm_cpw_username), NULL },
523 { "ntlm_auth_domain", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_mschap_t, ntlm_cpw_domain), NULL },
524 { "local_cpw", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_mschap_t, local_cpw), NULL },
525 { NULL, -1, 0, NULL, NULL } /* end the list */
527 static const CONF_PARSER module_config[] = {
529 * Cache the password by default.
531 { "use_mppe", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_mschap_t, use_mppe), "yes" },
532 { "require_encryption", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_mschap_t, require_encryption), "no" },
533 { "require_strong", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_mschap_t, require_strong), "no" },
534 { "with_ntdomain_hack", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_mschap_t, with_ntdomain_hack), "yes" },
535 { "ntlm_auth", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_mschap_t, ntlm_auth), NULL },
536 { "ntlm_auth_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_mschap_t, ntlm_auth_timeout), NULL },
537 { "passchange", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) passchange_config },
538 { "allow_retry", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_mschap_t, allow_retry), "yes" },
539 { "retry_msg", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_mschap_t, retry_msg), NULL },
540 #ifdef WITH_OPEN_DIRECTORY
541 { "use_open_directory", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_mschap_t, open_directory), "yes" },
544 { NULL, -1, 0, NULL, NULL } /* end the list */
549 * Create instance for our module. Allocate space for
550 * instance structure and read configuration parameters
552 static int mod_instantiate(CONF_SECTION *conf, void *instance)
555 rlm_mschap_t *inst = instance;
558 * Create the dynamic translation.
560 name = cf_section_name2(conf);
561 if (!name) name = cf_section_name1(conf);
562 inst->xlat_name = name;
563 xlat_register(inst->xlat_name, mschap_xlat, NULL, inst);
566 * For backwards compatibility
568 if (!dict_valbyname(PW_AUTH_TYPE, 0, inst->xlat_name)) {
569 inst->auth_type = "MS-CHAP";
571 inst->auth_type = inst->xlat_name;
575 * Check ntlm_auth_timeout is sane
577 if (!inst->ntlm_auth_timeout) {
578 inst->ntlm_auth_timeout = EXEC_TIMEOUT;
580 if (inst->ntlm_auth_timeout < 1) {
581 cf_log_err_cs(conf, "ntml_auth_timeout '%d' is too small (minimum: 1)",
582 inst->ntlm_auth_timeout);
585 if (inst->ntlm_auth_timeout > 10) {
586 cf_log_err_cs(conf, "ntlm_auth_timeout '%d' is too large (maximum: 10)",
587 inst->ntlm_auth_timeout);
595 * add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
596 * attribute to reply packet
598 void mschap_add_reply(REQUEST *request, unsigned char ident,
599 char const *name, char const *value, size_t len)
603 vp = pairmake_reply(name, NULL, T_OP_EQ);
605 REDEBUG("Failed to create attribute %s: %s", name, fr_strerror());
609 /* Account for the ident byte */
610 vp->length = len + 1;
611 if (vp->da->type == PW_TYPE_STRING) {
614 vp->vp_strvalue = p = talloc_array(vp, char, vp->length + 1);
615 p[vp->length] = '\0'; /* Always \0 terminate */
617 memcpy(p + 1, value, len);
621 vp->vp_octets = p = talloc_array(vp, uint8_t, vp->length);
623 memcpy(p + 1, value, len);
628 * Add MPPE attributes to the reply.
630 static void mppe_add_reply(REQUEST *request, char const* name, uint8_t const * value, size_t len)
634 vp = pairmake_reply(name, NULL, T_OP_EQ);
636 REDEBUG("mppe_add_reply failed to create attribute %s: %s", name, fr_strerror());
640 pairmemcpy(vp, value, len);
643 static int write_all(int fd, char const *buf, int len) {
647 rv = write(fd, buf+done, len-done);
656 * Perform an MS-CHAP2 password change
659 static int CC_HINT(nonnull (1, 2, 4, 5)) do_mschap_cpw(rlm_mschap_t *inst,
661 #ifdef HAVE_OPENSSL_CRYPTO_H
662 VALUE_PAIR *nt_password,
664 UNUSED VALUE_PAIR *nt_password,
666 uint8_t *new_nt_password,
667 uint8_t *old_nt_hash,
670 if (inst->ntlm_cpw && do_ntlm_auth) {
672 * we're going to run ntlm_auth in helper-mode
673 * we're expecting to use the ntlm-change-password-1 protocol
674 * which needs the following on stdin:
676 * username: %{mschap:User-Name}
677 * nt-domain: %{mschap:NT-Domain}
678 * new-nt-password-blob: bin2hex(new_nt_password) - 1032 bytes encoded
679 * old-nt-hash-blob: bin2hex(old_nt_hash) - 32 bytes encoded
680 * new-lm-password-blob: 00000...0000 - 1032 bytes null
681 * old-lm-hash-blob: 000....000 - 32 bytes null
684 * ...and it should then print out
686 * Password-Change: Yes
690 * Password-Change: No
691 * Password-Change-Error: blah
696 pid_t pid, child_pid;
702 RDEBUG("Doing MS-CHAPv2 password change via ntlm_auth helper");
705 * Start up ntlm_auth with a pipe on stdin and stdout
708 pid = radius_start_program(inst->ntlm_cpw, request, true, &to_child, &from_child, NULL, false);
710 REDEBUG("could not exec ntlm_auth cpw command");
715 * write the stuff to the client
718 if (inst->ntlm_cpw_username) {
719 len = radius_xlat(buf, sizeof(buf) - 2, request, inst->ntlm_cpw_username, NULL, NULL);
727 if (write_all(to_child, buf, len) != len) {
728 REDEBUG("Failed to write username to child");
732 RWDEBUG2("No ntlm_auth username set, passchange will definitely fail!");
735 if (inst->ntlm_cpw_domain) {
736 len = radius_xlat(buf, sizeof(buf) - 2, request, inst->ntlm_cpw_domain, NULL, NULL);
744 if (write_all(to_child, buf, len) != len) {
745 REDEBUG("Failed to write domain to child");
749 RWDEBUG2("No ntlm_auth domain set, username must be full-username to work");
752 /* now the password blobs */
753 len = sprintf(buf, "new-nt-password-blob: ");
754 fr_bin2hex(buf+len, new_nt_password, 516);
755 buf[len+1032] = '\n';
756 buf[len+1033] = '\0';
758 if (write_all(to_child, buf, len) != len) {
759 RDEBUG2("failed to write new password blob to child");
763 len = sprintf(buf, "old-nt-hash-blob: ");
764 fr_bin2hex(buf+len, old_nt_hash, NT_DIGEST_LENGTH);
768 if (write_all(to_child, buf, len) != len) {
769 REDEBUG("Failed to write old hash blob to child");
774 * In current samba versions, failure to supply empty LM password/hash
775 * blobs causes the change to fail.
777 len = sprintf(buf, "new-lm-password-blob: %01032i\n", 0);
778 if (write_all(to_child, buf, len) != len) {
779 REDEBUG("Failed to write dummy LM password to child");
782 len = sprintf(buf, "old-lm-hash-blob: %032i\n", 0);
783 if (write_all(to_child, buf, len) != len) {
784 REDEBUG("Failed to write dummy LM hash to child");
787 if (write_all(to_child, ".\n", 2) != 2) {
788 REDEBUG("Failed to send finish to child");
795 * Read from the child
797 len = radius_readfrom_program(from_child, pid, 10, buf, sizeof(buf));
799 /* radius_readfrom_program will have closed from_child for us */
800 REDEBUG("Failure reading from child");
807 RDEBUG2("ntlm_auth said: %s", buf);
809 child_pid = rad_waitpid(pid, &status);
810 if (child_pid == 0) {
811 REDEBUG("Timeout waiting for child");
814 if (child_pid != pid) {
815 REDEBUG("Abnormal exit status: %s", fr_syserror(errno));
819 if (strstr(buf, "Password-Change: Yes")) {
820 RDEBUG2("ntlm_auth password change succeeded");
824 pmsg = strstr(buf, "Password-Change-Error: ");
826 emsg = strsep(&pmsg, "\n");
828 emsg = "could not find error";
830 REDEBUG("ntlm auth password change failed: %s", emsg);
833 /* safe because these either need closing or are == -1 */
839 } else if (inst->local_cpw) {
840 #ifdef HAVE_OPENSSL_CRYPTO_H
842 * Decrypt the new password blob, add it as a temporary request
843 * variable, xlat the local_cpw string, then remove it
845 * this allows is to write e..g
847 * %{sql:insert into ...}
851 * %{exec:/path/to %{mschap:User-Name} %{MS-CHAP-New-Password}}"
854 VALUE_PAIR *new_pass, *new_hash;
861 uint8_t nt_pass_decrypted[516], old_nt_hash_expected[NT_DIGEST_LENGTH];
865 RDEBUG("Local MS-CHAPv2 password change requires NT-Password attribute");
868 RDEBUG("Doing MS-CHAPv2 password change locally");
874 RC4_set_key(&key, nt_password->length, nt_password->vp_octets);
875 RC4(&key, 516, new_nt_password, nt_pass_decrypted);
879 * 512-N bytes random pad
880 * N bytes password as utf-16-le
881 * 4 bytes - N as big-endian int
883 passlen = nt_pass_decrypted[512];
884 passlen += nt_pass_decrypted[513] << 8;
885 if ((nt_pass_decrypted[514] != 0) ||
886 (nt_pass_decrypted[515] != 0)) {
887 REDEBUG("Decrypted new password blob claims length > 65536, "
888 "probably an invalid NT-Password");
893 * Sanity check - passlen positive and <= 512 if not, crypto has probably gone wrong
896 REDEBUG("Decrypted new password blob claims length %zu > 512, "
897 "probably an invalid NT-Password", passlen);
901 p = nt_pass_decrypted + 512 - passlen;
904 * The new NT hash - this should be preferred over the
905 * cleartext password as it avoids unicode hassles.
907 new_hash = pairmake_packet("MS-CHAP-New-NT-Password", NULL, T_OP_EQ);
908 new_hash->length = NT_DIGEST_LENGTH;
909 new_hash->vp_octets = q = talloc_array(new_hash, uint8_t, new_hash->length);
910 fr_md4_calc(q, p, passlen);
913 * Check that nt_password encrypted with new_hash
914 * matches the old_hash value from the client.
916 smbhash(old_nt_hash_expected, nt_password->vp_octets, q);
917 smbhash(old_nt_hash_expected+8, nt_password->vp_octets+8, q + 7);
918 if (memcmp(old_nt_hash_expected, old_nt_hash, NT_DIGEST_LENGTH)!=0) {
919 REDEBUG("Old NT hash value from client does not match our value");
924 * The new cleartext password, which is utf-16 do some unpleasant vileness
925 * to turn it into utf8 without pulling in libraries like iconv.
927 * First pass: get the length of the converted string.
929 new_pass = pairmake_packet("MS-CHAP-New-Cleartext-Password", NULL, T_OP_EQ);
930 new_pass->length = 0;
933 while (i < passlen) {
940 * Gah. nasty. maybe we should just pull in iconv?
944 } else if (c < 0x7ff) {
945 new_pass->length += 2;
947 new_pass->length += 3;
951 new_pass->vp_strvalue = x = talloc_array(new_pass, char, new_pass->length + 1);
954 * Second pass: convert the characters from UTF-16 to UTF-8.
957 while (i < passlen) {
964 * Gah. nasty. maybe we should just pull in iconv?
969 } else if (c < 0x7ff) {
970 *x++ = 0xc0 + (c >> 6);
971 *x++ = 0x80 + (c & 0x3f);
974 *x++ = 0xe0 + (c >> 12);
975 *x++ = 0x80 + ((c>>6) & 0x3f);
976 *x++ = 0x80 + (c & 0x3f);
982 /* Perform the xlat */
983 result_len = radius_xlat(result, sizeof(result), request, inst->local_cpw, NULL, NULL);
986 } else if (result_len == 0) {
987 REDEBUG("Local MS-CHAPv2 password change - xlat didn't give any result, assuming failure");
991 RDEBUG("MS-CHAPv2 password change succeeded: %s", result);
994 * Update the NT-Password attribute with the new hash this lets us
995 * fall through to the authentication code using the new hash,
998 pairmemcpy(nt_password, new_hash->vp_octets, new_hash->length);
1001 * Rock on! password change succeeded.
1005 REDEBUG("Local MS-CHAPv2 password changes require OpenSSL support");
1009 REDEBUG("MS-CHAPv2 password change not configured");
1016 * Do the MS-CHAP stuff.
1018 * This function is here so that all of the MS-CHAP related
1019 * authentication is in one place, and we can perhaps later replace
1020 * it with code to call winbindd, or something similar.
1022 static int CC_HINT(nonnull (1, 2, 4, 5 ,6)) do_mschap(rlm_mschap_t *inst, REQUEST *request, VALUE_PAIR *password,
1023 uint8_t const *challenge, uint8_t const *response,
1024 uint8_t nthashhash[NT_DIGEST_LENGTH], bool do_ntlm_auth)
1026 uint8_t calculated[24];
1028 memset(nthashhash, 0, NT_DIGEST_LENGTH);
1030 * Do normal authentication.
1032 if (!do_ntlm_auth) {
1034 * No password: can't do authentication.
1037 REDEBUG("FAILED: No NT/LM-Password. Cannot perform authentication");
1041 smbdes_mschap(password->vp_octets, challenge, calculated);
1042 if (rad_digest_cmp(response, calculated, 24) != 0) {
1047 * If the password exists, and is an NT-Password,
1048 * then calculate the hash of the NT hash. Doing this
1049 * here minimizes work for later.
1051 if (password && !password->da->vendor &&
1052 (password->da->attr == PW_NT_PASSWORD)) {
1053 fr_md4_calc(nthashhash, password->vp_octets, MD4_DIGEST_LENGTH);
1055 } else { /* run ntlm_auth */
1061 * Run the program, and expect that we get 16
1063 result = radius_exec_program(buffer, sizeof(buffer), NULL, request, inst->ntlm_auth, NULL,
1064 true, true, inst->ntlm_auth_timeout);
1069 * look for "Password expired", or "Must change password".
1071 if (strcasestr(buffer, "Password expired") ||
1072 strcasestr(buffer, "Must change password")) {
1073 REDEBUG2("%s", buffer);
1077 RDEBUG2("External script failed");
1078 p = strchr(buffer, '\n');
1081 REDEBUG("External script says: %s", buffer);
1086 * Parse the answer as an nthashhash.
1088 * ntlm_auth currently returns:
1089 * NT_KEY: 000102030405060708090a0b0c0d0e0f
1091 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
1092 REDEBUG("Invalid output from ntlm_auth: expecting 'NT_KEY: ' prefix");
1097 * Check the length. It should be at least 32, with an LF at the end.
1099 len = strlen(buffer + 8);
1101 REDEBUG2("Invalid output from ntlm_auth: NT_KEY too short, expected 32 bytes got %zu bytes",
1108 * Update the NT hash hash, from the NT key.
1110 if (fr_hex2bin(nthashhash, NT_DIGEST_LENGTH, buffer + 8, len) != NT_DIGEST_LENGTH) {
1111 REDEBUG("Invalid output from ntlm_auth: NT_KEY has non-hex values");
1121 * Data for the hashes.
1123 static const uint8_t SHSpad1[40] =
1124 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1125 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1126 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1127 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
1129 static const uint8_t SHSpad2[40] =
1130 { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1131 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1132 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1133 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
1135 static const uint8_t magic1[27] =
1136 { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
1137 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
1138 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
1140 static const uint8_t magic2[84] =
1141 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
1142 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
1143 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1144 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
1145 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
1146 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
1147 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1148 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
1149 0x6b, 0x65, 0x79, 0x2e };
1151 static const uint8_t magic3[84] =
1152 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
1153 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
1154 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1155 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
1156 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
1157 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
1158 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
1159 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
1160 0x6b, 0x65, 0x79, 0x2e };
1163 static void mppe_GetMasterKey(uint8_t const *nt_hashhash,uint8_t const *nt_response,
1167 fr_SHA1_CTX Context;
1169 fr_sha1_init(&Context);
1170 fr_sha1_update(&Context,nt_hashhash,NT_DIGEST_LENGTH);
1171 fr_sha1_update(&Context,nt_response,24);
1172 fr_sha1_update(&Context,magic1,27);
1173 fr_sha1_final(digest,&Context);
1175 memcpy(masterkey,digest,16);
1179 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
1180 int keylen,int issend)
1184 fr_SHA1_CTX Context;
1186 memset(digest,0,20);
1194 fr_sha1_init(&Context);
1195 fr_sha1_update(&Context,masterkey,16);
1196 fr_sha1_update(&Context,SHSpad1,40);
1197 fr_sha1_update(&Context,s,84);
1198 fr_sha1_update(&Context,SHSpad2,40);
1199 fr_sha1_final(digest,&Context);
1201 memcpy(sesskey,digest,keylen);
1205 static void mppe_chap2_get_keys128(uint8_t const *nt_hashhash,uint8_t const *nt_response,
1206 uint8_t *sendkey,uint8_t *recvkey)
1208 uint8_t masterkey[16];
1210 mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
1212 mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
1213 mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
1217 * Generate MPPE keys.
1219 static void mppe_chap2_gen_keys128(uint8_t const *nt_hashhash,uint8_t const *response,
1220 uint8_t *sendkey,uint8_t *recvkey)
1222 uint8_t enckey1[16];
1223 uint8_t enckey2[16];
1225 mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
1228 * dictionary.microsoft defines these attributes as
1229 * 'encrypt=2'. The functions in src/lib/radius.c will
1230 * take care of encrypting/decrypting them as appropriate,
1231 * so that we don't have to.
1233 memcpy (sendkey, enckey1, 16);
1234 memcpy (recvkey, enckey2, 16);
1239 * mod_authorize() - authorize user if we can authenticate
1240 * it later. Add Auth-Type attribute if present in module
1241 * configuration (usually Auth-Type must be "MS-CHAP")
1243 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void * instance, REQUEST *request)
1245 rlm_mschap_t *inst = instance;
1246 VALUE_PAIR *challenge = NULL;
1248 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
1250 return RLM_MODULE_NOOP;
1253 if (!pairfind(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY) &&
1254 !pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY) &&
1255 !pairfind(request->packet->vps, PW_MSCHAP2_CPW, VENDORPEC_MICROSOFT, TAG_ANY)) {
1256 RDEBUG2("Found MS-CHAP-Challenge, but no MS-CHAP response or change-password");
1257 return RLM_MODULE_NOOP;
1260 if (pairfind(request->config_items, PW_AUTH_TYPE, 0, TAG_ANY)) {
1261 RWDEBUG2("Auth-Type already set. Not setting to MS-CHAP");
1262 return RLM_MODULE_NOOP;
1265 RDEBUG2("Found MS-CHAP attributes. Setting 'Auth-Type = %s'", inst->xlat_name);
1268 * Set Auth-Type to MS-CHAP. The authentication code
1269 * will take care of turning cleartext passwords into
1272 if (!pairmake_config("Auth-Type", inst->auth_type, T_OP_EQ)) {
1273 return RLM_MODULE_FAIL;
1276 return RLM_MODULE_OK;
1280 * mod_authenticate() - authenticate user based on given
1281 * attributes and configuration.
1282 * We will try to find out password in configuration
1283 * or in configured passwd file.
1284 * If one is found we will check paraneters given by NAS.
1286 * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
1288 * PAP: PW_USER_PASSWORD or
1289 * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
1290 * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
1291 * In case of password mismatch or locked account we MAY return
1292 * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
1293 * If MS-CHAP2 succeeds we MUST return
1294 * PW_MSCHAP2_SUCCESS
1296 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void * instance, REQUEST *request)
1298 #define inst ((rlm_mschap_t *)instance)
1299 VALUE_PAIR *challenge = NULL;
1300 VALUE_PAIR *response = NULL;
1301 VALUE_PAIR *cpw = NULL;
1302 VALUE_PAIR *password = NULL;
1303 VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
1304 VALUE_PAIR *username;
1305 uint8_t nthashhash[NT_DIGEST_LENGTH];
1308 char const *username_string;
1313 * If we have ntlm_auth configured, use it unless told
1316 do_ntlm_auth = (inst->ntlm_auth != NULL);
1319 * If we have an ntlm_auth configuration, then we may
1320 * want to suppress it.
1323 VALUE_PAIR *vp = pairfind(request->config_items, PW_MS_CHAP_USE_NTLM_AUTH, 0, TAG_ANY);
1324 if (vp) do_ntlm_auth = (vp->vp_integer > 0);
1328 * Find the SMB-Account-Ctrl attribute, or the
1329 * SMB-Account-Ctrl-Text attribute.
1331 smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL, 0, TAG_ANY);
1333 password = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL_TEXT, 0, TAG_ANY);
1335 smb_ctrl = pairmake_config("SMB-Account-CTRL", "0", T_OP_SET);
1337 smb_ctrl->vp_integer = pdb_decode_acct_ctrl(password->vp_strvalue);
1343 * We're configured to do MS-CHAP authentication.
1344 * and account control information exists. Enforce it.
1348 * Password is not required.
1350 if ((smb_ctrl->vp_integer & ACB_PWNOTREQ) != 0) {
1351 RDEBUG2("SMB-Account-Ctrl says no password is required");
1352 return RLM_MODULE_OK;
1357 * Decide how to get the passwords.
1359 password = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY);
1362 * We need an LM-Password.
1364 lm_password = pairfind(request->config_items, PW_LM_PASSWORD, 0, TAG_ANY);
1366 VERIFY_VP(lm_password);
1368 switch (lm_password->length) {
1369 case LM_DIGEST_LENGTH:
1370 RDEBUG2("Found LM-Password");
1376 RWDEBUG("LM-Password has not been normalized by the 'pap' module (likely still in hex format). "
1377 "Authentication may fail");
1382 RWDEBUG("LM-Password found but incorrect length, expected " STRINGIFY(LM_DIGEST_LENGTH)
1383 " bytes got %zu bytes. Authentication may fail", lm_password->length);
1389 * ... or a Cleartext-Password, which we now transform into an LM-Password
1391 if (!lm_password && password) {
1392 RDEBUG2("Found Cleartext-Password, hashing to create LM-Password");
1393 lm_password = pairmake_config("LM-Password", NULL, T_OP_EQ);
1395 RERROR("No memory");
1397 lm_password->length = LM_DIGEST_LENGTH;
1398 lm_password->vp_octets = p = talloc_array(lm_password, uint8_t, lm_password->length);
1399 smbdes_lmpwdhash(password->vp_strvalue, p);
1401 } else if (!do_ntlm_auth) {
1402 RWDEBUG2("No Cleartext-Password configured. Cannot create LM-Password");
1406 * or we need an NT-Password.
1408 nt_password = pairfind(request->config_items, PW_NT_PASSWORD, 0, TAG_ANY);
1410 VERIFY_VP(nt_password);
1412 switch (nt_password->length) {
1413 case NT_DIGEST_LENGTH:
1414 RDEBUG2("Found NT-Password");
1420 RWDEBUG("NT-Password has not been normalized by the 'pap' module (likely still in hex format). "
1421 "Authentication may fail");
1426 RWDEBUG("NT-Password found but incorrect length, expected " STRINGIFY(NT_DIGEST_LENGTH)
1427 " bytes got %zu bytes. Authentication may fail", nt_password->length);
1434 * ... or a Cleartext-Password, which we now transform into an NT-Password
1436 if (!nt_password && password) {
1437 RDEBUG2("Found Cleartext-Password, hashing to create NT-Password");
1438 nt_password = pairmake_config("NT-Password", NULL, T_OP_EQ);
1440 RERROR("No memory");
1441 return RLM_MODULE_FAIL;
1443 nt_password->length = NT_DIGEST_LENGTH;
1444 nt_password->vp_octets = p = talloc_array(nt_password, uint8_t, nt_password->length);
1446 if (mschap_ntpwdhash(p, password->vp_strvalue) < 0) {
1447 RERROR("Failed generating NT-Password");
1448 return RLM_MODULE_FAIL;
1450 } else if (!do_ntlm_auth) {
1451 RWDEBUG2("No Cleartext-Password configured. Cannot create NT-Password");
1454 cpw = pairfind(request->packet->vps, PW_MSCHAP2_CPW, VENDORPEC_MICROSOFT, TAG_ANY);
1457 * mschap2 password change request
1458 * we cheat - first decode and execute the passchange
1459 * we then extract the response, add it into the request
1460 * then jump into mschap2 auth with the chal/resp
1462 uint8_t new_nt_encrypted[516], old_nt_encrypted[NT_DIGEST_LENGTH];
1463 VALUE_PAIR *nt_enc=NULL;
1464 int seq, new_nt_enc_len=0;
1466 RDEBUG("MS-CHAPv2 password change request received");
1469 REDEBUG("No valid NT-Password attribute found, can't change password");
1470 return RLM_MODULE_INVALID;
1473 if (cpw->length != 68) {
1474 REDEBUG("MS-CHAP2-CPW has the wrong format: length %zu != 68", cpw->length);
1475 return RLM_MODULE_INVALID;
1476 } else if (cpw->vp_octets[0]!=7) {
1477 REDEBUG("MS-CHAP2-CPW has the wrong format: code %d != 7", cpw->vp_octets[0]);
1478 return RLM_MODULE_INVALID;
1482 * look for the new (encrypted) password
1483 * bah stupid composite attributes
1484 * we're expecting 3 attributes with the leading bytes
1485 * 06:<mschapid>:00:01:<1st chunk>
1486 * 06:<mschapid>:00:02:<2nd chunk>
1487 * 06:<mschapid>:00:03:<3rd chunk>
1489 for (seq = 1; seq < 4; seq++) {
1493 for (nt_enc = fr_cursor_init(&cursor, &request->packet->vps);
1495 nt_enc = fr_cursor_next(&cursor)) {
1496 if (nt_enc->da->vendor != VENDORPEC_MICROSOFT)
1499 if (nt_enc->da->attr != PW_MSCHAP_NT_ENC_PW)
1502 if (nt_enc->vp_octets[0] != 6) {
1503 REDEBUG("MS-CHAP-NT-Enc-PW with invalid format");
1504 return RLM_MODULE_INVALID;
1506 if (nt_enc->vp_octets[2]==0 && nt_enc->vp_octets[3]==seq) {
1513 REDEBUG("Could not find MS-CHAP-NT-Enc-PW w/ sequence number %d", seq);
1514 return RLM_MODULE_INVALID;
1518 * copy the data into the buffer
1520 memcpy(new_nt_encrypted + new_nt_enc_len, nt_enc->vp_octets + 4, nt_enc->length - 4);
1521 new_nt_enc_len += nt_enc->length - 4;
1523 if (new_nt_enc_len != 516) {
1524 REDEBUG("Unpacked MS-CHAP-NT-Enc-PW length != 516");
1525 return RLM_MODULE_INVALID;
1529 * RFC 2548 is confusing here
1534 * 16 octets - old hash encrypted with new hash
1535 * 24 octets - peer challenge
1537 * 16 octets - peer challenge
1538 * 8 octets - reserved
1539 * 24 octets - nt response
1540 * 2 octets - flags (ignored)
1543 memcpy(old_nt_encrypted, cpw->vp_octets + 2, sizeof(old_nt_encrypted));
1545 RDEBUG2("Password change payload valid");
1547 /* perform the actual password change */
1548 rad_assert(nt_password);
1549 if (do_mschap_cpw(inst, request, nt_password, new_nt_encrypted, old_nt_encrypted, do_ntlm_auth) < 0) {
1552 REDEBUG("Password change failed");
1554 snprintf(buffer, sizeof(buffer), "E=709 R=0 M=Password change failed");
1555 mschap_add_reply(request, cpw->vp_octets[1], "MS-CHAP-Error", buffer, strlen(buffer));
1557 return RLM_MODULE_REJECT;
1559 RDEBUG("Password change successful");
1562 * Clear any expiry bit so the user can now login;
1563 * obviously the password change action will need
1564 * to have cleared this bit in the config/SQL/wherever
1566 if (smb_ctrl && smb_ctrl->vp_integer & ACB_PW_EXPIRED) {
1567 RDEBUG("Clearing expiry bit in SMB-Acct-Ctrl to allow authentication");
1568 smb_ctrl->vp_integer &= ~ACB_PW_EXPIRED;
1572 * Extract the challenge & response from the end of the password
1573 * change, add them into the request and then continue with
1574 * the authentication
1577 response = radius_paircreate(request->packet, &request->packet->vps,
1578 PW_MSCHAP2_RESPONSE,
1579 VENDORPEC_MICROSOFT);
1580 response->length = 50;
1581 response->vp_octets = p = talloc_array(response, uint8_t, response->length);
1584 p[0] = cpw->vp_octets[1];
1586 /* peer challenge and client NT response */
1587 memcpy(p + 2, cpw->vp_octets + 18, 48);
1590 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
1592 REDEBUG("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1593 return RLM_MODULE_REJECT;
1597 * We also require an MS-CHAP-Response.
1599 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
1602 * MS-CHAP-Response, means MS-CHAPv1
1608 * MS-CHAPv1 challenges are 8 octets.
1610 if (challenge->length < 8) {
1611 REDEBUG("MS-CHAP-Challenge has the wrong format");
1612 return RLM_MODULE_INVALID;
1616 * Responses are 50 octets.
1618 if (response->length < 50) {
1619 REDEBUG("MS-CHAP-Response has the wrong format");
1620 return RLM_MODULE_INVALID;
1624 * We are doing MS-CHAP. Calculate the MS-CHAP
1627 if (response->vp_octets[1] & 0x01) {
1628 RDEBUG2("Client is using MS-CHAPv1 with NT-Password");
1629 password = nt_password;
1632 RDEBUG2("Client is using MS-CHAPv1 with LM-Password");
1633 password = lm_password;
1638 * Do the MS-CHAP authentication.
1640 if (do_mschap(inst, request, password, challenge->vp_octets, response->vp_octets + offset, nthashhash,
1641 do_ntlm_auth) < 0) {
1642 REDEBUG("MS-CHAP-Response is incorrect");
1648 } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY)) != NULL) {
1650 uint8_t mschapv1_challenge[16];
1651 VALUE_PAIR *name_attr, *response_name;
1654 * MS-CHAPv2 challenges are 16 octets.
1656 if (challenge->length < 16) {
1657 REDEBUG("MS-CHAP-Challenge has the wrong format");
1658 return RLM_MODULE_INVALID;
1662 * Responses are 50 octets.
1664 if (response->length < 50) {
1665 REDEBUG("MS-CHAP-Response has the wrong format");
1666 return RLM_MODULE_INVALID;
1670 * We also require a User-Name
1672 username = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
1674 REDEBUG("We require a User-Name for MS-CHAPv2");
1675 return RLM_MODULE_INVALID;
1679 * Check for MS-CHAP-User-Name and if found, use it
1680 * to construct the MSCHAPv1 challenge. This is
1681 * set by rlm_eap_mschap to the MS-CHAP Response
1682 * packet Name field.
1684 * We prefer this to the User-Name in the
1687 response_name = pairfind(request->packet->vps, PW_MS_CHAP_USER_NAME, 0, TAG_ANY);
1688 if (response_name) {
1689 name_attr = response_name;
1691 name_attr = username;
1695 * with_ntdomain_hack moved here, too.
1697 if ((username_string = strchr(name_attr->vp_strvalue, '\\')) != NULL) {
1698 if (inst->with_ntdomain_hack) {
1701 RWDEBUG2("NT Domain delimeter found, should with_ntdomain_hack of been enabled?");
1702 username_string = name_attr->vp_strvalue;
1705 username_string = name_attr->vp_strvalue;
1708 if (response_name && ((username->length != response_name->length) ||
1709 (strncasecmp(username->vp_strvalue, response_name->vp_strvalue, username->length) != 0))) {
1710 RWDEBUG("User-Name (%s) is not the same as MS-CHAP Name (%s) from EAP-MSCHAPv2",
1711 username->vp_strvalue, response_name->vp_strvalue);
1714 #ifdef WITH_OPEN_DIRECTORY
1716 * No "known good" NT-Password attribute. Try to do
1717 * OpenDirectory authentication.
1719 * If OD determines the user is an AD user it will return noop, which
1720 * indicates the auth process should continue directly to AD.
1721 * Otherwise OD will determine auth success/fail.
1723 if (!nt_password && inst->open_directory) {
1724 RDEBUG2("No NT-Password configured. Trying OpenDirectory Authentication");
1725 int odStatus = od_mschap_auth(request, challenge, username);
1726 if (odStatus != RLM_MODULE_NOOP) {
1732 * The old "mschapv2" function has been moved to
1735 * MS-CHAPv2 takes some additional data to create an
1736 * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1738 RDEBUG2("Creating challenge hash with username: %s", username_string);
1739 mschap_challenge_hash(response->vp_octets + 2, /* peer challenge */
1740 challenge->vp_octets, /* our challenge */
1741 username_string, /* user name */
1742 mschapv1_challenge); /* resulting challenge */
1744 RDEBUG2("Client is using MS-CHAPv2");
1746 mschap_result = do_mschap(inst, request, nt_password, mschapv1_challenge,
1747 response->vp_octets + 26, nthashhash, do_ntlm_auth);
1748 if (mschap_result == -648)
1749 goto password_expired;
1751 if (mschap_result < 0) {
1755 REDEBUG("MS-CHAP2-Response is incorrect");
1758 snprintf(buffer, sizeof(buffer), "E=691 R=%d", inst->allow_retry);
1760 if (inst->retry_msg) {
1761 snprintf(buffer + 9, sizeof(buffer) - 9, " C=");
1762 for (i = 0; i < 16; i++) {
1763 snprintf(buffer + 12 + (i * 2),
1764 sizeof(buffer) - 12 - (i * 2), "%02x",
1767 snprintf(buffer + 45, sizeof(buffer) - 45, " V=3 M=%s", inst->retry_msg);
1769 mschap_add_reply(request, *response->vp_octets, "MS-CHAP-Error", buffer, strlen(buffer));
1770 return RLM_MODULE_REJECT;
1774 * If the password is correct and it has expired
1775 * we can permit password changes (only in MS-CHAPv2)
1777 if (smb_ctrl && smb_ctrl->vp_integer & ACB_PW_EXPIRED) {
1779 char newchal[33], buffer[128];
1783 for (i = 0; i < 16; i++) {
1784 snprintf(newchal + (i * 2), 3, "%02x", fr_rand() & 0xff);
1787 snprintf(buffer, sizeof(buffer), "E=648 R=0 C=%s V=3 M=Password Expired", newchal);
1789 mschap_add_reply(request, *response->vp_octets, "MS-CHAP-Error", buffer, strlen(buffer));
1790 return RLM_MODULE_REJECT;
1793 mschap_auth_response(username_string, /* without the domain */
1794 nthashhash, /* nt-hash-hash */
1795 response->vp_octets + 26, /* peer response */
1796 response->vp_octets + 2, /* peer challenge */
1797 challenge->vp_octets, /* our challenge */
1798 msch2resp); /* calculated MPPE key */
1799 mschap_add_reply(request, *response->vp_octets, "MS-CHAP2-Success", msch2resp, 42);
1802 } else { /* Neither CHAPv1 or CHAPv2 response: die */
1803 REDEBUG("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1804 return RLM_MODULE_INVALID;
1808 * We have a CHAP response, but the account may be
1809 * disabled. Reject the user with the same error code
1810 * we use when their password is invalid.
1814 * Account is disabled.
1816 * They're found, but they don't exist, so we
1817 * return 'not found'.
1819 if (((smb_ctrl->vp_integer & ACB_DISABLED) != 0) ||
1820 ((smb_ctrl->vp_integer & (ACB_NORMAL|ACB_WSTRUST)) == 0)) {
1821 REDEBUG("SMB-Account-Ctrl says that the account is disabled, or is not a normal "
1822 "or workstation trust account");
1823 mschap_add_reply(request, *response->vp_octets, "MS-CHAP-Error", "E=691 R=1", 9);
1824 return RLM_MODULE_NOTFOUND;
1828 * User is locked out.
1830 if ((smb_ctrl->vp_integer & ACB_AUTOLOCK) != 0) {
1831 REDEBUG("SMB-Account-Ctrl says that the account is locked out");
1832 mschap_add_reply(request, *response->vp_octets, "MS-CHAP-Error", "E=647 R=0", 9);
1833 return RLM_MODULE_USERLOCK;
1837 /* now create MPPE attributes */
1838 if (inst->use_mppe) {
1839 uint8_t mppe_sendkey[34];
1840 uint8_t mppe_recvkey[34];
1843 RDEBUG2("adding MS-CHAPv1 MPPE keys");
1844 memset(mppe_sendkey, 0, 32);
1846 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1850 * According to RFC 2548 we
1851 * should send NT hash. But in
1852 * practice it doesn't work.
1853 * Instead, we should send nthashhash
1855 * This is an error on RFC 2548.
1858 * do_mschap cares to zero nthashhash if NT hash
1861 memcpy(mppe_sendkey + 8, nthashhash, NT_DIGEST_LENGTH);
1862 mppe_add_reply(request, "MS-CHAP-MPPE-Keys", mppe_sendkey, 32);
1863 } else if (chap == 2) {
1864 RDEBUG2("Adding MS-CHAPv2 MPPE keys");
1865 mppe_chap2_gen_keys128(nthashhash, response->vp_octets + 26, mppe_sendkey, mppe_recvkey);
1867 mppe_add_reply(request, "MS-MPPE-Recv-Key", mppe_recvkey, 16);
1868 mppe_add_reply(request, "MS-MPPE-Send-Key", mppe_sendkey, 16);
1871 pairmake_reply("MS-MPPE-Encryption-Policy",
1872 (inst->require_encryption) ? "0x00000002":"0x00000001", T_OP_EQ);
1873 pairmake_reply("MS-MPPE-Encryption-Types",
1874 (inst->require_strong) ? "0x00000004":"0x00000006", T_OP_EQ);
1875 } /* else we weren't asked to use MPPE */
1877 return RLM_MODULE_OK;
1881 module_t rlm_mschap = {
1884 RLM_TYPE_THREAD_SAFE | RLM_TYPE_HUP_SAFE, /* type */
1885 sizeof(rlm_mschap_t),
1887 mod_instantiate, /* instantiation */
1890 mod_authenticate, /* authenticate */
1891 mod_authorize, /* authorize */
1892 NULL, /* pre-accounting */
1893 NULL, /* accounting */
1894 NULL, /* checksimul */
1895 NULL, /* pre-proxy */
1896 NULL, /* post-proxy */
1897 NULL /* post-auth */