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 */
263 * Does dynamic translation of strings.
265 * Pulls NT-Response, LM-Response, or Challenge from MSCHAP
268 static int mschap_xlat(void *instance, REQUEST *request,
269 char *fmt, char *out, size_t outlen,
270 RADIUS_ESCAPE_STRING func)
273 uint8_t *data = NULL;
275 VALUE_PAIR *user_name;
276 VALUE_PAIR *chap_challenge, *response;
277 rlm_mschap_t *inst = instance;
279 chap_challenge = response = NULL;
281 func = func; /* -Wunused */
284 * Challenge means MS-CHAPv1 challenge, or
285 * hash of MS-CHAPv2 challenge, and peer challenge.
287 if (strcasecmp(fmt, "Challenge") == 0) {
288 chap_challenge = pairfind(request->packet->vps,
289 PW_MSCHAP_CHALLENGE);
290 if (!chap_challenge) {
291 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request.");
296 * MS-CHAP-Challenges are 8 octets,
299 if (chap_challenge->length == 8) {
300 DEBUG2(" mschap1: %02x",
301 chap_challenge->vp_octets[0]);
302 data = chap_challenge->vp_octets;
306 * MS-CHAP-Challenges are 16 octets,
309 } else if (chap_challenge->length == 16) {
310 char *username_string;
312 DEBUG2(" mschap2: %02x", chap_challenge->vp_octets[0]);
313 response = pairfind(request->packet->vps,
314 PW_MSCHAP2_RESPONSE);
316 DEBUG2(" rlm_mschap: MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge.");
321 * Responses are 50 octets.
323 if (response->length < 50) {
324 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
328 user_name = pairfind(request->packet->vps,
331 DEBUG2(" rlm_mschap: User-Name is required to calculateMS-CHAPv1 Challenge.");
336 * with_ntdomain_hack moved here, too.
338 if ((username_string = strchr(user_name->vp_strvalue, '\\')) != NULL) {
339 if (inst->with_ntdomain_hack) {
342 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
343 username_string = user_name->vp_strvalue;
346 username_string = user_name->vp_strvalue;
350 * Get the MS-CHAPv1 challenge
351 * from the MS-CHAPv2 peer challenge,
352 * our challenge, and the user name.
354 challenge_hash(response->vp_octets + 2,
355 chap_challenge->vp_octets,
356 username_string, buffer);
360 DEBUG2(" rlm_mschap: Invalid MS-CHAP challenge length");
365 * Get the MS-CHAPv1 response, or the MS-CHAPv2
368 } else if (strcasecmp(fmt, "NT-Response") == 0) {
369 response = pairfind(request->packet->vps,
371 if (!response) response = pairfind(request->packet->vps,
372 PW_MSCHAP2_RESPONSE);
374 DEBUG2(" rlm_mschap: No MS-CHAP-Response or MS-CHAP2-Response was found in the request.");
379 * For MS-CHAPv1, the NT-Response exists only
380 * if the second octet says so.
382 if ((response->attribute == PW_MSCHAP_RESPONSE) &&
383 ((response->vp_octets[1] & 0x01) == 0)) {
384 DEBUG2(" rlm_mschap: No NT-Response in MS-CHAP-Response");
389 * MS-CHAP-Response and MS-CHAP2-Response have
390 * the NT-Response at the same offset, and are
393 data = response->vp_octets + 26;
397 * LM-Response is deprecated, and exists only
398 * in MS-CHAPv1, and not often there.
400 } else if (strcasecmp(fmt, "LM-Response") == 0) {
401 response = pairfind(request->packet->vps,
404 DEBUG2(" rlm_mschap: No MS-CHAP-Response was found in the request.");
409 * For MS-CHAPv1, the NT-Response exists only
410 * if the second octet says so.
412 if ((response->vp_octets[1] & 0x01) != 0) {
413 DEBUG2(" rlm_mschap: No LM-Response in MS-CHAP-Response");
416 data = response->vp_octets + 2;
420 * Pull the NT-Domain out of the User-Name, if it exists.
422 } else if (strcasecmp(fmt, "NT-Domain") == 0) {
425 user_name = pairfind(request->packet->vps, PW_USER_NAME);
427 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
432 * First check to see if this is a host/ style User-Name
433 * (a la Kerberos host principal)
435 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
437 * If we're getting a User-Name formatted in this way,
438 * it's likely due to PEAP. The Windows Domain will be
439 * the first domain component following the hostname,
440 * or the machine name itself if only a hostname is supplied
442 p = strchr(user_name->vp_strvalue, '.');
444 DEBUG2(" rlm_mschap: setting NT-Domain to same as machine name");
445 strNcpy(out, user_name->vp_strvalue + 5, outlen);
447 p++; /* skip the period */
450 * use the same hack as below
451 * only if another period was found
454 strNcpy(out, p, outlen);
458 p = strchr(user_name->vp_strvalue, '\\');
460 DEBUG2(" rlm_mschap: No NT-Domain was found in the User-Name.");
465 * Hack. This is simpler than the alternatives.
468 strNcpy(out, user_name->vp_strvalue, outlen);
475 * Pull the User-Name out of the User-Name...
477 } else if (strcasecmp(fmt, "User-Name") == 0) {
480 user_name = pairfind(request->packet->vps, PW_USER_NAME);
482 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
487 * First check to see if this is a host/ style User-Name
488 * (a la Kerberos host principal)
490 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
492 * If we're getting a User-Name formatted in this way,
493 * it's likely due to PEAP. When authenticating this against
494 * a Domain, Windows will expect the User-Name to be in the
495 * format of hostname$, the SAM version of the name, so we
496 * have to convert it to that here. We do so by stripping
497 * off the first 5 characters (host/), and copying everything
498 * from that point to the first period into a string and appending
501 p = strchr(user_name->vp_strvalue, '.');
503 * use the same hack as above
504 * only if a period was found
507 snprintf(out, outlen, "%s$", user_name->vp_strvalue + 5);
510 p = strchr(user_name->vp_strvalue, '\\');
512 p++; /* skip the backslash */
514 p = user_name->vp_strvalue; /* use the whole User-Name */
516 strNcpy(out, p, outlen);
522 * Return the NT-Hash of the passed string
524 } else if (strncasecmp(fmt, "NT-Hash ", 8) == 0) {
527 p = fmt + 8; /* 7 is the length of 'NT-Hash' */
528 if ((p == '\0') || (outlen <= 32))
530 DEBUG("rlm_mschap: NT-Hash: %s",p);
533 lrad_bin2hex(buffer, out, 16);
535 DEBUG("rlm_mschap: NT-Hash: Result: %s",out);
539 * Return the LM-Hash of the passed string
541 } else if (strncasecmp(fmt, "LM-Hash ", 8) == 0) {
544 p = fmt + 8; /* 7 is the length of 'LM-Hash' */
545 if ((p == '\0') || (outlen <= 32))
548 DEBUG("rlm_mschap: LM-Hash: %s",p);
549 smbdes_lmpwdhash(p,buffer);
550 lrad_bin2hex(buffer, out, 16);
552 DEBUG("rlm_mschap: LM-Hash: Result: %s",out);
555 DEBUG2(" rlm_mschap: Unknown expansion string \"%s\"",
560 if (outlen == 0) return 0; /* nowhere to go, don't do anything */
563 * Didn't set anything: this is bad.
566 DEBUG2(" rlm_mschap: Failed to do anything intelligent");
571 * Check the output length.
573 if (outlen < ((data_len * 2) + 1)) {
574 data_len = (outlen - 1) / 2;
580 for (i = 0; i < data_len; i++) {
581 sprintf(out + (2 * i), "%02x", data[i]);
583 out[data_len * 2] = '\0';
589 static const CONF_PARSER module_config[] = {
591 * Cache the password by default.
593 { "use_mppe", PW_TYPE_BOOLEAN,
594 offsetof(rlm_mschap_t,use_mppe), NULL, "yes" },
595 { "require_encryption", PW_TYPE_BOOLEAN,
596 offsetof(rlm_mschap_t,require_encryption), NULL, "no" },
597 { "require_strong", PW_TYPE_BOOLEAN,
598 offsetof(rlm_mschap_t,require_strong), NULL, "no" },
599 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
600 offsetof(rlm_mschap_t,with_ntdomain_hack), NULL, "no" },
601 { "passwd", PW_TYPE_STRING_PTR,
602 offsetof(rlm_mschap_t, passwd_file), NULL, NULL },
603 { "ntlm_auth", PW_TYPE_STRING_PTR,
604 offsetof(rlm_mschap_t, ntlm_auth), NULL, NULL },
606 { NULL, -1, 0, NULL, NULL } /* end the list */
610 * deinstantiate module, free all memory allocated during
611 * mschap_instantiate()
613 static int mschap_detach(void *instance){
614 #define inst ((rlm_mschap_t *)instance)
615 free(inst->passwd_file);
616 free(inst->ntlm_auth);
617 if (inst->xlat_name) {
618 xlat_unregister(inst->xlat_name, mschap_xlat);
619 free(inst->xlat_name);
627 * Create instance for our module. Allocate space for
628 * instance structure and read configuration parameters
630 static int mschap_instantiate(CONF_SECTION *conf, void **instance)
632 const char *xlat_name;
635 inst = *instance = rad_malloc(sizeof(*inst));
639 memset(inst, 0, sizeof(*inst));
641 if (cf_section_parse(conf, inst, module_config) < 0) {
647 * This module used to support SMB Password files, but it
648 * made it too complicated. If the user tries to
649 * configure an SMB Password file, then die, with an
652 if (inst->passwd_file) {
653 radlog(L_ERR, "rlm_mschap: SMB password file is no longer supported in this module. Use rlm_passwd module instead");
659 * Create the dynamic translation.
661 if (cf_section_name1(conf))
662 xlat_register(cf_section_name1(conf),mschap_xlat, inst);
664 if ((xlat_name = cf_section_name2(conf)) != NULL)
665 xlat_register(xlat_name, mschap_xlat, inst);
666 if (xlat_name == NULL)
667 xlat_name = cf_section_name1(conf);
669 inst->xlat_name = strdup(xlat_name);
675 * add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
676 * attribute to reply packet
678 static void add_reply(VALUE_PAIR** vp, unsigned char ident,
679 const char* name, const char* value, int len)
681 VALUE_PAIR *reply_attr;
682 reply_attr = pairmake(name, "", T_OP_EQ);
684 DEBUG(" rlm_mschap: Failed to create attribute %s: %s\n", name, librad_errstr);
688 reply_attr->vp_octets[0] = ident;
689 memcpy(reply_attr->vp_octets + 1, value, len);
690 reply_attr->length = len + 1;
691 pairadd(vp, reply_attr);
695 * Add MPPE attributes to the reply.
697 static void mppe_add_reply(VALUE_PAIR **vp,
698 const char* name, const char* value, int len)
700 VALUE_PAIR *reply_attr;
701 reply_attr = pairmake(name, "", T_OP_EQ);
703 DEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, librad_errstr);
707 memcpy(reply_attr->vp_octets, value, len);
708 reply_attr->length = len;
709 pairadd(vp, reply_attr);
714 * Do the MS-CHAP stuff.
716 * This function is here so that all of the MS-CHAP related
717 * authentication is in one place, and we can perhaps later replace
718 * it with code to call winbindd, or something similar.
720 static int do_mschap(rlm_mschap_t *inst,
721 REQUEST *request, VALUE_PAIR *password,
722 uint8_t *challenge, uint8_t *response,
725 int do_ntlm_auth = 0;
726 uint8_t calculated[24];
727 VALUE_PAIR *vp = NULL;
730 * If we have ntlm_auth configured, use it unless told
733 if (inst->ntlm_auth) do_ntlm_auth = 1;
736 * If we have an ntlm_auth configuration, then we may
739 vp = pairfind(request->config_items,
740 PW_MS_CHAP_USE_NTLM_AUTH);
741 if (vp) do_ntlm_auth = vp->lvalue;
744 * No ntlm_auth configured, attribute to tell us to
745 * use it exists, and we're told to use it. We don't
748 if (!inst->ntlm_auth && do_ntlm_auth) {
749 DEBUG2(" rlm_mschap: Asked to use ntlm_auth, but it was not configured in the mschap{} section.");
754 * Do normal authentication.
758 * No password: can't do authentication.
761 DEBUG2(" rlm_mschap: FAILED: No NT/LM-Password. Cannot perform authentication.");
765 smbdes_mschap(password->vp_strvalue, challenge, calculated);
766 if (memcmp(response, calculated, 24) != 0) {
771 * If the password exists, and is an NT-Password,
772 * then calculate the hash of the NT hash. Doing this
773 * here minimizes work for later.
775 if (password && (password->attribute == PW_NT_PASSWORD)) {
776 md4_calc(nthashhash, password->vp_strvalue, 16);
778 memset(nthashhash, 0, 16);
780 } else { /* run ntlm_auth */
784 memset(nthashhash, 0, 16);
787 * Run the program, and expect that we get 16
789 result = radius_exec_program(inst->ntlm_auth, request,
791 buffer, sizeof(buffer),
794 DEBUG2(" rlm_mschap: External script failed.");
799 * Parse the answer as an nthashhash.
801 * ntlm_auth currently returns:
802 * NT_KEY: 000102030405060708090a0b0c0d0e0f
804 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
805 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: expecting NT_KEY");
810 * Check the length. It should be at least 32,
811 * with an LF at the end.
813 if (strlen(buffer + 8) < 32) {
814 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has unexpected length");
819 * Update the NT hash hash, from the NT key.
821 if (lrad_hex2bin(buffer + 8, nthashhash, 16) != 16) {
822 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has non-hex values");
832 * Data for the hashes.
834 static const uint8_t SHSpad1[40] =
835 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
836 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
837 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
838 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
840 static const uint8_t SHSpad2[40] =
841 { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
842 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
843 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
844 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
846 static const uint8_t magic1[27] =
847 { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
848 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
849 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
851 static const uint8_t magic2[84] =
852 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
853 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
854 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
855 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
856 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
857 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
858 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
859 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
860 0x6b, 0x65, 0x79, 0x2e };
862 static const uint8_t magic3[84] =
863 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
864 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
865 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
866 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
867 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
868 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
869 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
870 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
871 0x6b, 0x65, 0x79, 0x2e };
874 static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response,
881 SHA1Update(&Context,nt_hashhash,16);
882 SHA1Update(&Context,nt_response,24);
883 SHA1Update(&Context,magic1,27);
884 SHA1Final(digest,&Context);
886 memcpy(masterkey,digest,16);
890 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
891 int keylen,int issend)
906 SHA1Update(&Context,masterkey,16);
907 SHA1Update(&Context,SHSpad1,40);
908 SHA1Update(&Context,s,84);
909 SHA1Update(&Context,SHSpad2,40);
910 SHA1Final(digest,&Context);
912 memcpy(sesskey,digest,keylen);
916 static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response,
917 uint8_t *sendkey,uint8_t *recvkey)
919 uint8_t masterkey[16];
921 mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
923 mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
924 mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
928 * Generate MPPE keys.
930 static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response,
931 uint8_t *sendkey,uint8_t *recvkey)
936 mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
939 * dictionary.microsoft defines these attributes as
940 * 'encrypt=2'. The functions in src/lib/radius.c will
941 * take care of encrypting/decrypting them as appropriate,
942 * so that we don't have to.
944 memcpy (sendkey, enckey1, 16);
945 memcpy (recvkey, enckey2, 16);
950 * mschap_authorize() - authorize user if we can authenticate
951 * it later. Add Auth-Type attribute if present in module
952 * configuration (usually Auth-Type must be "MS-CHAP")
954 static int mschap_authorize(void * instance, REQUEST *request)
956 #define inst ((rlm_mschap_t *)instance)
957 VALUE_PAIR *challenge = NULL, *response = NULL;
960 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
962 return RLM_MODULE_NOOP;
965 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
967 response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE);
970 * Nothing we recognize. Don't do anything.
973 DEBUG2(" rlm_mschap: Found MS-CHAP-Challenge, but no MS-CHAP-Response.");
974 return RLM_MODULE_NOOP;
977 DEBUG2(" rlm_mschap: Found MS-CHAP attributes. Setting 'Auth-Type = %s'", inst->xlat_name);
980 * Set Auth-Type to MS-CHAP. The authentication code
981 * will take care of turning clear-text passwords into
984 vp = pairmake("Auth-Type", inst->xlat_name, T_OP_EQ);
985 rad_assert(vp != NULL);
986 pairmove(&request->config_items, &vp);
987 pairfree(&vp); /* may be NULL */
989 return RLM_MODULE_OK;
994 * mschap_authenticate() - authenticate user based on given
995 * attributes and configuration.
996 * We will try to find out password in configuration
997 * or in configured passwd file.
998 * If one is found we will check paraneters given by NAS.
1000 * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
1002 * PAP: PW_PASSWORD or
1003 * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
1004 * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
1005 * In case of password mismatch or locked account we MAY return
1006 * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
1007 * If MS-CHAP2 succeeds we MUST return
1008 * PW_MSCHAP2_SUCCESS
1010 static int mschap_authenticate(void * instance, REQUEST *request)
1012 #define inst ((rlm_mschap_t *)instance)
1013 VALUE_PAIR *challenge = NULL;
1014 VALUE_PAIR *response = NULL;
1015 VALUE_PAIR *password = NULL;
1016 VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
1017 VALUE_PAIR *username;
1018 VALUE_PAIR *reply_attr;
1019 uint8_t nthashhash[16];
1020 uint8_t msch2resp[42];
1021 char *username_string;
1025 * Find the SMB-Account-Ctrl attribute, or the
1026 * SMB-Account-Ctrl-Text attribute.
1028 smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL);
1030 password = pairfind(request->config_items,
1031 PW_SMB_ACCOUNT_CTRL_TEXT);
1033 smb_ctrl = pairmake("SMB-Account-CTRL", "0", T_OP_SET);
1034 pairadd(&request->config_items, smb_ctrl);
1035 smb_ctrl->lvalue = pdb_decode_acct_ctrl(password->vp_strvalue);
1040 * We're configured to do MS-CHAP authentication.
1041 * and account control information exists. Enforce it.
1045 * Password is not required.
1047 if ((smb_ctrl->lvalue & ACB_PWNOTREQ) != 0) {
1048 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says no password is required.");
1049 return RLM_MODULE_OK;
1054 * Decide how to get the passwords.
1056 password = pairfind(request->config_items, PW_PASSWORD);
1059 * We need an LM-Password.
1061 lm_password = pairfind(request->config_items, PW_LM_PASSWORD);
1066 if ((lm_password->length == 16) ||
1067 ((lm_password->length == 32) &&
1068 (lrad_hex2bin(lm_password->vp_strvalue,
1069 lm_password->vp_strvalue, 16) == 16))) {
1070 DEBUG2(" rlm_mschap: Found LM-Password");
1071 lm_password->length = 16;
1074 radlog(L_ERR, "rlm_mschap: Invalid LM-Password");
1078 } else if (!password) {
1079 DEBUG2(" rlm_mschap: No User-Password configured. Cannot create LM-Password.");
1081 } else { /* there is a configured User-Password */
1082 lm_password = pairmake("LM-Password", "", T_OP_EQ);
1084 radlog(L_ERR, "No memory");
1086 smbdes_lmpwdhash(password->vp_strvalue,
1087 lm_password->vp_strvalue);
1088 lm_password->length = 16;
1089 pairadd(&request->config_items, lm_password);
1094 * We need an NT-Password.
1096 nt_password = pairfind(request->config_items, PW_NT_PASSWORD);
1098 if ((nt_password->length == 16) ||
1099 ((nt_password->length == 32) &&
1100 (lrad_hex2bin(nt_password->vp_strvalue,
1101 nt_password->vp_strvalue, 16) == 16))) {
1102 DEBUG2(" rlm_mschap: Found NT-Password");
1103 nt_password->length = 16;
1106 radlog(L_ERR, "rlm_mschap: Invalid NT-Password");
1109 } else if (!password) {
1110 DEBUG2(" rlm_mschap: No User-Password configured. Cannot create NT-Password.");
1112 } else { /* there is a configured User-Password */
1113 nt_password = pairmake("NT-Password", "", T_OP_EQ);
1115 radlog(L_ERR, "No memory");
1116 return RLM_MODULE_FAIL;
1118 ntpwdhash(nt_password->vp_strvalue,
1119 password->vp_strvalue);
1120 nt_password->length = 16;
1121 pairadd(&request->config_items, nt_password);
1125 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
1127 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request");
1128 return RLM_MODULE_REJECT;
1132 * We also require an MS-CHAP-Response.
1134 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
1137 * MS-CHAP-Response, means MS-CHAPv1
1143 * MS-CHAPv1 challenges are 8 octets.
1145 if (challenge->length < 8) {
1146 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1147 return RLM_MODULE_INVALID;
1151 * Responses are 50 octets.
1153 if (response->length < 50) {
1154 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1155 return RLM_MODULE_INVALID;
1159 * We are doing MS-CHAP. Calculate the MS-CHAP
1162 if (response->vp_octets[1] & 0x01) {
1163 DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with NT-Password");
1164 password = nt_password;
1167 DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with LM-Password");
1168 password = lm_password;
1173 * Do the MS-CHAP authentication.
1175 if (do_mschap(inst, request, password, challenge->vp_octets,
1176 response->vp_octets + offset, nthashhash) < 0) {
1177 DEBUG2(" rlm_mschap: MS-CHAP-Response is incorrect.");
1178 add_reply(&request->reply->vps, *response->vp_octets,
1179 "MS-CHAP-Error", "E=691 R=1", 9);
1180 return RLM_MODULE_REJECT;
1185 } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE)) != NULL) {
1186 uint8_t mschapv1_challenge[16];
1189 * MS-CHAPv2 challenges are 16 octets.
1191 if (challenge->length < 16) {
1192 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1193 return RLM_MODULE_INVALID;
1197 * Responses are 50 octets.
1199 if (response->length < 50) {
1200 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1201 return RLM_MODULE_INVALID;
1205 * We also require a User-Name
1207 username = pairfind(request->packet->vps, PW_USER_NAME);
1209 radlog(L_AUTH, "rlm_mschap: We require a User-Name for MS-CHAPv2");
1210 return RLM_MODULE_INVALID;
1215 * with_ntdomain_hack moved here
1217 if ((username_string = strchr(username->vp_strvalue, '\\')) != NULL) {
1218 if (inst->with_ntdomain_hack) {
1221 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1222 username_string = username->vp_strvalue;
1225 username_string = username->vp_strvalue;
1229 * The old "mschapv2" function has been moved to
1232 * MS-CHAPv2 takes some additional data to create an
1233 * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1235 challenge_hash(response->vp_octets + 2, /* peer challenge */
1236 challenge->vp_octets, /* our challenge */
1237 username_string, /* user name */
1238 mschapv1_challenge); /* resulting challenge */
1240 DEBUG2(" rlm_mschap: Told to do MS-CHAPv2 for %s with NT-Password",
1243 if (do_mschap(inst, request, nt_password, mschapv1_challenge,
1244 response->vp_octets + 26, nthashhash) < 0) {
1245 DEBUG2(" rlm_mschap: FAILED: MS-CHAP2-Response is incorrect");
1246 add_reply(&request->reply->vps, *response->vp_octets,
1247 "MS-CHAP-Error", "E=691 R=1", 9);
1248 return RLM_MODULE_REJECT;
1252 * Get the NT-hash-hash, if necessary
1257 auth_response(username_string, /* without the domain */
1258 nthashhash, /* nt-hash-hash */
1259 response->vp_octets + 26, /* peer response */
1260 response->vp_octets + 2, /* peer challenge */
1261 challenge->vp_octets, /* our challenge */
1262 msch2resp); /* calculated MPPE key */
1263 add_reply( &request->reply->vps, *response->vp_octets,
1264 "MS-CHAP2-Success", msch2resp, 42);
1267 } else { /* Neither CHAPv1 or CHAPv2 response: die */
1268 radlog(L_AUTH, "rlm_mschap: No MS-CHAP response found");
1269 return RLM_MODULE_INVALID;
1273 * We have a CHAP response, but the account may be
1274 * disabled. Reject the user with the same error code
1275 * we use when their password is invalid.
1279 * Account is disabled.
1281 * They're found, but they don't exist, so we
1282 * return 'not found'.
1284 if (((smb_ctrl->lvalue & ACB_DISABLED) != 0) ||
1285 ((smb_ctrl->lvalue & ACB_NORMAL) == 0)) {
1286 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is disabled, or is not a normal account.");
1287 add_reply( &request->reply->vps, *response->vp_octets,
1288 "MS-CHAP-Error", "E=691 R=1", 9);
1289 return RLM_MODULE_NOTFOUND;
1293 * User is locked out.
1295 if ((smb_ctrl->lvalue & ACB_AUTOLOCK) != 0) {
1296 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is locked out.");
1297 add_reply( &request->reply->vps, *response->vp_octets,
1298 "MS-CHAP-Error", "E=647 R=0", 9);
1299 return RLM_MODULE_USERLOCK;
1303 /* now create MPPE attributes */
1304 if (inst->use_mppe) {
1305 uint8_t mppe_sendkey[34];
1306 uint8_t mppe_recvkey[34];
1309 DEBUG2("rlm_mschap: adding MS-CHAPv1 MPPE keys");
1310 memset(mppe_sendkey, 0, 32);
1312 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1316 * According to RFC 2548 we
1317 * should send NT hash. But in
1318 * practice it doesn't work.
1319 * Instead, we should send nthashhash
1321 * This is an error on RFC 2548.
1324 * do_mschap cares to zero nthashhash if NT hash
1327 memcpy(mppe_sendkey + 8,
1329 mppe_add_reply(&request->reply->vps,
1330 "MS-CHAP-MPPE-Keys",
1332 } else if (chap == 2) {
1333 DEBUG2("rlm_mschap: adding MS-CHAPv2 MPPE keys");
1334 mppe_chap2_gen_keys128(nthashhash,
1335 response->vp_octets + 26,
1336 mppe_sendkey, mppe_recvkey);
1338 mppe_add_reply(&request->reply->vps,
1341 mppe_add_reply(&request->reply->vps,
1346 reply_attr = pairmake("MS-MPPE-Encryption-Policy",
1347 (inst->require_encryption)? "0x00000002":"0x00000001",
1349 rad_assert(reply_attr != NULL);
1350 pairadd(&request->reply->vps, reply_attr);
1351 reply_attr = pairmake("MS-MPPE-Encryption-Types",
1352 (inst->require_strong)? "0x00000004":"0x00000006",
1354 rad_assert(reply_attr != NULL);
1355 pairadd(&request->reply->vps, reply_attr);
1357 } /* else we weren't asked to use MPPE */
1359 return RLM_MODULE_OK;
1363 module_t rlm_mschap = {
1366 RLM_TYPE_THREAD_SAFE, /* type */
1367 mschap_instantiate, /* instantiation */
1368 mschap_detach, /* detach */
1370 mschap_authenticate, /* authenticate */
1371 mschap_authorize, /* authorize */
1372 NULL, /* pre-accounting */
1373 NULL, /* accounting */
1374 NULL, /* checksimul */
1375 NULL, /* pre-proxy */
1376 NULL, /* post-proxy */
1377 NULL /* post-auth */