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, 0);
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, 0);
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) {
540 p = fmt + 8; /* 7 is the length of 'NT-Hash' */
541 if ((p == '\0') || (outlen <= 32))
544 while (isspace(*p)) p++;
546 if (!radius_xlat(buf2, sizeof(buf2),p,request,NULL)) {
547 RDEBUG("xlat failed");
552 ntpwdhash(buffer,buf2);
554 fr_bin2hex(buffer, out, 16);
556 RDEBUG("NT-Hash of %s = %s", buf2, out);
560 * Return the LM-Hash of the passed string
562 } else if (strncasecmp(fmt, "LM-Hash ", 8) == 0) {
566 p = fmt + 8; /* 7 is the length of 'LM-Hash' */
567 if ((p == '\0') || (outlen <= 32))
570 while (isspace(*p)) p++;
572 if (!radius_xlat(buf2, sizeof(buf2),p,request,NULL)) {
573 RDEBUG("xlat failed");
578 smbdes_lmpwdhash(buf2, buffer);
579 fr_bin2hex(buffer, out, 16);
581 RDEBUG("LM-Hash of %s = %s", buf2, out);
584 RDEBUG2("Unknown expansion string \"%s\"",
589 if (outlen == 0) return 0; /* nowhere to go, don't do anything */
592 * Didn't set anything: this is bad.
595 RDEBUG2("Failed to do anything intelligent");
600 * Check the output length.
602 if (outlen < ((data_len * 2) + 1)) {
603 data_len = (outlen - 1) / 2;
609 for (i = 0; i < data_len; i++) {
610 sprintf(out + (2 * i), "%02x", data[i]);
612 out[data_len * 2] = '\0';
618 static const CONF_PARSER module_config[] = {
620 * Cache the password by default.
622 { "use_mppe", PW_TYPE_BOOLEAN,
623 offsetof(rlm_mschap_t,use_mppe), NULL, "yes" },
624 { "require_encryption", PW_TYPE_BOOLEAN,
625 offsetof(rlm_mschap_t,require_encryption), NULL, "no" },
626 { "require_strong", PW_TYPE_BOOLEAN,
627 offsetof(rlm_mschap_t,require_strong), NULL, "no" },
628 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
629 offsetof(rlm_mschap_t,with_ntdomain_hack), NULL, "no" },
630 { "passwd", PW_TYPE_STRING_PTR,
631 offsetof(rlm_mschap_t, passwd_file), NULL, NULL },
632 { "ntlm_auth", PW_TYPE_STRING_PTR,
633 offsetof(rlm_mschap_t, ntlm_auth), NULL, NULL },
635 { "use_open_directory", PW_TYPE_BOOLEAN,
636 offsetof(rlm_mschap_t,open_directory), NULL, "yes" },
639 { NULL, -1, 0, NULL, NULL } /* end the list */
643 * deinstantiate module, free all memory allocated during
644 * mschap_instantiate()
646 static int mschap_detach(void *instance){
647 #define inst ((rlm_mschap_t *)instance)
648 if (inst->xlat_name) {
649 xlat_unregister(inst->xlat_name, mschap_xlat);
650 free(inst->xlat_name);
658 * Create instance for our module. Allocate space for
659 * instance structure and read configuration parameters
661 static int mschap_instantiate(CONF_SECTION *conf, void **instance)
665 inst = *instance = rad_malloc(sizeof(*inst));
669 memset(inst, 0, sizeof(*inst));
671 if (cf_section_parse(conf, inst, module_config) < 0) {
677 * This module used to support SMB Password files, but it
678 * made it too complicated. If the user tries to
679 * configure an SMB Password file, then die, with an
682 if (inst->passwd_file) {
683 radlog(L_ERR, "rlm_mschap: SMB password file is no longer supported in this module. Use rlm_passwd module instead");
689 * Create the dynamic translation.
691 inst->xlat_name = cf_section_name2(conf);
692 if (!inst->xlat_name) inst->xlat_name = cf_section_name1(conf);
693 inst->xlat_name = strdup(inst->xlat_name);
694 xlat_register(inst->xlat_name, mschap_xlat, inst);
697 * For backwards compatibility
699 if (!dict_valbyname(PW_AUTH_TYPE, inst->xlat_name)) {
700 inst->auth_type = "MS-CHAP";
702 inst->auth_type = inst->xlat_name;
709 * add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
710 * attribute to reply packet
712 void mschap_add_reply(REQUEST *request, VALUE_PAIR** vp, unsigned char ident,
713 const char* name, const char* value, int len)
715 VALUE_PAIR *reply_attr;
716 reply_attr = pairmake(name, "", T_OP_EQ);
718 RDEBUG("Failed to create attribute %s: %s\n", name, fr_strerror());
722 reply_attr->vp_octets[0] = ident;
723 memcpy(reply_attr->vp_octets + 1, value, len);
724 reply_attr->length = len + 1;
725 pairadd(vp, reply_attr);
729 * Add MPPE attributes to the reply.
731 static void mppe_add_reply(REQUEST *request,
732 const char* name, const uint8_t * value, int len)
735 vp = radius_pairmake(request, &request->reply->vps, name, "", T_OP_EQ);
737 RDEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, fr_strerror());
741 memcpy(vp->vp_octets, value, len);
747 * Do the MS-CHAP stuff.
749 * This function is here so that all of the MS-CHAP related
750 * authentication is in one place, and we can perhaps later replace
751 * it with code to call winbindd, or something similar.
753 static int do_mschap(rlm_mschap_t *inst,
754 REQUEST *request, VALUE_PAIR *password,
755 uint8_t *challenge, uint8_t *response,
756 uint8_t *nthashhash, int do_ntlm_auth)
758 uint8_t calculated[24];
761 * Do normal authentication.
765 * No password: can't do authentication.
768 RDEBUG2("FAILED: No NT/LM-Password. Cannot perform authentication.");
772 smbdes_mschap(password->vp_strvalue, challenge, calculated);
773 if (memcmp(response, calculated, 24) != 0) {
778 * If the password exists, and is an NT-Password,
779 * then calculate the hash of the NT hash. Doing this
780 * here minimizes work for later.
782 if (password && (password->attribute == PW_NT_PASSWORD)) {
783 fr_md4_calc(nthashhash, password->vp_octets, 16);
785 memset(nthashhash, 0, 16);
787 } else { /* run ntlm_auth */
791 memset(nthashhash, 0, 16);
794 * Run the program, and expect that we get 16
796 result = radius_exec_program(inst->ntlm_auth, request,
798 buffer, sizeof(buffer),
801 RDEBUG2("External script failed.");
806 * Parse the answer as an nthashhash.
808 * ntlm_auth currently returns:
809 * NT_KEY: 000102030405060708090a0b0c0d0e0f
811 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
812 RDEBUG2("Invalid output from ntlm_auth: expecting NT_KEY");
817 * Check the length. It should be at least 32,
818 * with an LF at the end.
820 if (strlen(buffer + 8) < 32) {
821 RDEBUG2("Invalid output from ntlm_auth: NT_KEY has unexpected length");
826 * Update the NT hash hash, from the NT key.
828 if (fr_hex2bin(buffer + 8, nthashhash, 16) != 16) {
829 RDEBUG2("Invalid output from ntlm_auth: NT_KEY has non-hex values");
839 * Data for the hashes.
841 static const uint8_t SHSpad1[40] =
842 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
843 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
844 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
845 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
847 static const uint8_t SHSpad2[40] =
848 { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
849 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
850 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
851 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
853 static const uint8_t magic1[27] =
854 { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
855 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
856 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
858 static const uint8_t magic2[84] =
859 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
860 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
861 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
862 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
863 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
864 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
865 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
866 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
867 0x6b, 0x65, 0x79, 0x2e };
869 static const uint8_t magic3[84] =
870 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
871 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
872 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
873 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
874 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
875 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
876 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
877 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
878 0x6b, 0x65, 0x79, 0x2e };
881 static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response,
887 fr_SHA1Init(&Context);
888 fr_SHA1Update(&Context,nt_hashhash,16);
889 fr_SHA1Update(&Context,nt_response,24);
890 fr_SHA1Update(&Context,magic1,27);
891 fr_SHA1Final(digest,&Context);
893 memcpy(masterkey,digest,16);
897 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
898 int keylen,int issend)
912 fr_SHA1Init(&Context);
913 fr_SHA1Update(&Context,masterkey,16);
914 fr_SHA1Update(&Context,SHSpad1,40);
915 fr_SHA1Update(&Context,s,84);
916 fr_SHA1Update(&Context,SHSpad2,40);
917 fr_SHA1Final(digest,&Context);
919 memcpy(sesskey,digest,keylen);
923 static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response,
924 uint8_t *sendkey,uint8_t *recvkey)
926 uint8_t masterkey[16];
928 mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
930 mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
931 mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
935 * Generate MPPE keys.
937 static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response,
938 uint8_t *sendkey,uint8_t *recvkey)
943 mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
946 * dictionary.microsoft defines these attributes as
947 * 'encrypt=2'. The functions in src/lib/radius.c will
948 * take care of encrypting/decrypting them as appropriate,
949 * so that we don't have to.
951 memcpy (sendkey, enckey1, 16);
952 memcpy (recvkey, enckey2, 16);
957 * mschap_authorize() - authorize user if we can authenticate
958 * it later. Add Auth-Type attribute if present in module
959 * configuration (usually Auth-Type must be "MS-CHAP")
961 static int mschap_authorize(void * instance, REQUEST *request)
963 #define inst ((rlm_mschap_t *)instance)
964 VALUE_PAIR *challenge = NULL, *response = NULL;
966 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE, 0);
968 return RLM_MODULE_NOOP;
971 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE, 0);
973 response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, 0);
976 * Nothing we recognize. Don't do anything.
979 RDEBUG2("Found MS-CHAP-Challenge, but no MS-CHAP-Response.");
980 return RLM_MODULE_NOOP;
983 if (pairfind(request->config_items, PW_AUTH_TYPE, 0)) {
984 RDEBUG2("Found existing Auth-Type. Not changing it.");
985 return RLM_MODULE_NOOP;
988 RDEBUG2("Found MS-CHAP attributes. Setting 'Auth-Type = %s'", inst->xlat_name);
991 * Set Auth-Type to MS-CHAP. The authentication code
992 * will take care of turning clear-text passwords into
995 if (!radius_pairmake(request, &request->config_items,
996 "Auth-Type", inst->auth_type, T_OP_EQ)) {
997 return RLM_MODULE_FAIL;
1000 return RLM_MODULE_OK;
1005 * mschap_authenticate() - authenticate user based on given
1006 * attributes and configuration.
1007 * We will try to find out password in configuration
1008 * or in configured passwd file.
1009 * If one is found we will check paraneters given by NAS.
1011 * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
1013 * PAP: PW_USER_PASSWORD or
1014 * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
1015 * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
1016 * In case of password mismatch or locked account we MAY return
1017 * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
1018 * If MS-CHAP2 succeeds we MUST return
1019 * PW_MSCHAP2_SUCCESS
1021 static int mschap_authenticate(void * instance, REQUEST *request)
1023 #define inst ((rlm_mschap_t *)instance)
1024 VALUE_PAIR *challenge = NULL;
1025 VALUE_PAIR *response = NULL;
1026 VALUE_PAIR *password = NULL;
1027 VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
1028 VALUE_PAIR *username;
1029 uint8_t nthashhash[16];
1031 char *username_string;
1036 * If we have ntlm_auth configured, use it unless told
1039 do_ntlm_auth = (inst->ntlm_auth != NULL);
1042 * If we have an ntlm_auth configuration, then we may
1043 * want to suppress it.
1046 VALUE_PAIR *vp = pairfind(request->config_items,
1047 PW_MS_CHAP_USE_NTLM_AUTH);
1048 if (vp) do_ntlm_auth = vp->vp_integer;
1052 * Find the SMB-Account-Ctrl attribute, or the
1053 * SMB-Account-Ctrl-Text attribute.
1055 smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL, 0);
1057 password = pairfind(request->config_items,
1058 PW_SMB_ACCOUNT_CTRL_TEXT);
1060 smb_ctrl = radius_pairmake(request,
1061 &request->config_items,
1062 "SMB-Account-CTRL", "0",
1065 smb_ctrl->vp_integer = pdb_decode_acct_ctrl(password->vp_strvalue);
1071 * We're configured to do MS-CHAP authentication.
1072 * and account control information exists. Enforce it.
1076 * Password is not required.
1078 if ((smb_ctrl->vp_integer & ACB_PWNOTREQ) != 0) {
1079 RDEBUG2("SMB-Account-Ctrl says no password is required.");
1080 return RLM_MODULE_OK;
1085 * Decide how to get the passwords.
1087 password = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD, 0);
1090 * We need an LM-Password.
1092 lm_password = pairfind(request->config_items, PW_LM_PASSWORD, 0);
1097 if ((lm_password->length == 16) ||
1098 ((lm_password->length == 32) &&
1099 (fr_hex2bin(lm_password->vp_strvalue,
1100 lm_password->vp_octets, 16) == 16))) {
1101 RDEBUG2("Found LM-Password");
1102 lm_password->length = 16;
1105 radlog_request(L_ERR, 0, request, "Invalid LM-Password");
1109 } else if (!password) {
1110 if (!do_ntlm_auth) RDEBUG2("No Cleartext-Password configured. Cannot create LM-Password.");
1112 } else { /* there is a configured Cleartext-Password */
1113 lm_password = radius_pairmake(request, &request->config_items,
1114 "LM-Password", "", T_OP_EQ);
1116 radlog_request(L_ERR, 0, request, "No memory");
1118 smbdes_lmpwdhash(password->vp_strvalue,
1119 lm_password->vp_octets);
1120 lm_password->length = 16;
1125 * We need an NT-Password.
1127 nt_password = pairfind(request->config_items, PW_NT_PASSWORD, 0);
1129 if ((nt_password->length == 16) ||
1130 ((nt_password->length == 32) &&
1131 (fr_hex2bin(nt_password->vp_strvalue,
1132 nt_password->vp_octets, 16) == 16))) {
1133 RDEBUG2("Found NT-Password");
1134 nt_password->length = 16;
1137 radlog_request(L_ERR, 0, request, "Invalid NT-Password");
1140 } else if (!password) {
1141 if (!do_ntlm_auth) RDEBUG2("No Cleartext-Password configured. Cannot create NT-Password.");
1143 } else { /* there is a configured Cleartext-Password */
1144 nt_password = radius_pairmake(request, &request->config_items,
1145 "NT-Password", "", T_OP_EQ);
1147 radlog_request(L_ERR, 0, request, "No memory");
1148 return RLM_MODULE_FAIL;
1150 ntpwdhash(nt_password->vp_octets,
1151 password->vp_strvalue);
1152 nt_password->length = 16;
1156 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE, 0);
1158 RDEBUG2("No MS-CHAP-Challenge in the request");
1159 return RLM_MODULE_REJECT;
1163 * We also require an MS-CHAP-Response.
1165 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE, 0);
1168 * MS-CHAP-Response, means MS-CHAPv1
1174 * MS-CHAPv1 challenges are 8 octets.
1176 if (challenge->length < 8) {
1177 radlog_request(L_AUTH, 0, request, "MS-CHAP-Challenge has the wrong format.");
1178 return RLM_MODULE_INVALID;
1182 * Responses are 50 octets.
1184 if (response->length < 50) {
1185 radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
1186 return RLM_MODULE_INVALID;
1190 * We are doing MS-CHAP. Calculate the MS-CHAP
1193 if (response->vp_octets[1] & 0x01) {
1194 RDEBUG2("Told to do MS-CHAPv1 with NT-Password");
1195 password = nt_password;
1198 RDEBUG2("Told to do MS-CHAPv1 with LM-Password");
1199 password = lm_password;
1204 * Do the MS-CHAP authentication.
1206 if (do_mschap(inst, request, password, challenge->vp_octets,
1207 response->vp_octets + offset, nthashhash,
1208 do_ntlm_auth) < 0) {
1209 RDEBUG2("MS-CHAP-Response is incorrect.");
1210 mschap_add_reply(request, &request->reply->vps,
1211 *response->vp_octets,
1212 "MS-CHAP-Error", "E=691 R=1", 9);
1213 return RLM_MODULE_REJECT;
1218 } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, 0)) != NULL) {
1219 uint8_t mschapv1_challenge[16];
1222 * MS-CHAPv2 challenges are 16 octets.
1224 if (challenge->length < 16) {
1225 radlog_request(L_AUTH, 0, request, "MS-CHAP-Challenge has the wrong format.");
1226 return RLM_MODULE_INVALID;
1230 * Responses are 50 octets.
1232 if (response->length < 50) {
1233 radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
1234 return RLM_MODULE_INVALID;
1238 * We also require a User-Name
1240 username = pairfind(request->packet->vps, PW_USER_NAME, 0);
1242 radlog_request(L_AUTH, 0, request, "We require a User-Name for MS-CHAPv2");
1243 return RLM_MODULE_INVALID;
1248 * with_ntdomain_hack moved here
1250 if ((username_string = strchr(username->vp_strvalue, '\\')) != NULL) {
1251 if (inst->with_ntdomain_hack) {
1254 RDEBUG2(" NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1255 username_string = username->vp_strvalue;
1258 username_string = username->vp_strvalue;
1263 * No "known good" NT-Password attribute. Try to do
1264 * OpenDirectory authentication.
1266 * If OD determines the user is an AD user it will return noop, which
1267 * indicates the auth process should continue directly to AD.
1268 * Otherwise OD will determine auth success/fail.
1270 if (!nt_password && inst->open_directory) {
1271 RDEBUG2("No NT-Password configured. Trying OpenDirectory Authentication.");
1272 int odStatus = od_mschap_auth(request, challenge, username);
1273 if (odStatus != RLM_MODULE_NOOP) {
1279 * The old "mschapv2" function has been moved to
1282 * MS-CHAPv2 takes some additional data to create an
1283 * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1285 challenge_hash(response->vp_octets + 2, /* peer challenge */
1286 challenge->vp_octets, /* our challenge */
1287 username_string, /* user name */
1288 mschapv1_challenge); /* resulting challenge */
1290 RDEBUG2("Told to do MS-CHAPv2 for %s with NT-Password",
1293 if (do_mschap(inst, request, nt_password, mschapv1_challenge,
1294 response->vp_octets + 26, nthashhash,
1295 do_ntlm_auth) < 0) {
1296 RDEBUG2("FAILED: MS-CHAP2-Response is incorrect");
1297 mschap_add_reply(request, &request->reply->vps,
1298 *response->vp_octets,
1299 "MS-CHAP-Error", "E=691 R=1", 9);
1300 return RLM_MODULE_REJECT;
1304 * Get the NT-hash-hash, if necessary
1309 auth_response(username_string, /* without the domain */
1310 nthashhash, /* nt-hash-hash */
1311 response->vp_octets + 26, /* peer response */
1312 response->vp_octets + 2, /* peer challenge */
1313 challenge->vp_octets, /* our challenge */
1314 msch2resp); /* calculated MPPE key */
1315 mschap_add_reply(request, &request->reply->vps, *response->vp_octets,
1316 "MS-CHAP2-Success", msch2resp, 42);
1319 } else { /* Neither CHAPv1 or CHAPv2 response: die */
1320 radlog_request(L_AUTH, 0, request, "No MS-CHAP response found");
1321 return RLM_MODULE_INVALID;
1325 * We have a CHAP response, but the account may be
1326 * disabled. Reject the user with the same error code
1327 * we use when their password is invalid.
1331 * Account is disabled.
1333 * They're found, but they don't exist, so we
1334 * return 'not found'.
1336 if (((smb_ctrl->vp_integer & ACB_DISABLED) != 0) ||
1337 ((smb_ctrl->vp_integer & ACB_NORMAL) == 0)) {
1338 RDEBUG2("SMB-Account-Ctrl says that the account is disabled, or is not a normal account.");
1339 mschap_add_reply(request, &request->reply->vps,
1340 *response->vp_octets,
1341 "MS-CHAP-Error", "E=691 R=1", 9);
1342 return RLM_MODULE_NOTFOUND;
1346 * User is locked out.
1348 if ((smb_ctrl->vp_integer & ACB_AUTOLOCK) != 0) {
1349 RDEBUG2("SMB-Account-Ctrl says that the account is locked out.");
1350 mschap_add_reply(request, &request->reply->vps,
1351 *response->vp_octets,
1352 "MS-CHAP-Error", "E=647 R=0", 9);
1353 return RLM_MODULE_USERLOCK;
1357 /* now create MPPE attributes */
1358 if (inst->use_mppe) {
1359 uint8_t mppe_sendkey[34];
1360 uint8_t mppe_recvkey[34];
1363 RDEBUG2("adding MS-CHAPv1 MPPE keys");
1364 memset(mppe_sendkey, 0, 32);
1366 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1370 * According to RFC 2548 we
1371 * should send NT hash. But in
1372 * practice it doesn't work.
1373 * Instead, we should send nthashhash
1375 * This is an error on RFC 2548.
1378 * do_mschap cares to zero nthashhash if NT hash
1381 memcpy(mppe_sendkey + 8,
1383 mppe_add_reply(request,
1384 "MS-CHAP-MPPE-Keys",
1386 } else if (chap == 2) {
1387 RDEBUG2("adding MS-CHAPv2 MPPE keys");
1388 mppe_chap2_gen_keys128(nthashhash,
1389 response->vp_octets + 26,
1390 mppe_sendkey, mppe_recvkey);
1392 mppe_add_reply(request,
1395 mppe_add_reply(request,
1400 radius_pairmake(request, &request->reply->vps,
1401 "MS-MPPE-Encryption-Policy",
1402 (inst->require_encryption)? "0x00000002":"0x00000001",
1404 radius_pairmake(request, &request->reply->vps,
1405 "MS-MPPE-Encryption-Types",
1406 (inst->require_strong)? "0x00000004":"0x00000006",
1408 } /* else we weren't asked to use MPPE */
1410 return RLM_MODULE_OK;
1414 module_t rlm_mschap = {
1417 RLM_TYPE_THREAD_SAFE, /* type */
1418 mschap_instantiate, /* instantiation */
1419 mschap_detach, /* detach */
1421 mschap_authenticate, /* authenticate */
1422 mschap_authorize, /* authorize */
1423 NULL, /* pre-accounting */
1424 NULL, /* accounting */
1425 NULL, /* checksimul */
1426 NULL, /* pre-proxy */
1427 NULL, /* post-proxy */
1428 NULL /* post-auth */