6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * Copyright 2000,2001,2006 The FreeRADIUS server project
25 * mschap.c MS-CHAP module
27 * This implements MS-CHAP, as described in RFC 2548
29 * http://www.freeradius.org/rfc/rfc2548.txt
34 * If you have any questions on NTLM (Samba) passwords
35 * support, LM authentication and MS-CHAP v2 support
38 * Vladimir Dubrovin vlad@sandy.ru
40 * ZARAZA 3APA3A@security.nnov.ru
43 /* MPPE support from Takahiro Wagatsuma <waga@sic.shibaura-it.ac.jp> */
45 #include <freeradius-devel/ident.h>
48 #include <freeradius-devel/radiusd.h>
49 #include <freeradius-devel/modules.h>
50 #include <freeradius-devel/rad_assert.h>
51 #include <freeradius-devel/md5.h>
52 #include <freeradius-devel/sha1.h>
59 extern int od_mschap_auth(REQUEST *request, VALUE_PAIR *challenge, VALUE_PAIR * usernamepair);
62 /* Allowable account control bits */
63 #define ACB_DISABLED 0x0001 /* 1 = User account disabled */
64 #define ACB_HOMDIRREQ 0x0002 /* 1 = Home directory required */
65 #define ACB_PWNOTREQ 0x0004 /* 1 = User password not required */
66 #define ACB_TEMPDUP 0x0008 /* 1 = Temporary duplicate account */
67 #define ACB_NORMAL 0x0010 /* 1 = Normal user account */
68 #define ACB_MNS 0x0020 /* 1 = MNS logon user account */
69 #define ACB_DOMTRUST 0x0040 /* 1 = Interdomain trust account */
70 #define ACB_WSTRUST 0x0080 /* 1 = Workstation trust account */
71 #define ACB_SVRTRUST 0x0100 /* 1 = Server trust account */
72 #define ACB_PWNOEXP 0x0200 /* 1 = User password does not expire */
73 #define ACB_AUTOLOCK 0x0400 /* 1 = Account auto locked */
75 static int pdb_decode_acct_ctrl(const char *p)
81 * Check if the account type bits have been encoded after the
82 * NT password (in the form [NDHTUWSLXI]).
85 if (*p != '[') return 0;
87 for (p++; *p && !finished; p++) {
89 case 'N': /* 'N'o password. */
90 acct_ctrl |= ACB_PWNOTREQ;
93 case 'D': /* 'D'isabled. */
94 acct_ctrl |= ACB_DISABLED ;
97 case 'H': /* 'H'omedir required. */
98 acct_ctrl |= ACB_HOMDIRREQ;
101 case 'T': /* 'T'emp account. */
102 acct_ctrl |= ACB_TEMPDUP;
105 case 'U': /* 'U'ser account (normal). */
106 acct_ctrl |= ACB_NORMAL;
109 case 'M': /* 'M'NS logon user account. What is this? */
110 acct_ctrl |= ACB_MNS;
113 case 'W': /* 'W'orkstation account. */
114 acct_ctrl |= ACB_WSTRUST;
117 case 'S': /* 'S'erver account. */
118 acct_ctrl |= ACB_SVRTRUST;
121 case 'L': /* 'L'ocked account. */
122 acct_ctrl |= ACB_AUTOLOCK;
125 case 'X': /* No 'X'piry on password */
126 acct_ctrl |= ACB_PWNOEXP;
129 case 'I': /* 'I'nterdomain trust account. */
130 acct_ctrl |= ACB_DOMTRUST;
133 case ' ': /* ignore spaces */
151 * ntpwdhash converts Unicode password to 16-byte NT hash
154 static void ntpwdhash (uint8_t *szHash, const char *szPassword)
156 char szUnicodePass[513];
161 * NT passwords are unicode. Convert plain text password
162 * to unicode by inserting a zero every other byte
164 nPasswordLen = strlen(szPassword);
165 for (i = 0; i < nPasswordLen; i++) {
166 szUnicodePass[i << 1] = szPassword[i];
167 szUnicodePass[(i << 1) + 1] = 0;
170 /* Encrypt Unicode password to a 16-byte MD4 hash */
171 fr_md4_calc(szHash, (uint8_t *) szUnicodePass, (nPasswordLen<<1) );
176 * challenge_hash() is used by mschap2() and auth_response()
177 * implements RFC2759 ChallengeHash()
178 * generates 64 bit challenge
180 static void challenge_hash( const uint8_t *peer_challenge,
181 const uint8_t *auth_challenge,
182 const char *user_name, uint8_t *challenge )
187 fr_SHA1Init(&Context);
188 fr_SHA1Update(&Context, peer_challenge, 16);
189 fr_SHA1Update(&Context, auth_challenge, 16);
190 fr_SHA1Update(&Context, (const uint8_t *) user_name,
192 fr_SHA1Final(hash, &Context);
193 memcpy(challenge, hash, 8);
197 * auth_response() generates MS-CHAP v2 SUCCESS response
198 * according to RFC 2759 GenerateAuthenticatorResponse()
199 * returns 42-octet response string
201 static void auth_response(const char *username,
202 const uint8_t *nt_hash_hash,
204 uint8_t *peer_challenge, uint8_t *auth_challenge,
208 static const uint8_t magic1[39] =
209 {0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
210 0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
211 0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
212 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74};
214 static const uint8_t magic2[41] =
215 {0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
216 0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
217 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
218 0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
221 static const char hex[16] = "0123456789ABCDEF";
224 uint8_t challenge[8];
227 fr_SHA1Init(&Context);
228 fr_SHA1Update(&Context, nt_hash_hash, 16);
229 fr_SHA1Update(&Context, ntresponse, 24);
230 fr_SHA1Update(&Context, magic1, 39);
231 fr_SHA1Final(digest, &Context);
232 challenge_hash(peer_challenge, auth_challenge, username, challenge);
233 fr_SHA1Init(&Context);
234 fr_SHA1Update(&Context, digest, 20);
235 fr_SHA1Update(&Context, challenge, 8);
236 fr_SHA1Update(&Context, magic2, 41);
237 fr_SHA1Final(digest, &Context);
240 * Encode the value of 'Digest' as "S=" followed by
241 * 40 ASCII hexadecimal digits and return it in
242 * AuthenticatorResponse.
244 * "S=0123456789ABCDEF0123456789ABCDEF01234567"
250 * The hexadecimal digits [A-F] MUST be uppercase.
252 for (i = 0; i < sizeof(digest); i++) {
253 response[2 + (i * 2)] = hex[(digest[i] >> 4) & 0x0f];
254 response[3 + (i * 2)] = hex[digest[i] & 0x0f];
259 typedef struct rlm_mschap_t {
261 int require_encryption;
263 int with_ntdomain_hack; /* this should be in another module */
265 const char *xlat_name;
267 const char *auth_type;
275 * Does dynamic translation of strings.
277 * Pulls NT-Response, LM-Response, or Challenge from MSCHAP
280 static size_t mschap_xlat(void *instance, REQUEST *request,
281 char *fmt, char *out, size_t outlen,
282 RADIUS_ESCAPE_STRING func)
285 uint8_t *data = NULL;
287 VALUE_PAIR *user_name;
288 VALUE_PAIR *chap_challenge, *response;
289 rlm_mschap_t *inst = instance;
293 func = func; /* -Wunused */
296 * Challenge means MS-CHAPv1 challenge, or
297 * hash of MS-CHAPv2 challenge, and peer challenge.
299 if (strncasecmp(fmt, "Challenge", 9) == 0) {
300 chap_challenge = pairfind(request->packet->vps,
301 PW_MSCHAP_CHALLENGE);
302 if (!chap_challenge) {
303 RDEBUG2("No MS-CHAP-Challenge in the request.");
308 * MS-CHAP-Challenges are 8 octets,
311 if (chap_challenge->length == 8) {
312 RDEBUG2(" mschap1: %02x",
313 chap_challenge->vp_octets[0]);
314 data = chap_challenge->vp_octets;
318 * MS-CHAP-Challenges are 16 octets,
321 } else if (chap_challenge->length == 16) {
322 char *username_string;
324 RDEBUG2(" mschap2: %02x", chap_challenge->vp_octets[0]);
325 response = pairfind(request->packet->vps,
326 PW_MSCHAP2_RESPONSE);
328 RDEBUG2("MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge.");
333 * Responses are 50 octets.
335 if (response->length < 50) {
336 radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
340 user_name = pairfind(request->packet->vps,
343 RDEBUG2("User-Name is required to calculateMS-CHAPv1 Challenge.");
348 * with_ntdomain_hack moved here, too.
350 if ((username_string = strchr(user_name->vp_strvalue, '\\')) != NULL) {
351 if (inst->with_ntdomain_hack) {
354 RDEBUG2("NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
355 username_string = user_name->vp_strvalue;
358 username_string = user_name->vp_strvalue;
362 * Get the MS-CHAPv1 challenge
363 * from the MS-CHAPv2 peer challenge,
364 * our challenge, and the user name.
366 challenge_hash(response->vp_octets + 2,
367 chap_challenge->vp_octets,
368 username_string, buffer);
372 RDEBUG2("Invalid MS-CHAP challenge length");
377 * Get the MS-CHAPv1 response, or the MS-CHAPv2
380 } else if (strncasecmp(fmt, "NT-Response", 11) == 0) {
381 response = pairfind(request->packet->vps,
383 if (!response) response = pairfind(request->packet->vps,
384 PW_MSCHAP2_RESPONSE);
386 RDEBUG2("No MS-CHAP-Response or MS-CHAP2-Response was found in the request.");
391 * For MS-CHAPv1, the NT-Response exists only
392 * if the second octet says so.
394 if ((response->attribute == PW_MSCHAP_RESPONSE) &&
395 ((response->vp_octets[1] & 0x01) == 0)) {
396 RDEBUG2("No NT-Response in MS-CHAP-Response");
401 * MS-CHAP-Response and MS-CHAP2-Response have
402 * the NT-Response at the same offset, and are
405 data = response->vp_octets + 26;
409 * LM-Response is deprecated, and exists only
410 * in MS-CHAPv1, and not often there.
412 } else if (strncasecmp(fmt, "LM-Response", 11) == 0) {
413 response = pairfind(request->packet->vps,
416 RDEBUG2("No MS-CHAP-Response was found in the request.");
421 * For MS-CHAPv1, the NT-Response exists only
422 * if the second octet says so.
424 if ((response->vp_octets[1] & 0x01) != 0) {
425 RDEBUG2("No LM-Response in MS-CHAP-Response");
428 data = response->vp_octets + 2;
432 * Pull the NT-Domain out of the User-Name, if it exists.
434 } else if (strncasecmp(fmt, "NT-Domain", 9) == 0) {
437 user_name = pairfind(request->packet->vps, PW_USER_NAME);
439 RDEBUG2("No User-Name was found in the request.");
444 * First check to see if this is a host/ style User-Name
445 * (a la Kerberos host principal)
447 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
449 * If we're getting a User-Name formatted in this way,
450 * it's likely due to PEAP. The Windows Domain will be
451 * the first domain component following the hostname,
452 * or the machine name itself if only a hostname is supplied
454 p = strchr(user_name->vp_strvalue, '.');
456 RDEBUG2("setting NT-Domain to same as machine name");
457 strlcpy(out, user_name->vp_strvalue + 5, outlen);
459 p++; /* skip the period */
462 * use the same hack as below
463 * only if another period was found
466 strlcpy(out, p, outlen);
470 p = strchr(user_name->vp_strvalue, '\\');
472 RDEBUG2("No NT-Domain was found in the User-Name.");
477 * Hack. This is simpler than the alternatives.
480 strlcpy(out, user_name->vp_strvalue, outlen);
487 * Pull the User-Name out of the User-Name...
489 } else if (strncasecmp(fmt, "User-Name", 9) == 0) {
492 user_name = pairfind(request->packet->vps, PW_USER_NAME);
494 RDEBUG2("No User-Name was found in the request.");
499 * First check to see if this is a host/ style User-Name
500 * (a la Kerberos host principal)
502 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
504 * If we're getting a User-Name formatted in this way,
505 * it's likely due to PEAP. When authenticating this against
506 * a Domain, Windows will expect the User-Name to be in the
507 * format of hostname$, the SAM version of the name, so we
508 * have to convert it to that here. We do so by stripping
509 * off the first 5 characters (host/), and copying everything
510 * from that point to the first period into a string and appending
513 p = strchr(user_name->vp_strvalue, '.');
515 * use the same hack as above
516 * only if a period was found
519 snprintf(out, outlen, "%s$", user_name->vp_strvalue + 5);
522 p = strchr(user_name->vp_strvalue, '\\');
524 p++; /* skip the backslash */
526 p = user_name->vp_strvalue; /* use the whole User-Name */
528 strlcpy(out, p, outlen);
534 * Return the NT-Hash of the passed string
536 } else if (strncasecmp(fmt, "NT-Hash ", 8) == 0) {
539 p = fmt + 8; /* 7 is the length of 'NT-Hash' */
540 if ((p == '\0') || (outlen <= 32))
542 RDEBUG("rlm_mschap: NT-Hash: %s",p);
545 fr_bin2hex(buffer, out, 16);
547 RDEBUG("rlm_mschap: NT-Hash: Result: %s",out);
551 * Return the LM-Hash of the passed string
553 } else if (strncasecmp(fmt, "LM-Hash ", 8) == 0) {
556 p = fmt + 8; /* 7 is the length of 'LM-Hash' */
557 if ((p == '\0') || (outlen <= 32))
560 RDEBUG("rlm_mschap: LM-Hash: %s",p);
561 smbdes_lmpwdhash(p, buffer);
562 fr_bin2hex(buffer, out, 16);
564 RDEBUG("rlm_mschap: LM-Hash: Result: %s",out);
567 RDEBUG2("Unknown expansion string \"%s\"",
572 if (outlen == 0) return 0; /* nowhere to go, don't do anything */
575 * Didn't set anything: this is bad.
578 RDEBUG2("Failed to do anything intelligent");
583 * Check the output length.
585 if (outlen < ((data_len * 2) + 1)) {
586 data_len = (outlen - 1) / 2;
592 for (i = 0; i < data_len; i++) {
593 sprintf(out + (2 * i), "%02x", data[i]);
595 out[data_len * 2] = '\0';
601 static const CONF_PARSER module_config[] = {
603 * Cache the password by default.
605 { "use_mppe", PW_TYPE_BOOLEAN,
606 offsetof(rlm_mschap_t,use_mppe), NULL, "yes" },
607 { "require_encryption", PW_TYPE_BOOLEAN,
608 offsetof(rlm_mschap_t,require_encryption), NULL, "no" },
609 { "require_strong", PW_TYPE_BOOLEAN,
610 offsetof(rlm_mschap_t,require_strong), NULL, "no" },
611 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
612 offsetof(rlm_mschap_t,with_ntdomain_hack), NULL, "no" },
613 { "passwd", PW_TYPE_STRING_PTR,
614 offsetof(rlm_mschap_t, passwd_file), NULL, NULL },
615 { "ntlm_auth", PW_TYPE_STRING_PTR,
616 offsetof(rlm_mschap_t, ntlm_auth), NULL, NULL },
618 { "use_open_directory", PW_TYPE_BOOLEAN,
619 offsetof(rlm_mschap_t,open_directory), NULL, "yes" },
622 { NULL, -1, 0, NULL, NULL } /* end the list */
626 * deinstantiate module, free all memory allocated during
627 * mschap_instantiate()
629 static int mschap_detach(void *instance){
630 #define inst ((rlm_mschap_t *)instance)
631 if (inst->xlat_name) {
632 xlat_unregister(inst->xlat_name, mschap_xlat);
633 free(inst->xlat_name);
641 * Create instance for our module. Allocate space for
642 * instance structure and read configuration parameters
644 static int mschap_instantiate(CONF_SECTION *conf, void **instance)
648 inst = *instance = rad_malloc(sizeof(*inst));
652 memset(inst, 0, sizeof(*inst));
654 if (cf_section_parse(conf, inst, module_config) < 0) {
660 * This module used to support SMB Password files, but it
661 * made it too complicated. If the user tries to
662 * configure an SMB Password file, then die, with an
665 if (inst->passwd_file) {
666 radlog(L_ERR, "rlm_mschap: SMB password file is no longer supported in this module. Use rlm_passwd module instead");
672 * Create the dynamic translation.
674 inst->xlat_name = cf_section_name2(conf);
675 if (!inst->xlat_name) inst->xlat_name = cf_section_name1(conf);
676 inst->xlat_name = strdup(inst->xlat_name);
677 xlat_register(inst->xlat_name, mschap_xlat, inst);
680 * For backwards compatibility
682 if (!dict_valbyname(PW_AUTH_TYPE, inst->xlat_name)) {
683 inst->auth_type = "MS-CHAP";
685 inst->auth_type = inst->xlat_name;
692 * add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
693 * attribute to reply packet
695 void mschap_add_reply(REQUEST *request, VALUE_PAIR** vp, unsigned char ident,
696 const char* name, const char* value, int len)
698 VALUE_PAIR *reply_attr;
699 reply_attr = pairmake(name, "", T_OP_EQ);
701 RDEBUG("Failed to create attribute %s: %s\n", name, fr_strerror());
705 reply_attr->vp_octets[0] = ident;
706 memcpy(reply_attr->vp_octets + 1, value, len);
707 reply_attr->length = len + 1;
708 pairadd(vp, reply_attr);
712 * Add MPPE attributes to the reply.
714 static void mppe_add_reply(REQUEST *request,
715 const char* name, const uint8_t * value, int len)
718 vp = radius_pairmake(request, &request->reply->vps, name, "", T_OP_EQ);
720 RDEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, fr_strerror());
724 memcpy(vp->vp_octets, value, len);
730 * Do the MS-CHAP stuff.
732 * This function is here so that all of the MS-CHAP related
733 * authentication is in one place, and we can perhaps later replace
734 * it with code to call winbindd, or something similar.
736 static int do_mschap(rlm_mschap_t *inst,
737 REQUEST *request, VALUE_PAIR *password,
738 uint8_t *challenge, uint8_t *response,
739 uint8_t *nthashhash, int do_ntlm_auth)
741 uint8_t calculated[24];
744 * Do normal authentication.
748 * No password: can't do authentication.
751 RDEBUG2("FAILED: No NT/LM-Password. Cannot perform authentication.");
755 smbdes_mschap(password->vp_strvalue, challenge, calculated);
756 if (memcmp(response, calculated, 24) != 0) {
761 * If the password exists, and is an NT-Password,
762 * then calculate the hash of the NT hash. Doing this
763 * here minimizes work for later.
765 if (password && (password->attribute == PW_NT_PASSWORD)) {
766 fr_md4_calc(nthashhash, password->vp_octets, 16);
768 memset(nthashhash, 0, 16);
770 } else { /* run ntlm_auth */
774 memset(nthashhash, 0, 16);
777 * Run the program, and expect that we get 16
779 result = radius_exec_program(inst->ntlm_auth, request,
781 buffer, sizeof(buffer),
784 RDEBUG2("External script failed.");
789 * Parse the answer as an nthashhash.
791 * ntlm_auth currently returns:
792 * NT_KEY: 000102030405060708090a0b0c0d0e0f
794 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
795 RDEBUG2("Invalid output from ntlm_auth: expecting NT_KEY");
800 * Check the length. It should be at least 32,
801 * with an LF at the end.
803 if (strlen(buffer + 8) < 32) {
804 RDEBUG2("Invalid output from ntlm_auth: NT_KEY has unexpected length");
809 * Update the NT hash hash, from the NT key.
811 if (fr_hex2bin(buffer + 8, nthashhash, 16) != 16) {
812 RDEBUG2("Invalid output from ntlm_auth: NT_KEY has non-hex values");
822 * Data for the hashes.
824 static const uint8_t SHSpad1[40] =
825 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
826 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
827 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
828 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
830 static const uint8_t SHSpad2[40] =
831 { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
832 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
833 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
834 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
836 static const uint8_t magic1[27] =
837 { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
838 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
839 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
841 static const uint8_t magic2[84] =
842 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
843 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
844 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
845 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
846 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
847 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
848 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
849 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
850 0x6b, 0x65, 0x79, 0x2e };
852 static const uint8_t magic3[84] =
853 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
854 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
855 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
856 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
857 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
858 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
859 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
860 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
861 0x6b, 0x65, 0x79, 0x2e };
864 static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response,
870 fr_SHA1Init(&Context);
871 fr_SHA1Update(&Context,nt_hashhash,16);
872 fr_SHA1Update(&Context,nt_response,24);
873 fr_SHA1Update(&Context,magic1,27);
874 fr_SHA1Final(digest,&Context);
876 memcpy(masterkey,digest,16);
880 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
881 int keylen,int issend)
895 fr_SHA1Init(&Context);
896 fr_SHA1Update(&Context,masterkey,16);
897 fr_SHA1Update(&Context,SHSpad1,40);
898 fr_SHA1Update(&Context,s,84);
899 fr_SHA1Update(&Context,SHSpad2,40);
900 fr_SHA1Final(digest,&Context);
902 memcpy(sesskey,digest,keylen);
906 static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response,
907 uint8_t *sendkey,uint8_t *recvkey)
909 uint8_t masterkey[16];
911 mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
913 mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
914 mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
918 * Generate MPPE keys.
920 static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response,
921 uint8_t *sendkey,uint8_t *recvkey)
926 mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
929 * dictionary.microsoft defines these attributes as
930 * 'encrypt=2'. The functions in src/lib/radius.c will
931 * take care of encrypting/decrypting them as appropriate,
932 * so that we don't have to.
934 memcpy (sendkey, enckey1, 16);
935 memcpy (recvkey, enckey2, 16);
940 * mschap_authorize() - authorize user if we can authenticate
941 * it later. Add Auth-Type attribute if present in module
942 * configuration (usually Auth-Type must be "MS-CHAP")
944 static int mschap_authorize(void * instance, REQUEST *request)
946 #define inst ((rlm_mschap_t *)instance)
947 VALUE_PAIR *challenge = NULL, *response = NULL;
949 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
951 return RLM_MODULE_NOOP;
954 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
956 response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE);
959 * Nothing we recognize. Don't do anything.
962 RDEBUG2("Found MS-CHAP-Challenge, but no MS-CHAP-Response.");
963 return RLM_MODULE_NOOP;
966 if (pairfind(request->config_items, PW_AUTH_TYPE)) {
967 RDEBUG2("Found existing Auth-Type. Not changing it.");
968 return RLM_MODULE_NOOP;
971 RDEBUG2("Found MS-CHAP attributes. Setting 'Auth-Type = %s'", inst->xlat_name);
974 * Set Auth-Type to MS-CHAP. The authentication code
975 * will take care of turning clear-text passwords into
978 if (!radius_pairmake(request, &request->config_items,
979 "Auth-Type", inst->auth_type, T_OP_EQ)) {
980 return RLM_MODULE_FAIL;
983 return RLM_MODULE_OK;
988 * mschap_authenticate() - authenticate user based on given
989 * attributes and configuration.
990 * We will try to find out password in configuration
991 * or in configured passwd file.
992 * If one is found we will check paraneters given by NAS.
994 * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
996 * PAP: PW_USER_PASSWORD or
997 * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
998 * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
999 * In case of password mismatch or locked account we MAY return
1000 * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
1001 * If MS-CHAP2 succeeds we MUST return
1002 * PW_MSCHAP2_SUCCESS
1004 static int mschap_authenticate(void * instance, REQUEST *request)
1006 #define inst ((rlm_mschap_t *)instance)
1007 VALUE_PAIR *challenge = NULL;
1008 VALUE_PAIR *response = NULL;
1009 VALUE_PAIR *password = NULL;
1010 VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
1011 VALUE_PAIR *username;
1012 uint8_t nthashhash[16];
1014 char *username_string;
1019 * If we have ntlm_auth configured, use it unless told
1022 do_ntlm_auth = inst->ntlm_auth;
1025 * If we have an ntlm_auth configuration, then we may
1026 * want to suppress it.
1029 VALUE_PAIR *vp = pairfind(request->config_items,
1030 PW_MS_CHAP_USE_NTLM_AUTH);
1031 if (vp) do_ntlm_auth = vp->vp_integer;
1035 * Find the SMB-Account-Ctrl attribute, or the
1036 * SMB-Account-Ctrl-Text attribute.
1038 smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL);
1040 password = pairfind(request->config_items,
1041 PW_SMB_ACCOUNT_CTRL_TEXT);
1043 smb_ctrl = radius_pairmake(request,
1044 &request->config_items,
1045 "SMB-Account-CTRL", "0",
1048 smb_ctrl->vp_integer = pdb_decode_acct_ctrl(password->vp_strvalue);
1054 * We're configured to do MS-CHAP authentication.
1055 * and account control information exists. Enforce it.
1059 * Password is not required.
1061 if ((smb_ctrl->vp_integer & ACB_PWNOTREQ) != 0) {
1062 RDEBUG2("SMB-Account-Ctrl says no password is required.");
1063 return RLM_MODULE_OK;
1068 * Decide how to get the passwords.
1070 password = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD);
1073 * We need an LM-Password.
1075 lm_password = pairfind(request->config_items, PW_LM_PASSWORD);
1080 if ((lm_password->length == 16) ||
1081 ((lm_password->length == 32) &&
1082 (fr_hex2bin(lm_password->vp_strvalue,
1083 lm_password->vp_octets, 16) == 16))) {
1084 RDEBUG2("Found LM-Password");
1085 lm_password->length = 16;
1088 radlog_request(L_ERR, 0, request, "Invalid LM-Password");
1092 } else if (!password) {
1093 if (!do_ntlm_auth) RDEBUG2("No Cleartext-Password configured. Cannot create LM-Password.");
1095 } else { /* there is a configured Cleartext-Password */
1096 lm_password = radius_pairmake(request, &request->config_items,
1097 "LM-Password", "", T_OP_EQ);
1099 radlog_request(L_ERR, 0, request, "No memory");
1101 smbdes_lmpwdhash(password->vp_strvalue,
1102 lm_password->vp_octets);
1103 lm_password->length = 16;
1108 * We need an NT-Password.
1110 nt_password = pairfind(request->config_items, PW_NT_PASSWORD);
1112 if ((nt_password->length == 16) ||
1113 ((nt_password->length == 32) &&
1114 (fr_hex2bin(nt_password->vp_strvalue,
1115 nt_password->vp_octets, 16) == 16))) {
1116 RDEBUG2("Found NT-Password");
1117 nt_password->length = 16;
1120 radlog_request(L_ERR, 0, request, "Invalid NT-Password");
1123 } else if (!password) {
1124 if (!do_ntlm_auth) RDEBUG2("No Cleartext-Password configured. Cannot create NT-Password.");
1126 } else { /* there is a configured Cleartext-Password */
1127 nt_password = radius_pairmake(request, &request->config_items,
1128 "NT-Password", "", T_OP_EQ);
1130 radlog_request(L_ERR, 0, request, "No memory");
1131 return RLM_MODULE_FAIL;
1133 ntpwdhash(nt_password->vp_octets,
1134 password->vp_strvalue);
1135 nt_password->length = 16;
1139 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
1141 RDEBUG2("No MS-CHAP-Challenge in the request");
1142 return RLM_MODULE_REJECT;
1146 * We also require an MS-CHAP-Response.
1148 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
1151 * MS-CHAP-Response, means MS-CHAPv1
1157 * MS-CHAPv1 challenges are 8 octets.
1159 if (challenge->length < 8) {
1160 radlog_request(L_AUTH, 0, request, "MS-CHAP-Challenge has the wrong format.");
1161 return RLM_MODULE_INVALID;
1165 * Responses are 50 octets.
1167 if (response->length < 50) {
1168 radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
1169 return RLM_MODULE_INVALID;
1173 * We are doing MS-CHAP. Calculate the MS-CHAP
1176 if (response->vp_octets[1] & 0x01) {
1177 RDEBUG2("Told to do MS-CHAPv1 with NT-Password");
1178 password = nt_password;
1181 RDEBUG2("Told to do MS-CHAPv1 with LM-Password");
1182 password = lm_password;
1187 * Do the MS-CHAP authentication.
1189 if (do_mschap(inst, request, password, challenge->vp_octets,
1190 response->vp_octets + offset, nthashhash,
1191 do_ntlm_auth) < 0) {
1192 RDEBUG2("MS-CHAP-Response is incorrect.");
1193 mschap_add_reply(request, &request->reply->vps,
1194 *response->vp_octets,
1195 "MS-CHAP-Error", "E=691 R=1", 9);
1196 return RLM_MODULE_REJECT;
1201 } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE)) != NULL) {
1202 uint8_t mschapv1_challenge[16];
1205 * MS-CHAPv2 challenges are 16 octets.
1207 if (challenge->length < 16) {
1208 radlog_request(L_AUTH, 0, request, "MS-CHAP-Challenge has the wrong format.");
1209 return RLM_MODULE_INVALID;
1213 * Responses are 50 octets.
1215 if (response->length < 50) {
1216 radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
1217 return RLM_MODULE_INVALID;
1221 * We also require a User-Name
1223 username = pairfind(request->packet->vps, PW_USER_NAME);
1225 radlog_request(L_AUTH, 0, request, "We require a User-Name for MS-CHAPv2");
1226 return RLM_MODULE_INVALID;
1231 * with_ntdomain_hack moved here
1233 if ((username_string = strchr(username->vp_strvalue, '\\')) != NULL) {
1234 if (inst->with_ntdomain_hack) {
1237 RDEBUG2(" NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1238 username_string = username->vp_strvalue;
1241 username_string = username->vp_strvalue;
1246 * No "known good" NT-Password attribute. Try to do
1247 * OpenDirectory authentication.
1249 if (!nt_password && inst->open_directory) {
1250 RDEBUG2("No NT-Password configured. Trying DirectoryService Authentication.");
1251 return od_mschap_auth(request, challenge, username);
1255 * The old "mschapv2" function has been moved to
1258 * MS-CHAPv2 takes some additional data to create an
1259 * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1261 challenge_hash(response->vp_octets + 2, /* peer challenge */
1262 challenge->vp_octets, /* our challenge */
1263 username_string, /* user name */
1264 mschapv1_challenge); /* resulting challenge */
1266 RDEBUG2("Told to do MS-CHAPv2 for %s with NT-Password",
1269 if (do_mschap(inst, request, nt_password, mschapv1_challenge,
1270 response->vp_octets + 26, nthashhash,
1271 do_ntlm_auth) < 0) {
1272 RDEBUG2("FAILED: MS-CHAP2-Response is incorrect");
1273 mschap_add_reply(request, &request->reply->vps,
1274 *response->vp_octets,
1275 "MS-CHAP-Error", "E=691 R=1", 9);
1276 return RLM_MODULE_REJECT;
1280 * Get the NT-hash-hash, if necessary
1285 auth_response(username_string, /* without the domain */
1286 nthashhash, /* nt-hash-hash */
1287 response->vp_octets + 26, /* peer response */
1288 response->vp_octets + 2, /* peer challenge */
1289 challenge->vp_octets, /* our challenge */
1290 msch2resp); /* calculated MPPE key */
1291 mschap_add_reply(request, &request->reply->vps, *response->vp_octets,
1292 "MS-CHAP2-Success", msch2resp, 42);
1295 } else { /* Neither CHAPv1 or CHAPv2 response: die */
1296 radlog_request(L_AUTH, 0, request, "No MS-CHAP response found");
1297 return RLM_MODULE_INVALID;
1301 * We have a CHAP response, but the account may be
1302 * disabled. Reject the user with the same error code
1303 * we use when their password is invalid.
1307 * Account is disabled.
1309 * They're found, but they don't exist, so we
1310 * return 'not found'.
1312 if (((smb_ctrl->vp_integer & ACB_DISABLED) != 0) ||
1313 ((smb_ctrl->vp_integer & ACB_NORMAL) == 0)) {
1314 RDEBUG2("SMB-Account-Ctrl says that the account is disabled, or is not a normal account.");
1315 mschap_add_reply(request, &request->reply->vps,
1316 *response->vp_octets,
1317 "MS-CHAP-Error", "E=691 R=1", 9);
1318 return RLM_MODULE_NOTFOUND;
1322 * User is locked out.
1324 if ((smb_ctrl->vp_integer & ACB_AUTOLOCK) != 0) {
1325 RDEBUG2("SMB-Account-Ctrl says that the account is locked out.");
1326 mschap_add_reply(request, &request->reply->vps,
1327 *response->vp_octets,
1328 "MS-CHAP-Error", "E=647 R=0", 9);
1329 return RLM_MODULE_USERLOCK;
1333 /* now create MPPE attributes */
1334 if (inst->use_mppe) {
1335 uint8_t mppe_sendkey[34];
1336 uint8_t mppe_recvkey[34];
1339 RDEBUG2("adding MS-CHAPv1 MPPE keys");
1340 memset(mppe_sendkey, 0, 32);
1342 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1346 * According to RFC 2548 we
1347 * should send NT hash. But in
1348 * practice it doesn't work.
1349 * Instead, we should send nthashhash
1351 * This is an error on RFC 2548.
1354 * do_mschap cares to zero nthashhash if NT hash
1357 memcpy(mppe_sendkey + 8,
1359 mppe_add_reply(request,
1360 "MS-CHAP-MPPE-Keys",
1362 } else if (chap == 2) {
1363 RDEBUG2("adding MS-CHAPv2 MPPE keys");
1364 mppe_chap2_gen_keys128(nthashhash,
1365 response->vp_octets + 26,
1366 mppe_sendkey, mppe_recvkey);
1368 mppe_add_reply(request,
1371 mppe_add_reply(request,
1376 radius_pairmake(request, &request->reply->vps,
1377 "MS-MPPE-Encryption-Policy",
1378 (inst->require_encryption)? "0x00000002":"0x00000001",
1380 radius_pairmake(request, &request->reply->vps,
1381 "MS-MPPE-Encryption-Types",
1382 (inst->require_strong)? "0x00000004":"0x00000006",
1384 } /* else we weren't asked to use MPPE */
1386 return RLM_MODULE_OK;
1390 module_t rlm_mschap = {
1393 RLM_TYPE_THREAD_SAFE, /* type */
1394 mschap_instantiate, /* instantiation */
1395 mschap_detach, /* detach */
1397 mschap_authenticate, /* authenticate */
1398 mschap_authorize, /* authorize */
1399 NULL, /* pre-accounting */
1400 NULL, /* accounting */
1401 NULL, /* checksimul */
1402 NULL, /* pre-proxy */
1403 NULL, /* post-proxy */
1404 NULL /* post-auth */