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> */
25 #include <freeradius-devel/ident.h>
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>
39 #ifdef HAVE_OPENSSL_CRYPTO_H
40 #include <openssl/rc4.h>
44 extern 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(const char *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 int require_encryption;
144 int with_ntdomain_hack; /* this should be in another module */
146 const char *xlat_name;
149 char *ntlm_cpw_username;
150 char *ntlm_cpw_domain;
152 const char *auth_type;
162 * Does dynamic translation of strings.
164 * Pulls NT-Response, LM-Response, or Challenge from MSCHAP
167 static size_t mschap_xlat(void *instance, REQUEST *request,
168 const char *fmt, char *out, size_t outlen)
171 uint8_t *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 RDEBUG2("No MS-CHAP-Challenge in the request.");
191 * MS-CHAP-Challenges are 8 octets,
194 if (chap_challenge->length == 8) {
195 RDEBUG2(" mschap1: %02x",
196 chap_challenge->vp_octets[0]);
197 data = chap_challenge->vp_octets;
201 * MS-CHAP-Challenges are 16 octets,
204 } else if (chap_challenge->length == 16) {
205 VALUE_PAIR *name_attr, *response_name;
206 char *username_string;
208 response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
210 RDEBUG2("MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge.");
215 * FIXME: Much of this is copied from
216 * below. We should put it into a
221 * Responses are 50 octets.
223 if (response->length < 50) {
224 radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
228 user_name = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
230 RDEBUG2("User-Name is required to calculate MS-CHAPv1 Challenge.");
235 * Check for MS-CHAP-User-Name and if found, use it
236 * to construct the MSCHAPv1 challenge. This is
237 * set by rlm_eap_mschap to the MS-CHAP Response
240 * We prefer this to the User-Name in the
243 response_name = pairfind(request->packet->vps, PW_MS_CHAP_USER_NAME, 0, TAG_ANY);
245 name_attr = response_name;
247 name_attr = user_name;
251 * with_ntdomain_hack moved here, too.
253 if ((username_string = strchr(name_attr->vp_strvalue, '\\')) != NULL) {
254 if (inst->with_ntdomain_hack) {
257 RDEBUG2("NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
258 username_string = name_attr->vp_strvalue;
261 username_string = name_attr->vp_strvalue;
265 ((user_name->length != response_name->length) ||
266 (strncasecmp(user_name->vp_strvalue, response_name->vp_strvalue, user_name->length) != 0))) {
267 RDEBUGW("User-Name (%s) is not the same as MS-CHAP Name (%s) from EAP-MSCHAPv2", 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",
277 mschap_challenge_hash(response->vp_octets + 2,
278 chap_challenge->vp_octets,
279 username_string, buffer);
283 RDEBUG2("Invalid MS-CHAP challenge length");
288 * Get the MS-CHAPv1 response, or the MS-CHAPv2
291 } else if (strncasecmp(fmt, "NT-Response", 11) == 0) {
292 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
293 if (!response) response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
295 RDEBUG2("No MS-CHAP-Response or MS-CHAP2-Response was found in the request.");
300 * For MS-CHAPv1, the NT-Response exists only
301 * if the second octet says so.
303 if ((response->da->vendor == VENDORPEC_MICROSOFT) &&
304 (response->da->attr == PW_MSCHAP_RESPONSE) &&
305 ((response->vp_octets[1] & 0x01) == 0)) {
306 RDEBUG2("No NT-Response in MS-CHAP-Response");
311 * MS-CHAP-Response and MS-CHAP2-Response have
312 * the NT-Response at the same offset, and are
315 data = response->vp_octets + 26;
319 * LM-Response is deprecated, and exists only
320 * in MS-CHAPv1, and not often there.
322 } else if (strncasecmp(fmt, "LM-Response", 11) == 0) {
323 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
325 RDEBUG2("No MS-CHAP-Response was found in the request.");
330 * For MS-CHAPv1, the NT-Response exists only
331 * if the second octet says so.
333 if ((response->vp_octets[1] & 0x01) != 0) {
334 RDEBUG2("No LM-Response in MS-CHAP-Response");
337 data = response->vp_octets + 2;
341 * Pull the NT-Domain out of the User-Name, if it exists.
343 } else if (strncasecmp(fmt, "NT-Domain", 9) == 0) {
346 user_name = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
348 RDEBUG2("No User-Name was found in the request.");
353 * First check to see if this is a host/ style User-Name
354 * (a la Kerberos host principal)
356 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
358 * If we're getting a User-Name formatted in this way,
359 * it's likely due to PEAP. The Windows Domain will be
360 * the first domain component following the hostname,
361 * or the machine name itself if only a hostname is supplied
363 p = strchr(user_name->vp_strvalue, '.');
365 RDEBUG2("setting NT-Domain to same as machine name");
366 strlcpy(out, user_name->vp_strvalue + 5, outlen);
368 p++; /* skip the period */
371 * use the same hack as below
372 * only if another period was found
375 strlcpy(out, p, outlen);
379 p = strchr(user_name->vp_strvalue, '\\');
381 RDEBUG2("No NT-Domain was found in the User-Name.");
386 * Hack. This is simpler than the alternatives.
389 strlcpy(out, user_name->vp_strvalue, outlen);
396 * Pull the User-Name out of the User-Name...
398 } else if (strncasecmp(fmt, "User-Name", 9) == 0) {
401 user_name = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
403 RDEBUG2("No User-Name was found in the request.");
408 * First check to see if this is a host/ style User-Name
409 * (a la Kerberos host principal)
411 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
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
422 p = strchr(user_name->vp_strvalue, '.');
424 * use the same hack as above
425 * only if a period was found
428 snprintf(out, outlen, "%s$", user_name->vp_strvalue + 5);
431 p = strchr(user_name->vp_strvalue, '\\');
433 p++; /* skip the backslash */
435 p = user_name->vp_strvalue; /* use the whole User-Name */
437 strlcpy(out, p, outlen);
443 * Return the NT-Hash of the passed string
445 } else if (strncasecmp(fmt, "NT-Hash ", 8) == 0) {
449 p = fmt + 8; /* 7 is the length of 'NT-Hash' */
450 if ((p == '\0') || (outlen <= 32))
453 while (isspace(*p)) p++;
455 if (!radius_xlat(buf2, sizeof(buf2),p,request,NULL,NULL)) {
456 RDEBUG("xlat failed");
461 mschap_ntpwdhash(buffer,buf2);
463 fr_bin2hex(buffer, out, 16);
465 RDEBUG("NT-Hash of %s = %s", buf2, out);
469 * Return the LM-Hash of the passed string
471 } 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 if (!radius_xlat(buf2, sizeof(buf2),p,request,NULL,NULL)) {
482 RDEBUG("xlat failed");
487 smbdes_lmpwdhash(buf2, buffer);
488 fr_bin2hex(buffer, out, 16);
490 RDEBUG("LM-Hash of %s = %s", buf2, out);
493 RDEBUG2("Unknown expansion string \"%s\"",
498 if (outlen == 0) return 0; /* nowhere to go, don't do anything */
501 * Didn't set anything: this is bad.
504 RDEBUG2("Failed to do anything intelligent");
509 * Check the output length.
511 if (outlen < ((data_len * 2) + 1)) {
512 data_len = (outlen - 1) / 2;
518 for (i = 0; i < data_len; i++) {
519 sprintf(out + (2 * i), "%02x", data[i]);
521 out[data_len * 2] = '\0';
527 static const CONF_PARSER passchange_config[] = {
528 { "ntlm_auth", PW_TYPE_STRING_PTR,
529 offsetof(rlm_mschap_t, ntlm_cpw), NULL, NULL },
530 { "ntlm_auth_username", PW_TYPE_STRING_PTR,
531 offsetof(rlm_mschap_t, ntlm_cpw_username), NULL, NULL },
532 { "ntlm_auth_domain", PW_TYPE_STRING_PTR,
533 offsetof(rlm_mschap_t, ntlm_cpw_domain), NULL, NULL },
534 { "local_cpw", PW_TYPE_STRING_PTR,
535 offsetof(rlm_mschap_t, local_cpw), NULL, NULL },
536 { NULL, -1, 0, NULL, NULL } /* end the list */
538 static const CONF_PARSER module_config[] = {
540 * Cache the password by default.
542 { "use_mppe", PW_TYPE_BOOLEAN,
543 offsetof(rlm_mschap_t,use_mppe), NULL, "yes" },
544 { "require_encryption", PW_TYPE_BOOLEAN,
545 offsetof(rlm_mschap_t,require_encryption), NULL, "no" },
546 { "require_strong", PW_TYPE_BOOLEAN,
547 offsetof(rlm_mschap_t,require_strong), NULL, "no" },
548 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
549 offsetof(rlm_mschap_t,with_ntdomain_hack), NULL, "yes" },
550 { "passwd", PW_TYPE_STRING_PTR,
551 offsetof(rlm_mschap_t, passwd_file), NULL, NULL },
552 { "ntlm_auth", PW_TYPE_STRING_PTR,
553 offsetof(rlm_mschap_t, ntlm_auth), NULL, NULL },
554 { "passchange", PW_TYPE_SUBSECTION, 0, NULL, (const void *) passchange_config },
555 { "allow_retry", PW_TYPE_BOOLEAN,
556 offsetof(rlm_mschap_t, allow_retry), NULL, "yes" },
557 { "retry_msg", PW_TYPE_STRING_PTR,
558 offsetof(rlm_mschap_t, retry_msg), NULL, NULL },
560 { "use_open_directory", PW_TYPE_BOOLEAN,
561 offsetof(rlm_mschap_t,open_directory), NULL, "yes" },
564 { NULL, -1, 0, NULL, NULL } /* end the list */
568 * deinstantiate module, free all memory allocated during
569 * mschap_instantiate()
571 static int mschap_detach(void *instance)
573 rlm_mschap_t *inst = instance;
574 if (inst->xlat_name) {
575 xlat_unregister(inst->xlat_name, mschap_xlat, instance);
581 * Create instance for our module. Allocate space for
582 * instance structure and read configuration parameters
584 static int mschap_instantiate(CONF_SECTION *conf, void **instance)
589 *instance = inst = talloc_zero(conf, rlm_mschap_t);
590 if (!inst) return -1;
592 if (cf_section_parse(conf, inst, module_config) < 0) {
597 * This module used to support SMB Password files, but it
598 * made it too complicated. If the user tries to
599 * configure an SMB Password file, then die, with an
602 if (inst->passwd_file) {
603 radlog(L_ERR, "rlm_mschap: SMB password file is no longer supported in this module. Use rlm_passwd module instead");
608 * Create the dynamic translation.
610 name = cf_section_name2(conf);
611 if (!name) name = cf_section_name1(conf);
612 inst->xlat_name = name;
613 xlat_register(inst->xlat_name, mschap_xlat, inst);
616 * For backwards compatibility
618 if (!dict_valbyname(PW_AUTH_TYPE, 0, inst->xlat_name)) {
619 inst->auth_type = "MS-CHAP";
621 inst->auth_type = inst->xlat_name;
628 * add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
629 * attribute to reply packet
631 void mschap_add_reply(REQUEST *request, VALUE_PAIR** vp, unsigned char ident,
632 const char* name, const char* value, int len)
634 VALUE_PAIR *reply_attr;
635 reply_attr = pairmake(name, "", T_OP_EQ);
637 RDEBUG("Failed to create attribute %s: %s\n", name, fr_strerror());
641 reply_attr->vp_octets[0] = ident;
642 memcpy(reply_attr->vp_octets + 1, value, len);
643 reply_attr->length = len + 1;
644 pairadd(vp, reply_attr);
648 * Add MPPE attributes to the reply.
650 static void mppe_add_reply(REQUEST *request,
651 const char* name, const uint8_t * value, int len)
654 vp = radius_pairmake(request, &request->reply->vps, name, "", T_OP_EQ);
656 RDEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, fr_strerror());
660 memcpy(vp->vp_octets, value, len);
664 static int write_all(int fd, const char *buf, int len) {
668 rv = write(fd, buf+done, len-done);
677 * Perform an MS-CHAP2 password change
680 static int do_mschap_cpw(rlm_mschap_t *inst,
681 REQUEST *request, VALUE_PAIR *nt_password,
682 uint8_t *new_nt_password,
683 uint8_t *old_nt_hash,
686 if (inst->ntlm_cpw && do_ntlm_auth) {
688 * we're going to run ntlm_auth in helper-mode
689 * we're expecting to use the ntlm-change-password-1 protocol
690 * which needs the following on stdin:
692 * username: %{mschap:User-Name}
693 * nt-domain: %{mschap:NT-Domain}
694 * new-nt-password-blob: bin2hex(new_nt_password) - 1032 bytes encoded
695 * old-nt-hash-blob: bin2hex(old_nt_hash) - 32 bytes encoded
696 * new-lm-password-blob: 00000...0000 - 1032 bytes null
697 * old-lm-hash-blob: 000....000 - 32 bytes null
700 * ...and it should then print out
702 * Password-Change: Yes
706 * Password-Change: No
707 * Password-Change-Error: blah
712 pid_t pid, child_pid;
718 RDEBUG("Doing MS-CHAPv2 password change via ntlm_auth helper");
721 * Start up ntlm_auth with a pipe on stdin and stdout
724 pid = radius_start_program(inst->ntlm_cpw, request, 1, &to_child, &from_child, NULL, 0);
726 RDEBUG2("could not exec ntlm_auth cpw command");
731 * write the stuff to the client
734 if (inst->ntlm_cpw_username) {
735 len = radius_xlat(buf, sizeof(buf) - 2, inst->ntlm_cpw_username, request, NULL, NULL);
739 if (write_all(to_child, buf, len) != len) {
740 RDEBUG2("failed to write username to child");
744 RDEBUG("No ntlm_auth username set - passchange will definitely fail!");
747 if (inst->ntlm_cpw_domain) {
748 len = radius_xlat(buf, sizeof(buf) - 2, inst->ntlm_cpw_domain, request, NULL, NULL);
752 if (write_all(to_child, buf, len) != len) {
753 RDEBUG2("failed to write domain to child");
757 RDEBUG("No ntlm_auth domain set - username must be full-username to work");
760 /* now the password blobs */
761 len = sprintf(buf, "new-nt-password-blob: ");
762 fr_bin2hex(new_nt_password, buf+len, 516);
763 buf[len+1032] = '\n';
764 buf[len+1033] = '\0';
766 if (write_all(to_child, buf, len) != len) {
767 RDEBUG2("failed to write new password blob to child");
771 len = sprintf(buf, "old-nt-hash-blob: ");
772 fr_bin2hex(old_nt_hash, buf+len, 16);
776 if (write_all(to_child, buf, len) != len) {
777 RDEBUG2("failed to write old hash blob to child");
782 * in current samba versions, failure to supply empty
783 * LM password/hash blobs causes the change to fail
785 len = sprintf(buf, "new-lm-password-blob: %01032i\n", 0);
786 if (write_all(to_child, buf, len) != len) {
787 RDEBUG2("failed to write dummy LM password to child");
790 len = sprintf(buf, "old-lm-hash-blob: %032i\n", 0);
791 if (write_all(to_child, buf, len) != len) {
792 RDEBUG2("failed to write dummy LM hash to child");
795 if (write_all(to_child, ".\n", 2) != 2) {
796 RDEBUG2("failed to send finish to child");
803 * Read from the child
805 len = radius_readfrom_program(from_child, pid, 10, buf, sizeof(buf));
807 /* radius_readfrom_program will have closed from_child for us */
808 RDEBUG2("Failure reading from child");
815 RDEBUG2("ntlm_auth said: %s", buf);
817 child_pid = rad_waitpid(pid, &status);
818 if (child_pid == 0) {
819 RDEBUG2("Timeout waiting for child");
822 if (child_pid != pid) {
823 RDEBUG("Abnormal exit status: %s", strerror(errno));
827 if (strstr(buf, "Password-Change: Yes")) {
828 RDEBUG2("ntlm_auth password change succeeded");
832 pmsg = strstr(buf, "Password-Change-Error: ");
834 emsg = strsep(&pmsg, "\n");
836 emsg = "could not find error";
838 RDEBUG2("ntlm auth password change failed: %s", emsg);
841 /* safe because these either need closing or are == -1 */
847 } else if (inst->local_cpw) {
848 #ifdef HAVE_OPENSSL_CRYPTO_H
850 * decrypt the new password blob, add it as a temporary request
851 * variable, xlat the local_cpw string, then remove it
853 * this allows is to write e..g
855 * %{sql:insert into ...}
859 * %{exec:/path/to %{mschap:User-Name} %{MS-CHAP-New-Password}}"
863 VALUE_PAIR *new_pass, *new_hash;
865 size_t i, result_len;
868 uint8_t nt_pass_decrypted[516], old_nt_hash_expected[16];
872 RDEBUG("Local MS-CHAPv2 password change requires NT-Password attribute");
875 RDEBUG("Doing MS-CHAPv2 password change locally");
881 RC4_set_key(&key, nt_password->length, nt_password->vp_octets);
882 RC4(&key, 516, new_nt_password, nt_pass_decrypted);
886 * 512-N bytes random pad
887 * N bytes password as utf-16-le
888 * 4 bytes - N as big-endian int
891 passlen = nt_pass_decrypted[512];
892 passlen += nt_pass_decrypted[513] << 8;
893 if ((nt_pass_decrypted[514] != 0) ||
894 (nt_pass_decrypted[515] != 0)) {
895 RDEBUG2("Decrypted new password blob claims length > 65536 - probably an invalid NT-Password");
900 * sanity check - passlen positive and <= 512
901 * if not, crypto has probably gone wrong
904 RDEBUG2("Decrypted new password blob claims length %u > 512 - probably an invalid NT-Password", passlen);
908 p = nt_pass_decrypted + 512 - passlen;
911 * the new NT hash - this should be preferred over the
912 * cleartext password as it avoids unicode hassles
914 new_hash = radius_pairmake(request, &request->packet->vps,
915 "MS-CHAP-New-NT-Password", "",
917 fr_md4_calc(new_hash->vp_octets, p, passlen);
918 new_hash->length = 16;
921 * check that nt_password encrypted with new_hash
922 * matches the old_hash value from the client
924 smbhash(old_nt_hash_expected, nt_password->vp_octets, new_hash->vp_octets);
925 smbhash(old_nt_hash_expected+8, nt_password->vp_octets+8, new_hash->vp_octets+7);
926 if (memcmp(old_nt_hash_expected, old_nt_hash, 16)!=0) {
927 RDEBUG2("old NT hash value from client does not match our value");
932 * the new cleartext password, which is utf-16
933 * do some unpleasant vileness to turn it into
934 * utf8 without pulling in libraries like iconv
936 new_pass = radius_pairmake(request, &request->packet->vps,
937 "MS-CHAP-New-Cleartext-Password", "",
939 new_pass->length = 0;
943 * The client-supplied password is utf-16.
944 * We really must perform a proper conversion
945 * to utf8 here, and the same in the other direction
946 * when we calculate NT-Password below, else non-ascii
947 * characters will fail - I know from experience that
948 * UK pound and Euro symbols are common in users
949 * passwords (money obsessed!)
957 * gah. nasty. maybe we should just pull in iconv?
962 if (new_pass->length >= 253) {
963 RDEBUG("Ran out of room turning new password into utf8 at %d - cleartext will be truncated!", i);
966 new_pass->vp_strvalue[new_pass->length++] = c;
967 } else if (c < 0x7ff) {
969 if (new_pass->length >= 252) {
970 RDEBUG("Ran out of room turning new password into utf8 at %d - cleartext will be truncated!", i);
973 new_pass->vp_strvalue[new_pass->length++] = 0xc0 + (c >> 6);
974 new_pass->vp_strvalue[new_pass->length++] = 0x80 + (c & 0x3f);
977 if (new_pass->length >= 251) {
978 RDEBUG("Ran out of room turning new password into utf8 at %d - cleartext will be truncated!", i);
981 new_pass->vp_strvalue[new_pass->length++] = 0xe0 + (c >> 12);
982 new_pass->vp_strvalue[new_pass->length++] = 0x80 + ((c>>6) & 0x3f);
983 new_pass->vp_strvalue[new_pass->length++] = 0x80 + (c & 0x3f);
993 result_len = radius_xlat(result, sizeof(result), inst->local_cpw, request, NULL, NULL);
995 RDEBUG("Local MS-CHAPv2 password change - xlat didn't give any result, assuming failure");
999 RDEBUG("MS-CHAPv2 password change succeeded: %s", result);
1002 * update the NT-Password attribute with the new hash
1003 * this lets us fall through to the authentication
1004 * code using the new hash, not the old one
1006 memcpy(nt_password->vp_octets, new_hash->vp_octets, new_hash->length);
1009 * rock on! password change succeeded
1013 RDEBUG("Local MS-CHAPv2 password changes require OpenSSL support");
1017 RDEBUG("MS-CHAPv2 password change not configured");
1024 * Do the MS-CHAP stuff.
1026 * This function is here so that all of the MS-CHAP related
1027 * authentication is in one place, and we can perhaps later replace
1028 * it with code to call winbindd, or something similar.
1030 static int do_mschap(rlm_mschap_t *inst,
1031 REQUEST *request, VALUE_PAIR *password,
1032 uint8_t *challenge, uint8_t *response,
1033 uint8_t *nthashhash, int do_ntlm_auth)
1035 uint8_t calculated[24];
1037 rad_assert(request != NULL);
1040 * Do normal authentication.
1042 if (!do_ntlm_auth) {
1044 * No password: can't do authentication.
1047 RDEBUG2("FAILED: No NT/LM-Password. Cannot perform authentication.");
1051 smbdes_mschap(password->vp_octets, challenge, calculated);
1052 if (rad_digest_cmp(response, calculated, 24) != 0) {
1057 * If the password exists, and is an NT-Password,
1058 * then calculate the hash of the NT hash. Doing this
1059 * here minimizes work for later.
1061 if (password && !password->da->vendor &&
1062 (password->da->attr == PW_NT_PASSWORD)) {
1063 fr_md4_calc(nthashhash, password->vp_octets, 16);
1065 memset(nthashhash, 0, 16);
1067 } else { /* run ntlm_auth */
1071 memset(nthashhash, 0, 16);
1074 * Run the program, and expect that we get 16
1076 result = radius_exec_program(inst->ntlm_auth, request,
1078 buffer, sizeof(buffer),
1082 VALUE_PAIR *vp = NULL;
1085 * look for "Password expired", or "Must
1088 if (strstr(buffer, "Password expired") ||
1089 strstr(buffer, "Must change password")) {
1090 RDEBUG2("ntlm_auth says %s", buffer);
1094 RDEBUG2("External script failed.");
1096 vp = pairmake("Module-Failure-Message", "", T_OP_EQ);
1098 radlog_request(L_ERR, 0, request, "No memory to allocate Module-Failure-Message");
1099 return RLM_MODULE_FAIL;
1102 p = strchr(buffer, '\n');
1104 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue),
1105 "%s: External script says %s",
1106 inst->xlat_name, buffer);
1107 vp->length = strlen(vp->vp_strvalue);
1108 pairadd(&request->packet->vps, vp);
1113 * Parse the answer as an nthashhash.
1115 * ntlm_auth currently returns:
1116 * NT_KEY: 000102030405060708090a0b0c0d0e0f
1118 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
1119 RDEBUG2("Invalid output from ntlm_auth: expecting NT_KEY");
1124 * Check the length. It should be at least 32,
1125 * with an LF at the end.
1127 if (strlen(buffer + 8) < 32) {
1128 RDEBUG2("Invalid output from ntlm_auth: NT_KEY has unexpected length");
1133 * Update the NT hash hash, from the NT key.
1135 if (fr_hex2bin(buffer + 8, nthashhash, 16) != 16) {
1136 RDEBUG2("Invalid output from ntlm_auth: NT_KEY has non-hex values");
1146 * Data for the hashes.
1148 static const uint8_t SHSpad1[40] =
1149 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1150 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1151 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1152 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
1154 static const uint8_t SHSpad2[40] =
1155 { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1156 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1157 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1158 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
1160 static const uint8_t magic1[27] =
1161 { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
1162 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
1163 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
1165 static const uint8_t magic2[84] =
1166 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
1167 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
1168 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1169 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
1170 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
1171 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
1172 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1173 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
1174 0x6b, 0x65, 0x79, 0x2e };
1176 static const uint8_t magic3[84] =
1177 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
1178 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
1179 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1180 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
1181 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
1182 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
1183 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
1184 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
1185 0x6b, 0x65, 0x79, 0x2e };
1188 static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response,
1192 fr_SHA1_CTX Context;
1194 fr_SHA1Init(&Context);
1195 fr_SHA1Update(&Context,nt_hashhash,16);
1196 fr_SHA1Update(&Context,nt_response,24);
1197 fr_SHA1Update(&Context,magic1,27);
1198 fr_SHA1Final(digest,&Context);
1200 memcpy(masterkey,digest,16);
1204 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
1205 int keylen,int issend)
1209 fr_SHA1_CTX Context;
1211 memset(digest,0,20);
1219 fr_SHA1Init(&Context);
1220 fr_SHA1Update(&Context,masterkey,16);
1221 fr_SHA1Update(&Context,SHSpad1,40);
1222 fr_SHA1Update(&Context,s,84);
1223 fr_SHA1Update(&Context,SHSpad2,40);
1224 fr_SHA1Final(digest,&Context);
1226 memcpy(sesskey,digest,keylen);
1230 static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response,
1231 uint8_t *sendkey,uint8_t *recvkey)
1233 uint8_t masterkey[16];
1235 mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
1237 mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
1238 mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
1242 * Generate MPPE keys.
1244 static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response,
1245 uint8_t *sendkey,uint8_t *recvkey)
1247 uint8_t enckey1[16];
1248 uint8_t enckey2[16];
1250 mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
1253 * dictionary.microsoft defines these attributes as
1254 * 'encrypt=2'. The functions in src/lib/radius.c will
1255 * take care of encrypting/decrypting them as appropriate,
1256 * so that we don't have to.
1258 memcpy (sendkey, enckey1, 16);
1259 memcpy (recvkey, enckey2, 16);
1264 * mschap_authorize() - authorize user if we can authenticate
1265 * it later. Add Auth-Type attribute if present in module
1266 * configuration (usually Auth-Type must be "MS-CHAP")
1268 static rlm_rcode_t mschap_authorize(void * instance, REQUEST *request)
1270 rlm_mschap_t *inst = instance;
1271 VALUE_PAIR *challenge = NULL;
1273 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
1275 return RLM_MODULE_NOOP;
1278 if (!pairfind(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY) &&
1279 !pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY) &&
1280 !pairfind(request->packet->vps, PW_MSCHAP2_CPW, VENDORPEC_MICROSOFT, TAG_ANY)) {
1281 RDEBUG2("Found MS-CHAP-Challenge, but no MS-CHAP response or change-password");
1282 return RLM_MODULE_NOOP;
1285 if (pairfind(request->config_items, PW_AUTH_TYPE, 0, TAG_ANY)) {
1286 RDEBUG2W("Auth-Type already set. Not setting to MS-CHAP");
1287 return RLM_MODULE_NOOP;
1290 RDEBUG2("Found MS-CHAP attributes. Setting 'Auth-Type = %s'", inst->xlat_name);
1293 * Set Auth-Type to MS-CHAP. The authentication code
1294 * will take care of turning clear-text passwords into
1297 if (!radius_pairmake(request, &request->config_items,
1298 "Auth-Type", inst->auth_type, T_OP_EQ)) {
1299 return RLM_MODULE_FAIL;
1302 return RLM_MODULE_OK;
1306 * mschap_authenticate() - authenticate user based on given
1307 * attributes and configuration.
1308 * We will try to find out password in configuration
1309 * or in configured passwd file.
1310 * If one is found we will check paraneters given by NAS.
1312 * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
1314 * PAP: PW_USER_PASSWORD or
1315 * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
1316 * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
1317 * In case of password mismatch or locked account we MAY return
1318 * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
1319 * If MS-CHAP2 succeeds we MUST return
1320 * PW_MSCHAP2_SUCCESS
1322 static rlm_rcode_t mschap_authenticate(void * instance, REQUEST *request)
1324 #define inst ((rlm_mschap_t *)instance)
1325 VALUE_PAIR *challenge = NULL;
1326 VALUE_PAIR *response = NULL;
1327 VALUE_PAIR *cpw = NULL;
1328 VALUE_PAIR *password = NULL;
1329 VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
1330 VALUE_PAIR *username;
1331 uint8_t nthashhash[16];
1333 char *username_string;
1338 * If we have ntlm_auth configured, use it unless told
1341 do_ntlm_auth = (inst->ntlm_auth != NULL);
1344 * If we have an ntlm_auth configuration, then we may
1345 * want to suppress it.
1348 VALUE_PAIR *vp = pairfind(request->config_items, PW_MS_CHAP_USE_NTLM_AUTH, 0, TAG_ANY);
1349 if (vp) do_ntlm_auth = vp->vp_integer;
1353 * Find the SMB-Account-Ctrl attribute, or the
1354 * SMB-Account-Ctrl-Text attribute.
1356 smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL, 0, TAG_ANY);
1358 password = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL_TEXT, 0, TAG_ANY);
1360 smb_ctrl = radius_pairmake(request,
1361 &request->config_items,
1362 "SMB-Account-CTRL", "0",
1365 smb_ctrl->vp_integer = pdb_decode_acct_ctrl(password->vp_strvalue);
1371 * We're configured to do MS-CHAP authentication.
1372 * and account control information exists. Enforce it.
1376 * Password is not required.
1378 if ((smb_ctrl->vp_integer & ACB_PWNOTREQ) != 0) {
1379 RDEBUG2("SMB-Account-Ctrl says no password is required.");
1380 return RLM_MODULE_OK;
1385 * Decide how to get the passwords.
1387 password = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY);
1390 * We need an LM-Password.
1392 lm_password = pairfind(request->config_items, PW_LM_PASSWORD, 0, TAG_ANY);
1397 if ((lm_password->length == 16) ||
1398 ((lm_password->length == 32) &&
1399 (fr_hex2bin(lm_password->vp_strvalue,
1400 lm_password->vp_octets, 16) == 16))) {
1401 RDEBUG2("Found LM-Password");
1402 lm_password->length = 16;
1405 radlog_request(L_ERR, 0, request, "Invalid LM-Password");
1409 } else if (!password) {
1410 if (!do_ntlm_auth) RDEBUG2("No Cleartext-Password configured. Cannot create LM-Password.");
1412 } else { /* there is a configured Cleartext-Password */
1413 lm_password = radius_pairmake(request, &request->config_items,
1414 "LM-Password", "", T_OP_EQ);
1416 radlog_request(L_ERR, 0, request, "No memory");
1418 smbdes_lmpwdhash(password->vp_strvalue,
1419 lm_password->vp_octets);
1420 lm_password->length = 16;
1425 * We need an NT-Password.
1427 nt_password = pairfind(request->config_items, PW_NT_PASSWORD, 0, TAG_ANY);
1429 if ((nt_password->length == 16) ||
1430 ((nt_password->length == 32) &&
1431 (fr_hex2bin(nt_password->vp_strvalue,
1432 nt_password->vp_octets, 16) == 16))) {
1433 RDEBUG2("Found NT-Password");
1434 nt_password->length = 16;
1437 radlog_request(L_ERR, 0, request, "Invalid NT-Password");
1440 } else if (!password) {
1441 if (!do_ntlm_auth) RDEBUG2("No Cleartext-Password configured. Cannot create NT-Password.");
1443 } else { /* there is a configured Cleartext-Password */
1444 nt_password = radius_pairmake(request, &request->config_items,
1445 "NT-Password", "", T_OP_EQ);
1447 radlog_request(L_ERR, 0, request, "No memory");
1448 return RLM_MODULE_FAIL;
1450 mschap_ntpwdhash(nt_password->vp_octets,
1451 password->vp_strvalue);
1452 nt_password->length = 16;
1456 cpw = pairfind(request->packet->vps, PW_MSCHAP2_CPW, VENDORPEC_MICROSOFT, TAG_ANY);
1459 * mschap2 password change request
1460 * we cheat - first decode and execute the passchange
1461 * we then extract the response, add it into the request
1462 * then jump into mschap2 auth with the chal/resp
1464 uint8_t new_nt_encrypted[516], old_nt_encrypted[16];
1465 VALUE_PAIR *nt_enc=NULL;
1466 int seq, new_nt_enc_len=0;
1468 RDEBUG("MS-CHAPv2 password change request received");
1470 if (cpw->length != 68) {
1471 RDEBUG2("MS-CHAP2-CPW has the wrong format - length %d!=68", cpw->length);
1472 return RLM_MODULE_INVALID;
1473 } else if (cpw->vp_octets[0]!=7) {
1474 RDEBUG2("MS-CHAP2-CPW has the wrong format - code %d!=7", cpw->vp_octets[0]);
1475 return RLM_MODULE_INVALID;
1479 * look for the new (encrypted) password
1480 * bah stupid composite attributes
1481 * we're expecting 3 attributes with the leading bytes
1482 * 06:<mschapid>:00:01:<1st chunk>
1483 * 06:<mschapid>:00:02:<2nd chunk>
1484 * 06:<mschapid>:00:03:<3rd chunk>
1486 for (seq=1;seq<4;seq++) {
1488 for (nt_enc=request->packet->vps; nt_enc; nt_enc=nt_enc->next) {
1489 if (nt_enc->da->vendor != VENDORPEC_MICROSOFT)
1492 if (nt_enc->da->attr != PW_MSCHAP_NT_ENC_PW)
1495 if (nt_enc->vp_octets[0] != 6) {
1496 RDEBUG2("MS-CHAP-NT-Enc-PW with invalid format");
1497 return RLM_MODULE_INVALID;
1499 if (nt_enc->vp_octets[2]==0 && nt_enc->vp_octets[3]==seq) {
1506 RDEBUG2("Could not find MS-CHAP-NT-Enc-PW w/ sequence number %d", seq);
1507 return RLM_MODULE_INVALID;
1511 * copy the data into the buffer
1513 memcpy(new_nt_encrypted + new_nt_enc_len, nt_enc->vp_octets + 4, nt_enc->length - 4);
1514 new_nt_enc_len += nt_enc->length - 4;
1516 if (new_nt_enc_len != 516) {
1517 RDEBUG2("Unpacked MS-CHAP-NT-Enc-PW length != 516");
1518 return RLM_MODULE_INVALID;
1522 * RFC 2548 is confusing here
1527 * 16 octets - old hash encrypted with new hash
1528 * 24 octets - peer challenge
1530 * 16 octets - peer challenge
1531 * 8 octets - reserved
1532 * 24 octets - nt response
1533 * 2 octets - flags (ignored)
1536 memcpy(old_nt_encrypted, cpw->vp_octets+2, sizeof(old_nt_encrypted));
1538 RDEBUG2("Password change payload valid");
1540 /* perform the actual password change */
1541 if (do_mschap_cpw(inst, request, nt_password, new_nt_encrypted, old_nt_encrypted, do_ntlm_auth) < 0) {
1544 RDEBUG("Password change failed");
1546 snprintf(buffer, sizeof(buffer), "E=709 R=0 M=Password change failed");
1547 mschap_add_reply(request, &request->reply->vps,
1548 cpw->vp_octets[1], "MS-CHAP-Error",
1549 buffer, strlen(buffer));
1550 return RLM_MODULE_REJECT;
1552 RDEBUG("Password change successful");
1555 * Clear any expiry bit so the user can now login;
1556 * obviously the password change action will need
1557 * to have cleared this bit in the config/SQL/wherever
1559 if (smb_ctrl && smb_ctrl->vp_integer & ACB_PW_EXPIRED) {
1560 RDEBUG("clearing expiry bit in SMB-Acct-Ctrl to allow authentication");
1561 smb_ctrl->vp_integer &= ~ACB_PW_EXPIRED;
1566 * extract the challenge & response from the end of the password
1567 * change, add them into the request and then continue with
1568 * the authentication
1571 response = radius_paircreate(request, &request->packet->vps,
1572 PW_MSCHAP2_RESPONSE,
1573 VENDORPEC_MICROSOFT);
1574 response->length = 50;
1576 response->vp_octets[0] = cpw->vp_octets[1];
1577 response->vp_octets[1] = 0;
1578 /* peer challenge and client NT response */
1579 memcpy(response->vp_octets+2, cpw->vp_octets + 18, 48);
1582 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
1584 RDEBUGE("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1585 return RLM_MODULE_REJECT;
1589 * We also require an MS-CHAP-Response.
1591 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
1594 * MS-CHAP-Response, means MS-CHAPv1
1600 * MS-CHAPv1 challenges are 8 octets.
1602 if (challenge->length < 8) {
1603 radlog_request(L_AUTH, 0, request, "MS-CHAP-Challenge has the wrong format.");
1604 return RLM_MODULE_INVALID;
1608 * Responses are 50 octets.
1610 if (response->length < 50) {
1611 radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
1612 return RLM_MODULE_INVALID;
1616 * We are doing MS-CHAP. Calculate the MS-CHAP
1619 if (response->vp_octets[1] & 0x01) {
1620 RDEBUG2("Client is using MS-CHAPv1 with NT-Password");
1621 password = nt_password;
1624 RDEBUG2("Client is using MS-CHAPv1 with LM-Password");
1625 password = lm_password;
1630 * Do the MS-CHAP authentication.
1632 if (do_mschap(inst, request, password, challenge->vp_octets,
1633 response->vp_octets + offset, nthashhash,
1634 do_ntlm_auth) < 0) {
1635 RDEBUG2("MS-CHAP-Response is incorrect.");
1641 } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY)) != NULL) {
1643 uint8_t mschapv1_challenge[16];
1644 VALUE_PAIR *name_attr, *response_name;
1647 * MS-CHAPv2 challenges are 16 octets.
1649 if (challenge->length < 16) {
1650 radlog_request(L_AUTH, 0, request, "MS-CHAP-Challenge has the wrong format.");
1651 return RLM_MODULE_INVALID;
1655 * Responses are 50 octets.
1657 if (response->length < 50) {
1658 radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
1659 return RLM_MODULE_INVALID;
1663 * We also require a User-Name
1665 username = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
1667 radlog_request(L_AUTH, 0, request, "We require a User-Name for MS-CHAPv2");
1668 return RLM_MODULE_INVALID;
1672 * Check for MS-CHAP-User-Name and if found, use it
1673 * to construct the MSCHAPv1 challenge. This is
1674 * set by rlm_eap_mschap to the MS-CHAP Response
1675 * packet Name field.
1677 * We prefer this to the User-Name in the
1680 response_name = pairfind(request->packet->vps, PW_MS_CHAP_USER_NAME, 0, TAG_ANY);
1681 if (response_name) {
1682 name_attr = response_name;
1684 name_attr = username;
1688 * with_ntdomain_hack moved here, too.
1690 if ((username_string = strchr(name_attr->vp_strvalue, '\\')) != NULL) {
1691 if (inst->with_ntdomain_hack) {
1694 RDEBUG2("NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1695 username_string = name_attr->vp_strvalue;
1698 username_string = name_attr->vp_strvalue;
1701 if (response_name &&
1702 ((username->length != response_name->length) ||
1703 (strncasecmp(username->vp_strvalue, response_name->vp_strvalue, username->length) != 0))) {
1704 RDEBUGE("User-Name (%s) is not the same as MS-CHAP Name (%s) from EAP-MSCHAPv2", username->vp_strvalue, response_name->vp_strvalue);
1705 return RLM_MODULE_REJECT;
1710 * No "known good" NT-Password attribute. Try to do
1711 * OpenDirectory authentication.
1713 * If OD determines the user is an AD user it will return noop, which
1714 * indicates the auth process should continue directly to AD.
1715 * Otherwise OD will determine auth success/fail.
1717 if (!nt_password && inst->open_directory) {
1718 RDEBUG2("No NT-Password configured. Trying OpenDirectory Authentication.");
1719 int odStatus = od_mschap_auth(request, challenge, username);
1720 if (odStatus != RLM_MODULE_NOOP) {
1726 * The old "mschapv2" function has been moved to
1729 * MS-CHAPv2 takes some additional data to create an
1730 * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1732 RDEBUG2("Creating challenge hash with username: %s",
1734 mschap_challenge_hash(response->vp_octets + 2, /* peer challenge */
1735 challenge->vp_octets, /* our challenge */
1736 username_string, /* user name */
1737 mschapv1_challenge); /* resulting challenge */
1739 RDEBUG2("Client is using MS-CHAPv2 for %s, we need NT-Password",
1742 mschap_result = do_mschap(inst, request, nt_password, mschapv1_challenge,
1743 response->vp_octets + 26, nthashhash,
1745 if (mschap_result == -648)
1746 goto password_expired;
1748 if (mschap_result < 0) {
1752 RDEBUG2("FAILED: MS-CHAP2-Response is incorrect");
1755 snprintf(buffer, sizeof(buffer), "E=691 R=%d",
1758 if (inst->retry_msg) {
1759 snprintf(buffer + 9, sizeof(buffer) - 9, " C=");
1760 for (i = 0; i < 16; i++) {
1761 snprintf(buffer + 12 + i*2,
1762 sizeof(buffer) - 12 - i*2, "%02x",
1765 snprintf(buffer + 45, sizeof(buffer) - 45,
1766 " V=3 M=%s", inst->retry_msg);
1768 mschap_add_reply(request, &request->reply->vps,
1769 *response->vp_octets, "MS-CHAP-Error",
1770 buffer, strlen(buffer));
1771 return RLM_MODULE_REJECT;
1774 if (smb_ctrl && smb_ctrl->vp_integer & ACB_PW_EXPIRED) {
1776 * if the password is correct and it has expired
1777 * we can permit password changes (only in MS-CHAPv2)
1779 char newchal[33], buffer[128];
1783 for (i = 0; i < 16; i++)
1784 snprintf(newchal + i*2, 3, "%02x", fr_rand() & 0xff);
1786 snprintf(buffer, sizeof(buffer), "E=648 R=0 C=%s V=3 M=Password Expired", newchal);
1788 mschap_add_reply(request, &request->reply->vps,
1789 *response->vp_octets, "MS-CHAP-Error",
1790 buffer, strlen(buffer));
1791 return RLM_MODULE_REJECT;
1794 mschap_auth_response(username_string, /* without the domain */
1795 nthashhash, /* nt-hash-hash */
1796 response->vp_octets + 26, /* peer response */
1797 response->vp_octets + 2, /* peer challenge */
1798 challenge->vp_octets, /* our challenge */
1799 msch2resp); /* calculated MPPE key */
1800 mschap_add_reply(request, &request->reply->vps, *response->vp_octets,
1801 "MS-CHAP2-Success", msch2resp, 42);
1804 } else { /* Neither CHAPv1 or CHAPv2 response: die */
1805 RDEBUGE("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1806 return RLM_MODULE_INVALID;
1810 * We have a CHAP response, but the account may be
1811 * disabled. Reject the user with the same error code
1812 * we use when their password is invalid.
1816 * Account is disabled.
1818 * They're found, but they don't exist, so we
1819 * return 'not found'.
1821 if (((smb_ctrl->vp_integer & ACB_DISABLED) != 0) ||
1822 ((smb_ctrl->vp_integer & (ACB_NORMAL|ACB_WSTRUST)) == 0)) {
1823 RDEBUG2("SMB-Account-Ctrl says that the account is disabled, or is not a normal or workstatin trust account.");
1824 mschap_add_reply(request, &request->reply->vps,
1825 *response->vp_octets,
1826 "MS-CHAP-Error", "E=691 R=1", 9);
1827 return RLM_MODULE_NOTFOUND;
1831 * User is locked out.
1833 if ((smb_ctrl->vp_integer & ACB_AUTOLOCK) != 0) {
1834 RDEBUG2("SMB-Account-Ctrl says that the account is locked out.");
1835 mschap_add_reply(request, &request->reply->vps,
1836 *response->vp_octets,
1837 "MS-CHAP-Error", "E=647 R=0", 9);
1838 return RLM_MODULE_USERLOCK;
1842 /* now create MPPE attributes */
1843 if (inst->use_mppe) {
1844 uint8_t mppe_sendkey[34];
1845 uint8_t mppe_recvkey[34];
1848 RDEBUG2("adding MS-CHAPv1 MPPE keys");
1849 memset(mppe_sendkey, 0, 32);
1851 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1855 * According to RFC 2548 we
1856 * should send NT hash. But in
1857 * practice it doesn't work.
1858 * Instead, we should send nthashhash
1860 * This is an error on RFC 2548.
1863 * do_mschap cares to zero nthashhash if NT hash
1866 memcpy(mppe_sendkey + 8,
1868 mppe_add_reply(request,
1869 "MS-CHAP-MPPE-Keys",
1871 } else if (chap == 2) {
1872 RDEBUG2("adding MS-CHAPv2 MPPE keys");
1873 mppe_chap2_gen_keys128(nthashhash,
1874 response->vp_octets + 26,
1875 mppe_sendkey, mppe_recvkey);
1877 mppe_add_reply(request,
1880 mppe_add_reply(request,
1885 radius_pairmake(request, &request->reply->vps,
1886 "MS-MPPE-Encryption-Policy",
1887 (inst->require_encryption)? "0x00000002":"0x00000001",
1889 radius_pairmake(request, &request->reply->vps,
1890 "MS-MPPE-Encryption-Types",
1891 (inst->require_strong)? "0x00000004":"0x00000006",
1893 } /* else we weren't asked to use MPPE */
1895 return RLM_MODULE_OK;
1899 module_t rlm_mschap = {
1902 RLM_TYPE_THREAD_SAFE | RLM_TYPE_HUP_SAFE, /* type */
1903 mschap_instantiate, /* instantiation */
1904 mschap_detach, /* detach */
1906 mschap_authenticate, /* authenticate */
1907 mschap_authorize, /* authorize */
1908 NULL, /* pre-accounting */
1909 NULL, /* accounting */
1910 NULL, /* checksimul */
1911 NULL, /* pre-proxy */
1912 NULL, /* post-proxy */
1913 NULL /* post-auth */