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 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(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 int require_encryption;
144 int with_ntdomain_hack; /* this should be in another module */
145 char const *xlat_name;
148 char *ntlm_cpw_username;
149 char *ntlm_cpw_domain;
151 char const *auth_type;
154 #ifdef WITH_OPEN_DIRECTORY
161 * Does dynamic translation of strings.
163 * Pulls NT-Response, LM-Response, or Challenge from MSCHAP
166 static ssize_t mschap_xlat(void *instance, REQUEST *request,
167 char const *fmt, char *out, size_t outlen)
170 uint8_t const *data = NULL;
172 VALUE_PAIR *user_name;
173 VALUE_PAIR *chap_challenge, *response;
174 rlm_mschap_t *inst = instance;
179 * Challenge means MS-CHAPv1 challenge, or
180 * hash of MS-CHAPv2 challenge, and peer challenge.
182 if (strncasecmp(fmt, "Challenge", 9) == 0) {
183 chap_challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
184 if (!chap_challenge) {
185 RDEBUG2("No MS-CHAP-Challenge in the request.");
190 * MS-CHAP-Challenges are 8 octets,
193 if (chap_challenge->length == 8) {
194 RDEBUG2(" mschap1: %02x",
195 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 RDEBUG2("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 RAUTH("MS-CHAP-Response has the wrong format.");
227 user_name = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
229 RDEBUG2("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 RDEBUG2("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 RWDEBUG("User-Name (%s) is not the same as MS-CHAP Name (%s) from EAP-MSCHAPv2", user_name->vp_strvalue, response_name->vp_strvalue);
270 * Get the MS-CHAPv1 challenge
271 * from the MS-CHAPv2 peer challenge,
272 * our challenge, and the user name.
274 RDEBUG2("Creating challenge hash with username: %s",
276 mschap_challenge_hash(response->vp_octets + 2,
277 chap_challenge->vp_octets,
278 username_string, buffer);
282 RDEBUG2("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 RDEBUG2("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 RDEBUG2("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 RDEBUG2("No MS-CHAP-Response was found in the request.");
329 * For MS-CHAPv1, the NT-Response exists only
330 * if the second octet says so.
332 if ((response->vp_octets[1] & 0x01) != 0) {
333 RDEBUG2("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 RDEBUG2("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 RDEBUG2("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 RDEBUG2("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) {
412 * If we're getting a User-Name formatted in this way,
413 * it's likely due to PEAP. When authenticating this against
414 * a Domain, Windows will expect the User-Name to be in the
415 * format of hostname$, the SAM version of the name, so we
416 * have to convert it to that here. We do so by stripping
417 * off the first 5 characters (host/), and copying everything
418 * from that point to the first period into a string and appending
421 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$",
429 (int) (p - user_name->vp_strvalue), user_name->vp_strvalue + 5);
431 snprintf(out, outlen, "%s$", user_name->vp_strvalue + 5);
434 p = strchr(user_name->vp_strvalue, '\\');
436 p++; /* skip the backslash */
438 p = user_name->vp_strvalue; /* use the whole User-Name */
440 strlcpy(out, p, outlen);
446 * Return the NT-Hash of the passed string
448 } 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 (radius_xlat(buf2, sizeof(buf2), request, p, NULL, NULL) < 0) {
463 if (mschap_ntpwdhash(buffer, buf2) < 0) {
464 RERROR("Failed generating NT-Password");
469 fr_bin2hex(out, buffer, 16);
471 RDEBUG("NT-Hash of %s = %s", buf2, out);
475 * Return the LM-Hash of the passed string
477 } else if (strncasecmp(fmt, "LM-Hash ", 8) == 0) {
481 p = fmt + 8; /* 7 is the length of 'LM-Hash' */
482 if ((p == '\0') || (outlen <= 32))
485 while (isspace(*p)) p++;
487 if (radius_xlat(buf2, sizeof(buf2), request, p, NULL, NULL) < 0) {
492 smbdes_lmpwdhash(buf2, buffer);
493 fr_bin2hex(out, buffer, 16);
495 RDEBUG("LM-Hash of %s = %s", buf2, out);
498 RDEBUG2("Unknown expansion string \"%s\"",
503 if (outlen == 0) return 0; /* nowhere to go, don't do anything */
506 * Didn't set anything: this is bad.
509 RDEBUG2("Failed to do anything intelligent");
514 * Check the output length.
516 if (outlen < ((data_len * 2) + 1)) {
517 data_len = (outlen - 1) / 2;
523 for (i = 0; i < data_len; i++) {
524 sprintf(out + (2 * i), "%02x", data[i]);
526 out[data_len * 2] = '\0';
532 static const CONF_PARSER passchange_config[] = {
533 { "ntlm_auth", PW_TYPE_STRING_PTR,
534 offsetof(rlm_mschap_t, ntlm_cpw), NULL, NULL },
535 { "ntlm_auth_username", PW_TYPE_STRING_PTR,
536 offsetof(rlm_mschap_t, ntlm_cpw_username), NULL, NULL },
537 { "ntlm_auth_domain", PW_TYPE_STRING_PTR,
538 offsetof(rlm_mschap_t, ntlm_cpw_domain), NULL, NULL },
539 { "local_cpw", PW_TYPE_STRING_PTR,
540 offsetof(rlm_mschap_t, local_cpw), NULL, NULL },
541 { NULL, -1, 0, NULL, NULL } /* end the list */
543 static const CONF_PARSER module_config[] = {
545 * Cache the password by default.
547 { "use_mppe", PW_TYPE_BOOLEAN,
548 offsetof(rlm_mschap_t,use_mppe), NULL, "yes" },
549 { "require_encryption", PW_TYPE_BOOLEAN,
550 offsetof(rlm_mschap_t,require_encryption), NULL, "no" },
551 { "require_strong", PW_TYPE_BOOLEAN,
552 offsetof(rlm_mschap_t,require_strong), NULL, "no" },
553 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
554 offsetof(rlm_mschap_t,with_ntdomain_hack), NULL, "yes" },
555 { "ntlm_auth", PW_TYPE_STRING_PTR,
556 offsetof(rlm_mschap_t, ntlm_auth), NULL, NULL },
557 { "passchange", PW_TYPE_SUBSECTION, 0, NULL, (void const *) passchange_config },
558 { "allow_retry", PW_TYPE_BOOLEAN,
559 offsetof(rlm_mschap_t, allow_retry), NULL, "yes" },
560 { "retry_msg", PW_TYPE_STRING_PTR,
561 offsetof(rlm_mschap_t, retry_msg), NULL, NULL },
562 #ifdef WITH_OPEN_DIRECTORY
563 { "use_open_directory", PW_TYPE_BOOLEAN,
564 offsetof(rlm_mschap_t,open_directory), NULL, "yes" },
567 { NULL, -1, 0, NULL, NULL } /* end the list */
572 * Create instance for our module. Allocate space for
573 * instance structure and read configuration parameters
575 static int mod_instantiate(CONF_SECTION *conf, void *instance)
578 rlm_mschap_t *inst = instance;
581 * Create the dynamic translation.
583 name = cf_section_name2(conf);
584 if (!name) name = cf_section_name1(conf);
585 inst->xlat_name = name;
586 xlat_register(inst->xlat_name, mschap_xlat, NULL, inst);
589 * For backwards compatibility
591 if (!dict_valbyname(PW_AUTH_TYPE, 0, inst->xlat_name)) {
592 inst->auth_type = "MS-CHAP";
594 inst->auth_type = inst->xlat_name;
601 * add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
602 * attribute to reply packet
604 void mschap_add_reply(REQUEST *request, unsigned char ident,
605 char const* name, char const* value, int len)
610 vp = pairmake_reply(name, NULL, T_OP_EQ);
612 RDEBUG("Failed to create attribute %s: %s\n", name, fr_strerror());
615 vp->length = len + 1;
616 vp->vp_octets = p = talloc_array(vp, uint8_t, vp->length);
619 memcpy(p + 1, value, len);
623 * Add MPPE attributes to the reply.
625 static void mppe_add_reply(REQUEST *request,
626 char const* name, uint8_t const * value, int len)
630 vp = pairmake_reply(name, NULL, T_OP_EQ);
632 RDEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, fr_strerror());
636 pairmemcpy(vp, value, len);
639 static int write_all(int fd, char const *buf, int len) {
643 rv = write(fd, buf+done, len-done);
652 * Perform an MS-CHAP2 password change
655 static int do_mschap_cpw(rlm_mschap_t *inst,
656 REQUEST *request, VALUE_PAIR *nt_password,
657 uint8_t *new_nt_password,
658 uint8_t *old_nt_hash,
661 if (inst->ntlm_cpw && do_ntlm_auth) {
663 * we're going to run ntlm_auth in helper-mode
664 * we're expecting to use the ntlm-change-password-1 protocol
665 * which needs the following on stdin:
667 * username: %{mschap:User-Name}
668 * nt-domain: %{mschap:NT-Domain}
669 * new-nt-password-blob: bin2hex(new_nt_password) - 1032 bytes encoded
670 * old-nt-hash-blob: bin2hex(old_nt_hash) - 32 bytes encoded
671 * new-lm-password-blob: 00000...0000 - 1032 bytes null
672 * old-lm-hash-blob: 000....000 - 32 bytes null
675 * ...and it should then print out
677 * Password-Change: Yes
681 * Password-Change: No
682 * Password-Change-Error: blah
687 pid_t pid, child_pid;
693 RDEBUG("Doing MS-CHAPv2 password change via ntlm_auth helper");
696 * Start up ntlm_auth with a pipe on stdin and stdout
699 pid = radius_start_program(inst->ntlm_cpw, request, 1, &to_child, &from_child, NULL, 0);
701 RDEBUG2("could not exec ntlm_auth cpw command");
706 * write the stuff to the client
709 if (inst->ntlm_cpw_username) {
710 len = radius_xlat(buf, sizeof(buf) - 2, request, inst->ntlm_cpw_username, NULL, NULL);
718 if (write_all(to_child, buf, len) != len) {
719 RDEBUG2("failed to write username to child");
723 RDEBUG("No ntlm_auth username set - passchange will definitely fail!");
726 if (inst->ntlm_cpw_domain) {
727 len = radius_xlat(buf, sizeof(buf) - 2, request, inst->ntlm_cpw_domain, NULL, NULL);
735 if (write_all(to_child, buf, len) != len) {
736 RDEBUG2("failed to write domain to child");
740 RDEBUG("No ntlm_auth domain set - username must be full-username to work");
743 /* now the password blobs */
744 len = sprintf(buf, "new-nt-password-blob: ");
745 fr_bin2hex(buf+len, new_nt_password, 516);
746 buf[len+1032] = '\n';
747 buf[len+1033] = '\0';
749 if (write_all(to_child, buf, len) != len) {
750 RDEBUG2("failed to write new password blob to child");
754 len = sprintf(buf, "old-nt-hash-blob: ");
755 fr_bin2hex(buf+len, old_nt_hash, 16);
759 if (write_all(to_child, buf, len) != len) {
760 RDEBUG2("failed to write old hash blob to child");
765 * in current samba versions, failure to supply empty
766 * LM password/hash blobs causes the change to fail
768 len = sprintf(buf, "new-lm-password-blob: %01032i\n", 0);
769 if (write_all(to_child, buf, len) != len) {
770 RDEBUG2("failed to write dummy LM password to child");
773 len = sprintf(buf, "old-lm-hash-blob: %032i\n", 0);
774 if (write_all(to_child, buf, len) != len) {
775 RDEBUG2("failed to write dummy LM hash to child");
778 if (write_all(to_child, ".\n", 2) != 2) {
779 RDEBUG2("failed to send finish to child");
786 * Read from the child
788 len = radius_readfrom_program(request, from_child, pid, 10, buf, sizeof(buf));
790 /* radius_readfrom_program will have closed from_child for us */
791 RDEBUG2("Failure reading from child");
798 RDEBUG2("ntlm_auth said: %s", buf);
800 child_pid = rad_waitpid(pid, &status);
801 if (child_pid == 0) {
802 RDEBUG2("Timeout waiting for child");
805 if (child_pid != pid) {
806 RDEBUG("Abnormal exit status: %s", strerror(errno));
810 if (strstr(buf, "Password-Change: Yes")) {
811 RDEBUG2("ntlm_auth password change succeeded");
815 pmsg = strstr(buf, "Password-Change-Error: ");
817 emsg = strsep(&pmsg, "\n");
819 emsg = "could not find error";
821 RDEBUG2("ntlm auth password change failed: %s", emsg);
824 /* safe because these either need closing or are == -1 */
830 } else if (inst->local_cpw) {
831 #ifdef HAVE_OPENSSL_CRYPTO_H
833 * decrypt the new password blob, add it as a temporary request
834 * variable, xlat the local_cpw string, then remove it
836 * this allows is to write e..g
838 * %{sql:insert into ...}
842 * %{exec:/path/to %{mschap:User-Name} %{MS-CHAP-New-Password}}"
846 VALUE_PAIR *new_pass, *new_hash;
853 uint8_t nt_pass_decrypted[516], old_nt_hash_expected[16];
857 RDEBUG("Local MS-CHAPv2 password change requires NT-Password attribute");
860 RDEBUG("Doing MS-CHAPv2 password change locally");
866 RC4_set_key(&key, nt_password->length, nt_password->vp_octets);
867 RC4(&key, 516, new_nt_password, nt_pass_decrypted);
871 * 512-N bytes random pad
872 * N bytes password as utf-16-le
873 * 4 bytes - N as big-endian int
876 passlen = nt_pass_decrypted[512];
877 passlen += nt_pass_decrypted[513] << 8;
878 if ((nt_pass_decrypted[514] != 0) ||
879 (nt_pass_decrypted[515] != 0)) {
880 RDEBUG2("Decrypted new password blob claims length > 65536 - probably an invalid NT-Password");
885 * sanity check - passlen positive and <= 512
886 * if not, crypto has probably gone wrong
889 RDEBUG2("Decrypted new password blob claims length %u > 512 - probably an invalid NT-Password", passlen);
893 p = nt_pass_decrypted + 512 - passlen;
896 * the new NT hash - this should be preferred over the
897 * cleartext password as it avoids unicode hassles
899 new_hash = pairmake_packet("MS-CHAP-New-NT-Password", NULL,
901 new_hash->length = 16;
902 new_hash->vp_octets = q = talloc_array(new_hash, uint8_t, new_hash->length);
903 fr_md4_calc(q, p, passlen);
906 * check that nt_password encrypted with new_hash
907 * matches the old_hash value from the client
909 smbhash(old_nt_hash_expected, nt_password->vp_octets, q);
910 smbhash(old_nt_hash_expected+8, nt_password->vp_octets+8, q + 7);
911 if (memcmp(old_nt_hash_expected, old_nt_hash, 16)!=0) {
912 RDEBUG2("old NT hash value from client does not match our value");
917 * the new cleartext password, which is utf-16
918 * do some unpleasant vileness to turn it into
919 * utf8 without pulling in libraries like iconv
921 new_pass = pairmake_packet("MS-CHAP-New-Cleartext-Password", NULL,
923 new_pass->length = 0;
924 new_pass->vp_strvalue = x = talloc_array(new_pass, char, 254);
928 * The client-supplied password is utf-16.
929 * We really must perform a proper conversion
930 * to utf8 here, and the same in the other direction
931 * when we calculate NT-Password below, else non-ascii
932 * characters will fail - I know from experience that
933 * UK pound and Euro symbols are common in users
934 * passwords (money obsessed!)
942 * gah. nasty. maybe we should just pull in iconv?
947 if (new_pass->length >= 253) {
948 RDEBUG("Ran out of room turning new password into utf8 at %d - cleartext will be truncated!", i);
951 x[new_pass->length++] = c;
952 } else if (c < 0x7ff) {
954 if (new_pass->length >= 252) {
955 RDEBUG("Ran out of room turning new password into utf8 at %d - cleartext will be truncated!", i);
958 x[new_pass->length++] = 0xc0 + (c >> 6);
959 x[new_pass->length++] = 0x80 + (c & 0x3f);
962 if (new_pass->length >= 251) {
963 RDEBUG("Ran out of room turning new password into utf8 at %d - cleartext will be truncated!", i);
966 x[new_pass->length++] = 0xe0 + (c >> 12);
967 x[new_pass->length++] = 0x80 + ((c>>6) & 0x3f);
968 x[new_pass->length++] = 0x80 + (c & 0x3f);
975 result_len = radius_xlat(result, sizeof(result), request, inst->local_cpw, NULL, NULL);
978 } else if (result_len == 0) {
979 RDEBUG("Local MS-CHAPv2 password change - xlat didn't give any result, assuming failure");
983 RDEBUG("MS-CHAPv2 password change succeeded: %s", result);
986 * update the NT-Password attribute with the new hash
987 * this lets us fall through to the authentication
988 * code using the new hash, not the old one
990 pairmemcpy(nt_password, new_hash->vp_octets, new_hash->length);
993 * rock on! password change succeeded
997 RDEBUG("Local MS-CHAPv2 password changes require OpenSSL support");
1001 RDEBUG("MS-CHAPv2 password change not configured");
1008 * Do the MS-CHAP stuff.
1010 * This function is here so that all of the MS-CHAP related
1011 * authentication is in one place, and we can perhaps later replace
1012 * it with code to call winbindd, or something similar.
1014 static int do_mschap(rlm_mschap_t *inst,
1015 REQUEST *request, VALUE_PAIR *password,
1016 uint8_t const *challenge, uint8_t const *response,
1017 uint8_t *nthashhash, int do_ntlm_auth)
1019 uint8_t calculated[24];
1021 rad_assert(request != NULL);
1024 * Do normal authentication.
1026 if (!do_ntlm_auth) {
1028 * No password: can't do authentication.
1031 RDEBUG2("FAILED: No NT/LM-Password. Cannot perform authentication.");
1035 smbdes_mschap(password->vp_octets, challenge, calculated);
1036 if (rad_digest_cmp(response, calculated, 24) != 0) {
1041 * If the password exists, and is an NT-Password,
1042 * then calculate the hash of the NT hash. Doing this
1043 * here minimizes work for later.
1045 if (password && !password->da->vendor &&
1046 (password->da->attr == PW_NT_PASSWORD)) {
1047 fr_md4_calc(nthashhash, password->vp_octets, 16);
1049 memset(nthashhash, 0, 16);
1051 } else { /* run ntlm_auth */
1055 memset(nthashhash, 0, 16);
1058 * Run the program, and expect that we get 16
1060 result = radius_exec_program(request, inst->ntlm_auth, true, true,
1061 buffer, sizeof(buffer),
1067 * look for "Password expired", or "Must
1070 if (strstr(buffer, "Password expired") ||
1071 strstr(buffer, "Must change password")) {
1072 RDEBUG2("ntlm_auth says %s", buffer);
1076 RDEBUG2("External script failed.");
1077 p = strchr(buffer, '\n');
1080 REDEBUG("External script says: %s",
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 RDEBUG2("Invalid output from ntlm_auth: expecting NT_KEY");
1097 * Check the length. It should be at least 32,
1098 * with an LF at the end.
1100 if (strlen(buffer + 8) < 32) {
1101 RDEBUG2("Invalid output from ntlm_auth: NT_KEY has unexpected length");
1106 * Update the NT hash hash, from the NT key.
1108 if (fr_hex2bin(nthashhash, buffer + 8, 16) != 16) {
1109 RDEBUG2("Invalid output from ntlm_auth: NT_KEY has non-hex values");
1119 * Data for the hashes.
1121 static const uint8_t SHSpad1[40] =
1122 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1123 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1124 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1125 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
1127 static const uint8_t SHSpad2[40] =
1128 { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1129 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1130 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1131 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
1133 static const uint8_t magic1[27] =
1134 { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
1135 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
1136 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
1138 static const uint8_t magic2[84] =
1139 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
1140 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
1141 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1142 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
1143 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
1144 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
1145 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1146 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
1147 0x6b, 0x65, 0x79, 0x2e };
1149 static const uint8_t magic3[84] =
1150 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
1151 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
1152 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1153 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
1154 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
1155 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
1156 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
1157 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
1158 0x6b, 0x65, 0x79, 0x2e };
1161 static void mppe_GetMasterKey(uint8_t const *nt_hashhash,uint8_t const *nt_response,
1165 fr_SHA1_CTX Context;
1167 fr_SHA1Init(&Context);
1168 fr_SHA1Update(&Context,nt_hashhash,16);
1169 fr_SHA1Update(&Context,nt_response,24);
1170 fr_SHA1Update(&Context,magic1,27);
1171 fr_SHA1Final(digest,&Context);
1173 memcpy(masterkey,digest,16);
1177 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
1178 int keylen,int issend)
1182 fr_SHA1_CTX Context;
1184 memset(digest,0,20);
1192 fr_SHA1Init(&Context);
1193 fr_SHA1Update(&Context,masterkey,16);
1194 fr_SHA1Update(&Context,SHSpad1,40);
1195 fr_SHA1Update(&Context,s,84);
1196 fr_SHA1Update(&Context,SHSpad2,40);
1197 fr_SHA1Final(digest,&Context);
1199 memcpy(sesskey,digest,keylen);
1203 static void mppe_chap2_get_keys128(uint8_t const *nt_hashhash,uint8_t const *nt_response,
1204 uint8_t *sendkey,uint8_t *recvkey)
1206 uint8_t masterkey[16];
1208 mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
1210 mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
1211 mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
1215 * Generate MPPE keys.
1217 static void mppe_chap2_gen_keys128(uint8_t const *nt_hashhash,uint8_t const *response,
1218 uint8_t *sendkey,uint8_t *recvkey)
1220 uint8_t enckey1[16];
1221 uint8_t enckey2[16];
1223 mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
1226 * dictionary.microsoft defines these attributes as
1227 * 'encrypt=2'. The functions in src/lib/radius.c will
1228 * take care of encrypting/decrypting them as appropriate,
1229 * so that we don't have to.
1231 memcpy (sendkey, enckey1, 16);
1232 memcpy (recvkey, enckey2, 16);
1237 * mod_authorize() - authorize user if we can authenticate
1238 * it later. Add Auth-Type attribute if present in module
1239 * configuration (usually Auth-Type must be "MS-CHAP")
1241 static rlm_rcode_t mod_authorize(void * instance, REQUEST *request)
1243 rlm_mschap_t *inst = instance;
1244 VALUE_PAIR *challenge = NULL;
1246 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
1248 return RLM_MODULE_NOOP;
1251 if (!pairfind(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY) &&
1252 !pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY) &&
1253 !pairfind(request->packet->vps, PW_MSCHAP2_CPW, VENDORPEC_MICROSOFT, TAG_ANY)) {
1254 RDEBUG2("Found MS-CHAP-Challenge, but no MS-CHAP response or change-password");
1255 return RLM_MODULE_NOOP;
1258 if (pairfind(request->config_items, PW_AUTH_TYPE, 0, TAG_ANY)) {
1259 RWDEBUG2("Auth-Type already set. Not setting to MS-CHAP");
1260 return RLM_MODULE_NOOP;
1263 RDEBUG2("Found MS-CHAP attributes. Setting 'Auth-Type = %s'", inst->xlat_name);
1266 * Set Auth-Type to MS-CHAP. The authentication code
1267 * will take care of turning clear-text passwords into
1270 if (!pairmake_config("Auth-Type", inst->auth_type, T_OP_EQ)) {
1271 return RLM_MODULE_FAIL;
1274 return RLM_MODULE_OK;
1278 * mod_authenticate() - authenticate user based on given
1279 * attributes and configuration.
1280 * We will try to find out password in configuration
1281 * or in configured passwd file.
1282 * If one is found we will check paraneters given by NAS.
1284 * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
1286 * PAP: PW_USER_PASSWORD or
1287 * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
1288 * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
1289 * In case of password mismatch or locked account we MAY return
1290 * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
1291 * If MS-CHAP2 succeeds we MUST return
1292 * PW_MSCHAP2_SUCCESS
1294 static rlm_rcode_t mod_authenticate(void * instance, REQUEST *request)
1296 #define inst ((rlm_mschap_t *)instance)
1297 VALUE_PAIR *challenge = NULL;
1298 VALUE_PAIR *response = NULL;
1299 VALUE_PAIR *cpw = NULL;
1300 VALUE_PAIR *password = NULL;
1301 VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
1302 VALUE_PAIR *username;
1303 uint8_t nthashhash[16];
1306 char const *username_string;
1311 * If we have ntlm_auth configured, use it unless told
1314 do_ntlm_auth = (inst->ntlm_auth != NULL);
1317 * If we have an ntlm_auth configuration, then we may
1318 * want to suppress it.
1321 VALUE_PAIR *vp = pairfind(request->config_items, PW_MS_CHAP_USE_NTLM_AUTH, 0, TAG_ANY);
1322 if (vp) do_ntlm_auth = vp->vp_integer;
1326 * Find the SMB-Account-Ctrl attribute, or the
1327 * SMB-Account-Ctrl-Text attribute.
1329 smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL, 0, TAG_ANY);
1331 password = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL_TEXT, 0, TAG_ANY);
1333 smb_ctrl = pairmake_config("SMB-Account-CTRL", "0",
1336 smb_ctrl->vp_integer = pdb_decode_acct_ctrl(password->vp_strvalue);
1342 * We're configured to do MS-CHAP authentication.
1343 * and account control information exists. Enforce it.
1347 * Password is not required.
1349 if ((smb_ctrl->vp_integer & ACB_PWNOTREQ) != 0) {
1350 RDEBUG2("SMB-Account-Ctrl says no password is required.");
1351 return RLM_MODULE_OK;
1356 * Decide how to get the passwords.
1358 password = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY);
1361 * We need an LM-Password.
1363 lm_password = pairfind(request->config_items, PW_LM_PASSWORD, 0, TAG_ANY);
1365 if (lm_password->length == 16) {
1366 RDEBUG2("Found LM-Password");
1368 RERROR("LM-Password has not been normalized by the \"pap\" module. Authentication will fail.");
1372 } else if (!password) {
1373 if (!do_ntlm_auth) RDEBUG2("No Cleartext-Password configured. Cannot create LM-Password.");
1375 } else { /* there is a configured Cleartext-Password */
1376 lm_password = pairmake_config("LM-Password", NULL, T_OP_EQ);
1378 RERROR("No memory");
1380 lm_password->length = 16;
1381 lm_password->vp_octets = p = talloc_array(lm_password, uint8_t, lm_password->length);
1382 smbdes_lmpwdhash(password->vp_strvalue,
1388 * We need an NT-Password.
1390 nt_password = pairfind(request->config_items, PW_NT_PASSWORD, 0, TAG_ANY);
1392 if (nt_password->length == 16) {
1393 RDEBUG2("Found NT-Password");
1395 RERROR("NT-Password has not been normalized by the \"pap\" module. Authentication will fail.");
1398 } else if (!password) {
1399 if (!do_ntlm_auth) RDEBUG2("No Cleartext-Password configured. Cannot create NT-Password.");
1401 } else { /* there is a configured Cleartext-Password */
1402 nt_password = pairmake_config("NT-Password", NULL, T_OP_EQ);
1404 RERROR("No memory");
1405 return RLM_MODULE_FAIL;
1407 nt_password->length = 16;
1408 nt_password->vp_octets = p = talloc_array(nt_password, uint8_t, nt_password->length);
1410 if (mschap_ntpwdhash(p, password->vp_strvalue) < 0) {
1411 RERROR("Failed generating NT-Password");
1412 return RLM_MODULE_FAIL;
1417 cpw = pairfind(request->packet->vps, PW_MSCHAP2_CPW, VENDORPEC_MICROSOFT, TAG_ANY);
1420 * mschap2 password change request
1421 * we cheat - first decode and execute the passchange
1422 * we then extract the response, add it into the request
1423 * then jump into mschap2 auth with the chal/resp
1425 uint8_t new_nt_encrypted[516], old_nt_encrypted[16];
1426 VALUE_PAIR *nt_enc=NULL;
1427 int seq, new_nt_enc_len=0;
1429 RDEBUG("MS-CHAPv2 password change request received");
1431 if (cpw->length != 68) {
1432 RDEBUG2("MS-CHAP2-CPW has the wrong format - length %d!=68", cpw->length);
1433 return RLM_MODULE_INVALID;
1434 } else if (cpw->vp_octets[0]!=7) {
1435 RDEBUG2("MS-CHAP2-CPW has the wrong format - code %d!=7", cpw->vp_octets[0]);
1436 return RLM_MODULE_INVALID;
1440 * look for the new (encrypted) password
1441 * bah stupid composite attributes
1442 * we're expecting 3 attributes with the leading bytes
1443 * 06:<mschapid>:00:01:<1st chunk>
1444 * 06:<mschapid>:00:02:<2nd chunk>
1445 * 06:<mschapid>:00:03:<3rd chunk>
1447 for (seq = 1; seq < 4; seq++) {
1451 for (nt_enc = paircursor(&cursor, &request->packet->vps);
1453 nt_enc = pairnext(&cursor)) {
1454 if (nt_enc->da->vendor != VENDORPEC_MICROSOFT)
1457 if (nt_enc->da->attr != PW_MSCHAP_NT_ENC_PW)
1460 if (nt_enc->vp_octets[0] != 6) {
1461 RDEBUG2("MS-CHAP-NT-Enc-PW with invalid format");
1462 return RLM_MODULE_INVALID;
1464 if (nt_enc->vp_octets[2]==0 && nt_enc->vp_octets[3]==seq) {
1471 RDEBUG2("Could not find MS-CHAP-NT-Enc-PW w/ sequence number %d", seq);
1472 return RLM_MODULE_INVALID;
1476 * copy the data into the buffer
1478 memcpy(new_nt_encrypted + new_nt_enc_len, nt_enc->vp_octets + 4, nt_enc->length - 4);
1479 new_nt_enc_len += nt_enc->length - 4;
1481 if (new_nt_enc_len != 516) {
1482 RDEBUG2("Unpacked MS-CHAP-NT-Enc-PW length != 516");
1483 return RLM_MODULE_INVALID;
1487 * RFC 2548 is confusing here
1492 * 16 octets - old hash encrypted with new hash
1493 * 24 octets - peer challenge
1495 * 16 octets - peer challenge
1496 * 8 octets - reserved
1497 * 24 octets - nt response
1498 * 2 octets - flags (ignored)
1501 memcpy(old_nt_encrypted, cpw->vp_octets+2, sizeof(old_nt_encrypted));
1503 RDEBUG2("Password change payload valid");
1505 /* perform the actual password change */
1506 if (do_mschap_cpw(inst, request, nt_password, new_nt_encrypted, old_nt_encrypted, do_ntlm_auth) < 0) {
1509 RDEBUG("Password change failed");
1511 snprintf(buffer, sizeof(buffer), "E=709 R=0 M=Password change failed");
1512 mschap_add_reply(request,
1513 cpw->vp_octets[1], "MS-CHAP-Error",
1514 buffer, strlen(buffer));
1515 return RLM_MODULE_REJECT;
1517 RDEBUG("Password change successful");
1520 * Clear any expiry bit so the user can now login;
1521 * obviously the password change action will need
1522 * to have cleared this bit in the config/SQL/wherever
1524 if (smb_ctrl && smb_ctrl->vp_integer & ACB_PW_EXPIRED) {
1525 RDEBUG("clearing expiry bit in SMB-Acct-Ctrl to allow authentication");
1526 smb_ctrl->vp_integer &= ~ACB_PW_EXPIRED;
1531 * extract the challenge & response from the end of the password
1532 * change, add them into the request and then continue with
1533 * the authentication
1536 response = radius_paircreate(request, &request->packet->vps,
1537 PW_MSCHAP2_RESPONSE,
1538 VENDORPEC_MICROSOFT);
1539 response->length = 50;
1540 response->vp_octets = p = talloc_array(response, uint8_t, response->length);
1543 p[0] = cpw->vp_octets[1];
1545 /* peer challenge and client NT response */
1546 memcpy(p + 2, cpw->vp_octets + 18, 48);
1549 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
1551 REDEBUG("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1552 return RLM_MODULE_REJECT;
1556 * We also require an MS-CHAP-Response.
1558 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
1561 * MS-CHAP-Response, means MS-CHAPv1
1567 * MS-CHAPv1 challenges are 8 octets.
1569 if (challenge->length < 8) {
1570 RAUTH("MS-CHAP-Challenge has the wrong format.");
1571 return RLM_MODULE_INVALID;
1575 * Responses are 50 octets.
1577 if (response->length < 50) {
1578 RAUTH("MS-CHAP-Response has the wrong format.");
1579 return RLM_MODULE_INVALID;
1583 * We are doing MS-CHAP. Calculate the MS-CHAP
1586 if (response->vp_octets[1] & 0x01) {
1587 RDEBUG2("Client is using MS-CHAPv1 with NT-Password");
1588 password = nt_password;
1591 RDEBUG2("Client is using MS-CHAPv1 with LM-Password");
1592 password = lm_password;
1597 * Do the MS-CHAP authentication.
1599 if (do_mschap(inst, request, password, challenge->vp_octets,
1600 response->vp_octets + offset, nthashhash,
1601 do_ntlm_auth) < 0) {
1602 RDEBUG2("MS-CHAP-Response is incorrect.");
1608 } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY)) != NULL) {
1610 uint8_t mschapv1_challenge[16];
1611 VALUE_PAIR *name_attr, *response_name;
1614 * MS-CHAPv2 challenges are 16 octets.
1616 if (challenge->length < 16) {
1617 RAUTH("MS-CHAP-Challenge has the wrong format.");
1618 return RLM_MODULE_INVALID;
1622 * Responses are 50 octets.
1624 if (response->length < 50) {
1625 RAUTH("MS-CHAP-Response has the wrong format.");
1626 return RLM_MODULE_INVALID;
1630 * We also require a User-Name
1632 username = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
1634 RAUTH("We require a User-Name for MS-CHAPv2");
1635 return RLM_MODULE_INVALID;
1639 * Check for MS-CHAP-User-Name and if found, use it
1640 * to construct the MSCHAPv1 challenge. This is
1641 * set by rlm_eap_mschap to the MS-CHAP Response
1642 * packet Name field.
1644 * We prefer this to the User-Name in the
1647 response_name = pairfind(request->packet->vps, PW_MS_CHAP_USER_NAME, 0, TAG_ANY);
1648 if (response_name) {
1649 name_attr = response_name;
1651 name_attr = username;
1655 * with_ntdomain_hack moved here, too.
1657 if ((username_string = strchr(name_attr->vp_strvalue, '\\')) != NULL) {
1658 if (inst->with_ntdomain_hack) {
1661 RDEBUG2("NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1662 username_string = name_attr->vp_strvalue;
1665 username_string = name_attr->vp_strvalue;
1668 if (response_name &&
1669 ((username->length != response_name->length) ||
1670 (strncasecmp(username->vp_strvalue, response_name->vp_strvalue, username->length) != 0))) {
1671 RWDEBUG("User-Name (%s) is not the same as MS-CHAP Name (%s) from EAP-MSCHAPv2", username->vp_strvalue, response_name->vp_strvalue);
1674 #ifdef WITH_OPEN_DIRECTORY
1676 * No "known good" NT-Password attribute. Try to do
1677 * OpenDirectory authentication.
1679 * If OD determines the user is an AD user it will return noop, which
1680 * indicates the auth process should continue directly to AD.
1681 * Otherwise OD will determine auth success/fail.
1683 if (!nt_password && inst->open_directory) {
1684 RDEBUG2("No NT-Password configured. Trying OpenDirectory Authentication.");
1685 int odStatus = od_mschap_auth(request, challenge, username);
1686 if (odStatus != RLM_MODULE_NOOP) {
1692 * The old "mschapv2" function has been moved to
1695 * MS-CHAPv2 takes some additional data to create an
1696 * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1698 RDEBUG2("Creating challenge hash with username: %s",
1700 mschap_challenge_hash(response->vp_octets + 2, /* peer challenge */
1701 challenge->vp_octets, /* our challenge */
1702 username_string, /* user name */
1703 mschapv1_challenge); /* resulting challenge */
1705 RDEBUG2("Client is using MS-CHAPv2 for %s, we need NT-Password",
1708 mschap_result = do_mschap(inst, request, nt_password, mschapv1_challenge,
1709 response->vp_octets + 26, nthashhash,
1711 if (mschap_result == -648)
1712 goto password_expired;
1714 if (mschap_result < 0) {
1718 RDEBUG2("FAILED: MS-CHAP2-Response is incorrect");
1721 snprintf(buffer, sizeof(buffer), "E=691 R=%d",
1724 if (inst->retry_msg) {
1725 snprintf(buffer + 9, sizeof(buffer) - 9, " C=");
1726 for (i = 0; i < 16; i++) {
1727 snprintf(buffer + 12 + i*2,
1728 sizeof(buffer) - 12 - i*2, "%02x",
1731 snprintf(buffer + 45, sizeof(buffer) - 45,
1732 " V=3 M=%s", inst->retry_msg);
1734 mschap_add_reply(request,
1735 *response->vp_octets, "MS-CHAP-Error",
1736 buffer, strlen(buffer));
1737 return RLM_MODULE_REJECT;
1740 if (smb_ctrl && smb_ctrl->vp_integer & ACB_PW_EXPIRED) {
1742 * if the password is correct and it has expired
1743 * we can permit password changes (only in MS-CHAPv2)
1745 char newchal[33], buffer[128];
1749 for (i = 0; i < 16; i++)
1750 snprintf(newchal + i*2, 3, "%02x", fr_rand() & 0xff);
1752 snprintf(buffer, sizeof(buffer), "E=648 R=0 C=%s V=3 M=Password Expired", newchal);
1754 mschap_add_reply(request,
1755 *response->vp_octets, "MS-CHAP-Error",
1756 buffer, strlen(buffer));
1757 return RLM_MODULE_REJECT;
1760 mschap_auth_response(username_string, /* without the domain */
1761 nthashhash, /* nt-hash-hash */
1762 response->vp_octets + 26, /* peer response */
1763 response->vp_octets + 2, /* peer challenge */
1764 challenge->vp_octets, /* our challenge */
1765 msch2resp); /* calculated MPPE key */
1766 mschap_add_reply(request, *response->vp_octets,
1767 "MS-CHAP2-Success", msch2resp, 42);
1770 } else { /* Neither CHAPv1 or CHAPv2 response: die */
1771 REDEBUG("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1772 return RLM_MODULE_INVALID;
1776 * We have a CHAP response, but the account may be
1777 * disabled. Reject the user with the same error code
1778 * we use when their password is invalid.
1782 * Account is disabled.
1784 * They're found, but they don't exist, so we
1785 * return 'not found'.
1787 if (((smb_ctrl->vp_integer & ACB_DISABLED) != 0) ||
1788 ((smb_ctrl->vp_integer & (ACB_NORMAL|ACB_WSTRUST)) == 0)) {
1789 RDEBUG2("SMB-Account-Ctrl says that the account is disabled, or is not a normal or workstation trust account.");
1790 mschap_add_reply(request,
1791 *response->vp_octets,
1792 "MS-CHAP-Error", "E=691 R=1", 9);
1793 return RLM_MODULE_NOTFOUND;
1797 * User is locked out.
1799 if ((smb_ctrl->vp_integer & ACB_AUTOLOCK) != 0) {
1800 RDEBUG2("SMB-Account-Ctrl says that the account is locked out.");
1801 mschap_add_reply(request,
1802 *response->vp_octets,
1803 "MS-CHAP-Error", "E=647 R=0", 9);
1804 return RLM_MODULE_USERLOCK;
1808 /* now create MPPE attributes */
1809 if (inst->use_mppe) {
1810 uint8_t mppe_sendkey[34];
1811 uint8_t mppe_recvkey[34];
1814 RDEBUG2("adding MS-CHAPv1 MPPE keys");
1815 memset(mppe_sendkey, 0, 32);
1817 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1821 * According to RFC 2548 we
1822 * should send NT hash. But in
1823 * practice it doesn't work.
1824 * Instead, we should send nthashhash
1826 * This is an error on RFC 2548.
1829 * do_mschap cares to zero nthashhash if NT hash
1832 memcpy(mppe_sendkey + 8,
1834 mppe_add_reply(request,
1835 "MS-CHAP-MPPE-Keys",
1837 } else if (chap == 2) {
1838 RDEBUG2("adding MS-CHAPv2 MPPE keys");
1839 mppe_chap2_gen_keys128(nthashhash,
1840 response->vp_octets + 26,
1841 mppe_sendkey, mppe_recvkey);
1843 mppe_add_reply(request,
1846 mppe_add_reply(request,
1851 pairmake_reply("MS-MPPE-Encryption-Policy",
1852 (inst->require_encryption)? "0x00000002":"0x00000001",
1854 pairmake_reply("MS-MPPE-Encryption-Types",
1855 (inst->require_strong)? "0x00000004":"0x00000006",
1857 } /* else we weren't asked to use MPPE */
1859 return RLM_MODULE_OK;
1863 module_t rlm_mschap = {
1866 RLM_TYPE_THREAD_SAFE | RLM_TYPE_HUP_SAFE, /* type */
1867 sizeof(rlm_mschap_t),
1869 mod_instantiate, /* instantiation */
1872 mod_authenticate, /* authenticate */
1873 mod_authorize, /* authorize */
1874 NULL, /* pre-accounting */
1875 NULL, /* accounting */
1876 NULL, /* checksimul */
1877 NULL, /* pre-proxy */
1878 NULL, /* post-proxy */
1879 NULL /* post-auth */