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>
58 extern int od_mschap_auth(REQUEST *request, VALUE_PAIR *challenge, VALUE_PAIR * usernamepair);
61 /* Allowable account control bits */
62 #define ACB_DISABLED 0x0001 /* 1 = User account disabled */
63 #define ACB_HOMDIRREQ 0x0002 /* 1 = Home directory required */
64 #define ACB_PWNOTREQ 0x0004 /* 1 = User password not required */
65 #define ACB_TEMPDUP 0x0008 /* 1 = Temporary duplicate account */
66 #define ACB_NORMAL 0x0010 /* 1 = Normal user account */
67 #define ACB_MNS 0x0020 /* 1 = MNS logon user account */
68 #define ACB_DOMTRUST 0x0040 /* 1 = Interdomain trust account */
69 #define ACB_WSTRUST 0x0080 /* 1 = Workstation trust account */
70 #define ACB_SVRTRUST 0x0100 /* 1 = Server trust account */
71 #define ACB_PWNOEXP 0x0200 /* 1 = User password does not expire */
72 #define ACB_AUTOLOCK 0x0400 /* 1 = Account auto locked */
74 static int pdb_decode_acct_ctrl(const char *p)
80 * Check if the account type bits have been encoded after the
81 * NT password (in the form [NDHTUWSLXI]).
84 if (*p != '[') return 0;
86 for (p++; *p && !finished; p++) {
88 case 'N': /* 'N'o password. */
89 acct_ctrl |= ACB_PWNOTREQ;
92 case 'D': /* 'D'isabled. */
93 acct_ctrl |= ACB_DISABLED ;
96 case 'H': /* 'H'omedir required. */
97 acct_ctrl |= ACB_HOMDIRREQ;
100 case 'T': /* 'T'emp account. */
101 acct_ctrl |= ACB_TEMPDUP;
104 case 'U': /* 'U'ser account (normal). */
105 acct_ctrl |= ACB_NORMAL;
108 case 'M': /* 'M'NS logon user account. What is this? */
109 acct_ctrl |= ACB_MNS;
112 case 'W': /* 'W'orkstation account. */
113 acct_ctrl |= ACB_WSTRUST;
116 case 'S': /* 'S'erver account. */
117 acct_ctrl |= ACB_SVRTRUST;
120 case 'L': /* 'L'ocked account. */
121 acct_ctrl |= ACB_AUTOLOCK;
124 case 'X': /* No 'X'piry on password */
125 acct_ctrl |= ACB_PWNOEXP;
128 case 'I': /* 'I'nterdomain trust account. */
129 acct_ctrl |= ACB_DOMTRUST;
132 case ' ': /* ignore spaces */
150 * ntpwdhash converts Unicode password to 16-byte NT hash
153 static void ntpwdhash (unsigned char *szHash, const char *szPassword)
155 char szUnicodePass[513];
160 * NT passwords are unicode. Convert plain text password
161 * to unicode by inserting a zero every other byte
163 nPasswordLen = strlen(szPassword);
164 for (i = 0; i < nPasswordLen; i++) {
165 szUnicodePass[i << 1] = szPassword[i];
166 szUnicodePass[(i << 1) + 1] = 0;
169 /* Encrypt Unicode password to a 16-byte MD4 hash */
170 md4_calc(szHash, szUnicodePass, (nPasswordLen<<1) );
175 * challenge_hash() is used by mschap2() and auth_response()
176 * implements RFC2759 ChallengeHash()
177 * generates 64 bit challenge
179 static void challenge_hash( const uint8_t *peer_challenge,
180 const uint8_t *auth_challenge,
181 const char *user_name, uint8_t *challenge )
187 SHA1Update(&Context, peer_challenge, 16);
188 SHA1Update(&Context, auth_challenge, 16);
189 SHA1Update(&Context, user_name, strlen(user_name));
190 SHA1Final(hash, &Context);
191 memcpy(challenge, hash, 8);
195 * auth_response() generates MS-CHAP v2 SUCCESS response
196 * according to RFC 2759 GenerateAuthenticatorResponse()
197 * returns 42-octet response string
199 static void auth_response(const char *username,
200 const uint8_t *nt_hash_hash,
202 char *peer_challenge, char *auth_challenge,
206 static const uint8_t magic1[39] =
207 {0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
208 0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
209 0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
210 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74};
212 static const uint8_t magic2[41] =
213 {0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
214 0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
215 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
216 0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
219 static const char hex[16] = "0123456789ABCDEF";
226 SHA1Update(&Context, nt_hash_hash, 16);
227 SHA1Update(&Context, ntresponse, 24);
228 SHA1Update(&Context, magic1, 39);
229 SHA1Final(digest, &Context);
230 challenge_hash(peer_challenge, auth_challenge, username, challenge);
232 SHA1Update(&Context, digest, 20);
233 SHA1Update(&Context, challenge, 8);
234 SHA1Update(&Context, magic2, 41);
235 SHA1Final(digest, &Context);
238 * Encode the value of 'Digest' as "S=" followed by
239 * 40 ASCII hexadecimal digits and return it in
240 * AuthenticatorResponse.
242 * "S=0123456789ABCDEF0123456789ABCDEF01234567"
248 * The hexadecimal digits [A-F] MUST be uppercase.
250 for (i = 0; i < sizeof(digest); i++) {
251 response[2 + (i * 2)] = hex[(digest[i] >> 4) & 0x0f];
252 response[3 + (i * 2)] = hex[digest[i] & 0x0f];
257 typedef struct rlm_mschap_t {
259 int require_encryption;
261 int with_ntdomain_hack; /* this should be in another module */
273 * Does dynamic translation of strings.
275 * Pulls NT-Response, LM-Response, or Challenge from MSCHAP
278 static int mschap_xlat(void *instance, REQUEST *request,
279 char *fmt, char *out, size_t outlen,
280 RADIUS_ESCAPE_STRING func)
283 uint8_t *data = NULL;
285 VALUE_PAIR *user_name;
286 VALUE_PAIR *chap_challenge, *response;
287 rlm_mschap_t *inst = instance;
289 chap_challenge = response = NULL;
291 func = func; /* -Wunused */
294 * Challenge means MS-CHAPv1 challenge, or
295 * hash of MS-CHAPv2 challenge, and peer challenge.
297 if (strncasecmp(fmt, "Challenge", 9) == 0) {
298 chap_challenge = pairfind(request->packet->vps,
299 PW_MSCHAP_CHALLENGE);
300 if (!chap_challenge) {
301 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request.");
306 * MS-CHAP-Challenges are 8 octets,
309 if (chap_challenge->length == 8) {
310 DEBUG2(" mschap1: %02x",
311 chap_challenge->vp_octets[0]);
312 data = chap_challenge->vp_octets;
316 * MS-CHAP-Challenges are 16 octets,
319 } else if (chap_challenge->length == 16) {
320 char *username_string;
322 DEBUG2(" mschap2: %02x", chap_challenge->vp_octets[0]);
323 response = pairfind(request->packet->vps,
324 PW_MSCHAP2_RESPONSE);
326 DEBUG2(" rlm_mschap: MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge.");
331 * Responses are 50 octets.
333 if (response->length < 50) {
334 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
338 user_name = pairfind(request->packet->vps,
341 DEBUG2(" rlm_mschap: User-Name is required to calculateMS-CHAPv1 Challenge.");
346 * with_ntdomain_hack moved here, too.
348 if ((username_string = strchr(user_name->vp_strvalue, '\\')) != NULL) {
349 if (inst->with_ntdomain_hack) {
352 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
353 username_string = user_name->vp_strvalue;
356 username_string = user_name->vp_strvalue;
360 * Get the MS-CHAPv1 challenge
361 * from the MS-CHAPv2 peer challenge,
362 * our challenge, and the user name.
364 challenge_hash(response->vp_octets + 2,
365 chap_challenge->vp_octets,
366 username_string, buffer);
370 DEBUG2(" rlm_mschap: Invalid MS-CHAP challenge length");
375 * Get the MS-CHAPv1 response, or the MS-CHAPv2
378 } else if (strncasecmp(fmt, "NT-Response", 11) == 0) {
379 response = pairfind(request->packet->vps,
381 if (!response) response = pairfind(request->packet->vps,
382 PW_MSCHAP2_RESPONSE);
384 DEBUG2(" rlm_mschap: No MS-CHAP-Response or MS-CHAP2-Response was found in the request.");
389 * For MS-CHAPv1, the NT-Response exists only
390 * if the second octet says so.
392 if ((response->attribute == PW_MSCHAP_RESPONSE) &&
393 ((response->vp_octets[1] & 0x01) == 0)) {
394 DEBUG2(" rlm_mschap: No NT-Response in MS-CHAP-Response");
399 * MS-CHAP-Response and MS-CHAP2-Response have
400 * the NT-Response at the same offset, and are
403 data = response->vp_octets + 26;
407 * LM-Response is deprecated, and exists only
408 * in MS-CHAPv1, and not often there.
410 } else if (strncasecmp(fmt, "LM-Response", 11) == 0) {
411 response = pairfind(request->packet->vps,
414 DEBUG2(" rlm_mschap: No MS-CHAP-Response was found in the request.");
419 * For MS-CHAPv1, the NT-Response exists only
420 * if the second octet says so.
422 if ((response->vp_octets[1] & 0x01) != 0) {
423 DEBUG2(" rlm_mschap: No LM-Response in MS-CHAP-Response");
426 data = response->vp_octets + 2;
430 * Pull the NT-Domain out of the User-Name, if it exists.
432 } else if (strncasecmp(fmt, "NT-Domain", 9) == 0) {
435 user_name = pairfind(request->packet->vps, PW_USER_NAME);
437 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
442 * First check to see if this is a host/ style User-Name
443 * (a la Kerberos host principal)
445 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
447 * If we're getting a User-Name formatted in this way,
448 * it's likely due to PEAP. The Windows Domain will be
449 * the first domain component following the hostname,
450 * or the machine name itself if only a hostname is supplied
452 p = strchr(user_name->vp_strvalue, '.');
454 DEBUG2(" rlm_mschap: setting NT-Domain to same as machine name");
455 strlcpy(out, user_name->vp_strvalue + 5, outlen);
457 p++; /* skip the period */
460 * use the same hack as below
461 * only if another period was found
464 strlcpy(out, p, outlen);
468 p = strchr(user_name->vp_strvalue, '\\');
470 DEBUG2(" rlm_mschap: No NT-Domain was found in the User-Name.");
475 * Hack. This is simpler than the alternatives.
478 strlcpy(out, user_name->vp_strvalue, outlen);
485 * Pull the User-Name out of the User-Name...
487 } else if (strncasecmp(fmt, "User-Name", 9) == 0) {
490 user_name = pairfind(request->packet->vps, PW_USER_NAME);
492 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
497 * First check to see if this is a host/ style User-Name
498 * (a la Kerberos host principal)
500 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
502 * If we're getting a User-Name formatted in this way,
503 * it's likely due to PEAP. When authenticating this against
504 * a Domain, Windows will expect the User-Name to be in the
505 * format of hostname$, the SAM version of the name, so we
506 * have to convert it to that here. We do so by stripping
507 * off the first 5 characters (host/), and copying everything
508 * from that point to the first period into a string and appending
511 p = strchr(user_name->vp_strvalue, '.');
513 * use the same hack as above
514 * only if a period was found
517 snprintf(out, outlen, "%s$", user_name->vp_strvalue + 5);
520 p = strchr(user_name->vp_strvalue, '\\');
522 p++; /* skip the backslash */
524 p = user_name->vp_strvalue; /* use the whole User-Name */
526 strlcpy(out, p, outlen);
532 * Return the NT-Hash of the passed string
534 } else if (strncasecmp(fmt, "NT-Hash ", 8) == 0) {
537 p = fmt + 8; /* 7 is the length of 'NT-Hash' */
538 if ((p == '\0') || (outlen <= 32))
540 DEBUG("rlm_mschap: NT-Hash: %s",p);
543 lrad_bin2hex(buffer, out, 16);
545 DEBUG("rlm_mschap: NT-Hash: Result: %s",out);
549 * Return the LM-Hash of the passed string
551 } else if (strncasecmp(fmt, "LM-Hash ", 8) == 0) {
554 p = fmt + 8; /* 7 is the length of 'LM-Hash' */
555 if ((p == '\0') || (outlen <= 32))
558 DEBUG("rlm_mschap: LM-Hash: %s",p);
559 smbdes_lmpwdhash(p,buffer);
560 lrad_bin2hex(buffer, out, 16);
562 DEBUG("rlm_mschap: LM-Hash: Result: %s",out);
565 DEBUG2(" rlm_mschap: Unknown expansion string \"%s\"",
570 if (outlen == 0) return 0; /* nowhere to go, don't do anything */
573 * Didn't set anything: this is bad.
576 DEBUG2(" rlm_mschap: Failed to do anything intelligent");
581 * Check the output length.
583 if (outlen < ((data_len * 2) + 1)) {
584 data_len = (outlen - 1) / 2;
590 for (i = 0; i < data_len; i++) {
591 sprintf(out + (2 * i), "%02x", data[i]);
593 out[data_len * 2] = '\0';
599 static const CONF_PARSER module_config[] = {
601 * Cache the password by default.
603 { "use_mppe", PW_TYPE_BOOLEAN,
604 offsetof(rlm_mschap_t,use_mppe), NULL, "yes" },
605 { "require_encryption", PW_TYPE_BOOLEAN,
606 offsetof(rlm_mschap_t,require_encryption), NULL, "no" },
607 { "require_strong", PW_TYPE_BOOLEAN,
608 offsetof(rlm_mschap_t,require_strong), NULL, "no" },
609 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
610 offsetof(rlm_mschap_t,with_ntdomain_hack), NULL, "no" },
611 { "passwd", PW_TYPE_STRING_PTR,
612 offsetof(rlm_mschap_t, passwd_file), NULL, NULL },
613 { "ntlm_auth", PW_TYPE_STRING_PTR,
614 offsetof(rlm_mschap_t, ntlm_auth), NULL, NULL },
616 { "use_open_directory", PW_TYPE_BOOLEAN,
617 offsetof(rlm_mschap_t,open_directory), NULL, "yes" },
620 { NULL, -1, 0, NULL, NULL } /* end the list */
624 * deinstantiate module, free all memory allocated during
625 * mschap_instantiate()
627 static int mschap_detach(void *instance){
628 #define inst ((rlm_mschap_t *)instance)
629 if (inst->xlat_name) {
630 xlat_unregister(inst->xlat_name, mschap_xlat);
638 * Create instance for our module. Allocate space for
639 * instance structure and read configuration parameters
641 static int mschap_instantiate(CONF_SECTION *conf, void **instance)
645 inst = *instance = rad_malloc(sizeof(*inst));
649 memset(inst, 0, sizeof(*inst));
651 if (cf_section_parse(conf, inst, module_config) < 0) {
657 * This module used to support SMB Password files, but it
658 * made it too complicated. If the user tries to
659 * configure an SMB Password file, then die, with an
662 if (inst->passwd_file) {
663 radlog(L_ERR, "rlm_mschap: SMB password file is no longer supported in this module. Use rlm_passwd module instead");
669 * Create the dynamic translation.
671 inst->xlat_name = cf_section_name2(conf);
672 if (!inst->xlat_name) inst->xlat_name = cf_section_name1(conf);
673 xlat_register(inst->xlat_name, mschap_xlat, inst);
676 * For backwards compatibility
678 if (!dict_valbyname(PW_AUTH_TYPE, inst->xlat_name)) {
679 inst->auth_type = "MS-CHAP";
681 inst->auth_type = inst->xlat_name;
688 * add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
689 * attribute to reply packet
691 static void add_reply(VALUE_PAIR** vp, unsigned char ident,
692 const char* name, const char* value, int len)
694 VALUE_PAIR *reply_attr;
695 reply_attr = pairmake(name, "", T_OP_EQ);
697 DEBUG(" rlm_mschap: Failed to create attribute %s: %s\n", name, librad_errstr);
701 reply_attr->vp_octets[0] = ident;
702 memcpy(reply_attr->vp_octets + 1, value, len);
703 reply_attr->length = len + 1;
704 pairadd(vp, reply_attr);
708 * Add MPPE attributes to the reply.
710 static void mppe_add_reply(VALUE_PAIR **vp,
711 const char* name, const char* value, int len)
713 VALUE_PAIR *reply_attr;
714 reply_attr = pairmake(name, "", T_OP_EQ);
716 DEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, librad_errstr);
720 memcpy(reply_attr->vp_octets, value, len);
721 reply_attr->length = len;
722 pairadd(vp, reply_attr);
727 * Do the MS-CHAP stuff.
729 * This function is here so that all of the MS-CHAP related
730 * authentication is in one place, and we can perhaps later replace
731 * it with code to call winbindd, or something similar.
733 static int do_mschap(rlm_mschap_t *inst,
734 REQUEST *request, VALUE_PAIR *password,
735 uint8_t *challenge, uint8_t *response,
738 int do_ntlm_auth = 0;
739 uint8_t calculated[24];
740 VALUE_PAIR *vp = NULL;
743 * If we have ntlm_auth configured, use it unless told
746 if (inst->ntlm_auth) do_ntlm_auth = 1;
749 * If we have an ntlm_auth configuration, then we may
752 vp = pairfind(request->config_items,
753 PW_MS_CHAP_USE_NTLM_AUTH);
754 if (vp) do_ntlm_auth = vp->vp_integer;
757 * No ntlm_auth configured, attribute to tell us to
758 * use it exists, and we're told to use it. We don't
761 if (!inst->ntlm_auth && do_ntlm_auth) {
762 DEBUG2(" rlm_mschap: Asked to use ntlm_auth, but it was not configured in the mschap{} section.");
767 * Do normal authentication.
771 * No password: can't do authentication.
774 DEBUG2(" rlm_mschap: FAILED: No NT/LM-Password. Cannot perform authentication.");
778 smbdes_mschap(password->vp_strvalue, challenge, calculated);
779 if (memcmp(response, calculated, 24) != 0) {
784 * If the password exists, and is an NT-Password,
785 * then calculate the hash of the NT hash. Doing this
786 * here minimizes work for later.
788 if (password && (password->attribute == PW_NT_PASSWORD)) {
789 md4_calc(nthashhash, password->vp_strvalue, 16);
791 memset(nthashhash, 0, 16);
793 } else { /* run ntlm_auth */
797 memset(nthashhash, 0, 16);
800 * Run the program, and expect that we get 16
802 result = radius_exec_program(inst->ntlm_auth, request,
804 buffer, sizeof(buffer),
807 DEBUG2(" rlm_mschap: External script failed.");
812 * Parse the answer as an nthashhash.
814 * ntlm_auth currently returns:
815 * NT_KEY: 000102030405060708090a0b0c0d0e0f
817 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
818 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: expecting NT_KEY");
823 * Check the length. It should be at least 32,
824 * with an LF at the end.
826 if (strlen(buffer + 8) < 32) {
827 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has unexpected length");
832 * Update the NT hash hash, from the NT key.
834 if (lrad_hex2bin(buffer + 8, nthashhash, 16) != 16) {
835 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has non-hex values");
845 * Data for the hashes.
847 static const uint8_t SHSpad1[40] =
848 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
849 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
850 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
851 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
853 static const uint8_t SHSpad2[40] =
854 { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
855 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
856 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
857 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
859 static const uint8_t magic1[27] =
860 { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
861 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
862 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
864 static const uint8_t magic2[84] =
865 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
866 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
867 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
868 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
869 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
870 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
871 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
872 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
873 0x6b, 0x65, 0x79, 0x2e };
875 static const uint8_t magic3[84] =
876 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
877 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
878 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
879 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
880 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
881 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
882 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
883 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
884 0x6b, 0x65, 0x79, 0x2e };
887 static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response,
894 SHA1Update(&Context,nt_hashhash,16);
895 SHA1Update(&Context,nt_response,24);
896 SHA1Update(&Context,magic1,27);
897 SHA1Final(digest,&Context);
899 memcpy(masterkey,digest,16);
903 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
904 int keylen,int issend)
919 SHA1Update(&Context,masterkey,16);
920 SHA1Update(&Context,SHSpad1,40);
921 SHA1Update(&Context,s,84);
922 SHA1Update(&Context,SHSpad2,40);
923 SHA1Final(digest,&Context);
925 memcpy(sesskey,digest,keylen);
929 static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response,
930 uint8_t *sendkey,uint8_t *recvkey)
932 uint8_t masterkey[16];
934 mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
936 mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
937 mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
941 * Generate MPPE keys.
943 static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response,
944 uint8_t *sendkey,uint8_t *recvkey)
949 mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
952 * dictionary.microsoft defines these attributes as
953 * 'encrypt=2'. The functions in src/lib/radius.c will
954 * take care of encrypting/decrypting them as appropriate,
955 * so that we don't have to.
957 memcpy (sendkey, enckey1, 16);
958 memcpy (recvkey, enckey2, 16);
963 * mschap_authorize() - authorize user if we can authenticate
964 * it later. Add Auth-Type attribute if present in module
965 * configuration (usually Auth-Type must be "MS-CHAP")
967 static int mschap_authorize(void * instance, REQUEST *request)
969 #define inst ((rlm_mschap_t *)instance)
970 VALUE_PAIR *challenge = NULL, *response = NULL;
973 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
975 return RLM_MODULE_NOOP;
978 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
980 response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE);
983 * Nothing we recognize. Don't do anything.
986 DEBUG2(" rlm_mschap: Found MS-CHAP-Challenge, but no MS-CHAP-Response.");
987 return RLM_MODULE_NOOP;
990 DEBUG2(" rlm_mschap: Found MS-CHAP attributes. Setting 'Auth-Type = %s'", inst->xlat_name);
993 * Set Auth-Type to MS-CHAP. The authentication code
994 * will take care of turning clear-text passwords into
997 vp = pairmake("Auth-Type", inst->auth_type, T_OP_EQ);
998 if (!vp) return RLM_MODULE_FAIL;
999 pairmove(&request->config_items, &vp);
1000 pairfree(&vp); /* may be NULL */
1002 return RLM_MODULE_OK;
1007 * mschap_authenticate() - authenticate user based on given
1008 * attributes and configuration.
1009 * We will try to find out password in configuration
1010 * or in configured passwd file.
1011 * If one is found we will check paraneters given by NAS.
1013 * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
1015 * PAP: PW_USER_PASSWORD or
1016 * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
1017 * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
1018 * In case of password mismatch or locked account we MAY return
1019 * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
1020 * If MS-CHAP2 succeeds we MUST return
1021 * PW_MSCHAP2_SUCCESS
1023 static int mschap_authenticate(void * instance, REQUEST *request)
1025 #define inst ((rlm_mschap_t *)instance)
1026 VALUE_PAIR *challenge = NULL;
1027 VALUE_PAIR *response = NULL;
1028 VALUE_PAIR *password = NULL;
1029 VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
1030 VALUE_PAIR *username;
1031 VALUE_PAIR *reply_attr;
1032 uint8_t nthashhash[16];
1033 uint8_t msch2resp[42];
1034 char *username_string;
1038 * Find the SMB-Account-Ctrl attribute, or the
1039 * SMB-Account-Ctrl-Text attribute.
1041 smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL);
1043 password = pairfind(request->config_items,
1044 PW_SMB_ACCOUNT_CTRL_TEXT);
1046 smb_ctrl = pairmake("SMB-Account-CTRL", "0", T_OP_SET);
1047 pairadd(&request->config_items, smb_ctrl);
1048 smb_ctrl->vp_integer = pdb_decode_acct_ctrl(password->vp_strvalue);
1053 * We're configured to do MS-CHAP authentication.
1054 * and account control information exists. Enforce it.
1058 * Password is not required.
1060 if ((smb_ctrl->vp_integer & ACB_PWNOTREQ) != 0) {
1061 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says no password is required.");
1062 return RLM_MODULE_OK;
1067 * Decide how to get the passwords.
1069 password = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD);
1072 * We need an LM-Password.
1074 lm_password = pairfind(request->config_items, PW_LM_PASSWORD);
1079 if ((lm_password->length == 16) ||
1080 ((lm_password->length == 32) &&
1081 (lrad_hex2bin(lm_password->vp_strvalue,
1082 lm_password->vp_strvalue, 16) == 16))) {
1083 DEBUG2(" rlm_mschap: Found LM-Password");
1084 lm_password->length = 16;
1087 radlog(L_ERR, "rlm_mschap: Invalid LM-Password");
1091 } else if (!password) {
1092 DEBUG2(" rlm_mschap: No Cleartext-Password configured. Cannot create LM-Password.");
1094 } else { /* there is a configured Cleartext-Password */
1095 lm_password = pairmake("LM-Password", "", T_OP_EQ);
1097 radlog(L_ERR, "No memory");
1099 smbdes_lmpwdhash(password->vp_strvalue,
1100 lm_password->vp_strvalue);
1101 lm_password->length = 16;
1102 pairadd(&request->config_items, lm_password);
1107 * We need an NT-Password.
1109 nt_password = pairfind(request->config_items, PW_NT_PASSWORD);
1111 if ((nt_password->length == 16) ||
1112 ((nt_password->length == 32) &&
1113 (lrad_hex2bin(nt_password->vp_strvalue,
1114 nt_password->vp_strvalue, 16) == 16))) {
1115 DEBUG2(" rlm_mschap: Found NT-Password");
1116 nt_password->length = 16;
1119 radlog(L_ERR, "rlm_mschap: Invalid NT-Password");
1122 } else if (!password) {
1123 DEBUG2(" rlm_mschap: No Cleartext-Password configured. Cannot create NT-Password.");
1125 } else { /* there is a configured Cleartext-Password */
1126 nt_password = pairmake("NT-Password", "", T_OP_EQ);
1128 radlog(L_ERR, "No memory");
1129 return RLM_MODULE_FAIL;
1131 ntpwdhash(nt_password->vp_strvalue,
1132 password->vp_strvalue);
1133 nt_password->length = 16;
1134 pairadd(&request->config_items, nt_password);
1138 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
1140 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request");
1141 return RLM_MODULE_REJECT;
1145 * We also require an MS-CHAP-Response.
1147 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
1150 * MS-CHAP-Response, means MS-CHAPv1
1156 * MS-CHAPv1 challenges are 8 octets.
1158 if (challenge->length < 8) {
1159 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1160 return RLM_MODULE_INVALID;
1164 * Responses are 50 octets.
1166 if (response->length < 50) {
1167 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1168 return RLM_MODULE_INVALID;
1172 * We are doing MS-CHAP. Calculate the MS-CHAP
1175 if (response->vp_octets[1] & 0x01) {
1176 DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with NT-Password");
1177 password = nt_password;
1180 DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with LM-Password");
1181 password = lm_password;
1186 * Do the MS-CHAP authentication.
1188 if (do_mschap(inst, request, password, challenge->vp_octets,
1189 response->vp_octets + offset, nthashhash) < 0) {
1190 DEBUG2(" rlm_mschap: MS-CHAP-Response is incorrect.");
1191 add_reply(&request->reply->vps, *response->vp_octets,
1192 "MS-CHAP-Error", "E=691 R=1", 9);
1193 return RLM_MODULE_REJECT;
1198 } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE)) != NULL) {
1199 uint8_t mschapv1_challenge[16];
1202 * MS-CHAPv2 challenges are 16 octets.
1204 if (challenge->length < 16) {
1205 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1206 return RLM_MODULE_INVALID;
1210 * Responses are 50 octets.
1212 if (response->length < 50) {
1213 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1214 return RLM_MODULE_INVALID;
1218 * We also require a User-Name
1220 username = pairfind(request->packet->vps, PW_USER_NAME);
1222 radlog(L_AUTH, "rlm_mschap: We require a User-Name for MS-CHAPv2");
1223 return RLM_MODULE_INVALID;
1228 * with_ntdomain_hack moved here
1230 if ((username_string = strchr(username->vp_strvalue, '\\')) != NULL) {
1231 if (inst->with_ntdomain_hack) {
1234 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1235 username_string = username->vp_strvalue;
1238 username_string = username->vp_strvalue;
1243 * No "known good" NT-Password attribute. Try to do
1244 * OpenDirectory authentication.
1246 if (!nt_password && inst->open_directory) {
1247 DEBUG2(" rlm_mschap: No NT-Password configured. Trying DirectoryService Authentication.");
1248 return od_mschap_auth(request, challenge, username);
1252 * The old "mschapv2" function has been moved to
1255 * MS-CHAPv2 takes some additional data to create an
1256 * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1258 challenge_hash(response->vp_octets + 2, /* peer challenge */
1259 challenge->vp_octets, /* our challenge */
1260 username_string, /* user name */
1261 mschapv1_challenge); /* resulting challenge */
1263 DEBUG2(" rlm_mschap: Told to do MS-CHAPv2 for %s with NT-Password",
1266 if (do_mschap(inst, request, nt_password, mschapv1_challenge,
1267 response->vp_octets + 26, nthashhash) < 0) {
1268 DEBUG2(" rlm_mschap: FAILED: MS-CHAP2-Response is incorrect");
1269 add_reply(&request->reply->vps, *response->vp_octets,
1270 "MS-CHAP-Error", "E=691 R=1", 9);
1271 return RLM_MODULE_REJECT;
1275 * Get the NT-hash-hash, if necessary
1280 auth_response(username_string, /* without the domain */
1281 nthashhash, /* nt-hash-hash */
1282 response->vp_octets + 26, /* peer response */
1283 response->vp_octets + 2, /* peer challenge */
1284 challenge->vp_octets, /* our challenge */
1285 msch2resp); /* calculated MPPE key */
1286 add_reply( &request->reply->vps, *response->vp_octets,
1287 "MS-CHAP2-Success", msch2resp, 42);
1290 } else { /* Neither CHAPv1 or CHAPv2 response: die */
1291 radlog(L_AUTH, "rlm_mschap: No MS-CHAP response found");
1292 return RLM_MODULE_INVALID;
1296 * We have a CHAP response, but the account may be
1297 * disabled. Reject the user with the same error code
1298 * we use when their password is invalid.
1302 * Account is disabled.
1304 * They're found, but they don't exist, so we
1305 * return 'not found'.
1307 if (((smb_ctrl->vp_integer & ACB_DISABLED) != 0) ||
1308 ((smb_ctrl->vp_integer & ACB_NORMAL) == 0)) {
1309 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is disabled, or is not a normal account.");
1310 add_reply( &request->reply->vps, *response->vp_octets,
1311 "MS-CHAP-Error", "E=691 R=1", 9);
1312 return RLM_MODULE_NOTFOUND;
1316 * User is locked out.
1318 if ((smb_ctrl->vp_integer & ACB_AUTOLOCK) != 0) {
1319 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is locked out.");
1320 add_reply( &request->reply->vps, *response->vp_octets,
1321 "MS-CHAP-Error", "E=647 R=0", 9);
1322 return RLM_MODULE_USERLOCK;
1326 /* now create MPPE attributes */
1327 if (inst->use_mppe) {
1328 uint8_t mppe_sendkey[34];
1329 uint8_t mppe_recvkey[34];
1332 DEBUG2("rlm_mschap: adding MS-CHAPv1 MPPE keys");
1333 memset(mppe_sendkey, 0, 32);
1335 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1339 * According to RFC 2548 we
1340 * should send NT hash. But in
1341 * practice it doesn't work.
1342 * Instead, we should send nthashhash
1344 * This is an error on RFC 2548.
1347 * do_mschap cares to zero nthashhash if NT hash
1350 memcpy(mppe_sendkey + 8,
1352 mppe_add_reply(&request->reply->vps,
1353 "MS-CHAP-MPPE-Keys",
1355 } else if (chap == 2) {
1356 DEBUG2("rlm_mschap: adding MS-CHAPv2 MPPE keys");
1357 mppe_chap2_gen_keys128(nthashhash,
1358 response->vp_octets + 26,
1359 mppe_sendkey, mppe_recvkey);
1361 mppe_add_reply(&request->reply->vps,
1364 mppe_add_reply(&request->reply->vps,
1369 reply_attr = pairmake("MS-MPPE-Encryption-Policy",
1370 (inst->require_encryption)? "0x00000002":"0x00000001",
1372 rad_assert(reply_attr != NULL);
1373 pairadd(&request->reply->vps, reply_attr);
1374 reply_attr = pairmake("MS-MPPE-Encryption-Types",
1375 (inst->require_strong)? "0x00000004":"0x00000006",
1377 rad_assert(reply_attr != NULL);
1378 pairadd(&request->reply->vps, reply_attr);
1380 } /* else we weren't asked to use MPPE */
1382 return RLM_MODULE_OK;
1386 module_t rlm_mschap = {
1389 RLM_TYPE_THREAD_SAFE, /* type */
1390 mschap_instantiate, /* instantiation */
1391 mschap_detach, /* detach */
1393 mschap_authenticate, /* authenticate */
1394 mschap_authorize, /* authorize */
1395 NULL, /* pre-accounting */
1396 NULL, /* accounting */
1397 NULL, /* checksimul */
1398 NULL, /* pre-proxy */
1399 NULL, /* post-proxy */
1400 NULL /* post-auth */