6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * Copyright 2000,2001,2006 The FreeRADIUS server project
25 * mschap.c MS-CHAP module
27 * This implements MS-CHAP, as described in RFC 2548
29 * http://www.freeradius.org/rfc/rfc2548.txt
34 * If you have any questions on NTLM (Samba) passwords
35 * support, LM authentication and MS-CHAP v2 support
38 * Vladimir Dubrovin vlad@sandy.ru
40 * ZARAZA 3APA3A@security.nnov.ru
43 /* MPPE support from Takahiro Wagatsuma <waga@sic.shibaura-it.ac.jp> */
45 #include <freeradius-devel/ident.h>
48 #include <freeradius-devel/radiusd.h>
49 #include <freeradius-devel/modules.h>
50 #include <freeradius-devel/rad_assert.h>
51 #include <freeradius-devel/md5.h>
57 /* Allowable account control bits */
58 #define ACB_DISABLED 0x0001 /* 1 = User account disabled */
59 #define ACB_HOMDIRREQ 0x0002 /* 1 = Home directory required */
60 #define ACB_PWNOTREQ 0x0004 /* 1 = User password not required */
61 #define ACB_TEMPDUP 0x0008 /* 1 = Temporary duplicate account */
62 #define ACB_NORMAL 0x0010 /* 1 = Normal user account */
63 #define ACB_MNS 0x0020 /* 1 = MNS logon user account */
64 #define ACB_DOMTRUST 0x0040 /* 1 = Interdomain trust account */
65 #define ACB_WSTRUST 0x0080 /* 1 = Workstation trust account */
66 #define ACB_SVRTRUST 0x0100 /* 1 = Server trust account */
67 #define ACB_PWNOEXP 0x0200 /* 1 = User password does not expire */
68 #define ACB_AUTOLOCK 0x0400 /* 1 = Account auto locked */
70 static int pdb_decode_acct_ctrl(const char *p)
76 * Check if the account type bits have been encoded after the
77 * NT password (in the form [NDHTUWSLXI]).
80 if (*p != '[') return 0;
82 for (p++; *p && !finished; p++) {
84 case 'N': /* 'N'o password. */
85 acct_ctrl |= ACB_PWNOTREQ;
88 case 'D': /* 'D'isabled. */
89 acct_ctrl |= ACB_DISABLED ;
92 case 'H': /* 'H'omedir required. */
93 acct_ctrl |= ACB_HOMDIRREQ;
96 case 'T': /* 'T'emp account. */
97 acct_ctrl |= ACB_TEMPDUP;
100 case 'U': /* 'U'ser account (normal). */
101 acct_ctrl |= ACB_NORMAL;
104 case 'M': /* 'M'NS logon user account. What is this? */
105 acct_ctrl |= ACB_MNS;
108 case 'W': /* 'W'orkstation account. */
109 acct_ctrl |= ACB_WSTRUST;
112 case 'S': /* 'S'erver account. */
113 acct_ctrl |= ACB_SVRTRUST;
116 case 'L': /* 'L'ocked account. */
117 acct_ctrl |= ACB_AUTOLOCK;
120 case 'X': /* No 'X'piry on password */
121 acct_ctrl |= ACB_PWNOEXP;
124 case 'I': /* 'I'nterdomain trust account. */
125 acct_ctrl |= ACB_DOMTRUST;
128 case ' ': /* ignore spaces */
146 * ntpwdhash converts Unicode password to 16-byte NT hash
149 static void ntpwdhash (unsigned char *szHash, const char *szPassword)
151 char szUnicodePass[513];
156 * NT passwords are unicode. Convert plain text password
157 * to unicode by inserting a zero every other byte
159 nPasswordLen = strlen(szPassword);
160 for (i = 0; i < nPasswordLen; i++) {
161 szUnicodePass[i << 1] = szPassword[i];
162 szUnicodePass[(i << 1) + 1] = 0;
165 /* Encrypt Unicode password to a 16-byte MD4 hash */
166 md4_calc(szHash, szUnicodePass, (nPasswordLen<<1) );
171 * challenge_hash() is used by mschap2() and auth_response()
172 * implements RFC2759 ChallengeHash()
173 * generates 64 bit challenge
175 static void challenge_hash( const uint8_t *peer_challenge,
176 const uint8_t *auth_challenge,
177 const char *user_name, uint8_t *challenge )
183 SHA1Update(&Context, peer_challenge, 16);
184 SHA1Update(&Context, auth_challenge, 16);
185 SHA1Update(&Context, user_name, strlen(user_name));
186 SHA1Final(hash, &Context);
187 memcpy(challenge, hash, 8);
191 * auth_response() generates MS-CHAP v2 SUCCESS response
192 * according to RFC 2759 GenerateAuthenticatorResponse()
193 * returns 42-octet response string
195 static void auth_response(const char *username,
196 const uint8_t *nt_hash_hash,
198 char *peer_challenge, char *auth_challenge,
202 const uint8_t magic1[39] =
203 {0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
204 0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
205 0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
206 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74};
208 const uint8_t magic2[41] =
209 {0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
210 0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
211 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
212 0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
219 SHA1Update(&Context, nt_hash_hash, 16);
220 SHA1Update(&Context, ntresponse, 24);
221 SHA1Update(&Context, magic1, 39);
222 SHA1Final(digest, &Context);
223 challenge_hash(peer_challenge, auth_challenge, username, challenge);
225 SHA1Update(&Context, digest, 20);
226 SHA1Update(&Context, challenge, 8);
227 SHA1Update(&Context, magic2, 41);
228 SHA1Final(digest, &Context);
231 * Encode the value of 'Digest' as "S=" followed by
232 * 40 ASCII hexadecimal digits and return it in
233 * AuthenticatorResponse.
235 * "S=0123456789ABCDEF0123456789ABCDEF01234567"
239 lrad_bin2hex(digest, response + 2, 20);
243 typedef struct rlm_mschap_t {
245 int require_encryption;
247 int with_ntdomain_hack; /* this should be in another module */
256 * Does dynamic translation of strings.
258 * Pulls NT-Response, LM-Response, or Challenge from MSCHAP
261 static int mschap_xlat(void *instance, REQUEST *request,
262 char *fmt, char *out, size_t outlen,
263 RADIUS_ESCAPE_STRING func)
266 uint8_t *data = NULL;
268 VALUE_PAIR *user_name;
269 VALUE_PAIR *chap_challenge, *response;
270 rlm_mschap_t *inst = instance;
272 chap_challenge = response = NULL;
274 func = func; /* -Wunused */
277 * Challenge means MS-CHAPv1 challenge, or
278 * hash of MS-CHAPv2 challenge, and peer challenge.
280 if (strcasecmp(fmt, "Challenge") == 0) {
281 chap_challenge = pairfind(request->packet->vps,
282 PW_MSCHAP_CHALLENGE);
283 if (!chap_challenge) {
284 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request.");
289 * MS-CHAP-Challenges are 8 octets,
292 if (chap_challenge->length == 8) {
293 DEBUG2(" mschap1: %02x",
294 chap_challenge->vp_octets[0]);
295 data = chap_challenge->vp_octets;
299 * MS-CHAP-Challenges are 16 octets,
302 } else if (chap_challenge->length == 16) {
303 char *username_string;
305 DEBUG2(" mschap2: %02x", chap_challenge->vp_octets[0]);
306 response = pairfind(request->packet->vps,
307 PW_MSCHAP2_RESPONSE);
309 DEBUG2(" rlm_mschap: MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge.");
314 * Responses are 50 octets.
316 if (response->length < 50) {
317 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
321 user_name = pairfind(request->packet->vps,
324 DEBUG2(" rlm_mschap: User-Name is required to calculateMS-CHAPv1 Challenge.");
329 * with_ntdomain_hack moved here, too.
331 if ((username_string = strchr(user_name->vp_strvalue, '\\')) != NULL) {
332 if (inst->with_ntdomain_hack) {
335 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
336 username_string = user_name->vp_strvalue;
339 username_string = user_name->vp_strvalue;
343 * Get the MS-CHAPv1 challenge
344 * from the MS-CHAPv2 peer challenge,
345 * our challenge, and the user name.
347 challenge_hash(response->vp_octets + 2,
348 chap_challenge->vp_octets,
349 username_string, buffer);
353 DEBUG2(" rlm_mschap: Invalid MS-CHAP challenge length");
358 * Get the MS-CHAPv1 response, or the MS-CHAPv2
361 } else if (strcasecmp(fmt, "NT-Response") == 0) {
362 response = pairfind(request->packet->vps,
364 if (!response) response = pairfind(request->packet->vps,
365 PW_MSCHAP2_RESPONSE);
367 DEBUG2(" rlm_mschap: No MS-CHAP-Response or MS-CHAP2-Response was found in the request.");
372 * For MS-CHAPv1, the NT-Response exists only
373 * if the second octet says so.
375 if ((response->attribute == PW_MSCHAP_RESPONSE) &&
376 ((response->vp_octets[1] & 0x01) == 0)) {
377 DEBUG2(" rlm_mschap: No NT-Response in MS-CHAP-Response");
382 * MS-CHAP-Response and MS-CHAP2-Response have
383 * the NT-Response at the same offset, and are
386 data = response->vp_octets + 26;
390 * LM-Response is deprecated, and exists only
391 * in MS-CHAPv1, and not often there.
393 } else if (strcasecmp(fmt, "LM-Response") == 0) {
394 response = pairfind(request->packet->vps,
397 DEBUG2(" rlm_mschap: No MS-CHAP-Response was found in the request.");
402 * For MS-CHAPv1, the NT-Response exists only
403 * if the second octet says so.
405 if ((response->vp_octets[1] & 0x01) != 0) {
406 DEBUG2(" rlm_mschap: No LM-Response in MS-CHAP-Response");
409 data = response->vp_octets + 2;
413 * Pull the NT-Domain out of the User-Name, if it exists.
415 } else if (strcasecmp(fmt, "NT-Domain") == 0) {
418 user_name = pairfind(request->packet->vps, PW_USER_NAME);
420 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
425 * First check to see if this is a host/ style User-Name
426 * (a la Kerberos host principal)
428 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
430 * If we're getting a User-Name formatted in this way,
431 * it's likely due to PEAP. The Windows Domain will be
432 * the first domain component following the hostname,
433 * or the machine name itself if only a hostname is supplied
435 p = strchr(user_name->vp_strvalue, '.');
437 DEBUG2(" rlm_mschap: setting NT-Domain to same as machine name");
438 strlcpy(out, user_name->vp_strvalue + 5, outlen);
440 p++; /* skip the period */
443 * use the same hack as below
444 * only if another period was found
447 strlcpy(out, p, outlen);
451 p = strchr(user_name->vp_strvalue, '\\');
453 DEBUG2(" rlm_mschap: No NT-Domain was found in the User-Name.");
458 * Hack. This is simpler than the alternatives.
461 strlcpy(out, user_name->vp_strvalue, outlen);
468 * Pull the User-Name out of the User-Name...
470 } else if (strcasecmp(fmt, "User-Name") == 0) {
473 user_name = pairfind(request->packet->vps, PW_USER_NAME);
475 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
480 * First check to see if this is a host/ style User-Name
481 * (a la Kerberos host principal)
483 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
485 * If we're getting a User-Name formatted in this way,
486 * it's likely due to PEAP. When authenticating this against
487 * a Domain, Windows will expect the User-Name to be in the
488 * format of hostname$, the SAM version of the name, so we
489 * have to convert it to that here. We do so by stripping
490 * off the first 5 characters (host/), and copying everything
491 * from that point to the first period into a string and appending
494 p = strchr(user_name->vp_strvalue, '.');
496 * use the same hack as above
497 * only if a period was found
500 snprintf(out, outlen, "%s$", user_name->vp_strvalue + 5);
503 p = strchr(user_name->vp_strvalue, '\\');
505 p++; /* skip the backslash */
507 p = user_name->vp_strvalue; /* use the whole User-Name */
509 strlcpy(out, p, outlen);
515 * Return the NT-Hash of the passed string
517 } else if (strncasecmp(fmt, "NT-Hash ", 8) == 0) {
520 p = fmt + 8; /* 7 is the length of 'NT-Hash' */
521 if ((p == '\0') || (outlen <= 32))
523 DEBUG("rlm_mschap: NT-Hash: %s",p);
526 lrad_bin2hex(buffer, out, 16);
528 DEBUG("rlm_mschap: NT-Hash: Result: %s",out);
532 * Return the LM-Hash of the passed string
534 } else if (strncasecmp(fmt, "LM-Hash ", 8) == 0) {
537 p = fmt + 8; /* 7 is the length of 'LM-Hash' */
538 if ((p == '\0') || (outlen <= 32))
541 DEBUG("rlm_mschap: LM-Hash: %s",p);
542 smbdes_lmpwdhash(p,buffer);
543 lrad_bin2hex(buffer, out, 16);
545 DEBUG("rlm_mschap: LM-Hash: Result: %s",out);
548 DEBUG2(" rlm_mschap: Unknown expansion string \"%s\"",
553 if (outlen == 0) return 0; /* nowhere to go, don't do anything */
556 * Didn't set anything: this is bad.
559 DEBUG2(" rlm_mschap: Failed to do anything intelligent");
564 * Check the output length.
566 if (outlen < ((data_len * 2) + 1)) {
567 data_len = (outlen - 1) / 2;
573 for (i = 0; i < data_len; i++) {
574 sprintf(out + (2 * i), "%02x", data[i]);
576 out[data_len * 2] = '\0';
582 static const CONF_PARSER module_config[] = {
584 * Cache the password by default.
586 { "use_mppe", PW_TYPE_BOOLEAN,
587 offsetof(rlm_mschap_t,use_mppe), NULL, "yes" },
588 { "require_encryption", PW_TYPE_BOOLEAN,
589 offsetof(rlm_mschap_t,require_encryption), NULL, "no" },
590 { "require_strong", PW_TYPE_BOOLEAN,
591 offsetof(rlm_mschap_t,require_strong), NULL, "no" },
592 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
593 offsetof(rlm_mschap_t,with_ntdomain_hack), NULL, "no" },
594 { "passwd", PW_TYPE_STRING_PTR,
595 offsetof(rlm_mschap_t, passwd_file), NULL, NULL },
596 { "ntlm_auth", PW_TYPE_STRING_PTR,
597 offsetof(rlm_mschap_t, ntlm_auth), NULL, NULL },
599 { NULL, -1, 0, NULL, NULL } /* end the list */
603 * deinstantiate module, free all memory allocated during
604 * mschap_instantiate()
606 static int mschap_detach(void *instance){
607 #define inst ((rlm_mschap_t *)instance)
608 if (inst->xlat_name) {
609 xlat_unregister(inst->xlat_name, mschap_xlat);
610 free(inst->xlat_name);
618 * Create instance for our module. Allocate space for
619 * instance structure and read configuration parameters
621 static int mschap_instantiate(CONF_SECTION *conf, void **instance)
623 const char *xlat_name;
626 inst = *instance = rad_malloc(sizeof(*inst));
630 memset(inst, 0, sizeof(*inst));
632 if (cf_section_parse(conf, inst, module_config) < 0) {
638 * This module used to support SMB Password files, but it
639 * made it too complicated. If the user tries to
640 * configure an SMB Password file, then die, with an
643 if (inst->passwd_file) {
644 radlog(L_ERR, "rlm_mschap: SMB password file is no longer supported in this module. Use rlm_passwd module instead");
650 * Create the dynamic translation.
652 if (cf_section_name1(conf))
653 xlat_register(cf_section_name1(conf),mschap_xlat, inst);
655 if ((xlat_name = cf_section_name2(conf)) != NULL)
656 xlat_register(xlat_name, mschap_xlat, inst);
657 if (xlat_name == NULL)
658 xlat_name = cf_section_name1(conf);
660 inst->xlat_name = strdup(xlat_name);
663 * For backwards compatibility
665 if (!dict_valbyname(PW_AUTH_TYPE, inst->xlat_name)) {
666 inst->auth_type = "MS-CHAP";
673 * add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
674 * attribute to reply packet
676 static void add_reply(VALUE_PAIR** vp, unsigned char ident,
677 const char* name, const char* value, int len)
679 VALUE_PAIR *reply_attr;
680 reply_attr = pairmake(name, "", T_OP_EQ);
682 DEBUG(" rlm_mschap: Failed to create attribute %s: %s\n", name, librad_errstr);
686 reply_attr->vp_octets[0] = ident;
687 memcpy(reply_attr->vp_octets + 1, value, len);
688 reply_attr->length = len + 1;
689 pairadd(vp, reply_attr);
693 * Add MPPE attributes to the reply.
695 static void mppe_add_reply(VALUE_PAIR **vp,
696 const char* name, const char* value, int len)
698 VALUE_PAIR *reply_attr;
699 reply_attr = pairmake(name, "", T_OP_EQ);
701 DEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, librad_errstr);
705 memcpy(reply_attr->vp_octets, value, len);
706 reply_attr->length = len;
707 pairadd(vp, reply_attr);
712 * Do the MS-CHAP stuff.
714 * This function is here so that all of the MS-CHAP related
715 * authentication is in one place, and we can perhaps later replace
716 * it with code to call winbindd, or something similar.
718 static int do_mschap(rlm_mschap_t *inst,
719 REQUEST *request, VALUE_PAIR *password,
720 uint8_t *challenge, uint8_t *response,
723 int do_ntlm_auth = 0;
724 uint8_t calculated[24];
725 VALUE_PAIR *vp = NULL;
728 * If we have ntlm_auth configured, use it unless told
731 if (inst->ntlm_auth) do_ntlm_auth = 1;
734 * If we have an ntlm_auth configuration, then we may
737 vp = pairfind(request->config_items,
738 PW_MS_CHAP_USE_NTLM_AUTH);
739 if (vp) do_ntlm_auth = vp->lvalue;
742 * No ntlm_auth configured, attribute to tell us to
743 * use it exists, and we're told to use it. We don't
746 if (!inst->ntlm_auth && do_ntlm_auth) {
747 DEBUG2(" rlm_mschap: Asked to use ntlm_auth, but it was not configured in the mschap{} section.");
752 * Do normal authentication.
756 * No password: can't do authentication.
759 DEBUG2(" rlm_mschap: FAILED: No NT/LM-Password. Cannot perform authentication.");
763 smbdes_mschap(password->vp_strvalue, challenge, calculated);
764 if (memcmp(response, calculated, 24) != 0) {
769 * If the password exists, and is an NT-Password,
770 * then calculate the hash of the NT hash. Doing this
771 * here minimizes work for later.
773 if (password && (password->attribute == PW_NT_PASSWORD)) {
774 md4_calc(nthashhash, password->vp_strvalue, 16);
776 memset(nthashhash, 0, 16);
778 } else { /* run ntlm_auth */
782 memset(nthashhash, 0, 16);
785 * Run the program, and expect that we get 16
787 result = radius_exec_program(inst->ntlm_auth, request,
789 buffer, sizeof(buffer),
792 DEBUG2(" rlm_mschap: External script failed.");
797 * Parse the answer as an nthashhash.
799 * ntlm_auth currently returns:
800 * NT_KEY: 000102030405060708090a0b0c0d0e0f
802 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
803 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: expecting NT_KEY");
808 * Check the length. It should be at least 32,
809 * with an LF at the end.
811 if (strlen(buffer + 8) < 32) {
812 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has unexpected length");
817 * Update the NT hash hash, from the NT key.
819 if (lrad_hex2bin(buffer + 8, nthashhash, 16) != 16) {
820 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has non-hex values");
830 * Data for the hashes.
832 static const uint8_t SHSpad1[40] =
833 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
834 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
835 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
836 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
838 static const uint8_t SHSpad2[40] =
839 { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
840 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
841 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
842 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
844 static const uint8_t magic1[27] =
845 { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
846 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
847 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
849 static const uint8_t magic2[84] =
850 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
851 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
852 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
853 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
854 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
855 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
856 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
857 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
858 0x6b, 0x65, 0x79, 0x2e };
860 static const uint8_t magic3[84] =
861 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
862 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
863 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
864 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
865 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
866 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
867 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
868 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
869 0x6b, 0x65, 0x79, 0x2e };
872 static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response,
879 SHA1Update(&Context,nt_hashhash,16);
880 SHA1Update(&Context,nt_response,24);
881 SHA1Update(&Context,magic1,27);
882 SHA1Final(digest,&Context);
884 memcpy(masterkey,digest,16);
888 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
889 int keylen,int issend)
904 SHA1Update(&Context,masterkey,16);
905 SHA1Update(&Context,SHSpad1,40);
906 SHA1Update(&Context,s,84);
907 SHA1Update(&Context,SHSpad2,40);
908 SHA1Final(digest,&Context);
910 memcpy(sesskey,digest,keylen);
914 static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response,
915 uint8_t *sendkey,uint8_t *recvkey)
917 uint8_t masterkey[16];
919 mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
921 mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
922 mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
926 * Generate MPPE keys.
928 static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response,
929 uint8_t *sendkey,uint8_t *recvkey)
934 mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
937 * dictionary.microsoft defines these attributes as
938 * 'encrypt=2'. The functions in src/lib/radius.c will
939 * take care of encrypting/decrypting them as appropriate,
940 * so that we don't have to.
942 memcpy (sendkey, enckey1, 16);
943 memcpy (recvkey, enckey2, 16);
948 * mschap_authorize() - authorize user if we can authenticate
949 * it later. Add Auth-Type attribute if present in module
950 * configuration (usually Auth-Type must be "MS-CHAP")
952 static int mschap_authorize(void * instance, REQUEST *request)
954 #define inst ((rlm_mschap_t *)instance)
955 VALUE_PAIR *challenge = NULL, *response = NULL;
958 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
960 return RLM_MODULE_NOOP;
963 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
965 response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE);
968 * Nothing we recognize. Don't do anything.
971 DEBUG2(" rlm_mschap: Found MS-CHAP-Challenge, but no MS-CHAP-Response.");
972 return RLM_MODULE_NOOP;
975 DEBUG2(" rlm_mschap: Found MS-CHAP attributes. Setting 'Auth-Type = %s'", inst->xlat_name);
978 * Set Auth-Type to MS-CHAP. The authentication code
979 * will take care of turning clear-text passwords into
982 vp = pairmake("Auth-Type", inst->auth_type, T_OP_EQ);
983 if (!vp) return RLM_MODULE_FAIL;
984 pairmove(&request->config_items, &vp);
985 pairfree(&vp); /* may be NULL */
987 return RLM_MODULE_OK;
992 * mschap_authenticate() - authenticate user based on given
993 * attributes and configuration.
994 * We will try to find out password in configuration
995 * or in configured passwd file.
996 * If one is found we will check paraneters given by NAS.
998 * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
1000 * PAP: PW_USER_PASSWORD or
1001 * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
1002 * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
1003 * In case of password mismatch or locked account we MAY return
1004 * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
1005 * If MS-CHAP2 succeeds we MUST return
1006 * PW_MSCHAP2_SUCCESS
1008 static int mschap_authenticate(void * instance, REQUEST *request)
1010 #define inst ((rlm_mschap_t *)instance)
1011 VALUE_PAIR *challenge = NULL;
1012 VALUE_PAIR *response = NULL;
1013 VALUE_PAIR *password = NULL;
1014 VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
1015 VALUE_PAIR *username;
1016 VALUE_PAIR *reply_attr;
1017 uint8_t nthashhash[16];
1018 uint8_t msch2resp[42];
1019 char *username_string;
1023 * Find the SMB-Account-Ctrl attribute, or the
1024 * SMB-Account-Ctrl-Text attribute.
1026 smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL);
1028 password = pairfind(request->config_items,
1029 PW_SMB_ACCOUNT_CTRL_TEXT);
1031 smb_ctrl = pairmake("SMB-Account-CTRL", "0", T_OP_SET);
1032 pairadd(&request->config_items, smb_ctrl);
1033 smb_ctrl->lvalue = pdb_decode_acct_ctrl(password->vp_strvalue);
1038 * We're configured to do MS-CHAP authentication.
1039 * and account control information exists. Enforce it.
1043 * Password is not required.
1045 if ((smb_ctrl->lvalue & ACB_PWNOTREQ) != 0) {
1046 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says no password is required.");
1047 return RLM_MODULE_OK;
1052 * Decide how to get the passwords.
1054 password = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD);
1057 * We need an LM-Password.
1059 lm_password = pairfind(request->config_items, PW_LM_PASSWORD);
1064 if ((lm_password->length == 16) ||
1065 ((lm_password->length == 32) &&
1066 (lrad_hex2bin(lm_password->vp_strvalue,
1067 lm_password->vp_strvalue, 16) == 16))) {
1068 DEBUG2(" rlm_mschap: Found LM-Password");
1069 lm_password->length = 16;
1072 radlog(L_ERR, "rlm_mschap: Invalid LM-Password");
1076 } else if (!password) {
1077 DEBUG2(" rlm_mschap: No Cleartext-Password configured. Cannot create LM-Password.");
1079 } else { /* there is a configured Cleartext-Password */
1080 lm_password = pairmake("LM-Password", "", T_OP_EQ);
1082 radlog(L_ERR, "No memory");
1084 smbdes_lmpwdhash(password->vp_strvalue,
1085 lm_password->vp_strvalue);
1086 lm_password->length = 16;
1087 pairadd(&request->config_items, lm_password);
1092 * We need an NT-Password.
1094 nt_password = pairfind(request->config_items, PW_NT_PASSWORD);
1096 if ((nt_password->length == 16) ||
1097 ((nt_password->length == 32) &&
1098 (lrad_hex2bin(nt_password->vp_strvalue,
1099 nt_password->vp_strvalue, 16) == 16))) {
1100 DEBUG2(" rlm_mschap: Found NT-Password");
1101 nt_password->length = 16;
1104 radlog(L_ERR, "rlm_mschap: Invalid NT-Password");
1107 } else if (!password) {
1108 DEBUG2(" rlm_mschap: No Cleartext-Password configured. Cannot create NT-Password.");
1110 } else { /* there is a configured Cleartext-Password */
1111 nt_password = pairmake("NT-Password", "", T_OP_EQ);
1113 radlog(L_ERR, "No memory");
1114 return RLM_MODULE_FAIL;
1116 ntpwdhash(nt_password->vp_strvalue,
1117 password->vp_strvalue);
1118 nt_password->length = 16;
1119 pairadd(&request->config_items, nt_password);
1123 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
1125 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request");
1126 return RLM_MODULE_REJECT;
1130 * We also require an MS-CHAP-Response.
1132 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
1135 * MS-CHAP-Response, means MS-CHAPv1
1141 * MS-CHAPv1 challenges are 8 octets.
1143 if (challenge->length < 8) {
1144 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1145 return RLM_MODULE_INVALID;
1149 * Responses are 50 octets.
1151 if (response->length < 50) {
1152 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1153 return RLM_MODULE_INVALID;
1157 * We are doing MS-CHAP. Calculate the MS-CHAP
1160 if (response->vp_octets[1] & 0x01) {
1161 DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with NT-Password");
1162 password = nt_password;
1165 DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with LM-Password");
1166 password = lm_password;
1171 * Do the MS-CHAP authentication.
1173 if (do_mschap(inst, request, password, challenge->vp_octets,
1174 response->vp_octets + offset, nthashhash) < 0) {
1175 DEBUG2(" rlm_mschap: MS-CHAP-Response is incorrect.");
1176 add_reply(&request->reply->vps, *response->vp_octets,
1177 "MS-CHAP-Error", "E=691 R=1", 9);
1178 return RLM_MODULE_REJECT;
1183 } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE)) != NULL) {
1184 uint8_t mschapv1_challenge[16];
1187 * MS-CHAPv2 challenges are 16 octets.
1189 if (challenge->length < 16) {
1190 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1191 return RLM_MODULE_INVALID;
1195 * Responses are 50 octets.
1197 if (response->length < 50) {
1198 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1199 return RLM_MODULE_INVALID;
1203 * We also require a User-Name
1205 username = pairfind(request->packet->vps, PW_USER_NAME);
1207 radlog(L_AUTH, "rlm_mschap: We require a User-Name for MS-CHAPv2");
1208 return RLM_MODULE_INVALID;
1213 * with_ntdomain_hack moved here
1215 if ((username_string = strchr(username->vp_strvalue, '\\')) != NULL) {
1216 if (inst->with_ntdomain_hack) {
1219 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1220 username_string = username->vp_strvalue;
1223 username_string = username->vp_strvalue;
1227 * The old "mschapv2" function has been moved to
1230 * MS-CHAPv2 takes some additional data to create an
1231 * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1233 challenge_hash(response->vp_octets + 2, /* peer challenge */
1234 challenge->vp_octets, /* our challenge */
1235 username_string, /* user name */
1236 mschapv1_challenge); /* resulting challenge */
1238 DEBUG2(" rlm_mschap: Told to do MS-CHAPv2 for %s with NT-Password",
1241 if (do_mschap(inst, request, nt_password, mschapv1_challenge,
1242 response->vp_octets + 26, nthashhash) < 0) {
1243 DEBUG2(" rlm_mschap: FAILED: MS-CHAP2-Response is incorrect");
1244 add_reply(&request->reply->vps, *response->vp_octets,
1245 "MS-CHAP-Error", "E=691 R=1", 9);
1246 return RLM_MODULE_REJECT;
1250 * Get the NT-hash-hash, if necessary
1255 auth_response(username_string, /* without the domain */
1256 nthashhash, /* nt-hash-hash */
1257 response->vp_octets + 26, /* peer response */
1258 response->vp_octets + 2, /* peer challenge */
1259 challenge->vp_octets, /* our challenge */
1260 msch2resp); /* calculated MPPE key */
1261 add_reply( &request->reply->vps, *response->vp_octets,
1262 "MS-CHAP2-Success", msch2resp, 42);
1265 } else { /* Neither CHAPv1 or CHAPv2 response: die */
1266 radlog(L_AUTH, "rlm_mschap: No MS-CHAP response found");
1267 return RLM_MODULE_INVALID;
1271 * We have a CHAP response, but the account may be
1272 * disabled. Reject the user with the same error code
1273 * we use when their password is invalid.
1277 * Account is disabled.
1279 * They're found, but they don't exist, so we
1280 * return 'not found'.
1282 if (((smb_ctrl->lvalue & ACB_DISABLED) != 0) ||
1283 ((smb_ctrl->lvalue & ACB_NORMAL) == 0)) {
1284 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is disabled, or is not a normal account.");
1285 add_reply( &request->reply->vps, *response->vp_octets,
1286 "MS-CHAP-Error", "E=691 R=1", 9);
1287 return RLM_MODULE_NOTFOUND;
1291 * User is locked out.
1293 if ((smb_ctrl->lvalue & ACB_AUTOLOCK) != 0) {
1294 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is locked out.");
1295 add_reply( &request->reply->vps, *response->vp_octets,
1296 "MS-CHAP-Error", "E=647 R=0", 9);
1297 return RLM_MODULE_USERLOCK;
1301 /* now create MPPE attributes */
1302 if (inst->use_mppe) {
1303 uint8_t mppe_sendkey[34];
1304 uint8_t mppe_recvkey[34];
1307 DEBUG2("rlm_mschap: adding MS-CHAPv1 MPPE keys");
1308 memset(mppe_sendkey, 0, 32);
1310 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1314 * According to RFC 2548 we
1315 * should send NT hash. But in
1316 * practice it doesn't work.
1317 * Instead, we should send nthashhash
1319 * This is an error on RFC 2548.
1322 * do_mschap cares to zero nthashhash if NT hash
1325 memcpy(mppe_sendkey + 8,
1327 mppe_add_reply(&request->reply->vps,
1328 "MS-CHAP-MPPE-Keys",
1330 } else if (chap == 2) {
1331 DEBUG2("rlm_mschap: adding MS-CHAPv2 MPPE keys");
1332 mppe_chap2_gen_keys128(nthashhash,
1333 response->vp_octets + 26,
1334 mppe_sendkey, mppe_recvkey);
1336 mppe_add_reply(&request->reply->vps,
1339 mppe_add_reply(&request->reply->vps,
1344 reply_attr = pairmake("MS-MPPE-Encryption-Policy",
1345 (inst->require_encryption)? "0x00000002":"0x00000001",
1347 rad_assert(reply_attr != NULL);
1348 pairadd(&request->reply->vps, reply_attr);
1349 reply_attr = pairmake("MS-MPPE-Encryption-Types",
1350 (inst->require_strong)? "0x00000004":"0x00000006",
1352 rad_assert(reply_attr != NULL);
1353 pairadd(&request->reply->vps, reply_attr);
1355 } /* else we weren't asked to use MPPE */
1357 return RLM_MODULE_OK;
1361 module_t rlm_mschap = {
1364 RLM_TYPE_THREAD_SAFE, /* type */
1365 mschap_instantiate, /* instantiation */
1366 mschap_detach, /* detach */
1368 mschap_authenticate, /* authenticate */
1369 mschap_authorize, /* authorize */
1370 NULL, /* pre-accounting */
1371 NULL, /* accounting */
1372 NULL, /* checksimul */
1373 NULL, /* pre-proxy */
1374 NULL, /* post-proxy */
1375 NULL /* post-auth */