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 (uint8_t *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 fr_md4_calc(szHash, (uint8_t *) 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 )
186 fr_SHA1Init(&Context);
187 fr_SHA1Update(&Context, peer_challenge, 16);
188 fr_SHA1Update(&Context, auth_challenge, 16);
189 fr_SHA1Update(&Context, (const uint8_t *) user_name,
191 fr_SHA1Final(hash, &Context);
192 memcpy(challenge, hash, 8);
196 * auth_response() generates MS-CHAP v2 SUCCESS response
197 * according to RFC 2759 GenerateAuthenticatorResponse()
198 * returns 42-octet response string
200 static void auth_response(const char *username,
201 const uint8_t *nt_hash_hash,
203 uint8_t *peer_challenge, uint8_t *auth_challenge,
207 static const uint8_t magic1[39] =
208 {0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
209 0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
210 0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
211 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74};
213 static const uint8_t magic2[41] =
214 {0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
215 0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
216 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
217 0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
220 static const char hex[16] = "0123456789ABCDEF";
223 uint8_t challenge[8];
226 fr_SHA1Init(&Context);
227 fr_SHA1Update(&Context, nt_hash_hash, 16);
228 fr_SHA1Update(&Context, ntresponse, 24);
229 fr_SHA1Update(&Context, magic1, 39);
230 fr_SHA1Final(digest, &Context);
231 challenge_hash(peer_challenge, auth_challenge, username, challenge);
232 fr_SHA1Init(&Context);
233 fr_SHA1Update(&Context, digest, 20);
234 fr_SHA1Update(&Context, challenge, 8);
235 fr_SHA1Update(&Context, magic2, 41);
236 fr_SHA1Final(digest, &Context);
239 * Encode the value of 'Digest' as "S=" followed by
240 * 40 ASCII hexadecimal digits and return it in
241 * AuthenticatorResponse.
243 * "S=0123456789ABCDEF0123456789ABCDEF01234567"
249 * The hexadecimal digits [A-F] MUST be uppercase.
251 for (i = 0; i < sizeof(digest); i++) {
252 response[2 + (i * 2)] = hex[(digest[i] >> 4) & 0x0f];
253 response[3 + (i * 2)] = hex[digest[i] & 0x0f];
258 typedef struct rlm_mschap_t {
260 int require_encryption;
262 int with_ntdomain_hack; /* this should be in another module */
264 const char *xlat_name;
266 const char *auth_type;
274 * Does dynamic translation of strings.
276 * Pulls NT-Response, LM-Response, or Challenge from MSCHAP
279 static size_t mschap_xlat(void *instance, REQUEST *request,
280 char *fmt, char *out, size_t outlen,
281 RADIUS_ESCAPE_STRING func)
284 uint8_t *data = NULL;
286 VALUE_PAIR *user_name;
287 VALUE_PAIR *chap_challenge, *response;
288 rlm_mschap_t *inst = instance;
290 chap_challenge = response = NULL;
292 func = func; /* -Wunused */
295 * Challenge means MS-CHAPv1 challenge, or
296 * hash of MS-CHAPv2 challenge, and peer challenge.
298 if (strncasecmp(fmt, "Challenge", 9) == 0) {
299 chap_challenge = pairfind(request->packet->vps,
300 PW_MSCHAP_CHALLENGE);
301 if (!chap_challenge) {
302 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request.");
307 * MS-CHAP-Challenges are 8 octets,
310 if (chap_challenge->length == 8) {
311 DEBUG2(" mschap1: %02x",
312 chap_challenge->vp_octets[0]);
313 data = chap_challenge->vp_octets;
317 * MS-CHAP-Challenges are 16 octets,
320 } else if (chap_challenge->length == 16) {
321 char *username_string;
323 DEBUG2(" mschap2: %02x", chap_challenge->vp_octets[0]);
324 response = pairfind(request->packet->vps,
325 PW_MSCHAP2_RESPONSE);
327 DEBUG2(" rlm_mschap: MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge.");
332 * Responses are 50 octets.
334 if (response->length < 50) {
335 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
339 user_name = pairfind(request->packet->vps,
342 DEBUG2(" rlm_mschap: User-Name is required to calculateMS-CHAPv1 Challenge.");
347 * with_ntdomain_hack moved here, too.
349 if ((username_string = strchr(user_name->vp_strvalue, '\\')) != NULL) {
350 if (inst->with_ntdomain_hack) {
353 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
354 username_string = user_name->vp_strvalue;
357 username_string = user_name->vp_strvalue;
361 * Get the MS-CHAPv1 challenge
362 * from the MS-CHAPv2 peer challenge,
363 * our challenge, and the user name.
365 challenge_hash(response->vp_octets + 2,
366 chap_challenge->vp_octets,
367 username_string, buffer);
371 DEBUG2(" rlm_mschap: Invalid MS-CHAP challenge length");
376 * Get the MS-CHAPv1 response, or the MS-CHAPv2
379 } else if (strncasecmp(fmt, "NT-Response", 11) == 0) {
380 response = pairfind(request->packet->vps,
382 if (!response) response = pairfind(request->packet->vps,
383 PW_MSCHAP2_RESPONSE);
385 DEBUG2(" rlm_mschap: No MS-CHAP-Response or MS-CHAP2-Response was found in the request.");
390 * For MS-CHAPv1, the NT-Response exists only
391 * if the second octet says so.
393 if ((response->attribute == PW_MSCHAP_RESPONSE) &&
394 ((response->vp_octets[1] & 0x01) == 0)) {
395 DEBUG2(" rlm_mschap: No NT-Response in MS-CHAP-Response");
400 * MS-CHAP-Response and MS-CHAP2-Response have
401 * the NT-Response at the same offset, and are
404 data = response->vp_octets + 26;
408 * LM-Response is deprecated, and exists only
409 * in MS-CHAPv1, and not often there.
411 } else if (strncasecmp(fmt, "LM-Response", 11) == 0) {
412 response = pairfind(request->packet->vps,
415 DEBUG2(" rlm_mschap: No MS-CHAP-Response was found in the request.");
420 * For MS-CHAPv1, the NT-Response exists only
421 * if the second octet says so.
423 if ((response->vp_octets[1] & 0x01) != 0) {
424 DEBUG2(" rlm_mschap: No LM-Response in MS-CHAP-Response");
427 data = response->vp_octets + 2;
431 * Pull the NT-Domain out of the User-Name, if it exists.
433 } else if (strncasecmp(fmt, "NT-Domain", 9) == 0) {
436 user_name = pairfind(request->packet->vps, PW_USER_NAME);
438 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
443 * First check to see if this is a host/ style User-Name
444 * (a la Kerberos host principal)
446 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
448 * If we're getting a User-Name formatted in this way,
449 * it's likely due to PEAP. The Windows Domain will be
450 * the first domain component following the hostname,
451 * or the machine name itself if only a hostname is supplied
453 p = strchr(user_name->vp_strvalue, '.');
455 DEBUG2(" rlm_mschap: setting NT-Domain to same as machine name");
456 strlcpy(out, user_name->vp_strvalue + 5, outlen);
458 p++; /* skip the period */
461 * use the same hack as below
462 * only if another period was found
465 strlcpy(out, p, outlen);
469 p = strchr(user_name->vp_strvalue, '\\');
471 DEBUG2(" rlm_mschap: No NT-Domain was found in the User-Name.");
476 * Hack. This is simpler than the alternatives.
479 strlcpy(out, user_name->vp_strvalue, outlen);
486 * Pull the User-Name out of the User-Name...
488 } else if (strncasecmp(fmt, "User-Name", 9) == 0) {
491 user_name = pairfind(request->packet->vps, PW_USER_NAME);
493 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
498 * First check to see if this is a host/ style User-Name
499 * (a la Kerberos host principal)
501 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
503 * If we're getting a User-Name formatted in this way,
504 * it's likely due to PEAP. When authenticating this against
505 * a Domain, Windows will expect the User-Name to be in the
506 * format of hostname$, the SAM version of the name, so we
507 * have to convert it to that here. We do so by stripping
508 * off the first 5 characters (host/), and copying everything
509 * from that point to the first period into a string and appending
512 p = strchr(user_name->vp_strvalue, '.');
514 * use the same hack as above
515 * only if a period was found
518 snprintf(out, outlen, "%s$", user_name->vp_strvalue + 5);
521 p = strchr(user_name->vp_strvalue, '\\');
523 p++; /* skip the backslash */
525 p = user_name->vp_strvalue; /* use the whole User-Name */
527 strlcpy(out, p, outlen);
533 * Return the NT-Hash of the passed string
535 } else if (strncasecmp(fmt, "NT-Hash ", 8) == 0) {
538 p = fmt + 8; /* 7 is the length of 'NT-Hash' */
539 if ((p == '\0') || (outlen <= 32))
541 DEBUG("rlm_mschap: NT-Hash: %s",p);
544 fr_bin2hex(buffer, out, 16);
546 DEBUG("rlm_mschap: NT-Hash: Result: %s",out);
550 * Return the LM-Hash of the passed string
552 } else if (strncasecmp(fmt, "LM-Hash ", 8) == 0) {
555 p = fmt + 8; /* 7 is the length of 'LM-Hash' */
556 if ((p == '\0') || (outlen <= 32))
559 DEBUG("rlm_mschap: LM-Hash: %s",p);
560 smbdes_lmpwdhash(p, buffer);
561 fr_bin2hex(buffer, out, 16);
563 DEBUG("rlm_mschap: LM-Hash: Result: %s",out);
566 DEBUG2(" rlm_mschap: Unknown expansion string \"%s\"",
571 if (outlen == 0) return 0; /* nowhere to go, don't do anything */
574 * Didn't set anything: this is bad.
577 DEBUG2(" rlm_mschap: Failed to do anything intelligent");
582 * Check the output length.
584 if (outlen < ((data_len * 2) + 1)) {
585 data_len = (outlen - 1) / 2;
591 for (i = 0; i < data_len; i++) {
592 sprintf(out + (2 * i), "%02x", data[i]);
594 out[data_len * 2] = '\0';
600 static const CONF_PARSER module_config[] = {
602 * Cache the password by default.
604 { "use_mppe", PW_TYPE_BOOLEAN,
605 offsetof(rlm_mschap_t,use_mppe), NULL, "yes" },
606 { "require_encryption", PW_TYPE_BOOLEAN,
607 offsetof(rlm_mschap_t,require_encryption), NULL, "no" },
608 { "require_strong", PW_TYPE_BOOLEAN,
609 offsetof(rlm_mschap_t,require_strong), NULL, "no" },
610 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
611 offsetof(rlm_mschap_t,with_ntdomain_hack), NULL, "no" },
612 { "passwd", PW_TYPE_STRING_PTR,
613 offsetof(rlm_mschap_t, passwd_file), NULL, NULL },
614 { "ntlm_auth", PW_TYPE_STRING_PTR,
615 offsetof(rlm_mschap_t, ntlm_auth), NULL, NULL },
617 { "use_open_directory", PW_TYPE_BOOLEAN,
618 offsetof(rlm_mschap_t,open_directory), NULL, "yes" },
621 { NULL, -1, 0, NULL, NULL } /* end the list */
625 * deinstantiate module, free all memory allocated during
626 * mschap_instantiate()
628 static int mschap_detach(void *instance){
629 #define inst ((rlm_mschap_t *)instance)
630 if (inst->xlat_name) {
631 xlat_unregister(inst->xlat_name, mschap_xlat);
639 * Create instance for our module. Allocate space for
640 * instance structure and read configuration parameters
642 static int mschap_instantiate(CONF_SECTION *conf, void **instance)
646 inst = *instance = rad_malloc(sizeof(*inst));
650 memset(inst, 0, sizeof(*inst));
652 if (cf_section_parse(conf, inst, module_config) < 0) {
658 * This module used to support SMB Password files, but it
659 * made it too complicated. If the user tries to
660 * configure an SMB Password file, then die, with an
663 if (inst->passwd_file) {
664 radlog(L_ERR, "rlm_mschap: SMB password file is no longer supported in this module. Use rlm_passwd module instead");
670 * Create the dynamic translation.
672 inst->xlat_name = cf_section_name2(conf);
673 if (!inst->xlat_name) inst->xlat_name = cf_section_name1(conf);
674 xlat_register(inst->xlat_name, mschap_xlat, inst);
677 * For backwards compatibility
679 if (!dict_valbyname(PW_AUTH_TYPE, inst->xlat_name)) {
680 inst->auth_type = "MS-CHAP";
682 inst->auth_type = inst->xlat_name;
689 * add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
690 * attribute to reply packet
692 void mschap_add_reply(VALUE_PAIR** vp, unsigned char ident,
693 const char* name, const char* value, int len)
695 VALUE_PAIR *reply_attr;
696 reply_attr = pairmake(name, "", T_OP_EQ);
698 DEBUG(" rlm_mschap: Failed to create attribute %s: %s\n", name, librad_errstr);
702 reply_attr->vp_octets[0] = ident;
703 memcpy(reply_attr->vp_octets + 1, value, len);
704 reply_attr->length = len + 1;
705 pairadd(vp, reply_attr);
709 * Add MPPE attributes to the reply.
711 static void mppe_add_reply(VALUE_PAIR **vp,
712 const char* name, const uint8_t * value, int len)
714 VALUE_PAIR *reply_attr;
715 reply_attr = pairmake(name, "", T_OP_EQ);
717 DEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, librad_errstr);
721 memcpy(reply_attr->vp_octets, value, len);
722 reply_attr->length = len;
723 pairadd(vp, reply_attr);
728 * Do the MS-CHAP stuff.
730 * This function is here so that all of the MS-CHAP related
731 * authentication is in one place, and we can perhaps later replace
732 * it with code to call winbindd, or something similar.
734 static int do_mschap(rlm_mschap_t *inst,
735 REQUEST *request, VALUE_PAIR *password,
736 uint8_t *challenge, uint8_t *response,
739 int do_ntlm_auth = 0;
740 uint8_t calculated[24];
741 VALUE_PAIR *vp = NULL;
744 * If we have ntlm_auth configured, use it unless told
747 if (inst->ntlm_auth) do_ntlm_auth = 1;
750 * If we have an ntlm_auth configuration, then we may
753 vp = pairfind(request->config_items,
754 PW_MS_CHAP_USE_NTLM_AUTH);
755 if (vp) do_ntlm_auth = vp->vp_integer;
758 * No ntlm_auth configured, attribute to tell us to
759 * use it exists, and we're told to use it. We don't
762 if (!inst->ntlm_auth && do_ntlm_auth) {
763 DEBUG2(" rlm_mschap: Asked to use ntlm_auth, but it was not configured in the mschap{} section.");
768 * Do normal authentication.
772 * No password: can't do authentication.
775 DEBUG2(" rlm_mschap: FAILED: No NT/LM-Password. Cannot perform authentication.");
779 smbdes_mschap(password->vp_strvalue, challenge, calculated);
780 if (memcmp(response, calculated, 24) != 0) {
785 * If the password exists, and is an NT-Password,
786 * then calculate the hash of the NT hash. Doing this
787 * here minimizes work for later.
789 if (password && (password->attribute == PW_NT_PASSWORD)) {
790 fr_md4_calc(nthashhash, password->vp_octets, 16);
792 memset(nthashhash, 0, 16);
794 } else { /* run ntlm_auth */
798 memset(nthashhash, 0, 16);
801 * Run the program, and expect that we get 16
803 result = radius_exec_program(inst->ntlm_auth, request,
805 buffer, sizeof(buffer),
808 DEBUG2(" rlm_mschap: External script failed.");
813 * Parse the answer as an nthashhash.
815 * ntlm_auth currently returns:
816 * NT_KEY: 000102030405060708090a0b0c0d0e0f
818 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
819 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: expecting NT_KEY");
824 * Check the length. It should be at least 32,
825 * with an LF at the end.
827 if (strlen(buffer + 8) < 32) {
828 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has unexpected length");
833 * Update the NT hash hash, from the NT key.
835 if (fr_hex2bin(buffer + 8, nthashhash, 16) != 16) {
836 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has non-hex values");
846 * Data for the hashes.
848 static const uint8_t SHSpad1[40] =
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,
852 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
854 static const uint8_t SHSpad2[40] =
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,
858 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
860 static const uint8_t magic1[27] =
861 { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
862 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
863 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
865 static const uint8_t magic2[84] =
866 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
867 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
868 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
869 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
870 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
871 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
872 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
873 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
874 0x6b, 0x65, 0x79, 0x2e };
876 static const uint8_t magic3[84] =
877 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
878 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
879 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
880 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
881 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
882 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
883 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
884 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
885 0x6b, 0x65, 0x79, 0x2e };
888 static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response,
894 fr_SHA1Init(&Context);
895 fr_SHA1Update(&Context,nt_hashhash,16);
896 fr_SHA1Update(&Context,nt_response,24);
897 fr_SHA1Update(&Context,magic1,27);
898 fr_SHA1Final(digest,&Context);
900 memcpy(masterkey,digest,16);
904 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
905 int keylen,int issend)
919 fr_SHA1Init(&Context);
920 fr_SHA1Update(&Context,masterkey,16);
921 fr_SHA1Update(&Context,SHSpad1,40);
922 fr_SHA1Update(&Context,s,84);
923 fr_SHA1Update(&Context,SHSpad2,40);
924 fr_SHA1Final(digest,&Context);
926 memcpy(sesskey,digest,keylen);
930 static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response,
931 uint8_t *sendkey,uint8_t *recvkey)
933 uint8_t masterkey[16];
935 mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
937 mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
938 mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
942 * Generate MPPE keys.
944 static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response,
945 uint8_t *sendkey,uint8_t *recvkey)
950 mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
953 * dictionary.microsoft defines these attributes as
954 * 'encrypt=2'. The functions in src/lib/radius.c will
955 * take care of encrypting/decrypting them as appropriate,
956 * so that we don't have to.
958 memcpy (sendkey, enckey1, 16);
959 memcpy (recvkey, enckey2, 16);
964 * mschap_authorize() - authorize user if we can authenticate
965 * it later. Add Auth-Type attribute if present in module
966 * configuration (usually Auth-Type must be "MS-CHAP")
968 static int mschap_authorize(void * instance, REQUEST *request)
970 #define inst ((rlm_mschap_t *)instance)
971 VALUE_PAIR *challenge = NULL, *response = NULL;
974 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
976 return RLM_MODULE_NOOP;
979 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
981 response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE);
984 * Nothing we recognize. Don't do anything.
987 DEBUG2(" rlm_mschap: Found MS-CHAP-Challenge, but no MS-CHAP-Response.");
988 return RLM_MODULE_NOOP;
991 DEBUG2(" rlm_mschap: Found MS-CHAP attributes. Setting 'Auth-Type = %s'", inst->xlat_name);
994 * Set Auth-Type to MS-CHAP. The authentication code
995 * will take care of turning clear-text passwords into
998 vp = pairmake("Auth-Type", inst->auth_type, T_OP_EQ);
999 if (!vp) return RLM_MODULE_FAIL;
1000 pairmove(&request->config_items, &vp);
1001 pairfree(&vp); /* may be NULL */
1003 return RLM_MODULE_OK;
1008 * mschap_authenticate() - authenticate user based on given
1009 * attributes and configuration.
1010 * We will try to find out password in configuration
1011 * or in configured passwd file.
1012 * If one is found we will check paraneters given by NAS.
1014 * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
1016 * PAP: PW_USER_PASSWORD or
1017 * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
1018 * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
1019 * In case of password mismatch or locked account we MAY return
1020 * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
1021 * If MS-CHAP2 succeeds we MUST return
1022 * PW_MSCHAP2_SUCCESS
1024 static int mschap_authenticate(void * instance, REQUEST *request)
1026 #define inst ((rlm_mschap_t *)instance)
1027 VALUE_PAIR *challenge = NULL;
1028 VALUE_PAIR *response = NULL;
1029 VALUE_PAIR *password = NULL;
1030 VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
1031 VALUE_PAIR *username;
1032 VALUE_PAIR *reply_attr;
1033 uint8_t nthashhash[16];
1035 char *username_string;
1039 * Find the SMB-Account-Ctrl attribute, or the
1040 * SMB-Account-Ctrl-Text attribute.
1042 smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL);
1044 password = pairfind(request->config_items,
1045 PW_SMB_ACCOUNT_CTRL_TEXT);
1047 smb_ctrl = pairmake("SMB-Account-CTRL", "0", T_OP_SET);
1048 pairadd(&request->config_items, smb_ctrl);
1049 smb_ctrl->vp_integer = pdb_decode_acct_ctrl(password->vp_strvalue);
1054 * We're configured to do MS-CHAP authentication.
1055 * and account control information exists. Enforce it.
1059 * Password is not required.
1061 if ((smb_ctrl->vp_integer & ACB_PWNOTREQ) != 0) {
1062 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says no password is required.");
1063 return RLM_MODULE_OK;
1068 * Decide how to get the passwords.
1070 password = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD);
1073 * We need an LM-Password.
1075 lm_password = pairfind(request->config_items, PW_LM_PASSWORD);
1080 if ((lm_password->length == 16) ||
1081 ((lm_password->length == 32) &&
1082 (fr_hex2bin(lm_password->vp_strvalue,
1083 lm_password->vp_octets, 16) == 16))) {
1084 DEBUG2(" rlm_mschap: Found LM-Password");
1085 lm_password->length = 16;
1088 radlog(L_ERR, "rlm_mschap: Invalid LM-Password");
1092 } else if (!password) {
1093 DEBUG2(" rlm_mschap: No Cleartext-Password configured. Cannot create LM-Password.");
1095 } else { /* there is a configured Cleartext-Password */
1096 lm_password = pairmake("LM-Password", "", T_OP_EQ);
1098 radlog(L_ERR, "No memory");
1100 smbdes_lmpwdhash(password->vp_strvalue,
1101 lm_password->vp_octets);
1102 lm_password->length = 16;
1103 pairadd(&request->config_items, lm_password);
1108 * We need an NT-Password.
1110 nt_password = pairfind(request->config_items, PW_NT_PASSWORD);
1112 if ((nt_password->length == 16) ||
1113 ((nt_password->length == 32) &&
1114 (fr_hex2bin(nt_password->vp_strvalue,
1115 nt_password->vp_octets, 16) == 16))) {
1116 DEBUG2(" rlm_mschap: Found NT-Password");
1117 nt_password->length = 16;
1120 radlog(L_ERR, "rlm_mschap: Invalid NT-Password");
1123 } else if (!password) {
1124 DEBUG2(" rlm_mschap: No Cleartext-Password configured. Cannot create NT-Password.");
1126 } else { /* there is a configured Cleartext-Password */
1127 nt_password = pairmake("NT-Password", "", T_OP_EQ);
1129 radlog(L_ERR, "No memory");
1130 return RLM_MODULE_FAIL;
1132 ntpwdhash(nt_password->vp_octets,
1133 password->vp_strvalue);
1134 nt_password->length = 16;
1135 pairadd(&request->config_items, nt_password);
1139 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
1141 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request");
1142 return RLM_MODULE_REJECT;
1146 * We also require an MS-CHAP-Response.
1148 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
1151 * MS-CHAP-Response, means MS-CHAPv1
1157 * MS-CHAPv1 challenges are 8 octets.
1159 if (challenge->length < 8) {
1160 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1161 return RLM_MODULE_INVALID;
1165 * Responses are 50 octets.
1167 if (response->length < 50) {
1168 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1169 return RLM_MODULE_INVALID;
1173 * We are doing MS-CHAP. Calculate the MS-CHAP
1176 if (response->vp_octets[1] & 0x01) {
1177 DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with NT-Password");
1178 password = nt_password;
1181 DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with LM-Password");
1182 password = lm_password;
1187 * Do the MS-CHAP authentication.
1189 if (do_mschap(inst, request, password, challenge->vp_octets,
1190 response->vp_octets + offset, nthashhash) < 0) {
1191 DEBUG2(" rlm_mschap: MS-CHAP-Response is incorrect.");
1192 mschap_add_reply(&request->reply->vps,
1193 *response->vp_octets,
1194 "MS-CHAP-Error", "E=691 R=1", 9);
1195 return RLM_MODULE_REJECT;
1200 } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE)) != NULL) {
1201 uint8_t mschapv1_challenge[16];
1204 * MS-CHAPv2 challenges are 16 octets.
1206 if (challenge->length < 16) {
1207 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1208 return RLM_MODULE_INVALID;
1212 * Responses are 50 octets.
1214 if (response->length < 50) {
1215 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1216 return RLM_MODULE_INVALID;
1220 * We also require a User-Name
1222 username = pairfind(request->packet->vps, PW_USER_NAME);
1224 radlog(L_AUTH, "rlm_mschap: We require a User-Name for MS-CHAPv2");
1225 return RLM_MODULE_INVALID;
1230 * with_ntdomain_hack moved here
1232 if ((username_string = strchr(username->vp_strvalue, '\\')) != NULL) {
1233 if (inst->with_ntdomain_hack) {
1236 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1237 username_string = username->vp_strvalue;
1240 username_string = username->vp_strvalue;
1245 * No "known good" NT-Password attribute. Try to do
1246 * OpenDirectory authentication.
1248 if (!nt_password && inst->open_directory) {
1249 DEBUG2(" rlm_mschap: No NT-Password configured. Trying DirectoryService Authentication.");
1250 return od_mschap_auth(request, challenge, username);
1254 * The old "mschapv2" function has been moved to
1257 * MS-CHAPv2 takes some additional data to create an
1258 * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1260 challenge_hash(response->vp_octets + 2, /* peer challenge */
1261 challenge->vp_octets, /* our challenge */
1262 username_string, /* user name */
1263 mschapv1_challenge); /* resulting challenge */
1265 DEBUG2(" rlm_mschap: Told to do MS-CHAPv2 for %s with NT-Password",
1268 if (do_mschap(inst, request, nt_password, mschapv1_challenge,
1269 response->vp_octets + 26, nthashhash) < 0) {
1270 DEBUG2(" rlm_mschap: FAILED: MS-CHAP2-Response is incorrect");
1271 mschap_add_reply(&request->reply->vps,
1272 *response->vp_octets,
1273 "MS-CHAP-Error", "E=691 R=1", 9);
1274 return RLM_MODULE_REJECT;
1278 * Get the NT-hash-hash, if necessary
1283 auth_response(username_string, /* without the domain */
1284 nthashhash, /* nt-hash-hash */
1285 response->vp_octets + 26, /* peer response */
1286 response->vp_octets + 2, /* peer challenge */
1287 challenge->vp_octets, /* our challenge */
1288 msch2resp); /* calculated MPPE key */
1289 mschap_add_reply(&request->reply->vps, *response->vp_octets,
1290 "MS-CHAP2-Success", msch2resp, 42);
1293 } else { /* Neither CHAPv1 or CHAPv2 response: die */
1294 radlog(L_AUTH, "rlm_mschap: No MS-CHAP response found");
1295 return RLM_MODULE_INVALID;
1299 * We have a CHAP response, but the account may be
1300 * disabled. Reject the user with the same error code
1301 * we use when their password is invalid.
1305 * Account is disabled.
1307 * They're found, but they don't exist, so we
1308 * return 'not found'.
1310 if (((smb_ctrl->vp_integer & ACB_DISABLED) != 0) ||
1311 ((smb_ctrl->vp_integer & ACB_NORMAL) == 0)) {
1312 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is disabled, or is not a normal account.");
1313 mschap_add_reply( &request->reply->vps,
1314 *response->vp_octets,
1315 "MS-CHAP-Error", "E=691 R=1", 9);
1316 return RLM_MODULE_NOTFOUND;
1320 * User is locked out.
1322 if ((smb_ctrl->vp_integer & ACB_AUTOLOCK) != 0) {
1323 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is locked out.");
1324 mschap_add_reply( &request->reply->vps,
1325 *response->vp_octets,
1326 "MS-CHAP-Error", "E=647 R=0", 9);
1327 return RLM_MODULE_USERLOCK;
1331 /* now create MPPE attributes */
1332 if (inst->use_mppe) {
1333 uint8_t mppe_sendkey[34];
1334 uint8_t mppe_recvkey[34];
1337 DEBUG2("rlm_mschap: adding MS-CHAPv1 MPPE keys");
1338 memset(mppe_sendkey, 0, 32);
1340 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1344 * According to RFC 2548 we
1345 * should send NT hash. But in
1346 * practice it doesn't work.
1347 * Instead, we should send nthashhash
1349 * This is an error on RFC 2548.
1352 * do_mschap cares to zero nthashhash if NT hash
1355 memcpy(mppe_sendkey + 8,
1357 mppe_add_reply(&request->reply->vps,
1358 "MS-CHAP-MPPE-Keys",
1360 } else if (chap == 2) {
1361 DEBUG2("rlm_mschap: adding MS-CHAPv2 MPPE keys");
1362 mppe_chap2_gen_keys128(nthashhash,
1363 response->vp_octets + 26,
1364 mppe_sendkey, mppe_recvkey);
1366 mppe_add_reply(&request->reply->vps,
1369 mppe_add_reply(&request->reply->vps,
1374 reply_attr = pairmake("MS-MPPE-Encryption-Policy",
1375 (inst->require_encryption)? "0x00000002":"0x00000001",
1377 rad_assert(reply_attr != NULL);
1378 pairadd(&request->reply->vps, reply_attr);
1379 reply_attr = pairmake("MS-MPPE-Encryption-Types",
1380 (inst->require_strong)? "0x00000004":"0x00000006",
1382 rad_assert(reply_attr != NULL);
1383 pairadd(&request->reply->vps, reply_attr);
1385 } /* else we weren't asked to use MPPE */
1387 return RLM_MODULE_OK;
1391 module_t rlm_mschap = {
1394 RLM_TYPE_THREAD_SAFE, /* type */
1395 mschap_instantiate, /* instantiation */
1396 mschap_detach, /* detach */
1398 mschap_authenticate, /* authenticate */
1399 mschap_authorize, /* authorize */
1400 NULL, /* pre-accounting */
1401 NULL, /* accounting */
1402 NULL, /* checksimul */
1403 NULL, /* pre-proxy */
1404 NULL, /* post-proxy */
1405 NULL /* post-auth */