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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 * Copyright 2000,2001 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> */
46 #include "libradius.h"
59 #include "rad_assert.h"
63 static const char rcsid[] = "$Id$";
65 static const char *letters = "0123456789ABCDEF";
68 * hex2bin converts hexadecimal strings into binary
70 static int hex2bin (const char *szHex, unsigned char* szBin, int len)
75 for (i = 0; i < len; i++) {
76 if( !(c1 = memchr(letters, toupper((int) szHex[i << 1]), 16)) ||
77 !(c2 = memchr(letters, toupper((int) szHex[(i << 1) + 1]), 16)))
79 szBin[i] = ((c1-letters)<<4) + (c2-letters);
85 * bin2hex creates hexadecimal presentation
88 static void bin2hex (const unsigned char *szBin, char *szHex, int len)
91 for (i = 0; i < len; i++) {
92 szHex[i<<1] = letters[szBin[i] >> 4];
93 szHex[(i<<1) + 1] = letters[szBin[i] & 0x0F];
98 /* Allowable account control bits */
99 #define ACB_DISABLED 0x0001 /* 1 = User account disabled */
100 #define ACB_HOMDIRREQ 0x0002 /* 1 = Home directory required */
101 #define ACB_PWNOTREQ 0x0004 /* 1 = User password not required */
102 #define ACB_TEMPDUP 0x0008 /* 1 = Temporary duplicate account */
103 #define ACB_NORMAL 0x0010 /* 1 = Normal user account */
104 #define ACB_MNS 0x0020 /* 1 = MNS logon user account */
105 #define ACB_DOMTRUST 0x0040 /* 1 = Interdomain trust account */
106 #define ACB_WSTRUST 0x0080 /* 1 = Workstation trust account */
107 #define ACB_SVRTRUST 0x0100 /* 1 = Server trust account */
108 #define ACB_PWNOEXP 0x0200 /* 1 = User password does not expire */
109 #define ACB_AUTOLOCK 0x0400 /* 1 = Account auto locked */
111 static int pdb_decode_acct_ctrl(const char *p)
117 * Check if the account type bits have been encoded after the
118 * NT password (in the form [NDHTUWSLXI]).
121 if (*p != '[') return 0;
123 for (p++; *p && !finished; p++) {
125 case 'N': /* 'N'o password. */
126 acct_ctrl |= ACB_PWNOTREQ;
129 case 'D': /* 'D'isabled. */
130 acct_ctrl |= ACB_DISABLED ;
133 case 'H': /* 'H'omedir required. */
134 acct_ctrl |= ACB_HOMDIRREQ;
137 case 'T': /* 'T'emp account. */
138 acct_ctrl |= ACB_TEMPDUP;
141 case 'U': /* 'U'ser account (normal). */
142 acct_ctrl |= ACB_NORMAL;
145 case 'M': /* 'M'NS logon user account. What is this? */
146 acct_ctrl |= ACB_MNS;
149 case 'W': /* 'W'orkstation account. */
150 acct_ctrl |= ACB_WSTRUST;
153 case 'S': /* 'S'erver account. */
154 acct_ctrl |= ACB_SVRTRUST;
157 case 'L': /* 'L'ocked account. */
158 acct_ctrl |= ACB_AUTOLOCK;
161 case 'X': /* No 'X'piry on password */
162 acct_ctrl |= ACB_PWNOEXP;
165 case 'I': /* 'I'nterdomain trust account. */
166 acct_ctrl |= ACB_DOMTRUST;
169 case ' ': /* ignore spaces */
187 * ntpwdhash converts Unicode password to 16-byte NT hash
190 static void ntpwdhash (unsigned char *szHash, const char *szPassword)
192 char szUnicodePass[513];
197 * NT passwords are unicode. Convert plain text password
198 * to unicode by inserting a zero every other byte
200 nPasswordLen = strlen(szPassword);
201 for (i = 0; i < nPasswordLen; i++) {
202 szUnicodePass[i << 1] = szPassword[i];
203 szUnicodePass[(i << 1) + 1] = 0;
206 /* Encrypt Unicode password to a 16-byte MD4 hash */
207 md4_calc(szHash, szUnicodePass, (nPasswordLen<<1) );
212 * challenge_hash() is used by mschap2() and auth_response()
213 * implements RFC2759 ChallengeHash()
214 * generates 64 bit challenge
216 static void challenge_hash( const char *peer_challenge,
217 const char *auth_challenge,
218 const char *user_name, char *challenge )
221 unsigned char hash[20];
224 SHA1Update(&Context, peer_challenge, 16);
225 SHA1Update(&Context, auth_challenge, 16);
226 SHA1Update(&Context, user_name, strlen(user_name));
227 SHA1Final(hash, &Context);
228 memcpy(challenge, hash, 8);
232 * auth_response() generates MS-CHAP v2 SUCCESS response
233 * according to RFC 2759 GenerateAuthenticatorResponse()
234 * returns 42-octet response string
236 static void auth_response(const char *username,
237 const unsigned char *nt_hash_hash,
238 unsigned char *ntresponse,
239 char *peer_challenge, char *auth_challenge,
243 const unsigned char magic1[39] =
244 {0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
245 0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
246 0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
247 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74};
249 const unsigned char magic2[41] =
250 {0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
251 0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
252 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
253 0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
257 unsigned char digest[20];
260 SHA1Update(&Context, nt_hash_hash, 16);
261 SHA1Update(&Context, ntresponse, 24);
262 SHA1Update(&Context, magic1, 39);
263 SHA1Final(digest, &Context);
264 challenge_hash(peer_challenge, auth_challenge, username, challenge);
266 SHA1Update(&Context, digest, 20);
267 SHA1Update(&Context, challenge, 8);
268 SHA1Update(&Context, magic2, 41);
269 SHA1Final(digest, &Context);
272 * Encode the value of 'Digest' as "S=" followed by
273 * 40 ASCII hexadecimal digits and return it in
274 * AuthenticatorResponse.
276 * "S=0123456789ABCDEF0123456789ABCDEF01234567"
280 bin2hex(digest, response + 2, 20);
284 typedef struct rlm_mschap_t {
286 int require_encryption;
288 int with_ntdomain_hack; /* this should be in another module */
297 * Does dynamic translation of strings.
299 * Pulls NT-Response, LM-Response, or Challenge from MSCHAP
302 static int mschap_xlat(void *instance, REQUEST *request,
303 char *fmt, char *out, size_t outlen,
304 RADIUS_ESCAPE_STRING func)
307 uint8_t *data = NULL;
309 VALUE_PAIR *user_name;
310 VALUE_PAIR *chap_challenge, *response;
311 rlm_mschap_t *inst = instance;
313 chap_challenge = response = NULL;
315 func = func; /* -Wunused */
318 * Challenge means MS-CHAPv1 challenge, or
319 * hash of MS-CHAPv2 challenge, and peer challenge.
321 if (strcasecmp(fmt, "Challenge") == 0) {
322 chap_challenge = pairfind(request->packet->vps,
323 PW_MSCHAP_CHALLENGE);
324 if (!chap_challenge) {
325 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request.");
330 * MS-CHAP-Challenges are 8 octets,
333 if (chap_challenge->length == 8) {
334 DEBUG2(" mschap1: %02x", chap_challenge->strvalue[0]);
335 data = chap_challenge->strvalue;
339 * MS-CHAP-Challenges are 16 octets,
342 } else if (chap_challenge->length == 16) {
343 char *username_string;
345 DEBUG2(" mschap2: %02x", chap_challenge->strvalue[0]);
346 response = pairfind(request->packet->vps,
347 PW_MSCHAP2_RESPONSE);
349 DEBUG2(" rlm_mschap: MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge.");
354 * Responses are 50 octets.
356 if (response->length < 50) {
357 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
361 user_name = pairfind(request->packet->vps,
364 DEBUG2(" rlm_mschap: User-Name is required to calculateMS-CHAPv1 Challenge.");
369 * with_ntdomain_hack moved here, too.
371 if ((username_string = strchr(user_name->strvalue, '\\')) != NULL) {
372 if (inst->with_ntdomain_hack) {
375 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
376 username_string = user_name->strvalue;
379 username_string = user_name->strvalue;
383 * Get the MS-CHAPv1 challenge
384 * from the MS-CHAPv2 peer challenge,
385 * our challenge, and the user name.
387 challenge_hash(response->strvalue + 2,
388 chap_challenge->strvalue,
389 username_string, buffer);
393 DEBUG2(" rlm_mschap: Invalid MS-CHAP challenge length");
398 * Get the MS-CHAPv1 response, or the MS-CHAPv2
401 } else if (strcasecmp(fmt, "NT-Response") == 0) {
402 response = pairfind(request->packet->vps,
404 if (!response) response = pairfind(request->packet->vps,
405 PW_MSCHAP2_RESPONSE);
407 DEBUG2(" rlm_mschap: No MS-CHAP-Response or MS-CHAP2-Response was found in the request.");
412 * For MS-CHAPv1, the NT-Response exists only
413 * if the second octet says so.
415 if ((response->attribute == PW_MSCHAP_RESPONSE) &&
416 ((response->strvalue[1] & 0x01) == 0)) {
417 DEBUG2(" rlm_mschap: No NT-Response in MS-CHAP-Response");
422 * MS-CHAP-Response and MS-CHAP2-Response have
423 * the NT-Response at the same offset, and are
426 data = response->strvalue + 26;
430 * LM-Response is deprecated, and exists only
431 * in MS-CHAPv1, and not often there.
433 } else if (strcasecmp(fmt, "LM-Response") == 0) {
434 response = pairfind(request->packet->vps,
437 DEBUG2(" rlm_mschap: No MS-CHAP-Response was found in the request.");
442 * For MS-CHAPv1, the NT-Response exists only
443 * if the second octet says so.
445 if ((response->strvalue[1] & 0x01) != 0) {
446 DEBUG2(" rlm_mschap: No LM-Response in MS-CHAP-Response");
449 data = response->strvalue + 2;
453 * Pull the NT-Domain out of the User-Name, if it exists.
455 } else if (strcasecmp(fmt, "NT-Domain") == 0) {
458 user_name = pairfind(request->packet->vps, PW_USER_NAME);
460 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
465 * First check to see if this is a host/ style User-Name
466 * (a la Kerberos host principal)
468 if (strncmp(user_name->strvalue, "host/", 5) == 0) {
470 * If we're getting a User-Name formatted in this way,
471 * it's likely due to PEAP. The Windows Domain will be
472 * the first domain component following the hostname,
473 * or the machine name itself if only a hostname is supplied
475 p = strchr(user_name->strvalue, '.');
477 DEBUG2(" rlm_mschap: setting NT-Domain to same as machine name");
478 strNcpy(out, user_name->strvalue + 5, outlen);
480 p++; /* skip the period */
483 * use the same hack as below
484 * only if another period was found
487 strNcpy(out, p, outlen);
491 p = strchr(user_name->strvalue, '\\');
493 DEBUG2(" rlm_mschap: No NT-Domain was found in the User-Name.");
498 * Hack. This is simpler than the alternatives.
501 strNcpy(out, user_name->strvalue, outlen);
508 * Pull the User-Name out of the User-Name...
510 } else if (strcasecmp(fmt, "User-Name") == 0) {
513 user_name = pairfind(request->packet->vps, PW_USER_NAME);
515 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
520 * First check to see if this is a host/ style User-Name
521 * (a la Kerberos host principal)
523 if (strncmp(user_name->strvalue, "host/", 5) == 0) {
525 * If we're getting a User-Name formatted in this way,
526 * it's likely due to PEAP. When authenticating this against
527 * a Domain, Windows will expect the User-Name to be in the
528 * format of hostname$, the SAM version of the name, so we
529 * have to convert it to that here. We do so by stripping
530 * off the first 5 characters (host/), and copying everything
531 * from that point to the first period into a string and appending
534 p = strchr(user_name->strvalue, '.');
536 * use the same hack as above
537 * only if a period was found
540 snprintf(out, outlen, "%s$", user_name->strvalue + 5);
543 p = strchr(user_name->strvalue, '\\');
545 p++; /* skip the backslash */
547 p = user_name->strvalue; /* use the whole User-Name */
549 strNcpy(out, p, outlen);
555 * Return the NT-Hash of the passed string
557 } else if (strncasecmp(fmt, "NT-Hash ", 8) == 0) {
560 p = fmt + 8; /* 7 is the length of 'NT-Hash' */
561 if ((p == '\0') || (outlen <= 32))
563 DEBUG("rlm_mschap: NT-Hash: %s",p);
566 lrad_bin2hex(buffer, out, 16);
568 DEBUG("rlm_mschap: NT-Hash: Result: %s",out);
572 * Return the LM-Hash of the passed string
574 } else if (strncasecmp(fmt, "LM-Hash ", 8) == 0) {
577 p = fmt + 8; /* 7 is the length of 'LM-Hash' */
578 if ((p == '\0') || (outlen <= 32))
581 DEBUG("rlm_mschap: LM-Hash: %s",p);
582 smbdes_lmpwdhash(p,buffer);
583 lrad_bin2hex(buffer, out, 16);
585 DEBUG("rlm_mschap: LM-Hash: Result: %s",out);
588 DEBUG2(" rlm_mschap: Unknown expansion string \"%s\"",
593 if (outlen == 0) return 0; /* nowhere to go, don't do anything */
596 * Didn't set anything: this is bad.
599 DEBUG2(" rlm_mschap: Failed to do anything intelligent");
604 * Check the output length.
606 if (outlen < ((data_len * 2) + 1)) {
607 data_len = (outlen - 1) / 2;
613 for (i = 0; i < data_len; i++) {
614 sprintf(out + (2 * i), "%02x", data[i]);
616 out[data_len * 2] = '\0';
622 static CONF_PARSER module_config[] = {
624 * Cache the password by default.
626 { "use_mppe", PW_TYPE_BOOLEAN,
627 offsetof(rlm_mschap_t,use_mppe), NULL, "yes" },
628 { "require_encryption", PW_TYPE_BOOLEAN,
629 offsetof(rlm_mschap_t,require_encryption), NULL, "no" },
630 { "require_strong", PW_TYPE_BOOLEAN,
631 offsetof(rlm_mschap_t,require_strong), NULL, "no" },
632 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
633 offsetof(rlm_mschap_t,with_ntdomain_hack), NULL, "no" },
634 { "passwd", PW_TYPE_STRING_PTR,
635 offsetof(rlm_mschap_t, passwd_file), NULL, NULL },
636 { "ntlm_auth", PW_TYPE_STRING_PTR,
637 offsetof(rlm_mschap_t, ntlm_auth), NULL, NULL },
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 free(inst->passwd_file);
649 free(inst->ntlm_auth);
650 if (inst->xlat_name) {
651 xlat_unregister(inst->xlat_name, mschap_xlat);
652 free(inst->xlat_name);
660 * Create instance for our module. Allocate space for
661 * instance structure and read configuration parameters
663 static int mschap_instantiate(CONF_SECTION *conf, void **instance)
665 const char *xlat_name;
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 xlat_name = cf_section_name2(conf);
695 if (xlat_name == NULL)
696 xlat_name = cf_section_name1(conf);
698 inst->xlat_name = strdup(xlat_name);
699 xlat_register(xlat_name, mschap_xlat, inst);
703 * For backwards compatibility
705 if (!dict_valbyname(PW_AUTH_TYPE, inst->xlat_name)) {
706 inst->auth_type = "MS-CHAP";
713 * add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
714 * attribute to reply packet
716 static void add_reply(VALUE_PAIR** vp, unsigned char ident,
717 const char* name, const char* value, int len)
719 VALUE_PAIR *reply_attr;
720 reply_attr = pairmake(name, "", T_OP_EQ);
722 DEBUG(" rlm_mschap: Failed to create attribute %s: %s\n", name, librad_errstr);
726 reply_attr->strvalue[0] = ident;
727 memcpy(reply_attr->strvalue + 1, value, len);
728 reply_attr->length = len + 1;
729 pairadd(vp, reply_attr);
733 * Add MPPE attributes to the reply.
735 static void mppe_add_reply(VALUE_PAIR **vp,
736 const char* name, const char* value, int len)
738 VALUE_PAIR *reply_attr;
739 reply_attr = pairmake(name, "", T_OP_EQ);
741 DEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, librad_errstr);
745 memcpy(reply_attr->strvalue, value, len);
746 reply_attr->length = len;
747 pairadd(vp, reply_attr);
752 * Do the MS-CHAP stuff.
754 * This function is here so that all of the MS-CHAP related
755 * authentication is in one place, and we can perhaps later replace
756 * it with code to call winbindd, or something similar.
758 static int do_mschap(rlm_mschap_t *inst,
759 REQUEST *request, VALUE_PAIR *password,
760 uint8_t *challenge, uint8_t *response,
763 int do_ntlm_auth = 0;
764 uint8_t calculated[24];
765 VALUE_PAIR *vp = NULL;
768 * If we have ntlm_auth configured, use it unless told
771 if (inst->ntlm_auth) do_ntlm_auth = 1;
774 * If we have an ntlm_auth configuration, then we may
777 vp = pairfind(request->config_items,
778 PW_MS_CHAP_USE_NTLM_AUTH);
779 if (vp) do_ntlm_auth = vp->lvalue;
782 * No ntlm_auth configured, attribute to tell us to
783 * use it exists, and we're told to use it. We don't
786 if (!inst->ntlm_auth && do_ntlm_auth) {
787 DEBUG2(" rlm_mschap: Asked to use ntlm_auth, but it was not configured in the mschap{} section.");
792 * Do normal authentication.
796 * No password: can't do authentication.
799 DEBUG2(" rlm_mschap: FAILED: No NT/LM-Password. Cannot perform authentication.");
803 smbdes_mschap(password->strvalue, challenge, calculated);
804 if (memcmp(response, calculated, 24) != 0) {
809 * If the password exists, and is an NT-Password,
810 * then calculate the hash of the NT hash. Doing this
811 * here minimizes work for later.
813 if (password && (password->attribute == PW_NT_PASSWORD)) {
814 md4_calc(nthashhash, password->strvalue, 16);
816 memset(nthashhash, 0, 16);
818 } else { /* run ntlm_auth */
822 memset(nthashhash, 0, 16);
825 * Run the program, and expect that we get 16
827 result = radius_exec_program(inst->ntlm_auth, request,
829 buffer, sizeof(buffer),
834 DEBUG2(" rlm_mschap: External script failed.");
836 vp = pairmake("Module-Failure-Message", "", T_OP_EQ);
838 radlog(L_ERR, "No memory");
842 p = strchr(buffer, '\n');
844 snprintf(vp->strvalue, sizeof(vp->strvalue),
845 "rlm_mschap: %s", buffer);
846 vp->length = strlen(vp->strvalue);
847 pairadd(&request->packet->vps, vp);
852 * Parse the answer as an nthashhash.
854 * ntlm_auth currently returns:
855 * NT_KEY: 000102030405060708090a0b0c0d0e0f
857 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
858 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: expecting NT_KEY");
863 * Check the length. It should be at least 32,
864 * with an LF at the end.
866 if (strlen(buffer + 8) < 32) {
867 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has unexpected length");
872 * Update the NT hash hash, from the NT key.
874 if (hex2bin(buffer + 8, nthashhash, 16) != 16) {
875 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has non-hex values");
885 * Data for the hashes.
887 static const uint8_t SHSpad1[40] =
888 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
889 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
890 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
891 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
893 static const uint8_t SHSpad2[40] =
894 { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
895 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
896 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
897 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
899 static const uint8_t magic1[27] =
900 { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
901 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
902 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
904 static const uint8_t magic2[84] =
905 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
906 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
907 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
908 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
909 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
910 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
911 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
912 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
913 0x6b, 0x65, 0x79, 0x2e };
915 static const uint8_t magic3[84] =
916 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
917 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
918 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
919 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
920 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
921 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
922 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
923 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
924 0x6b, 0x65, 0x79, 0x2e };
927 static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response,
934 SHA1Update(&Context,nt_hashhash,16);
935 SHA1Update(&Context,nt_response,24);
936 SHA1Update(&Context,magic1,27);
937 SHA1Final(digest,&Context);
939 memcpy(masterkey,digest,16);
943 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
944 int keylen,int issend)
959 SHA1Update(&Context,masterkey,16);
960 SHA1Update(&Context,SHSpad1,40);
961 SHA1Update(&Context,s,84);
962 SHA1Update(&Context,SHSpad2,40);
963 SHA1Final(digest,&Context);
965 memcpy(sesskey,digest,keylen);
969 static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response,
970 uint8_t *sendkey,uint8_t *recvkey)
972 uint8_t masterkey[16];
974 mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
976 mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
977 mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
981 * Generate MPPE keys.
983 static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response,
984 uint8_t *sendkey,uint8_t *recvkey)
989 mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
992 * dictionary.microsoft defines these attributes as
993 * 'encrypt=2'. The functions in src/lib/radius.c will
994 * take care of encrypting/decrypting them as appropriate,
995 * so that we don't have to.
997 memcpy (sendkey, enckey1, 16);
998 memcpy (recvkey, enckey2, 16);
1003 * mschap_authorize() - authorize user if we can authenticate
1004 * it later. Add Auth-Type attribute if present in module
1005 * configuration (usually Auth-Type must be "MS-CHAP")
1007 static int mschap_authorize(void * instance, REQUEST *request)
1009 #define inst ((rlm_mschap_t *)instance)
1010 VALUE_PAIR *challenge = NULL;
1011 VALUE_PAIR *response = NULL;
1014 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
1016 return RLM_MODULE_NOOP;
1019 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
1021 response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE);
1024 * Nothing we recognize. Don't do anything.
1027 DEBUG2(" rlm_mschap: Found MS-CHAP-Challenge, but no MS-CHAP-Response.");
1028 return RLM_MODULE_NOOP;
1031 DEBUG2(" rlm_mschap: Found MS-CHAP attributes. Setting 'Auth-Type = %s'", inst->xlat_name);
1034 * Set Auth-Type to MS-CHAP. The authentication code
1035 * will take care of turning clear-text passwords into
1038 vp = pairmake("Auth-Type", inst->auth_type, T_OP_EQ);
1039 if (!vp) return RLM_MODULE_FAIL;
1040 pairmove(&request->config_items, &vp);
1041 pairfree(&vp); /* may be NULL */
1043 return RLM_MODULE_OK;
1048 * mschap_authenticate() - authenticate user based on given
1049 * attributes and configuration.
1050 * We will try to find out password in configuration
1051 * or in configured passwd file.
1052 * If one is found we will check paraneters given by NAS.
1054 * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
1056 * PAP: PW_PASSWORD or
1057 * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
1058 * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
1059 * In case of password mismatch or locked account we MAY return
1060 * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
1061 * If MS-CHAP2 succeeds we MUST return
1062 * PW_MSCHAP2_SUCCESS
1064 static int mschap_authenticate(void * instance, REQUEST *request)
1066 #define inst ((rlm_mschap_t *)instance)
1067 VALUE_PAIR *challenge = NULL;
1068 VALUE_PAIR *response = NULL;
1069 VALUE_PAIR *password = NULL;
1070 VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
1071 VALUE_PAIR *username;
1072 VALUE_PAIR *reply_attr;
1073 uint8_t nthashhash[16];
1074 uint8_t msch2resp[42];
1075 char *username_string;
1079 * Find the SMB-Account-Ctrl attribute, or the
1080 * SMB-Account-Ctrl-Text attribute.
1082 smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL);
1084 password = pairfind(request->config_items,
1085 PW_SMB_ACCOUNT_CTRL_TEXT);
1087 smb_ctrl = pairmake("SMB-Account-CTRL", "0", T_OP_SET);
1088 pairadd(&request->config_items, smb_ctrl);
1089 smb_ctrl->lvalue = pdb_decode_acct_ctrl(password->strvalue);
1094 * We're configured to do MS-CHAP authentication.
1095 * and account control information exists. Enforce it.
1099 * Password is not required.
1101 if ((smb_ctrl->lvalue & ACB_PWNOTREQ) != 0) {
1102 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says no password is required.");
1103 return RLM_MODULE_OK;
1108 * Decide how to get the passwords.
1110 password = pairfind(request->config_items, PW_PASSWORD);
1113 * We need an LM-Password.
1115 lm_password = pairfind(request->config_items, PW_LM_PASSWORD);
1120 if ((lm_password->length == 16) ||
1121 ((lm_password->length == 32) &&
1122 (hex2bin(lm_password->strvalue,
1123 lm_password->strvalue, 16) == 16))) {
1124 DEBUG2(" rlm_mschap: Found LM-Password");
1125 lm_password->length = 16;
1128 radlog(L_ERR, "rlm_mschap: Invalid LM-Password");
1132 } else if (!password) {
1133 DEBUG2(" rlm_mschap: No User-Password configured. Cannot create LM-Password.");
1135 } else { /* there is a configured User-Password */
1136 lm_password = pairmake("LM-Password", "", T_OP_EQ);
1138 radlog(L_ERR, "No memory");
1140 smbdes_lmpwdhash(password->strvalue,
1141 lm_password->strvalue);
1142 lm_password->length = 16;
1143 pairadd(&request->config_items, lm_password);
1148 * We need an NT-Password.
1150 nt_password = pairfind(request->config_items, PW_NT_PASSWORD);
1152 if ((nt_password->length == 16) ||
1153 ((nt_password->length == 32) &&
1154 (hex2bin(nt_password->strvalue,
1155 nt_password->strvalue, 16) == 16))) {
1156 DEBUG2(" rlm_mschap: Found NT-Password");
1157 nt_password->length = 16;
1160 radlog(L_ERR, "rlm_mschap: Invalid NT-Password");
1163 } else if (!password) {
1164 DEBUG2(" rlm_mschap: No User-Password configured. Cannot create NT-Password.");
1166 } else { /* there is a configured User-Password */
1167 nt_password = pairmake("NT-Password", "", T_OP_EQ);
1169 radlog(L_ERR, "No memory");
1170 return RLM_MODULE_FAIL;
1172 ntpwdhash(nt_password->strvalue, password->strvalue);
1173 nt_password->length = 16;
1174 pairadd(&request->config_items, nt_password);
1178 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
1180 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request");
1181 return RLM_MODULE_REJECT;
1185 * We also require an MS-CHAP-Response.
1187 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
1190 * MS-CHAP-Response, means MS-CHAPv1
1196 * MS-CHAPv1 challenges are 8 octets.
1198 if (challenge->length < 8) {
1199 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1200 return RLM_MODULE_INVALID;
1204 * Responses are 50 octets.
1206 if (response->length < 50) {
1207 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1208 return RLM_MODULE_INVALID;
1212 * We are doing MS-CHAP. Calculate the MS-CHAP
1215 if (response->strvalue[1] & 0x01) {
1216 DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with NT-Password");
1217 password = nt_password;
1220 DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with LM-Password");
1221 password = lm_password;
1226 * Do the MS-CHAP authentication.
1228 if (do_mschap(inst, request, password, challenge->strvalue,
1229 response->strvalue + offset, nthashhash) < 0) {
1230 DEBUG2(" rlm_mschap: MS-CHAP-Response is incorrect.");
1231 add_reply(&request->reply->vps, *response->strvalue,
1232 "MS-CHAP-Error", "E=691 R=1", 9);
1233 return RLM_MODULE_REJECT;
1238 } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE)) != NULL) {
1239 uint8_t mschapv1_challenge[16];
1242 * MS-CHAPv2 challenges are 16 octets.
1244 if (challenge->length < 16) {
1245 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1246 return RLM_MODULE_INVALID;
1250 * Responses are 50 octets.
1252 if (response->length < 50) {
1253 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1254 return RLM_MODULE_INVALID;
1258 * We also require a User-Name
1260 username = pairfind(request->packet->vps, PW_USER_NAME);
1262 radlog(L_AUTH, "rlm_mschap: We require a User-Name for MS-CHAPv2");
1263 return RLM_MODULE_INVALID;
1268 * with_ntdomain_hack moved here
1270 if ((username_string = strchr(username->strvalue, '\\')) != NULL) {
1271 if (inst->with_ntdomain_hack) {
1274 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1275 username_string = username->strvalue;
1278 username_string = username->strvalue;
1282 * The old "mschapv2" function has been moved to
1285 * MS-CHAPv2 takes some additional data to create an
1286 * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1288 challenge_hash(response->strvalue + 2, /* peer challenge */
1289 challenge->strvalue, /* our challenge */
1290 username_string, /* user name */
1291 mschapv1_challenge); /* resulting challenge */
1293 DEBUG2(" rlm_mschap: Told to do MS-CHAPv2 for %s with NT-Password",
1296 if (do_mschap(inst, request, nt_password, mschapv1_challenge,
1297 response->strvalue + 26, nthashhash) < 0) {
1298 DEBUG2(" rlm_mschap: FAILED: MS-CHAP2-Response is incorrect");
1299 add_reply(&request->reply->vps, *response->strvalue,
1300 "MS-CHAP-Error", "E=691 R=1", 9);
1301 return RLM_MODULE_REJECT;
1305 * Get the NT-hash-hash, if necessary
1310 auth_response(username_string, /* without the domain */
1311 nthashhash, /* nt-hash-hash */
1312 response->strvalue + 26, /* peer response */
1313 response->strvalue + 2, /* peer challenge */
1314 challenge->strvalue, /* our challenge */
1315 msch2resp); /* calculated MPPE key */
1316 add_reply( &request->reply->vps, *response->strvalue,
1317 "MS-CHAP2-Success", msch2resp, 42);
1320 } else { /* Neither CHAPv1 or CHAPv2 response: die */
1321 radlog(L_AUTH, "rlm_mschap: No MS-CHAP response found");
1322 return RLM_MODULE_INVALID;
1326 * We have a CHAP response, but the account may be
1327 * disabled. Reject the user with the same error code
1328 * we use when their password is invalid.
1332 * Account is disabled.
1334 * They're found, but they don't exist, so we
1335 * return 'not found'.
1337 if (((smb_ctrl->lvalue & ACB_DISABLED) != 0) ||
1338 ((smb_ctrl->lvalue & ACB_NORMAL) == 0)) {
1339 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is disabled, or is not a normal account.");
1340 add_reply( &request->reply->vps, *response->strvalue,
1341 "MS-CHAP-Error", "E=691 R=1", 9);
1342 return RLM_MODULE_NOTFOUND;
1346 * User is locked out.
1348 if ((smb_ctrl->lvalue & ACB_AUTOLOCK) != 0) {
1349 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is locked out.");
1350 add_reply( &request->reply->vps, *response->strvalue,
1351 "MS-CHAP-Error", "E=647 R=0", 9);
1352 return RLM_MODULE_USERLOCK;
1356 /* now create MPPE attributes */
1357 if (inst->use_mppe) {
1358 uint8_t mppe_sendkey[34];
1359 uint8_t mppe_recvkey[34];
1362 DEBUG2("rlm_mschap: adding MS-CHAPv1 MPPE keys");
1363 memset(mppe_sendkey, 0, 32);
1365 memcpy(mppe_sendkey, lm_password->strvalue, 8);
1369 * According to RFC 2548 we
1370 * should send NT hash. But in
1371 * practice it doesn't work.
1372 * Instead, we should send nthashhash
1374 * This is an error on RFC 2548.
1377 * do_mschap cares to zero nthashhash if NT hash
1380 memcpy(mppe_sendkey + 8,
1382 mppe_add_reply(&request->reply->vps,
1383 "MS-CHAP-MPPE-Keys",
1385 } else if (chap == 2) {
1386 DEBUG2("rlm_mschap: adding MS-CHAPv2 MPPE keys");
1387 mppe_chap2_gen_keys128(nthashhash,
1388 response->strvalue + 26,
1389 mppe_sendkey, mppe_recvkey);
1391 mppe_add_reply(&request->reply->vps,
1394 mppe_add_reply(&request->reply->vps,
1399 reply_attr = pairmake("MS-MPPE-Encryption-Policy",
1400 (inst->require_encryption)? "0x00000002":"0x00000001",
1402 rad_assert(reply_attr != NULL);
1403 pairadd(&request->reply->vps, reply_attr);
1404 reply_attr = pairmake("MS-MPPE-Encryption-Types",
1405 (inst->require_strong)? "0x00000004":"0x00000006",
1407 rad_assert(reply_attr != NULL);
1408 pairadd(&request->reply->vps, reply_attr);
1410 } /* else we weren't asked to use MPPE */
1412 return RLM_MODULE_OK;
1416 module_t rlm_mschap = {
1418 RLM_TYPE_THREAD_SAFE, /* type */
1419 NULL, /* initialize */
1420 mschap_instantiate, /* instantiation */
1422 mschap_authenticate, /* authenticate */
1423 mschap_authorize, /* authorize */
1424 NULL, /* pre-accounting */
1425 NULL, /* accounting */
1426 NULL, /* checksimul */
1427 NULL, /* pre-proxy */
1428 NULL, /* post-proxy */
1429 NULL /* post-auth */
1431 mschap_detach, /* detach */