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/autoconf.h>
55 #include <freeradius-devel/radiusd.h>
56 #include <freeradius-devel/modules.h>
58 #include <freeradius-devel/md5.h>
59 #include <freeradius-devel/rad_assert.h>
63 /* Allowable account control bits */
64 #define ACB_DISABLED 0x0001 /* 1 = User account disabled */
65 #define ACB_HOMDIRREQ 0x0002 /* 1 = Home directory required */
66 #define ACB_PWNOTREQ 0x0004 /* 1 = User password not required */
67 #define ACB_TEMPDUP 0x0008 /* 1 = Temporary duplicate account */
68 #define ACB_NORMAL 0x0010 /* 1 = Normal user account */
69 #define ACB_MNS 0x0020 /* 1 = MNS logon user account */
70 #define ACB_DOMTRUST 0x0040 /* 1 = Interdomain trust account */
71 #define ACB_WSTRUST 0x0080 /* 1 = Workstation trust account */
72 #define ACB_SVRTRUST 0x0100 /* 1 = Server trust account */
73 #define ACB_PWNOEXP 0x0200 /* 1 = User password does not expire */
74 #define ACB_AUTOLOCK 0x0400 /* 1 = Account auto locked */
76 static int pdb_decode_acct_ctrl(const char *p)
82 * Check if the account type bits have been encoded after the
83 * NT password (in the form [NDHTUWSLXI]).
86 if (*p != '[') return 0;
88 for (p++; *p && !finished; p++) {
90 case 'N': /* 'N'o password. */
91 acct_ctrl |= ACB_PWNOTREQ;
94 case 'D': /* 'D'isabled. */
95 acct_ctrl |= ACB_DISABLED ;
98 case 'H': /* 'H'omedir required. */
99 acct_ctrl |= ACB_HOMDIRREQ;
102 case 'T': /* 'T'emp account. */
103 acct_ctrl |= ACB_TEMPDUP;
106 case 'U': /* 'U'ser account (normal). */
107 acct_ctrl |= ACB_NORMAL;
110 case 'M': /* 'M'NS logon user account. What is this? */
111 acct_ctrl |= ACB_MNS;
114 case 'W': /* 'W'orkstation account. */
115 acct_ctrl |= ACB_WSTRUST;
118 case 'S': /* 'S'erver account. */
119 acct_ctrl |= ACB_SVRTRUST;
122 case 'L': /* 'L'ocked account. */
123 acct_ctrl |= ACB_AUTOLOCK;
126 case 'X': /* No 'X'piry on password */
127 acct_ctrl |= ACB_PWNOEXP;
130 case 'I': /* 'I'nterdomain trust account. */
131 acct_ctrl |= ACB_DOMTRUST;
134 case ' ': /* ignore spaces */
152 * ntpwdhash converts Unicode password to 16-byte NT hash
155 static void ntpwdhash (unsigned char *szHash, const char *szPassword)
157 char szUnicodePass[513];
162 * NT passwords are unicode. Convert plain text password
163 * to unicode by inserting a zero every other byte
165 nPasswordLen = strlen(szPassword);
166 for (i = 0; i < nPasswordLen; i++) {
167 szUnicodePass[i << 1] = szPassword[i];
168 szUnicodePass[(i << 1) + 1] = 0;
171 /* Encrypt Unicode password to a 16-byte MD4 hash */
172 md4_calc(szHash, szUnicodePass, (nPasswordLen<<1) );
177 * challenge_hash() is used by mschap2() and auth_response()
178 * implements RFC2759 ChallengeHash()
179 * generates 64 bit challenge
181 static void challenge_hash( const uint8_t *peer_challenge,
182 const uint8_t *auth_challenge,
183 const char *user_name, uint8_t *challenge )
189 SHA1Update(&Context, peer_challenge, 16);
190 SHA1Update(&Context, auth_challenge, 16);
191 SHA1Update(&Context, user_name, strlen(user_name));
192 SHA1Final(hash, &Context);
193 memcpy(challenge, hash, 8);
197 * auth_response() generates MS-CHAP v2 SUCCESS response
198 * according to RFC 2759 GenerateAuthenticatorResponse()
199 * returns 42-octet response string
201 static void auth_response(const char *username,
202 const uint8_t *nt_hash_hash,
204 char *peer_challenge, char *auth_challenge,
208 const uint8_t magic1[39] =
209 {0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
210 0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
211 0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
212 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74};
214 const uint8_t magic2[41] =
215 {0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
216 0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
217 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
218 0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
225 SHA1Update(&Context, nt_hash_hash, 16);
226 SHA1Update(&Context, ntresponse, 24);
227 SHA1Update(&Context, magic1, 39);
228 SHA1Final(digest, &Context);
229 challenge_hash(peer_challenge, auth_challenge, username, challenge);
231 SHA1Update(&Context, digest, 20);
232 SHA1Update(&Context, challenge, 8);
233 SHA1Update(&Context, magic2, 41);
234 SHA1Final(digest, &Context);
237 * Encode the value of 'Digest' as "S=" followed by
238 * 40 ASCII hexadecimal digits and return it in
239 * AuthenticatorResponse.
241 * "S=0123456789ABCDEF0123456789ABCDEF01234567"
245 lrad_bin2hex(digest, response + 2, 20);
249 typedef struct rlm_mschap_t {
251 int require_encryption;
253 int with_ntdomain_hack; /* this should be in another module */
262 * Does dynamic translation of strings.
264 * Pulls NT-Response, LM-Response, or Challenge from MSCHAP
267 static int mschap_xlat(void *instance, REQUEST *request,
268 char *fmt, char *out, size_t outlen,
269 RADIUS_ESCAPE_STRING func)
272 uint8_t *data = NULL;
274 VALUE_PAIR *user_name;
275 VALUE_PAIR *chap_challenge, *response;
276 rlm_mschap_t *inst = instance;
278 chap_challenge = response = NULL;
280 func = func; /* -Wunused */
283 * Challenge means MS-CHAPv1 challenge, or
284 * hash of MS-CHAPv2 challenge, and peer challenge.
286 if (strcasecmp(fmt, "Challenge") == 0) {
287 chap_challenge = pairfind(request->packet->vps,
288 PW_MSCHAP_CHALLENGE);
289 if (!chap_challenge) {
290 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request.");
295 * MS-CHAP-Challenges are 8 octets,
298 if (chap_challenge->length == 8) {
299 DEBUG2(" mschap1: %02x",
300 chap_challenge->vp_octets[0]);
301 data = chap_challenge->vp_octets;
305 * MS-CHAP-Challenges are 16 octets,
308 } else if (chap_challenge->length == 16) {
309 char *username_string;
311 DEBUG2(" mschap2: %02x", chap_challenge->vp_octets[0]);
312 response = pairfind(request->packet->vps,
313 PW_MSCHAP2_RESPONSE);
315 DEBUG2(" rlm_mschap: MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge.");
320 * Responses are 50 octets.
322 if (response->length < 50) {
323 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
327 user_name = pairfind(request->packet->vps,
330 DEBUG2(" rlm_mschap: User-Name is required to calculateMS-CHAPv1 Challenge.");
335 * with_ntdomain_hack moved here, too.
337 if ((username_string = strchr(user_name->vp_strvalue, '\\')) != NULL) {
338 if (inst->with_ntdomain_hack) {
341 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
342 username_string = user_name->vp_strvalue;
345 username_string = user_name->vp_strvalue;
349 * Get the MS-CHAPv1 challenge
350 * from the MS-CHAPv2 peer challenge,
351 * our challenge, and the user name.
353 challenge_hash(response->vp_octets + 2,
354 chap_challenge->vp_octets,
355 username_string, buffer);
359 DEBUG2(" rlm_mschap: Invalid MS-CHAP challenge length");
364 * Get the MS-CHAPv1 response, or the MS-CHAPv2
367 } else if (strcasecmp(fmt, "NT-Response") == 0) {
368 response = pairfind(request->packet->vps,
370 if (!response) response = pairfind(request->packet->vps,
371 PW_MSCHAP2_RESPONSE);
373 DEBUG2(" rlm_mschap: No MS-CHAP-Response or MS-CHAP2-Response was found in the request.");
378 * For MS-CHAPv1, the NT-Response exists only
379 * if the second octet says so.
381 if ((response->attribute == PW_MSCHAP_RESPONSE) &&
382 ((response->vp_octets[1] & 0x01) == 0)) {
383 DEBUG2(" rlm_mschap: No NT-Response in MS-CHAP-Response");
388 * MS-CHAP-Response and MS-CHAP2-Response have
389 * the NT-Response at the same offset, and are
392 data = response->vp_octets + 26;
396 * LM-Response is deprecated, and exists only
397 * in MS-CHAPv1, and not often there.
399 } else if (strcasecmp(fmt, "LM-Response") == 0) {
400 response = pairfind(request->packet->vps,
403 DEBUG2(" rlm_mschap: No MS-CHAP-Response was found in the request.");
408 * For MS-CHAPv1, the NT-Response exists only
409 * if the second octet says so.
411 if ((response->vp_octets[1] & 0x01) != 0) {
412 DEBUG2(" rlm_mschap: No LM-Response in MS-CHAP-Response");
415 data = response->vp_octets + 2;
419 * Pull the NT-Domain out of the User-Name, if it exists.
421 } else if (strcasecmp(fmt, "NT-Domain") == 0) {
424 user_name = pairfind(request->packet->vps, PW_USER_NAME);
426 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
431 * First check to see if this is a host/ style User-Name
432 * (a la Kerberos host principal)
434 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
436 * If we're getting a User-Name formatted in this way,
437 * it's likely due to PEAP. The Windows Domain will be
438 * the first domain component following the hostname,
439 * or the machine name itself if only a hostname is supplied
441 p = strchr(user_name->vp_strvalue, '.');
443 DEBUG2(" rlm_mschap: setting NT-Domain to same as machine name");
444 strNcpy(out, user_name->vp_strvalue + 5, outlen);
446 p++; /* skip the period */
449 * use the same hack as below
450 * only if another period was found
453 strNcpy(out, p, outlen);
457 p = strchr(user_name->vp_strvalue, '\\');
459 DEBUG2(" rlm_mschap: No NT-Domain was found in the User-Name.");
464 * Hack. This is simpler than the alternatives.
467 strNcpy(out, user_name->vp_strvalue, outlen);
474 * Pull the User-Name out of the User-Name...
476 } else if (strcasecmp(fmt, "User-Name") == 0) {
479 user_name = pairfind(request->packet->vps, PW_USER_NAME);
481 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
486 * First check to see if this is a host/ style User-Name
487 * (a la Kerberos host principal)
489 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
491 * If we're getting a User-Name formatted in this way,
492 * it's likely due to PEAP. When authenticating this against
493 * a Domain, Windows will expect the User-Name to be in the
494 * format of hostname$, the SAM version of the name, so we
495 * have to convert it to that here. We do so by stripping
496 * off the first 5 characters (host/), and copying everything
497 * from that point to the first period into a string and appending
500 p = strchr(user_name->vp_strvalue, '.');
502 * use the same hack as above
503 * only if a period was found
506 snprintf(out, outlen, "%s$", user_name->vp_strvalue + 5);
509 p = strchr(user_name->vp_strvalue, '\\');
511 p++; /* skip the backslash */
513 p = user_name->vp_strvalue; /* use the whole User-Name */
515 strNcpy(out, p, outlen);
521 * Return the NT-Hash of the passed string
523 } else if (strncasecmp(fmt, "NT-Hash ", 8) == 0) {
526 p = fmt + 8; /* 7 is the length of 'NT-Hash' */
527 if ((p == '\0') || (outlen <= 32))
529 DEBUG("rlm_mschap: NT-Hash: %s",p);
532 lrad_bin2hex(buffer, out, 16);
534 DEBUG("rlm_mschap: NT-Hash: Result: %s",out);
538 * Return the LM-Hash of the passed string
540 } else if (strncasecmp(fmt, "LM-Hash ", 8) == 0) {
543 p = fmt + 8; /* 7 is the length of 'LM-Hash' */
544 if ((p == '\0') || (outlen <= 32))
547 DEBUG("rlm_mschap: LM-Hash: %s",p);
548 smbdes_lmpwdhash(p,buffer);
549 lrad_bin2hex(buffer, out, 16);
551 DEBUG("rlm_mschap: LM-Hash: Result: %s",out);
554 DEBUG2(" rlm_mschap: Unknown expansion string \"%s\"",
559 if (outlen == 0) return 0; /* nowhere to go, don't do anything */
562 * Didn't set anything: this is bad.
565 DEBUG2(" rlm_mschap: Failed to do anything intelligent");
570 * Check the output length.
572 if (outlen < ((data_len * 2) + 1)) {
573 data_len = (outlen - 1) / 2;
579 for (i = 0; i < data_len; i++) {
580 sprintf(out + (2 * i), "%02x", data[i]);
582 out[data_len * 2] = '\0';
588 static const CONF_PARSER module_config[] = {
590 * Cache the password by default.
592 { "use_mppe", PW_TYPE_BOOLEAN,
593 offsetof(rlm_mschap_t,use_mppe), NULL, "yes" },
594 { "require_encryption", PW_TYPE_BOOLEAN,
595 offsetof(rlm_mschap_t,require_encryption), NULL, "no" },
596 { "require_strong", PW_TYPE_BOOLEAN,
597 offsetof(rlm_mschap_t,require_strong), NULL, "no" },
598 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
599 offsetof(rlm_mschap_t,with_ntdomain_hack), NULL, "no" },
600 { "passwd", PW_TYPE_STRING_PTR,
601 offsetof(rlm_mschap_t, passwd_file), NULL, NULL },
602 { "ntlm_auth", PW_TYPE_STRING_PTR,
603 offsetof(rlm_mschap_t, ntlm_auth), NULL, NULL },
605 { NULL, -1, 0, NULL, NULL } /* end the list */
609 * deinstantiate module, free all memory allocated during
610 * mschap_instantiate()
612 static int mschap_detach(void *instance){
613 #define inst ((rlm_mschap_t *)instance)
614 free(inst->passwd_file);
615 free(inst->ntlm_auth);
616 if (inst->xlat_name) {
617 xlat_unregister(inst->xlat_name, mschap_xlat);
618 free(inst->xlat_name);
626 * Create instance for our module. Allocate space for
627 * instance structure and read configuration parameters
629 static int mschap_instantiate(CONF_SECTION *conf, void **instance)
631 const char *xlat_name;
634 inst = *instance = rad_malloc(sizeof(*inst));
638 memset(inst, 0, sizeof(*inst));
640 if (cf_section_parse(conf, inst, module_config) < 0) {
646 * This module used to support SMB Password files, but it
647 * made it too complicated. If the user tries to
648 * configure an SMB Password file, then die, with an
651 if (inst->passwd_file) {
652 radlog(L_ERR, "rlm_mschap: SMB password file is no longer supported in this module. Use rlm_passwd module instead");
658 * Create the dynamic translation.
660 if (cf_section_name1(conf))
661 xlat_register(cf_section_name1(conf),mschap_xlat, inst);
663 if ((xlat_name = cf_section_name2(conf)) != NULL)
664 xlat_register(xlat_name, mschap_xlat, inst);
665 if (xlat_name == NULL)
666 xlat_name = cf_section_name1(conf);
668 inst->xlat_name = strdup(xlat_name);
671 * For backwards compatibility
673 if (!dict_valbyname(PW_AUTH_TYPE, inst->xlat_name)) {
674 inst->auth_type = "MS-CHAP";
681 * add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
682 * attribute to reply packet
684 static void add_reply(VALUE_PAIR** vp, unsigned char ident,
685 const char* name, const char* value, int len)
687 VALUE_PAIR *reply_attr;
688 reply_attr = pairmake(name, "", T_OP_EQ);
690 DEBUG(" rlm_mschap: Failed to create attribute %s: %s\n", name, librad_errstr);
694 reply_attr->vp_octets[0] = ident;
695 memcpy(reply_attr->vp_octets + 1, value, len);
696 reply_attr->length = len + 1;
697 pairadd(vp, reply_attr);
701 * Add MPPE attributes to the reply.
703 static void mppe_add_reply(VALUE_PAIR **vp,
704 const char* name, const char* value, int len)
706 VALUE_PAIR *reply_attr;
707 reply_attr = pairmake(name, "", T_OP_EQ);
709 DEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, librad_errstr);
713 memcpy(reply_attr->vp_octets, value, len);
714 reply_attr->length = len;
715 pairadd(vp, reply_attr);
720 * Do the MS-CHAP stuff.
722 * This function is here so that all of the MS-CHAP related
723 * authentication is in one place, and we can perhaps later replace
724 * it with code to call winbindd, or something similar.
726 static int do_mschap(rlm_mschap_t *inst,
727 REQUEST *request, VALUE_PAIR *password,
728 uint8_t *challenge, uint8_t *response,
731 int do_ntlm_auth = 0;
732 uint8_t calculated[24];
733 VALUE_PAIR *vp = NULL;
736 * If we have ntlm_auth configured, use it unless told
739 if (inst->ntlm_auth) do_ntlm_auth = 1;
742 * If we have an ntlm_auth configuration, then we may
745 vp = pairfind(request->config_items,
746 PW_MS_CHAP_USE_NTLM_AUTH);
747 if (vp) do_ntlm_auth = vp->lvalue;
750 * No ntlm_auth configured, attribute to tell us to
751 * use it exists, and we're told to use it. We don't
754 if (!inst->ntlm_auth && do_ntlm_auth) {
755 DEBUG2(" rlm_mschap: Asked to use ntlm_auth, but it was not configured in the mschap{} section.");
760 * Do normal authentication.
764 * No password: can't do authentication.
767 DEBUG2(" rlm_mschap: FAILED: No NT/LM-Password. Cannot perform authentication.");
771 smbdes_mschap(password->vp_strvalue, challenge, calculated);
772 if (memcmp(response, calculated, 24) != 0) {
777 * If the password exists, and is an NT-Password,
778 * then calculate the hash of the NT hash. Doing this
779 * here minimizes work for later.
781 if (password && (password->attribute == PW_NT_PASSWORD)) {
782 md4_calc(nthashhash, password->vp_strvalue, 16);
784 memset(nthashhash, 0, 16);
786 } else { /* run ntlm_auth */
790 memset(nthashhash, 0, 16);
793 * Run the program, and expect that we get 16
795 result = radius_exec_program(inst->ntlm_auth, request,
797 buffer, sizeof(buffer),
800 DEBUG2(" rlm_mschap: External script failed.");
805 * Parse the answer as an nthashhash.
807 * ntlm_auth currently returns:
808 * NT_KEY: 000102030405060708090a0b0c0d0e0f
810 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
811 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: expecting NT_KEY");
816 * Check the length. It should be at least 32,
817 * with an LF at the end.
819 if (strlen(buffer + 8) < 32) {
820 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has unexpected length");
825 * Update the NT hash hash, from the NT key.
827 if (lrad_hex2bin(buffer + 8, nthashhash, 16) != 16) {
828 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has non-hex values");
838 * Data for the hashes.
840 static const uint8_t SHSpad1[40] =
841 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
842 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
843 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
844 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
846 static const uint8_t SHSpad2[40] =
847 { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
848 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
849 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
850 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
852 static const uint8_t magic1[27] =
853 { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
854 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
855 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
857 static const uint8_t magic2[84] =
858 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
859 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
860 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
861 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
862 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
863 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
864 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
865 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
866 0x6b, 0x65, 0x79, 0x2e };
868 static const uint8_t magic3[84] =
869 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
870 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
871 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
872 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
873 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
874 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
875 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
876 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
877 0x6b, 0x65, 0x79, 0x2e };
880 static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response,
887 SHA1Update(&Context,nt_hashhash,16);
888 SHA1Update(&Context,nt_response,24);
889 SHA1Update(&Context,magic1,27);
890 SHA1Final(digest,&Context);
892 memcpy(masterkey,digest,16);
896 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
897 int keylen,int issend)
912 SHA1Update(&Context,masterkey,16);
913 SHA1Update(&Context,SHSpad1,40);
914 SHA1Update(&Context,s,84);
915 SHA1Update(&Context,SHSpad2,40);
916 SHA1Final(digest,&Context);
918 memcpy(sesskey,digest,keylen);
922 static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response,
923 uint8_t *sendkey,uint8_t *recvkey)
925 uint8_t masterkey[16];
927 mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
929 mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
930 mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
934 * Generate MPPE keys.
936 static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response,
937 uint8_t *sendkey,uint8_t *recvkey)
942 mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
945 * dictionary.microsoft defines these attributes as
946 * 'encrypt=2'. The functions in src/lib/radius.c will
947 * take care of encrypting/decrypting them as appropriate,
948 * so that we don't have to.
950 memcpy (sendkey, enckey1, 16);
951 memcpy (recvkey, enckey2, 16);
956 * mschap_authorize() - authorize user if we can authenticate
957 * it later. Add Auth-Type attribute if present in module
958 * configuration (usually Auth-Type must be "MS-CHAP")
960 static int mschap_authorize(void * instance, REQUEST *request)
962 #define inst ((rlm_mschap_t *)instance)
963 VALUE_PAIR *challenge = NULL, *response = NULL;
966 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
968 return RLM_MODULE_NOOP;
971 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
973 response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE);
976 * Nothing we recognize. Don't do anything.
979 DEBUG2(" rlm_mschap: Found MS-CHAP-Challenge, but no MS-CHAP-Response.");
980 return RLM_MODULE_NOOP;
983 DEBUG2(" rlm_mschap: Found MS-CHAP attributes. Setting 'Auth-Type = %s'", inst->xlat_name);
986 * Set Auth-Type to MS-CHAP. The authentication code
987 * will take care of turning clear-text passwords into
990 vp = pairmake("Auth-Type", inst->auth_type, T_OP_EQ);
991 if (!vp) return RLM_MODULE_FAIL;
992 pairmove(&request->config_items, &vp);
993 pairfree(&vp); /* may be NULL */
995 return RLM_MODULE_OK;
1000 * mschap_authenticate() - authenticate user based on given
1001 * attributes and configuration.
1002 * We will try to find out password in configuration
1003 * or in configured passwd file.
1004 * If one is found we will check paraneters given by NAS.
1006 * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
1008 * PAP: PW_USER_PASSWORD or
1009 * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
1010 * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
1011 * In case of password mismatch or locked account we MAY return
1012 * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
1013 * If MS-CHAP2 succeeds we MUST return
1014 * PW_MSCHAP2_SUCCESS
1016 static int mschap_authenticate(void * instance, REQUEST *request)
1018 #define inst ((rlm_mschap_t *)instance)
1019 VALUE_PAIR *challenge = NULL;
1020 VALUE_PAIR *response = NULL;
1021 VALUE_PAIR *password = NULL;
1022 VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
1023 VALUE_PAIR *username;
1024 VALUE_PAIR *reply_attr;
1025 uint8_t nthashhash[16];
1026 uint8_t msch2resp[42];
1027 char *username_string;
1031 * Find the SMB-Account-Ctrl attribute, or the
1032 * SMB-Account-Ctrl-Text attribute.
1034 smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL);
1036 password = pairfind(request->config_items,
1037 PW_SMB_ACCOUNT_CTRL_TEXT);
1039 smb_ctrl = pairmake("SMB-Account-CTRL", "0", T_OP_SET);
1040 pairadd(&request->config_items, smb_ctrl);
1041 smb_ctrl->lvalue = pdb_decode_acct_ctrl(password->vp_strvalue);
1046 * We're configured to do MS-CHAP authentication.
1047 * and account control information exists. Enforce it.
1051 * Password is not required.
1053 if ((smb_ctrl->lvalue & ACB_PWNOTREQ) != 0) {
1054 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says no password is required.");
1055 return RLM_MODULE_OK;
1060 * Decide how to get the passwords.
1062 password = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD);
1065 * We need an LM-Password.
1067 lm_password = pairfind(request->config_items, PW_LM_PASSWORD);
1072 if ((lm_password->length == 16) ||
1073 ((lm_password->length == 32) &&
1074 (lrad_hex2bin(lm_password->vp_strvalue,
1075 lm_password->vp_strvalue, 16) == 16))) {
1076 DEBUG2(" rlm_mschap: Found LM-Password");
1077 lm_password->length = 16;
1080 radlog(L_ERR, "rlm_mschap: Invalid LM-Password");
1084 } else if (!password) {
1085 DEBUG2(" rlm_mschap: No Cleartext-Password configured. Cannot create LM-Password.");
1087 } else { /* there is a configured Cleartext-Password */
1088 lm_password = pairmake("LM-Password", "", T_OP_EQ);
1090 radlog(L_ERR, "No memory");
1092 smbdes_lmpwdhash(password->vp_strvalue,
1093 lm_password->vp_strvalue);
1094 lm_password->length = 16;
1095 pairadd(&request->config_items, lm_password);
1100 * We need an NT-Password.
1102 nt_password = pairfind(request->config_items, PW_NT_PASSWORD);
1104 if ((nt_password->length == 16) ||
1105 ((nt_password->length == 32) &&
1106 (lrad_hex2bin(nt_password->vp_strvalue,
1107 nt_password->vp_strvalue, 16) == 16))) {
1108 DEBUG2(" rlm_mschap: Found NT-Password");
1109 nt_password->length = 16;
1112 radlog(L_ERR, "rlm_mschap: Invalid NT-Password");
1115 } else if (!password) {
1116 DEBUG2(" rlm_mschap: No Cleartext-Password configured. Cannot create NT-Password.");
1118 } else { /* there is a configured Cleartext-Password */
1119 nt_password = pairmake("NT-Password", "", T_OP_EQ);
1121 radlog(L_ERR, "No memory");
1122 return RLM_MODULE_FAIL;
1124 ntpwdhash(nt_password->vp_strvalue,
1125 password->vp_strvalue);
1126 nt_password->length = 16;
1127 pairadd(&request->config_items, nt_password);
1131 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
1133 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request");
1134 return RLM_MODULE_REJECT;
1138 * We also require an MS-CHAP-Response.
1140 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
1143 * MS-CHAP-Response, means MS-CHAPv1
1149 * MS-CHAPv1 challenges are 8 octets.
1151 if (challenge->length < 8) {
1152 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1153 return RLM_MODULE_INVALID;
1157 * Responses are 50 octets.
1159 if (response->length < 50) {
1160 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1161 return RLM_MODULE_INVALID;
1165 * We are doing MS-CHAP. Calculate the MS-CHAP
1168 if (response->vp_octets[1] & 0x01) {
1169 DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with NT-Password");
1170 password = nt_password;
1173 DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with LM-Password");
1174 password = lm_password;
1179 * Do the MS-CHAP authentication.
1181 if (do_mschap(inst, request, password, challenge->vp_octets,
1182 response->vp_octets + offset, nthashhash) < 0) {
1183 DEBUG2(" rlm_mschap: MS-CHAP-Response is incorrect.");
1184 add_reply(&request->reply->vps, *response->vp_octets,
1185 "MS-CHAP-Error", "E=691 R=1", 9);
1186 return RLM_MODULE_REJECT;
1191 } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE)) != NULL) {
1192 uint8_t mschapv1_challenge[16];
1195 * MS-CHAPv2 challenges are 16 octets.
1197 if (challenge->length < 16) {
1198 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1199 return RLM_MODULE_INVALID;
1203 * Responses are 50 octets.
1205 if (response->length < 50) {
1206 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1207 return RLM_MODULE_INVALID;
1211 * We also require a User-Name
1213 username = pairfind(request->packet->vps, PW_USER_NAME);
1215 radlog(L_AUTH, "rlm_mschap: We require a User-Name for MS-CHAPv2");
1216 return RLM_MODULE_INVALID;
1221 * with_ntdomain_hack moved here
1223 if ((username_string = strchr(username->vp_strvalue, '\\')) != NULL) {
1224 if (inst->with_ntdomain_hack) {
1227 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1228 username_string = username->vp_strvalue;
1231 username_string = username->vp_strvalue;
1235 * The old "mschapv2" function has been moved to
1238 * MS-CHAPv2 takes some additional data to create an
1239 * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1241 challenge_hash(response->vp_octets + 2, /* peer challenge */
1242 challenge->vp_octets, /* our challenge */
1243 username_string, /* user name */
1244 mschapv1_challenge); /* resulting challenge */
1246 DEBUG2(" rlm_mschap: Told to do MS-CHAPv2 for %s with NT-Password",
1249 if (do_mschap(inst, request, nt_password, mschapv1_challenge,
1250 response->vp_octets + 26, nthashhash) < 0) {
1251 DEBUG2(" rlm_mschap: FAILED: MS-CHAP2-Response is incorrect");
1252 add_reply(&request->reply->vps, *response->vp_octets,
1253 "MS-CHAP-Error", "E=691 R=1", 9);
1254 return RLM_MODULE_REJECT;
1258 * Get the NT-hash-hash, if necessary
1263 auth_response(username_string, /* without the domain */
1264 nthashhash, /* nt-hash-hash */
1265 response->vp_octets + 26, /* peer response */
1266 response->vp_octets + 2, /* peer challenge */
1267 challenge->vp_octets, /* our challenge */
1268 msch2resp); /* calculated MPPE key */
1269 add_reply( &request->reply->vps, *response->vp_octets,
1270 "MS-CHAP2-Success", msch2resp, 42);
1273 } else { /* Neither CHAPv1 or CHAPv2 response: die */
1274 radlog(L_AUTH, "rlm_mschap: No MS-CHAP response found");
1275 return RLM_MODULE_INVALID;
1279 * We have a CHAP response, but the account may be
1280 * disabled. Reject the user with the same error code
1281 * we use when their password is invalid.
1285 * Account is disabled.
1287 * They're found, but they don't exist, so we
1288 * return 'not found'.
1290 if (((smb_ctrl->lvalue & ACB_DISABLED) != 0) ||
1291 ((smb_ctrl->lvalue & ACB_NORMAL) == 0)) {
1292 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is disabled, or is not a normal account.");
1293 add_reply( &request->reply->vps, *response->vp_octets,
1294 "MS-CHAP-Error", "E=691 R=1", 9);
1295 return RLM_MODULE_NOTFOUND;
1299 * User is locked out.
1301 if ((smb_ctrl->lvalue & ACB_AUTOLOCK) != 0) {
1302 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is locked out.");
1303 add_reply( &request->reply->vps, *response->vp_octets,
1304 "MS-CHAP-Error", "E=647 R=0", 9);
1305 return RLM_MODULE_USERLOCK;
1309 /* now create MPPE attributes */
1310 if (inst->use_mppe) {
1311 uint8_t mppe_sendkey[34];
1312 uint8_t mppe_recvkey[34];
1315 DEBUG2("rlm_mschap: adding MS-CHAPv1 MPPE keys");
1316 memset(mppe_sendkey, 0, 32);
1318 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1322 * According to RFC 2548 we
1323 * should send NT hash. But in
1324 * practice it doesn't work.
1325 * Instead, we should send nthashhash
1327 * This is an error on RFC 2548.
1330 * do_mschap cares to zero nthashhash if NT hash
1333 memcpy(mppe_sendkey + 8,
1335 mppe_add_reply(&request->reply->vps,
1336 "MS-CHAP-MPPE-Keys",
1338 } else if (chap == 2) {
1339 DEBUG2("rlm_mschap: adding MS-CHAPv2 MPPE keys");
1340 mppe_chap2_gen_keys128(nthashhash,
1341 response->vp_octets + 26,
1342 mppe_sendkey, mppe_recvkey);
1344 mppe_add_reply(&request->reply->vps,
1347 mppe_add_reply(&request->reply->vps,
1352 reply_attr = pairmake("MS-MPPE-Encryption-Policy",
1353 (inst->require_encryption)? "0x00000002":"0x00000001",
1355 rad_assert(reply_attr != NULL);
1356 pairadd(&request->reply->vps, reply_attr);
1357 reply_attr = pairmake("MS-MPPE-Encryption-Types",
1358 (inst->require_strong)? "0x00000004":"0x00000006",
1360 rad_assert(reply_attr != NULL);
1361 pairadd(&request->reply->vps, reply_attr);
1363 } /* else we weren't asked to use MPPE */
1365 return RLM_MODULE_OK;
1369 module_t rlm_mschap = {
1372 RLM_TYPE_THREAD_SAFE, /* type */
1373 mschap_instantiate, /* instantiation */
1374 mschap_detach, /* detach */
1376 mschap_authenticate, /* authenticate */
1377 mschap_authorize, /* authorize */
1378 NULL, /* pre-accounting */
1379 NULL, /* accounting */
1380 NULL, /* checksimul */
1381 NULL, /* pre-proxy */
1382 NULL, /* post-proxy */
1383 NULL /* post-auth */