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 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/autoconf.h>
52 #include <freeradius-devel/radiusd.h>
53 #include <freeradius-devel/modules.h>
55 #include <freeradius-devel/md4.h>
56 #include <freeradius-devel/md5.h>
57 #include <freeradius-devel/sha1.h>
58 #include <freeradius-devel/rad_assert.h>
62 static const char rcsid[] = "$Id$";
65 /* Allowable account control bits */
66 #define ACB_DISABLED 0x0001 /* 1 = User account disabled */
67 #define ACB_HOMDIRREQ 0x0002 /* 1 = Home directory required */
68 #define ACB_PWNOTREQ 0x0004 /* 1 = User password not required */
69 #define ACB_TEMPDUP 0x0008 /* 1 = Temporary duplicate account */
70 #define ACB_NORMAL 0x0010 /* 1 = Normal user account */
71 #define ACB_MNS 0x0020 /* 1 = MNS logon user account */
72 #define ACB_DOMTRUST 0x0040 /* 1 = Interdomain trust account */
73 #define ACB_WSTRUST 0x0080 /* 1 = Workstation trust account */
74 #define ACB_SVRTRUST 0x0100 /* 1 = Server trust account */
75 #define ACB_PWNOEXP 0x0200 /* 1 = User password does not expire */
76 #define ACB_AUTOLOCK 0x0400 /* 1 = Account auto locked */
78 static int pdb_decode_acct_ctrl(const char *p)
84 * Check if the account type bits have been encoded after the
85 * NT password (in the form [NDHTUWSLXI]).
88 if (*p != '[') return 0;
90 for (p++; *p && !finished; p++) {
92 case 'N': /* 'N'o password. */
93 acct_ctrl |= ACB_PWNOTREQ;
96 case 'D': /* 'D'isabled. */
97 acct_ctrl |= ACB_DISABLED ;
100 case 'H': /* 'H'omedir required. */
101 acct_ctrl |= ACB_HOMDIRREQ;
104 case 'T': /* 'T'emp account. */
105 acct_ctrl |= ACB_TEMPDUP;
108 case 'U': /* 'U'ser account (normal). */
109 acct_ctrl |= ACB_NORMAL;
112 case 'M': /* 'M'NS logon user account. What is this? */
113 acct_ctrl |= ACB_MNS;
116 case 'W': /* 'W'orkstation account. */
117 acct_ctrl |= ACB_WSTRUST;
120 case 'S': /* 'S'erver account. */
121 acct_ctrl |= ACB_SVRTRUST;
124 case 'L': /* 'L'ocked account. */
125 acct_ctrl |= ACB_AUTOLOCK;
128 case 'X': /* No 'X'piry on password */
129 acct_ctrl |= ACB_PWNOEXP;
132 case 'I': /* 'I'nterdomain trust account. */
133 acct_ctrl |= ACB_DOMTRUST;
136 case ' ': /* ignore spaces */
154 * ntpwdhash converts Unicode password to 16-byte NT hash
157 static void ntpwdhash (unsigned char *szHash, const char *szPassword)
159 char szUnicodePass[513];
164 * NT passwords are unicode. Convert plain text password
165 * to unicode by inserting a zero every other byte
167 nPasswordLen = strlen(szPassword);
168 for (i = 0; i < nPasswordLen; i++) {
169 szUnicodePass[i << 1] = szPassword[i];
170 szUnicodePass[(i << 1) + 1] = 0;
173 /* Encrypt Unicode password to a 16-byte MD4 hash */
174 md4_calc(szHash, szUnicodePass, (nPasswordLen<<1) );
179 * challenge_hash() is used by mschap2() and auth_response()
180 * implements RFC2759 ChallengeHash()
181 * generates 64 bit challenge
183 static void challenge_hash( const uint8_t *peer_challenge,
184 const uint8_t *auth_challenge,
185 const char *user_name, uint8_t *challenge )
191 SHA1Update(&Context, peer_challenge, 16);
192 SHA1Update(&Context, auth_challenge, 16);
193 SHA1Update(&Context, user_name, strlen(user_name));
194 SHA1Final(hash, &Context);
195 memcpy(challenge, hash, 8);
199 * auth_response() generates MS-CHAP v2 SUCCESS response
200 * according to RFC 2759 GenerateAuthenticatorResponse()
201 * returns 42-octet response string
203 static void auth_response(const char *username,
204 const uint8_t *nt_hash_hash,
206 char *peer_challenge, char *auth_challenge,
210 const uint8_t magic1[39] =
211 {0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
212 0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
213 0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
214 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74};
216 const uint8_t magic2[41] =
217 {0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
218 0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
219 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
220 0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
227 SHA1Update(&Context, nt_hash_hash, 16);
228 SHA1Update(&Context, ntresponse, 24);
229 SHA1Update(&Context, magic1, 39);
230 SHA1Final(digest, &Context);
231 challenge_hash(peer_challenge, auth_challenge, username, challenge);
233 SHA1Update(&Context, digest, 20);
234 SHA1Update(&Context, challenge, 8);
235 SHA1Update(&Context, magic2, 41);
236 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"
247 lrad_bin2hex(digest, response + 2, 20);
251 typedef struct rlm_mschap_t {
253 int require_encryption;
255 int with_ntdomain_hack; /* this should be in another module */
264 * Does dynamic translation of strings.
266 * Pulls NT-Response, LM-Response, or Challenge from MSCHAP
269 static int mschap_xlat(void *instance, REQUEST *request,
270 char *fmt, char *out, size_t outlen,
271 RADIUS_ESCAPE_STRING func)
274 uint8_t *data = NULL;
276 VALUE_PAIR *user_name;
277 VALUE_PAIR *chap_challenge, *response;
278 rlm_mschap_t *inst = instance;
280 chap_challenge = response = NULL;
282 func = func; /* -Wunused */
285 * Challenge means MS-CHAPv1 challenge, or
286 * hash of MS-CHAPv2 challenge, and peer challenge.
288 if (strcasecmp(fmt, "Challenge") == 0) {
289 chap_challenge = pairfind(request->packet->vps,
290 PW_MSCHAP_CHALLENGE);
291 if (!chap_challenge) {
292 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request.");
297 * MS-CHAP-Challenges are 8 octets,
300 if (chap_challenge->length == 8) {
301 DEBUG2(" mschap1: %02x",
302 chap_challenge->vp_octets[0]);
303 data = chap_challenge->vp_octets;
307 * MS-CHAP-Challenges are 16 octets,
310 } else if (chap_challenge->length == 16) {
311 char *username_string;
313 DEBUG2(" mschap2: %02x", chap_challenge->vp_octets[0]);
314 response = pairfind(request->packet->vps,
315 PW_MSCHAP2_RESPONSE);
317 DEBUG2(" rlm_mschap: MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge.");
322 * Responses are 50 octets.
324 if (response->length < 50) {
325 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
329 user_name = pairfind(request->packet->vps,
332 DEBUG2(" rlm_mschap: User-Name is required to calculateMS-CHAPv1 Challenge.");
337 * with_ntdomain_hack moved here, too.
339 if ((username_string = strchr(user_name->vp_strvalue, '\\')) != NULL) {
340 if (inst->with_ntdomain_hack) {
343 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
344 username_string = user_name->vp_strvalue;
347 username_string = user_name->vp_strvalue;
351 * Get the MS-CHAPv1 challenge
352 * from the MS-CHAPv2 peer challenge,
353 * our challenge, and the user name.
355 challenge_hash(response->vp_octets + 2,
356 chap_challenge->vp_octets,
357 username_string, buffer);
361 DEBUG2(" rlm_mschap: Invalid MS-CHAP challenge length");
366 * Get the MS-CHAPv1 response, or the MS-CHAPv2
369 } else if (strcasecmp(fmt, "NT-Response") == 0) {
370 response = pairfind(request->packet->vps,
372 if (!response) response = pairfind(request->packet->vps,
373 PW_MSCHAP2_RESPONSE);
375 DEBUG2(" rlm_mschap: No MS-CHAP-Response or MS-CHAP2-Response was found in the request.");
380 * For MS-CHAPv1, the NT-Response exists only
381 * if the second octet says so.
383 if ((response->attribute == PW_MSCHAP_RESPONSE) &&
384 ((response->vp_octets[1] & 0x01) == 0)) {
385 DEBUG2(" rlm_mschap: No NT-Response in MS-CHAP-Response");
390 * MS-CHAP-Response and MS-CHAP2-Response have
391 * the NT-Response at the same offset, and are
394 data = response->vp_octets + 26;
398 * LM-Response is deprecated, and exists only
399 * in MS-CHAPv1, and not often there.
401 } else if (strcasecmp(fmt, "LM-Response") == 0) {
402 response = pairfind(request->packet->vps,
405 DEBUG2(" rlm_mschap: No MS-CHAP-Response was found in the request.");
410 * For MS-CHAPv1, the NT-Response exists only
411 * if the second octet says so.
413 if ((response->vp_octets[1] & 0x01) != 0) {
414 DEBUG2(" rlm_mschap: No LM-Response in MS-CHAP-Response");
417 data = response->vp_octets + 2;
421 * Pull the NT-Domain out of the User-Name, if it exists.
423 } else if (strcasecmp(fmt, "NT-Domain") == 0) {
426 user_name = pairfind(request->packet->vps, PW_USER_NAME);
428 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
433 * First check to see if this is a host/ style User-Name
434 * (a la Kerberos host principal)
436 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
438 * If we're getting a User-Name formatted in this way,
439 * it's likely due to PEAP. The Windows Domain will be
440 * the first domain component following the hostname,
441 * or the machine name itself if only a hostname is supplied
443 p = strchr(user_name->vp_strvalue, '.');
445 DEBUG2(" rlm_mschap: setting NT-Domain to same as machine name");
446 strNcpy(out, user_name->vp_strvalue + 5, outlen);
448 p++; /* skip the period */
451 * use the same hack as below
452 * only if another period was found
455 strNcpy(out, p, outlen);
459 p = strchr(user_name->vp_strvalue, '\\');
461 DEBUG2(" rlm_mschap: No NT-Domain was found in the User-Name.");
466 * Hack. This is simpler than the alternatives.
469 strNcpy(out, user_name->vp_strvalue, outlen);
476 * Pull the User-Name out of the User-Name...
478 } else if (strcasecmp(fmt, "User-Name") == 0) {
481 user_name = pairfind(request->packet->vps, PW_USER_NAME);
483 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
488 * First check to see if this is a host/ style User-Name
489 * (a la Kerberos host principal)
491 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
493 * If we're getting a User-Name formatted in this way,
494 * it's likely due to PEAP. When authenticating this against
495 * a Domain, Windows will expect the User-Name to be in the
496 * format of hostname$, the SAM version of the name, so we
497 * have to convert it to that here. We do so by stripping
498 * off the first 5 characters (host/), and copying everything
499 * from that point to the first period into a string and appending
502 p = strchr(user_name->vp_strvalue, '.');
504 * use the same hack as above
505 * only if a period was found
508 snprintf(out, outlen, "%s$", user_name->vp_strvalue + 5);
511 p = strchr(user_name->vp_strvalue, '\\');
513 p++; /* skip the backslash */
515 p = user_name->vp_strvalue; /* use the whole User-Name */
517 strNcpy(out, p, outlen);
523 * Return the NT-Hash of the passed string
525 } else if (strncasecmp(fmt, "NT-Hash ", 8) == 0) {
528 p = fmt + 8; /* 7 is the length of 'NT-Hash' */
529 if ((p == '\0') || (outlen <= 32))
531 DEBUG("rlm_mschap: NT-Hash: %s",p);
534 lrad_bin2hex(buffer, out, 16);
536 DEBUG("rlm_mschap: NT-Hash: Result: %s",out);
540 * Return the LM-Hash of the passed string
542 } else if (strncasecmp(fmt, "LM-Hash ", 8) == 0) {
545 p = fmt + 8; /* 7 is the length of 'LM-Hash' */
546 if ((p == '\0') || (outlen <= 32))
549 DEBUG("rlm_mschap: LM-Hash: %s",p);
550 smbdes_lmpwdhash(p,buffer);
551 lrad_bin2hex(buffer, out, 16);
553 DEBUG("rlm_mschap: LM-Hash: Result: %s",out);
556 DEBUG2(" rlm_mschap: Unknown expansion string \"%s\"",
561 if (outlen == 0) return 0; /* nowhere to go, don't do anything */
564 * Didn't set anything: this is bad.
567 DEBUG2(" rlm_mschap: Failed to do anything intelligent");
572 * Check the output length.
574 if (outlen < ((data_len * 2) + 1)) {
575 data_len = (outlen - 1) / 2;
581 for (i = 0; i < data_len; i++) {
582 sprintf(out + (2 * i), "%02x", data[i]);
584 out[data_len * 2] = '\0';
590 static const CONF_PARSER module_config[] = {
592 * Cache the password by default.
594 { "use_mppe", PW_TYPE_BOOLEAN,
595 offsetof(rlm_mschap_t,use_mppe), NULL, "yes" },
596 { "require_encryption", PW_TYPE_BOOLEAN,
597 offsetof(rlm_mschap_t,require_encryption), NULL, "no" },
598 { "require_strong", PW_TYPE_BOOLEAN,
599 offsetof(rlm_mschap_t,require_strong), NULL, "no" },
600 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
601 offsetof(rlm_mschap_t,with_ntdomain_hack), NULL, "no" },
602 { "passwd", PW_TYPE_STRING_PTR,
603 offsetof(rlm_mschap_t, passwd_file), NULL, NULL },
604 { "ntlm_auth", PW_TYPE_STRING_PTR,
605 offsetof(rlm_mschap_t, ntlm_auth), NULL, NULL },
607 { NULL, -1, 0, NULL, NULL } /* end the list */
611 * deinstantiate module, free all memory allocated during
612 * mschap_instantiate()
614 static int mschap_detach(void *instance){
615 #define inst ((rlm_mschap_t *)instance)
616 free(inst->passwd_file);
617 free(inst->ntlm_auth);
618 if (inst->xlat_name) {
619 xlat_unregister(inst->xlat_name, mschap_xlat);
620 free(inst->xlat_name);
628 * Create instance for our module. Allocate space for
629 * instance structure and read configuration parameters
631 static int mschap_instantiate(CONF_SECTION *conf, void **instance)
633 const char *xlat_name;
636 inst = *instance = rad_malloc(sizeof(*inst));
640 memset(inst, 0, sizeof(*inst));
642 if (cf_section_parse(conf, inst, module_config) < 0) {
648 * This module used to support SMB Password files, but it
649 * made it too complicated. If the user tries to
650 * configure an SMB Password file, then die, with an
653 if (inst->passwd_file) {
654 radlog(L_ERR, "rlm_mschap: SMB password file is no longer supported in this module. Use rlm_passwd module instead");
660 * Create the dynamic translation.
662 if (cf_section_name1(conf))
663 xlat_register(cf_section_name1(conf),mschap_xlat, inst);
665 if ((xlat_name = cf_section_name2(conf)) != NULL)
666 xlat_register(xlat_name, mschap_xlat, inst);
667 if (xlat_name == NULL)
668 xlat_name = cf_section_name1(conf);
670 inst->xlat_name = strdup(xlat_name);
673 * For backwards compatibility
675 if (!dict_valbyname(PW_AUTH_TYPE, inst->xlat_name)) {
676 inst->auth_type = "MS-CHAP";
683 * add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
684 * attribute to reply packet
686 static void add_reply(VALUE_PAIR** vp, unsigned char ident,
687 const char* name, const char* value, int len)
689 VALUE_PAIR *reply_attr;
690 reply_attr = pairmake(name, "", T_OP_EQ);
692 DEBUG(" rlm_mschap: Failed to create attribute %s: %s\n", name, librad_errstr);
696 reply_attr->vp_octets[0] = ident;
697 memcpy(reply_attr->vp_octets + 1, value, len);
698 reply_attr->length = len + 1;
699 pairadd(vp, reply_attr);
703 * Add MPPE attributes to the reply.
705 static void mppe_add_reply(VALUE_PAIR **vp,
706 const char* name, const char* value, int len)
708 VALUE_PAIR *reply_attr;
709 reply_attr = pairmake(name, "", T_OP_EQ);
711 DEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, librad_errstr);
715 memcpy(reply_attr->vp_octets, value, len);
716 reply_attr->length = len;
717 pairadd(vp, reply_attr);
722 * Do the MS-CHAP stuff.
724 * This function is here so that all of the MS-CHAP related
725 * authentication is in one place, and we can perhaps later replace
726 * it with code to call winbindd, or something similar.
728 static int do_mschap(rlm_mschap_t *inst,
729 REQUEST *request, VALUE_PAIR *password,
730 uint8_t *challenge, uint8_t *response,
733 int do_ntlm_auth = 0;
734 uint8_t calculated[24];
735 VALUE_PAIR *vp = NULL;
738 * If we have ntlm_auth configured, use it unless told
741 if (inst->ntlm_auth) do_ntlm_auth = 1;
744 * If we have an ntlm_auth configuration, then we may
747 vp = pairfind(request->config_items,
748 PW_MS_CHAP_USE_NTLM_AUTH);
749 if (vp) do_ntlm_auth = vp->lvalue;
752 * No ntlm_auth configured, attribute to tell us to
753 * use it exists, and we're told to use it. We don't
756 if (!inst->ntlm_auth && do_ntlm_auth) {
757 DEBUG2(" rlm_mschap: Asked to use ntlm_auth, but it was not configured in the mschap{} section.");
762 * Do normal authentication.
766 * No password: can't do authentication.
769 DEBUG2(" rlm_mschap: FAILED: No NT/LM-Password. Cannot perform authentication.");
773 smbdes_mschap(password->vp_strvalue, challenge, calculated);
774 if (memcmp(response, calculated, 24) != 0) {
779 * If the password exists, and is an NT-Password,
780 * then calculate the hash of the NT hash. Doing this
781 * here minimizes work for later.
783 if (password && (password->attribute == PW_NT_PASSWORD)) {
784 md4_calc(nthashhash, password->vp_strvalue, 16);
786 memset(nthashhash, 0, 16);
788 } else { /* run ntlm_auth */
792 memset(nthashhash, 0, 16);
795 * Run the program, and expect that we get 16
797 result = radius_exec_program(inst->ntlm_auth, request,
799 buffer, sizeof(buffer),
802 DEBUG2(" rlm_mschap: External script failed.");
807 * Parse the answer as an nthashhash.
809 * ntlm_auth currently returns:
810 * NT_KEY: 000102030405060708090a0b0c0d0e0f
812 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
813 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: expecting NT_KEY");
818 * Check the length. It should be at least 32,
819 * with an LF at the end.
821 if (strlen(buffer + 8) < 32) {
822 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has unexpected length");
827 * Update the NT hash hash, from the NT key.
829 if (lrad_hex2bin(buffer + 8, nthashhash, 16) != 16) {
830 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has non-hex values");
840 * Data for the hashes.
842 static const uint8_t SHSpad1[40] =
843 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
844 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
845 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
846 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
848 static const uint8_t SHSpad2[40] =
849 { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
850 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
851 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
852 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
854 static const uint8_t magic1[27] =
855 { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
856 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
857 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
859 static const uint8_t magic2[84] =
860 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
861 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
862 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
863 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
864 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
865 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
866 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
867 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
868 0x6b, 0x65, 0x79, 0x2e };
870 static const uint8_t magic3[84] =
871 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
872 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
873 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
874 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
875 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
876 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
877 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
878 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
879 0x6b, 0x65, 0x79, 0x2e };
882 static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response,
889 SHA1Update(&Context,nt_hashhash,16);
890 SHA1Update(&Context,nt_response,24);
891 SHA1Update(&Context,magic1,27);
892 SHA1Final(digest,&Context);
894 memcpy(masterkey,digest,16);
898 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
899 int keylen,int issend)
914 SHA1Update(&Context,masterkey,16);
915 SHA1Update(&Context,SHSpad1,40);
916 SHA1Update(&Context,s,84);
917 SHA1Update(&Context,SHSpad2,40);
918 SHA1Final(digest,&Context);
920 memcpy(sesskey,digest,keylen);
924 static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response,
925 uint8_t *sendkey,uint8_t *recvkey)
927 uint8_t masterkey[16];
929 mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
931 mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
932 mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
936 * Generate MPPE keys.
938 static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response,
939 uint8_t *sendkey,uint8_t *recvkey)
944 mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
947 * dictionary.microsoft defines these attributes as
948 * 'encrypt=2'. The functions in src/lib/radius.c will
949 * take care of encrypting/decrypting them as appropriate,
950 * so that we don't have to.
952 memcpy (sendkey, enckey1, 16);
953 memcpy (recvkey, enckey2, 16);
958 * mschap_authorize() - authorize user if we can authenticate
959 * it later. Add Auth-Type attribute if present in module
960 * configuration (usually Auth-Type must be "MS-CHAP")
962 static int mschap_authorize(void * instance, REQUEST *request)
964 #define inst ((rlm_mschap_t *)instance)
965 VALUE_PAIR *challenge = NULL, *response = NULL;
968 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
970 return RLM_MODULE_NOOP;
973 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
975 response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE);
978 * Nothing we recognize. Don't do anything.
981 DEBUG2(" rlm_mschap: Found MS-CHAP-Challenge, but no MS-CHAP-Response.");
982 return RLM_MODULE_NOOP;
985 DEBUG2(" rlm_mschap: Found MS-CHAP attributes. Setting 'Auth-Type = %s'", inst->xlat_name);
988 * Set Auth-Type to MS-CHAP. The authentication code
989 * will take care of turning clear-text passwords into
992 vp = pairmake("Auth-Type", inst->auth_type, T_OP_EQ);
993 if (!vp) return RLM_MODULE_FAIL;
994 pairmove(&request->config_items, &vp);
995 pairfree(&vp); /* may be NULL */
997 return RLM_MODULE_OK;
1002 * mschap_authenticate() - authenticate user based on given
1003 * attributes and configuration.
1004 * We will try to find out password in configuration
1005 * or in configured passwd file.
1006 * If one is found we will check paraneters given by NAS.
1008 * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
1010 * PAP: PW_PASSWORD or
1011 * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
1012 * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
1013 * In case of password mismatch or locked account we MAY return
1014 * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
1015 * If MS-CHAP2 succeeds we MUST return
1016 * PW_MSCHAP2_SUCCESS
1018 static int mschap_authenticate(void * instance, REQUEST *request)
1020 #define inst ((rlm_mschap_t *)instance)
1021 VALUE_PAIR *challenge = NULL;
1022 VALUE_PAIR *response = NULL;
1023 VALUE_PAIR *password = NULL;
1024 VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
1025 VALUE_PAIR *username;
1026 VALUE_PAIR *reply_attr;
1027 uint8_t nthashhash[16];
1028 uint8_t msch2resp[42];
1029 char *username_string;
1033 * Find the SMB-Account-Ctrl attribute, or the
1034 * SMB-Account-Ctrl-Text attribute.
1036 smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL);
1038 password = pairfind(request->config_items,
1039 PW_SMB_ACCOUNT_CTRL_TEXT);
1041 smb_ctrl = pairmake("SMB-Account-CTRL", "0", T_OP_SET);
1042 pairadd(&request->config_items, smb_ctrl);
1043 smb_ctrl->lvalue = pdb_decode_acct_ctrl(password->vp_strvalue);
1048 * We're configured to do MS-CHAP authentication.
1049 * and account control information exists. Enforce it.
1053 * Password is not required.
1055 if ((smb_ctrl->lvalue & ACB_PWNOTREQ) != 0) {
1056 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says no password is required.");
1057 return RLM_MODULE_OK;
1062 * Decide how to get the passwords.
1064 password = pairfind(request->config_items, PW_PASSWORD);
1067 * We need an LM-Password.
1069 lm_password = pairfind(request->config_items, PW_LM_PASSWORD);
1074 if ((lm_password->length == 16) ||
1075 ((lm_password->length == 32) &&
1076 (lrad_hex2bin(lm_password->vp_strvalue,
1077 lm_password->vp_strvalue, 16) == 16))) {
1078 DEBUG2(" rlm_mschap: Found LM-Password");
1079 lm_password->length = 16;
1082 radlog(L_ERR, "rlm_mschap: Invalid LM-Password");
1086 } else if (!password) {
1087 DEBUG2(" rlm_mschap: No User-Password configured. Cannot create LM-Password.");
1089 } else { /* there is a configured User-Password */
1090 lm_password = pairmake("LM-Password", "", T_OP_EQ);
1092 radlog(L_ERR, "No memory");
1094 smbdes_lmpwdhash(password->vp_strvalue,
1095 lm_password->vp_strvalue);
1096 lm_password->length = 16;
1097 pairadd(&request->config_items, lm_password);
1102 * We need an NT-Password.
1104 nt_password = pairfind(request->config_items, PW_NT_PASSWORD);
1106 if ((nt_password->length == 16) ||
1107 ((nt_password->length == 32) &&
1108 (lrad_hex2bin(nt_password->vp_strvalue,
1109 nt_password->vp_strvalue, 16) == 16))) {
1110 DEBUG2(" rlm_mschap: Found NT-Password");
1111 nt_password->length = 16;
1114 radlog(L_ERR, "rlm_mschap: Invalid NT-Password");
1117 } else if (!password) {
1118 DEBUG2(" rlm_mschap: No User-Password configured. Cannot create NT-Password.");
1120 } else { /* there is a configured User-Password */
1121 nt_password = pairmake("NT-Password", "", T_OP_EQ);
1123 radlog(L_ERR, "No memory");
1124 return RLM_MODULE_FAIL;
1126 ntpwdhash(nt_password->vp_strvalue,
1127 password->vp_strvalue);
1128 nt_password->length = 16;
1129 pairadd(&request->config_items, nt_password);
1133 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
1135 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request");
1136 return RLM_MODULE_REJECT;
1140 * We also require an MS-CHAP-Response.
1142 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
1145 * MS-CHAP-Response, means MS-CHAPv1
1151 * MS-CHAPv1 challenges are 8 octets.
1153 if (challenge->length < 8) {
1154 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1155 return RLM_MODULE_INVALID;
1159 * Responses are 50 octets.
1161 if (response->length < 50) {
1162 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1163 return RLM_MODULE_INVALID;
1167 * We are doing MS-CHAP. Calculate the MS-CHAP
1170 if (response->vp_octets[1] & 0x01) {
1171 DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with NT-Password");
1172 password = nt_password;
1175 DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with LM-Password");
1176 password = lm_password;
1181 * Do the MS-CHAP authentication.
1183 if (do_mschap(inst, request, password, challenge->vp_octets,
1184 response->vp_octets + offset, nthashhash) < 0) {
1185 DEBUG2(" rlm_mschap: MS-CHAP-Response is incorrect.");
1186 add_reply(&request->reply->vps, *response->vp_octets,
1187 "MS-CHAP-Error", "E=691 R=1", 9);
1188 return RLM_MODULE_REJECT;
1193 } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE)) != NULL) {
1194 uint8_t mschapv1_challenge[16];
1197 * MS-CHAPv2 challenges are 16 octets.
1199 if (challenge->length < 16) {
1200 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1201 return RLM_MODULE_INVALID;
1205 * Responses are 50 octets.
1207 if (response->length < 50) {
1208 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1209 return RLM_MODULE_INVALID;
1213 * We also require a User-Name
1215 username = pairfind(request->packet->vps, PW_USER_NAME);
1217 radlog(L_AUTH, "rlm_mschap: We require a User-Name for MS-CHAPv2");
1218 return RLM_MODULE_INVALID;
1223 * with_ntdomain_hack moved here
1225 if ((username_string = strchr(username->vp_strvalue, '\\')) != NULL) {
1226 if (inst->with_ntdomain_hack) {
1229 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1230 username_string = username->vp_strvalue;
1233 username_string = username->vp_strvalue;
1237 * The old "mschapv2" function has been moved to
1240 * MS-CHAPv2 takes some additional data to create an
1241 * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1243 challenge_hash(response->vp_octets + 2, /* peer challenge */
1244 challenge->vp_octets, /* our challenge */
1245 username_string, /* user name */
1246 mschapv1_challenge); /* resulting challenge */
1248 DEBUG2(" rlm_mschap: Told to do MS-CHAPv2 for %s with NT-Password",
1251 if (do_mschap(inst, request, nt_password, mschapv1_challenge,
1252 response->vp_octets + 26, nthashhash) < 0) {
1253 DEBUG2(" rlm_mschap: FAILED: MS-CHAP2-Response is incorrect");
1254 add_reply(&request->reply->vps, *response->vp_octets,
1255 "MS-CHAP-Error", "E=691 R=1", 9);
1256 return RLM_MODULE_REJECT;
1260 * Get the NT-hash-hash, if necessary
1265 auth_response(username_string, /* without the domain */
1266 nthashhash, /* nt-hash-hash */
1267 response->vp_octets + 26, /* peer response */
1268 response->vp_octets + 2, /* peer challenge */
1269 challenge->vp_octets, /* our challenge */
1270 msch2resp); /* calculated MPPE key */
1271 add_reply( &request->reply->vps, *response->vp_octets,
1272 "MS-CHAP2-Success", msch2resp, 42);
1275 } else { /* Neither CHAPv1 or CHAPv2 response: die */
1276 radlog(L_AUTH, "rlm_mschap: No MS-CHAP response found");
1277 return RLM_MODULE_INVALID;
1281 * We have a CHAP response, but the account may be
1282 * disabled. Reject the user with the same error code
1283 * we use when their password is invalid.
1287 * Account is disabled.
1289 * They're found, but they don't exist, so we
1290 * return 'not found'.
1292 if (((smb_ctrl->lvalue & ACB_DISABLED) != 0) ||
1293 ((smb_ctrl->lvalue & ACB_NORMAL) == 0)) {
1294 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is disabled, or is not a normal account.");
1295 add_reply( &request->reply->vps, *response->vp_octets,
1296 "MS-CHAP-Error", "E=691 R=1", 9);
1297 return RLM_MODULE_NOTFOUND;
1301 * User is locked out.
1303 if ((smb_ctrl->lvalue & ACB_AUTOLOCK) != 0) {
1304 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is locked out.");
1305 add_reply( &request->reply->vps, *response->vp_octets,
1306 "MS-CHAP-Error", "E=647 R=0", 9);
1307 return RLM_MODULE_USERLOCK;
1311 /* now create MPPE attributes */
1312 if (inst->use_mppe) {
1313 uint8_t mppe_sendkey[34];
1314 uint8_t mppe_recvkey[34];
1317 DEBUG2("rlm_mschap: adding MS-CHAPv1 MPPE keys");
1318 memset(mppe_sendkey, 0, 32);
1320 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1324 * According to RFC 2548 we
1325 * should send NT hash. But in
1326 * practice it doesn't work.
1327 * Instead, we should send nthashhash
1329 * This is an error on RFC 2548.
1332 * do_mschap cares to zero nthashhash if NT hash
1335 memcpy(mppe_sendkey + 8,
1337 mppe_add_reply(&request->reply->vps,
1338 "MS-CHAP-MPPE-Keys",
1340 } else if (chap == 2) {
1341 DEBUG2("rlm_mschap: adding MS-CHAPv2 MPPE keys");
1342 mppe_chap2_gen_keys128(nthashhash,
1343 response->vp_octets + 26,
1344 mppe_sendkey, mppe_recvkey);
1346 mppe_add_reply(&request->reply->vps,
1349 mppe_add_reply(&request->reply->vps,
1354 reply_attr = pairmake("MS-MPPE-Encryption-Policy",
1355 (inst->require_encryption)? "0x00000002":"0x00000001",
1357 rad_assert(reply_attr != NULL);
1358 pairadd(&request->reply->vps, reply_attr);
1359 reply_attr = pairmake("MS-MPPE-Encryption-Types",
1360 (inst->require_strong)? "0x00000004":"0x00000006",
1362 rad_assert(reply_attr != NULL);
1363 pairadd(&request->reply->vps, reply_attr);
1365 } /* else we weren't asked to use MPPE */
1367 return RLM_MODULE_OK;
1371 module_t rlm_mschap = {
1374 RLM_TYPE_THREAD_SAFE, /* type */
1375 mschap_instantiate, /* instantiation */
1376 mschap_detach, /* detach */
1378 mschap_authenticate, /* authenticate */
1379 mschap_authorize, /* authorize */
1380 NULL, /* pre-accounting */
1381 NULL, /* accounting */
1382 NULL, /* checksimul */
1383 NULL, /* pre-proxy */
1384 NULL, /* post-proxy */
1385 NULL /* post-auth */