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,
302 VENDORPEC_MICROSOFT);
303 if (!chap_challenge) {
304 RDEBUG2("No MS-CHAP-Challenge in the request.");
309 * MS-CHAP-Challenges are 8 octets,
312 if (chap_challenge->length == 8) {
313 RDEBUG2(" mschap1: %02x",
314 chap_challenge->vp_octets[0]);
315 data = chap_challenge->vp_octets;
319 * MS-CHAP-Challenges are 16 octets,
322 } else if (chap_challenge->length == 16) {
323 char *username_string;
325 RDEBUG2(" mschap2: %02x", chap_challenge->vp_octets[0]);
326 response = pairfind(request->packet->vps,
328 VENDORPEC_MICROSOFT);
330 RDEBUG2("MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge.");
335 * Responses are 50 octets.
337 if (response->length < 50) {
338 radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
342 user_name = pairfind(request->packet->vps,
345 RDEBUG2("User-Name is required to calculateMS-CHAPv1 Challenge.");
350 * with_ntdomain_hack moved here, too.
352 if ((username_string = strchr(user_name->vp_strvalue, '\\')) != NULL) {
353 if (inst->with_ntdomain_hack) {
356 RDEBUG2("NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
357 username_string = user_name->vp_strvalue;
360 username_string = user_name->vp_strvalue;
364 * Get the MS-CHAPv1 challenge
365 * from the MS-CHAPv2 peer challenge,
366 * our challenge, and the user name.
368 challenge_hash(response->vp_octets + 2,
369 chap_challenge->vp_octets,
370 username_string, buffer);
374 RDEBUG2("Invalid MS-CHAP challenge length");
379 * Get the MS-CHAPv1 response, or the MS-CHAPv2
382 } else if (strncasecmp(fmt, "NT-Response", 11) == 0) {
383 response = pairfind(request->packet->vps,
384 PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT);
385 if (!response) response = pairfind(request->packet->vps,
387 VENDORPEC_MICROSOFT);
389 RDEBUG2("No MS-CHAP-Response or MS-CHAP2-Response was found in the request.");
394 * For MS-CHAPv1, the NT-Response exists only
395 * if the second octet says so.
397 if ((response->attribute == PW_MSCHAP_RESPONSE) &&
398 ((response->vp_octets[1] & 0x01) == 0)) {
399 RDEBUG2("No NT-Response in MS-CHAP-Response");
404 * MS-CHAP-Response and MS-CHAP2-Response have
405 * the NT-Response at the same offset, and are
408 data = response->vp_octets + 26;
412 * LM-Response is deprecated, and exists only
413 * in MS-CHAPv1, and not often there.
415 } else if (strncasecmp(fmt, "LM-Response", 11) == 0) {
416 response = pairfind(request->packet->vps,
417 PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT);
419 RDEBUG2("No MS-CHAP-Response was found in the request.");
424 * For MS-CHAPv1, the NT-Response exists only
425 * if the second octet says so.
427 if ((response->vp_octets[1] & 0x01) != 0) {
428 RDEBUG2("No LM-Response in MS-CHAP-Response");
431 data = response->vp_octets + 2;
435 * Pull the NT-Domain out of the User-Name, if it exists.
437 } else if (strncasecmp(fmt, "NT-Domain", 9) == 0) {
440 user_name = pairfind(request->packet->vps, PW_USER_NAME, 0);
442 RDEBUG2("No User-Name was found in the request.");
447 * First check to see if this is a host/ style User-Name
448 * (a la Kerberos host principal)
450 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
452 * If we're getting a User-Name formatted in this way,
453 * it's likely due to PEAP. The Windows Domain will be
454 * the first domain component following the hostname,
455 * or the machine name itself if only a hostname is supplied
457 p = strchr(user_name->vp_strvalue, '.');
459 RDEBUG2("setting NT-Domain to same as machine name");
460 strlcpy(out, user_name->vp_strvalue + 5, outlen);
462 p++; /* skip the period */
465 * use the same hack as below
466 * only if another period was found
469 strlcpy(out, p, outlen);
473 p = strchr(user_name->vp_strvalue, '\\');
475 RDEBUG2("No NT-Domain was found in the User-Name.");
480 * Hack. This is simpler than the alternatives.
483 strlcpy(out, user_name->vp_strvalue, outlen);
490 * Pull the User-Name out of the User-Name...
492 } else if (strncasecmp(fmt, "User-Name", 9) == 0) {
495 user_name = pairfind(request->packet->vps, PW_USER_NAME, 0);
497 RDEBUG2("No User-Name was found in the request.");
502 * First check to see if this is a host/ style User-Name
503 * (a la Kerberos host principal)
505 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
507 * If we're getting a User-Name formatted in this way,
508 * it's likely due to PEAP. When authenticating this against
509 * a Domain, Windows will expect the User-Name to be in the
510 * format of hostname$, the SAM version of the name, so we
511 * have to convert it to that here. We do so by stripping
512 * off the first 5 characters (host/), and copying everything
513 * from that point to the first period into a string and appending
516 p = strchr(user_name->vp_strvalue, '.');
518 * use the same hack as above
519 * only if a period was found
522 snprintf(out, outlen, "%s$", user_name->vp_strvalue + 5);
525 p = strchr(user_name->vp_strvalue, '\\');
527 p++; /* skip the backslash */
529 p = user_name->vp_strvalue; /* use the whole User-Name */
531 strlcpy(out, p, outlen);
537 * Return the NT-Hash of the passed string
539 } else if (strncasecmp(fmt, "NT-Hash ", 8) == 0) {
543 p = fmt + 8; /* 7 is the length of 'NT-Hash' */
544 if ((p == '\0') || (outlen <= 32))
547 while (isspace(*p)) p++;
549 if (!radius_xlat(buf2, sizeof(buf2),p,request,NULL)) {
550 RDEBUG("xlat failed");
555 ntpwdhash(buffer,buf2);
557 fr_bin2hex(buffer, out, 16);
559 RDEBUG("NT-Hash of %s = %s", buf2, out);
563 * Return the LM-Hash of the passed string
565 } else if (strncasecmp(fmt, "LM-Hash ", 8) == 0) {
569 p = fmt + 8; /* 7 is the length of 'LM-Hash' */
570 if ((p == '\0') || (outlen <= 32))
573 while (isspace(*p)) p++;
575 if (!radius_xlat(buf2, sizeof(buf2),p,request,NULL)) {
576 RDEBUG("xlat failed");
581 smbdes_lmpwdhash(buf2, buffer);
582 fr_bin2hex(buffer, out, 16);
584 RDEBUG("LM-Hash of %s = %s", buf2, out);
587 RDEBUG2("Unknown expansion string \"%s\"",
592 if (outlen == 0) return 0; /* nowhere to go, don't do anything */
595 * Didn't set anything: this is bad.
598 RDEBUG2("Failed to do anything intelligent");
603 * Check the output length.
605 if (outlen < ((data_len * 2) + 1)) {
606 data_len = (outlen - 1) / 2;
612 for (i = 0; i < data_len; i++) {
613 sprintf(out + (2 * i), "%02x", data[i]);
615 out[data_len * 2] = '\0';
621 static const CONF_PARSER module_config[] = {
623 * Cache the password by default.
625 { "use_mppe", PW_TYPE_BOOLEAN,
626 offsetof(rlm_mschap_t,use_mppe), NULL, "yes" },
627 { "require_encryption", PW_TYPE_BOOLEAN,
628 offsetof(rlm_mschap_t,require_encryption), NULL, "no" },
629 { "require_strong", PW_TYPE_BOOLEAN,
630 offsetof(rlm_mschap_t,require_strong), NULL, "no" },
631 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
632 offsetof(rlm_mschap_t,with_ntdomain_hack), NULL, "no" },
633 { "passwd", PW_TYPE_STRING_PTR,
634 offsetof(rlm_mschap_t, passwd_file), NULL, NULL },
635 { "ntlm_auth", PW_TYPE_STRING_PTR,
636 offsetof(rlm_mschap_t, ntlm_auth), NULL, NULL },
638 { "use_open_directory", PW_TYPE_BOOLEAN,
639 offsetof(rlm_mschap_t,open_directory), NULL, "yes" },
642 { NULL, -1, 0, NULL, NULL } /* end the list */
646 * deinstantiate module, free all memory allocated during
647 * mschap_instantiate()
649 static int mschap_detach(void *instance){
650 #define inst ((rlm_mschap_t *)instance)
651 if (inst->xlat_name) {
652 xlat_unregister(inst->xlat_name, mschap_xlat);
653 free(inst->xlat_name);
661 * Create instance for our module. Allocate space for
662 * instance structure and read configuration parameters
664 static int mschap_instantiate(CONF_SECTION *conf, void **instance)
668 inst = *instance = rad_malloc(sizeof(*inst));
672 memset(inst, 0, sizeof(*inst));
674 if (cf_section_parse(conf, inst, module_config) < 0) {
680 * This module used to support SMB Password files, but it
681 * made it too complicated. If the user tries to
682 * configure an SMB Password file, then die, with an
685 if (inst->passwd_file) {
686 radlog(L_ERR, "rlm_mschap: SMB password file is no longer supported in this module. Use rlm_passwd module instead");
692 * Create the dynamic translation.
694 inst->xlat_name = cf_section_name2(conf);
695 if (!inst->xlat_name) inst->xlat_name = cf_section_name1(conf);
696 inst->xlat_name = strdup(inst->xlat_name);
697 xlat_register(inst->xlat_name, mschap_xlat, inst);
700 * For backwards compatibility
702 if (!dict_valbyname(PW_AUTH_TYPE, 0, inst->xlat_name)) {
703 inst->auth_type = "MS-CHAP";
705 inst->auth_type = inst->xlat_name;
712 * add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
713 * attribute to reply packet
715 void mschap_add_reply(REQUEST *request, VALUE_PAIR** vp, unsigned char ident,
716 const char* name, const char* value, int len)
718 VALUE_PAIR *reply_attr;
719 reply_attr = pairmake(name, "", T_OP_EQ);
721 RDEBUG("Failed to create attribute %s: %s\n", name, fr_strerror());
725 reply_attr->vp_octets[0] = ident;
726 memcpy(reply_attr->vp_octets + 1, value, len);
727 reply_attr->length = len + 1;
728 pairadd(vp, reply_attr);
732 * Add MPPE attributes to the reply.
734 static void mppe_add_reply(REQUEST *request,
735 const char* name, const uint8_t * value, int len)
738 vp = radius_pairmake(request, &request->reply->vps, name, "", T_OP_EQ);
740 RDEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, fr_strerror());
744 memcpy(vp->vp_octets, value, len);
750 * Do the MS-CHAP stuff.
752 * This function is here so that all of the MS-CHAP related
753 * authentication is in one place, and we can perhaps later replace
754 * it with code to call winbindd, or something similar.
756 static int do_mschap(rlm_mschap_t *inst,
757 REQUEST *request, VALUE_PAIR *password,
758 uint8_t *challenge, uint8_t *response,
759 uint8_t *nthashhash, int do_ntlm_auth)
761 uint8_t calculated[24];
764 * Do normal authentication.
768 * No password: can't do authentication.
771 RDEBUG2("FAILED: No NT/LM-Password. Cannot perform authentication.");
775 smbdes_mschap(password->vp_strvalue, challenge, calculated);
776 if (memcmp(response, calculated, 24) != 0) {
781 * If the password exists, and is an NT-Password,
782 * then calculate the hash of the NT hash. Doing this
783 * here minimizes work for later.
785 if (password && (password->attribute == PW_NT_PASSWORD)) {
786 fr_md4_calc(nthashhash, password->vp_octets, 16);
788 memset(nthashhash, 0, 16);
790 } else { /* run ntlm_auth */
794 memset(nthashhash, 0, 16);
797 * Run the program, and expect that we get 16
799 result = radius_exec_program(inst->ntlm_auth, request,
801 buffer, sizeof(buffer),
804 RDEBUG2("External script failed.");
809 * Parse the answer as an nthashhash.
811 * ntlm_auth currently returns:
812 * NT_KEY: 000102030405060708090a0b0c0d0e0f
814 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
815 RDEBUG2("Invalid output from ntlm_auth: expecting NT_KEY");
820 * Check the length. It should be at least 32,
821 * with an LF at the end.
823 if (strlen(buffer + 8) < 32) {
824 RDEBUG2("Invalid output from ntlm_auth: NT_KEY has unexpected length");
829 * Update the NT hash hash, from the NT key.
831 if (fr_hex2bin(buffer + 8, nthashhash, 16) != 16) {
832 RDEBUG2("Invalid output from ntlm_auth: NT_KEY has non-hex values");
842 * Data for the hashes.
844 static const uint8_t SHSpad1[40] =
845 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
846 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
847 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
848 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
850 static const uint8_t SHSpad2[40] =
851 { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
852 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
853 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
854 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
856 static const uint8_t magic1[27] =
857 { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
858 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
859 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
861 static const uint8_t magic2[84] =
862 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
863 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
864 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
865 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
866 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
867 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
868 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
869 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
870 0x6b, 0x65, 0x79, 0x2e };
872 static const uint8_t magic3[84] =
873 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
874 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
875 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
876 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
877 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
878 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
879 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
880 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
881 0x6b, 0x65, 0x79, 0x2e };
884 static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response,
890 fr_SHA1Init(&Context);
891 fr_SHA1Update(&Context,nt_hashhash,16);
892 fr_SHA1Update(&Context,nt_response,24);
893 fr_SHA1Update(&Context,magic1,27);
894 fr_SHA1Final(digest,&Context);
896 memcpy(masterkey,digest,16);
900 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
901 int keylen,int issend)
915 fr_SHA1Init(&Context);
916 fr_SHA1Update(&Context,masterkey,16);
917 fr_SHA1Update(&Context,SHSpad1,40);
918 fr_SHA1Update(&Context,s,84);
919 fr_SHA1Update(&Context,SHSpad2,40);
920 fr_SHA1Final(digest,&Context);
922 memcpy(sesskey,digest,keylen);
926 static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response,
927 uint8_t *sendkey,uint8_t *recvkey)
929 uint8_t masterkey[16];
931 mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
933 mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
934 mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
938 * Generate MPPE keys.
940 static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response,
941 uint8_t *sendkey,uint8_t *recvkey)
946 mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
949 * dictionary.microsoft defines these attributes as
950 * 'encrypt=2'. The functions in src/lib/radius.c will
951 * take care of encrypting/decrypting them as appropriate,
952 * so that we don't have to.
954 memcpy (sendkey, enckey1, 16);
955 memcpy (recvkey, enckey2, 16);
960 * mschap_authorize() - authorize user if we can authenticate
961 * it later. Add Auth-Type attribute if present in module
962 * configuration (usually Auth-Type must be "MS-CHAP")
964 static int mschap_authorize(void * instance, REQUEST *request)
966 #define inst ((rlm_mschap_t *)instance)
967 VALUE_PAIR *challenge = NULL, *response = NULL;
969 challenge = pairfind(request->packet->vps,
971 VENDORPEC_MICROSOFT);
973 return RLM_MODULE_NOOP;
976 response = pairfind(request->packet->vps,
978 VENDORPEC_MICROSOFT);
980 response = pairfind(request->packet->vps,
982 VENDORPEC_MICROSOFT);
985 * Nothing we recognize. Don't do anything.
988 RDEBUG2("Found MS-CHAP-Challenge, but no MS-CHAP-Response.");
989 return RLM_MODULE_NOOP;
992 if (pairfind(request->config_items, PW_AUTH_TYPE, 0)) {
993 RDEBUG2("Found existing Auth-Type. Not changing it.");
994 return RLM_MODULE_NOOP;
997 RDEBUG2("Found MS-CHAP attributes. Setting 'Auth-Type = %s'", inst->xlat_name);
1000 * Set Auth-Type to MS-CHAP. The authentication code
1001 * will take care of turning clear-text passwords into
1004 if (!radius_pairmake(request, &request->config_items,
1005 "Auth-Type", inst->auth_type, T_OP_EQ)) {
1006 return RLM_MODULE_FAIL;
1009 return RLM_MODULE_OK;
1014 * mschap_authenticate() - authenticate user based on given
1015 * attributes and configuration.
1016 * We will try to find out password in configuration
1017 * or in configured passwd file.
1018 * If one is found we will check paraneters given by NAS.
1020 * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
1022 * PAP: PW_USER_PASSWORD or
1023 * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
1024 * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
1025 * In case of password mismatch or locked account we MAY return
1026 * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
1027 * If MS-CHAP2 succeeds we MUST return
1028 * PW_MSCHAP2_SUCCESS
1030 static int mschap_authenticate(void * instance, REQUEST *request)
1032 #define inst ((rlm_mschap_t *)instance)
1033 VALUE_PAIR *challenge = NULL;
1034 VALUE_PAIR *response = NULL;
1035 VALUE_PAIR *password = NULL;
1036 VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
1037 VALUE_PAIR *username;
1038 uint8_t nthashhash[16];
1040 char *username_string;
1045 * If we have ntlm_auth configured, use it unless told
1048 do_ntlm_auth = (inst->ntlm_auth != NULL);
1051 * If we have an ntlm_auth configuration, then we may
1052 * want to suppress it.
1055 VALUE_PAIR *vp = pairfind(request->config_items,
1056 PW_MS_CHAP_USE_NTLM_AUTH, 0);
1057 if (vp) do_ntlm_auth = vp->vp_integer;
1061 * Find the SMB-Account-Ctrl attribute, or the
1062 * SMB-Account-Ctrl-Text attribute.
1064 smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL, 0);
1066 password = pairfind(request->config_items,
1067 PW_SMB_ACCOUNT_CTRL_TEXT, 0);
1069 smb_ctrl = radius_pairmake(request,
1070 &request->config_items,
1071 "SMB-Account-CTRL", "0",
1074 smb_ctrl->vp_integer = pdb_decode_acct_ctrl(password->vp_strvalue);
1080 * We're configured to do MS-CHAP authentication.
1081 * and account control information exists. Enforce it.
1085 * Password is not required.
1087 if ((smb_ctrl->vp_integer & ACB_PWNOTREQ) != 0) {
1088 RDEBUG2("SMB-Account-Ctrl says no password is required.");
1089 return RLM_MODULE_OK;
1094 * Decide how to get the passwords.
1096 password = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD, 0);
1099 * We need an LM-Password.
1101 lm_password = pairfind(request->config_items, PW_LM_PASSWORD, 0);
1106 if ((lm_password->length == 16) ||
1107 ((lm_password->length == 32) &&
1108 (fr_hex2bin(lm_password->vp_strvalue,
1109 lm_password->vp_octets, 16) == 16))) {
1110 RDEBUG2("Found LM-Password");
1111 lm_password->length = 16;
1114 radlog_request(L_ERR, 0, request, "Invalid LM-Password");
1118 } else if (!password) {
1119 if (!do_ntlm_auth) RDEBUG2("No Cleartext-Password configured. Cannot create LM-Password.");
1121 } else { /* there is a configured Cleartext-Password */
1122 lm_password = radius_pairmake(request, &request->config_items,
1123 "LM-Password", "", T_OP_EQ);
1125 radlog_request(L_ERR, 0, request, "No memory");
1127 smbdes_lmpwdhash(password->vp_strvalue,
1128 lm_password->vp_octets);
1129 lm_password->length = 16;
1134 * We need an NT-Password.
1136 nt_password = pairfind(request->config_items, PW_NT_PASSWORD, 0);
1138 if ((nt_password->length == 16) ||
1139 ((nt_password->length == 32) &&
1140 (fr_hex2bin(nt_password->vp_strvalue,
1141 nt_password->vp_octets, 16) == 16))) {
1142 RDEBUG2("Found NT-Password");
1143 nt_password->length = 16;
1146 radlog_request(L_ERR, 0, request, "Invalid NT-Password");
1149 } else if (!password) {
1150 if (!do_ntlm_auth) RDEBUG2("No Cleartext-Password configured. Cannot create NT-Password.");
1152 } else { /* there is a configured Cleartext-Password */
1153 nt_password = radius_pairmake(request, &request->config_items,
1154 "NT-Password", "", T_OP_EQ);
1156 radlog_request(L_ERR, 0, request, "No memory");
1157 return RLM_MODULE_FAIL;
1159 ntpwdhash(nt_password->vp_octets,
1160 password->vp_strvalue);
1161 nt_password->length = 16;
1165 challenge = pairfind(request->packet->vps,
1166 PW_MSCHAP_CHALLENGE,
1167 VENDORPEC_MICROSOFT);
1169 RDEBUG2("No MS-CHAP-Challenge in the request");
1170 return RLM_MODULE_REJECT;
1174 * We also require an MS-CHAP-Response.
1176 response = pairfind(request->packet->vps,
1178 VENDORPEC_MICROSOFT);
1181 * MS-CHAP-Response, means MS-CHAPv1
1187 * MS-CHAPv1 challenges are 8 octets.
1189 if (challenge->length < 8) {
1190 radlog_request(L_AUTH, 0, request, "MS-CHAP-Challenge has the wrong format.");
1191 return RLM_MODULE_INVALID;
1195 * Responses are 50 octets.
1197 if (response->length < 50) {
1198 radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
1199 return RLM_MODULE_INVALID;
1203 * We are doing MS-CHAP. Calculate the MS-CHAP
1206 if (response->vp_octets[1] & 0x01) {
1207 RDEBUG2("Told to do MS-CHAPv1 with NT-Password");
1208 password = nt_password;
1211 RDEBUG2("Told to do MS-CHAPv1 with LM-Password");
1212 password = lm_password;
1217 * Do the MS-CHAP authentication.
1219 if (do_mschap(inst, request, password, challenge->vp_octets,
1220 response->vp_octets + offset, nthashhash,
1221 do_ntlm_auth) < 0) {
1222 RDEBUG2("MS-CHAP-Response is incorrect.");
1223 mschap_add_reply(request, &request->reply->vps,
1224 *response->vp_octets,
1225 "MS-CHAP-Error", "E=691 R=1", 9);
1226 return RLM_MODULE_REJECT;
1231 } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT)) != NULL) {
1232 uint8_t mschapv1_challenge[16];
1235 * MS-CHAPv2 challenges are 16 octets.
1237 if (challenge->length < 16) {
1238 radlog_request(L_AUTH, 0, request, "MS-CHAP-Challenge has the wrong format.");
1239 return RLM_MODULE_INVALID;
1243 * Responses are 50 octets.
1245 if (response->length < 50) {
1246 radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
1247 return RLM_MODULE_INVALID;
1251 * We also require a User-Name
1253 username = pairfind(request->packet->vps, PW_USER_NAME, 0);
1255 radlog_request(L_AUTH, 0, request, "We require a User-Name for MS-CHAPv2");
1256 return RLM_MODULE_INVALID;
1261 * with_ntdomain_hack moved here
1263 if ((username_string = strchr(username->vp_strvalue, '\\')) != NULL) {
1264 if (inst->with_ntdomain_hack) {
1267 RDEBUG2(" NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1268 username_string = username->vp_strvalue;
1271 username_string = username->vp_strvalue;
1276 * No "known good" NT-Password attribute. Try to do
1277 * OpenDirectory authentication.
1279 * If OD determines the user is an AD user it will return noop, which
1280 * indicates the auth process should continue directly to AD.
1281 * Otherwise OD will determine auth success/fail.
1283 if (!nt_password && inst->open_directory) {
1284 RDEBUG2("No NT-Password configured. Trying OpenDirectory Authentication.");
1285 int odStatus = od_mschap_auth(request, challenge, username);
1286 if (odStatus != RLM_MODULE_NOOP) {
1292 * The old "mschapv2" function has been moved to
1295 * MS-CHAPv2 takes some additional data to create an
1296 * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1298 challenge_hash(response->vp_octets + 2, /* peer challenge */
1299 challenge->vp_octets, /* our challenge */
1300 username_string, /* user name */
1301 mschapv1_challenge); /* resulting challenge */
1303 RDEBUG2("Told to do MS-CHAPv2 for %s with NT-Password",
1306 if (do_mschap(inst, request, nt_password, mschapv1_challenge,
1307 response->vp_octets + 26, nthashhash,
1308 do_ntlm_auth) < 0) {
1309 RDEBUG2("FAILED: MS-CHAP2-Response is incorrect");
1310 mschap_add_reply(request, &request->reply->vps,
1311 *response->vp_octets,
1312 "MS-CHAP-Error", "E=691 R=1", 9);
1313 return RLM_MODULE_REJECT;
1317 * Get the NT-hash-hash, if necessary
1322 auth_response(username_string, /* without the domain */
1323 nthashhash, /* nt-hash-hash */
1324 response->vp_octets + 26, /* peer response */
1325 response->vp_octets + 2, /* peer challenge */
1326 challenge->vp_octets, /* our challenge */
1327 msch2resp); /* calculated MPPE key */
1328 mschap_add_reply(request, &request->reply->vps, *response->vp_octets,
1329 "MS-CHAP2-Success", msch2resp, 42);
1332 } else { /* Neither CHAPv1 or CHAPv2 response: die */
1333 radlog_request(L_AUTH, 0, request, "No MS-CHAP response found");
1334 return RLM_MODULE_INVALID;
1338 * We have a CHAP response, but the account may be
1339 * disabled. Reject the user with the same error code
1340 * we use when their password is invalid.
1344 * Account is disabled.
1346 * They're found, but they don't exist, so we
1347 * return 'not found'.
1349 if (((smb_ctrl->vp_integer & ACB_DISABLED) != 0) ||
1350 ((smb_ctrl->vp_integer & ACB_NORMAL) == 0)) {
1351 RDEBUG2("SMB-Account-Ctrl says that the account is disabled, or is not a normal account.");
1352 mschap_add_reply(request, &request->reply->vps,
1353 *response->vp_octets,
1354 "MS-CHAP-Error", "E=691 R=1", 9);
1355 return RLM_MODULE_NOTFOUND;
1359 * User is locked out.
1361 if ((smb_ctrl->vp_integer & ACB_AUTOLOCK) != 0) {
1362 RDEBUG2("SMB-Account-Ctrl says that the account is locked out.");
1363 mschap_add_reply(request, &request->reply->vps,
1364 *response->vp_octets,
1365 "MS-CHAP-Error", "E=647 R=0", 9);
1366 return RLM_MODULE_USERLOCK;
1370 /* now create MPPE attributes */
1371 if (inst->use_mppe) {
1372 uint8_t mppe_sendkey[34];
1373 uint8_t mppe_recvkey[34];
1376 RDEBUG2("adding MS-CHAPv1 MPPE keys");
1377 memset(mppe_sendkey, 0, 32);
1379 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1383 * According to RFC 2548 we
1384 * should send NT hash. But in
1385 * practice it doesn't work.
1386 * Instead, we should send nthashhash
1388 * This is an error on RFC 2548.
1391 * do_mschap cares to zero nthashhash if NT hash
1394 memcpy(mppe_sendkey + 8,
1396 mppe_add_reply(request,
1397 "MS-CHAP-MPPE-Keys",
1399 } else if (chap == 2) {
1400 RDEBUG2("adding MS-CHAPv2 MPPE keys");
1401 mppe_chap2_gen_keys128(nthashhash,
1402 response->vp_octets + 26,
1403 mppe_sendkey, mppe_recvkey);
1405 mppe_add_reply(request,
1408 mppe_add_reply(request,
1413 radius_pairmake(request, &request->reply->vps,
1414 "MS-MPPE-Encryption-Policy",
1415 (inst->require_encryption)? "0x00000002":"0x00000001",
1417 radius_pairmake(request, &request->reply->vps,
1418 "MS-MPPE-Encryption-Types",
1419 (inst->require_strong)? "0x00000004":"0x00000006",
1421 } /* else we weren't asked to use MPPE */
1423 return RLM_MODULE_OK;
1427 module_t rlm_mschap = {
1430 RLM_TYPE_THREAD_SAFE, /* type */
1431 mschap_instantiate, /* instantiation */
1432 mschap_detach, /* detach */
1434 mschap_authenticate, /* authenticate */
1435 mschap_authorize, /* authorize */
1436 NULL, /* pre-accounting */
1437 NULL, /* accounting */
1438 NULL, /* checksimul */
1439 NULL, /* pre-proxy */
1440 NULL, /* post-proxy */
1441 NULL /* post-auth */