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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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> */
46 #include "libradius.h"
59 #include "rad_assert.h"
63 static const char rcsid[] = "$Id$";
65 static const char *letters = "0123456789ABCDEF";
68 * hex2bin converts hexadecimal strings into binary
70 static int hex2bin (const char *szHex, unsigned char* szBin, int len)
75 for (i = 0; i < len; i++) {
76 if( !(c1 = memchr(letters, toupper((int) szHex[i << 1]), 16)) ||
77 !(c2 = memchr(letters, toupper((int) szHex[(i << 1) + 1]), 16)))
79 szBin[i] = ((c1-letters)<<4) + (c2-letters);
85 * bin2hex creates hexadecimal presentation
88 static void bin2hex (const unsigned char *szBin, char *szHex, int len)
91 for (i = 0; i < len; i++) {
92 szHex[i<<1] = letters[szBin[i] >> 4];
93 szHex[(i<<1) + 1] = letters[szBin[i] & 0x0F];
98 /* Allowable account control bits */
99 #define ACB_DISABLED 0x0001 /* 1 = User account disabled */
100 #define ACB_HOMDIRREQ 0x0002 /* 1 = Home directory required */
101 #define ACB_PWNOTREQ 0x0004 /* 1 = User password not required */
102 #define ACB_TEMPDUP 0x0008 /* 1 = Temporary duplicate account */
103 #define ACB_NORMAL 0x0010 /* 1 = Normal user account */
104 #define ACB_MNS 0x0020 /* 1 = MNS logon user account */
105 #define ACB_DOMTRUST 0x0040 /* 1 = Interdomain trust account */
106 #define ACB_WSTRUST 0x0080 /* 1 = Workstation trust account */
107 #define ACB_SVRTRUST 0x0100 /* 1 = Server trust account */
108 #define ACB_PWNOEXP 0x0200 /* 1 = User password does not expire */
109 #define ACB_AUTOLOCK 0x0400 /* 1 = Account auto locked */
111 static int pdb_decode_acct_ctrl(const char *p)
117 * Check if the account type bits have been encoded after the
118 * NT password (in the form [NDHTUWSLXI]).
121 if (*p != '[') return 0;
123 for (p++; *p && !finished; p++) {
125 case 'N': /* 'N'o password. */
126 acct_ctrl |= ACB_PWNOTREQ;
129 case 'D': /* 'D'isabled. */
130 acct_ctrl |= ACB_DISABLED ;
133 case 'H': /* 'H'omedir required. */
134 acct_ctrl |= ACB_HOMDIRREQ;
137 case 'T': /* 'T'emp account. */
138 acct_ctrl |= ACB_TEMPDUP;
141 case 'U': /* 'U'ser account (normal). */
142 acct_ctrl |= ACB_NORMAL;
145 case 'M': /* 'M'NS logon user account. What is this? */
146 acct_ctrl |= ACB_MNS;
149 case 'W': /* 'W'orkstation account. */
150 acct_ctrl |= ACB_WSTRUST;
153 case 'S': /* 'S'erver account. */
154 acct_ctrl |= ACB_SVRTRUST;
157 case 'L': /* 'L'ocked account. */
158 acct_ctrl |= ACB_AUTOLOCK;
161 case 'X': /* No 'X'piry on password */
162 acct_ctrl |= ACB_PWNOEXP;
165 case 'I': /* 'I'nterdomain trust account. */
166 acct_ctrl |= ACB_DOMTRUST;
169 case ' ': /* ignore spaces */
187 * ntpwdhash converts Unicode password to 16-byte NT hash
190 static void ntpwdhash (char *szHash, const char *szPassword)
192 char szUnicodePass[513];
197 * NT passwords are unicode. Convert plain text password
198 * to unicode by inserting a zero every other byte
200 nPasswordLen = strlen(szPassword);
201 for (i = 0; i < nPasswordLen; i++) {
202 szUnicodePass[i << 1] = szPassword[i];
203 szUnicodePass[(i << 1) + 1] = 0;
206 /* Encrypt Unicode password to a 16-byte MD4 hash */
207 md4_calc(szHash, szUnicodePass, (nPasswordLen<<1) );
212 * challenge_hash() is used by mschap2() and auth_response()
213 * implements RFC2759 ChallengeHash()
214 * generates 64 bit challenge
216 static void challenge_hash( const char *peer_challenge,
217 const char *auth_challenge,
218 const char *user_name, char *challenge )
224 SHA1Update(&Context, peer_challenge, 16);
225 SHA1Update(&Context, auth_challenge, 16);
226 SHA1Update(&Context, user_name, strlen(user_name));
227 SHA1Final(hash, &Context);
228 memcpy(challenge, hash, 8);
232 * auth_response() generates MS-CHAP v2 SUCCESS response
233 * according to RFC 2759 GenerateAuthenticatorResponse()
234 * returns 42-octet response string
236 static void auth_response(const char *username,
237 const char *nt_hash_hash,
239 char *peer_challenge, char *auth_challenge,
243 const char magic1[39] =
244 {0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
245 0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
246 0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
247 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74};
249 const char magic2[41] =
250 {0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
251 0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
252 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
253 0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
260 SHA1Update(&Context, nt_hash_hash, 16);
261 SHA1Update(&Context, ntresponse, 24);
262 SHA1Update(&Context, magic1, 39);
263 SHA1Final(digest, &Context);
264 challenge_hash(peer_challenge, auth_challenge, username, challenge);
266 SHA1Update(&Context, digest, 20);
267 SHA1Update(&Context, challenge, 8);
268 SHA1Update(&Context, magic2, 41);
269 SHA1Final(digest, &Context);
272 * Encode the value of 'Digest' as "S=" followed by
273 * 40 ASCII hexadecimal digits and return it in
274 * AuthenticatorResponse.
276 * "S=0123456789ABCDEF0123456789ABCDEF01234567"
280 bin2hex(digest, response + 2, 20);
284 typedef struct rlm_mschap_t {
286 int require_encryption;
288 int with_ntdomain_hack; /* this should be in another module */
291 char *auth_type; /* I don't think this is needed... */
297 * Does dynamic translation of strings.
299 * Pulls NT-Response, LM-Response, or Challenge from MSCHAP
302 static int mschap_xlat(void *instance, REQUEST *request,
303 char *fmt, char *out, size_t outlen,
304 RADIUS_ESCAPE_STRING func)
307 uint8_t *data = NULL;
309 VALUE_PAIR *user_name;
310 VALUE_PAIR *chap_challenge, *response;
311 rlm_mschap_t *inst = instance;
313 chap_challenge = response = NULL;
315 func = func; /* -Wunused */
318 * Challenge means MS-CHAPv1 challenge, or
319 * hash of MS-CHAPv2 challenge, and peer challenge.
321 if (strcasecmp(fmt, "Challenge") == 0) {
322 chap_challenge = pairfind(request->packet->vps,
323 PW_MSCHAP_CHALLENGE);
324 if (!chap_challenge) {
325 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request.");
330 * MS-CHAP-Challenges are 8 octets,
333 if (chap_challenge->length == 8) {
334 DEBUG2(" mschap1: %02x", chap_challenge->strvalue[0]);
335 data = chap_challenge->strvalue;
339 * MS-CHAP-Challenges are 16 octets,
342 } else if (chap_challenge->length == 16) {
343 char *username_string;
345 DEBUG2(" mschap2: %02x", chap_challenge->strvalue[0]);
346 response = pairfind(request->packet->vps,
347 PW_MSCHAP2_RESPONSE);
349 DEBUG2(" rlm_mschap: MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge.");
354 * Responses are 50 octets.
356 if (response->length < 50) {
357 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
361 user_name = pairfind(request->packet->vps,
364 DEBUG2(" rlm_mschap: User-Name is required to calculateMS-CHAPv1 Challenge.");
369 * with_ntdomain_hack moved here, too.
371 if ((username_string = strchr(user_name->strvalue, '\\')) != NULL) {
372 if (inst->with_ntdomain_hack) {
375 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
376 username_string = user_name->strvalue;
379 username_string = user_name->strvalue;
383 * Get the MS-CHAPv1 challenge
384 * from the MS-CHAPv2 peer challenge,
385 * our challenge, and the user name.
387 challenge_hash(response->strvalue + 2,
388 chap_challenge->strvalue,
389 username_string, buffer);
393 DEBUG2(" rlm_mschap: Invalid MS-CHAP challenge length");
398 * Get the MS-CHAPv1 response, or the MS-CHAPv2
401 } else if (strcasecmp(fmt, "NT-Response") == 0) {
402 response = pairfind(request->packet->vps,
404 if (!response) response = pairfind(request->packet->vps,
405 PW_MSCHAP2_RESPONSE);
407 DEBUG2(" rlm_mschap: No MS-CHAP-Response or MS-CHAP2-Response was found in the request.");
412 * For MS-CHAPv1, the NT-Response exists only
413 * if the second octet says so.
415 if ((response->attribute == PW_MSCHAP_RESPONSE) &&
416 ((response->strvalue[1] & 0x01) == 0)) {
417 DEBUG2(" rlm_mschap: No NT-Response in MS-CHAP-Response");
422 * MS-CHAP-Response and MS-CHAP2-Response have
423 * the NT-Response at the same offset, and are
426 data = response->strvalue + 26;
430 * LM-Response is deprecated, and exists only
431 * in MS-CHAPv1, and not often there.
433 } else if (strcasecmp(fmt, "LM-Response") == 0) {
434 response = pairfind(request->packet->vps,
437 DEBUG2(" rlm_mschap: No MS-CHAP-Response was found in the request.");
442 * For MS-CHAPv1, the NT-Response exists only
443 * if the second octet says so.
445 if ((response->strvalue[1] & 0x01) != 0) {
446 DEBUG2(" rlm_mschap: No LM-Response in MS-CHAP-Response");
449 data = response->strvalue + 2;
453 * Pull the NT-Domain out of the User-Name, if it exists.
455 } else if (strcasecmp(fmt, "NT-Domain") == 0) {
458 user_name = pairfind(request->packet->vps, PW_USER_NAME);
460 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
464 p = strchr(user_name->strvalue, '\\');
466 DEBUG2(" rlm_mschap: No NT-Domain was found in the User-Name.");
471 * Hack. This is simpler than the alternatives.
474 strNcpy(out, user_name->strvalue, outlen);
480 * Pull the User-Name out of the User-Name...
482 } else if (strcasecmp(fmt, "User-Name") == 0) {
485 user_name = pairfind(request->packet->vps, PW_USER_NAME);
487 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
491 p = strchr(user_name->strvalue, '\\');
493 p++; /* skip the backslash */
495 p = user_name->strvalue; /* use the whole User-Name */
498 strNcpy(out, p, outlen);
502 DEBUG2(" rlm_mschap: Unknown expansion string \"%s\"",
507 if (outlen == 0) return 0; /* nowhere to go, don't do anything */
510 * Didn't set anything: this is bad.
513 DEBUG2(" rlm_mschap: Failed to do anything intelligent");
518 * Check the output length.
520 if (outlen < ((data_len * 2) + 1)) {
521 data_len = (outlen - 1) / 2;
527 for (i = 0; i < data_len; i++) {
528 sprintf(out + (2 * i), "%02x", data[i]);
530 out[data_len * 2] = '\0';
536 static CONF_PARSER module_config[] = {
538 * Cache the password by default.
540 { "use_mppe", PW_TYPE_BOOLEAN,
541 offsetof(rlm_mschap_t,use_mppe), NULL, "yes" },
542 { "require_encryption", PW_TYPE_BOOLEAN,
543 offsetof(rlm_mschap_t,require_encryption), NULL, "no" },
544 { "require_strong", PW_TYPE_BOOLEAN,
545 offsetof(rlm_mschap_t,require_strong), NULL, "no" },
546 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
547 offsetof(rlm_mschap_t,with_ntdomain_hack), NULL, "no" },
548 { "passwd", PW_TYPE_STRING_PTR,
549 offsetof(rlm_mschap_t, passwd_file), NULL, NULL },
550 { "authtype", PW_TYPE_STRING_PTR,
551 offsetof(rlm_mschap_t, auth_type), NULL, NULL },
552 { "ntlm_auth", PW_TYPE_STRING_PTR,
553 offsetof(rlm_mschap_t, ntlm_auth), NULL, NULL },
555 { NULL, -1, 0, NULL, NULL } /* end the list */
559 * deinstantiate module, free all memory allocated during
560 * mschap_instantiate()
562 static int mschap_detach(void *instance){
563 #define inst ((rlm_mschap_t *)instance)
564 if (inst->passwd_file) free(inst->passwd_file);
565 if (inst->auth_type) free(inst->auth_type);
566 if (inst->ntlm_auth) free(inst->ntlm_auth);
567 if (inst->xlat_name) {
568 xlat_unregister(inst->xlat_name, mschap_xlat);
569 free(inst->xlat_name);
577 * Create instance for our module. Allocate space for
578 * instance structure and read configuration parameters
580 static int mschap_instantiate(CONF_SECTION *conf, void **instance)
582 const char *xlat_name;
585 inst = *instance = rad_malloc(sizeof(*inst));
589 memset(inst, 0, sizeof(*inst));
591 if (cf_section_parse(conf, inst, module_config) < 0) {
597 * This module used to support SMB Password files, but it
598 * made it too complicated. If the user tries to
599 * configure an SMB Password file, then die, with an
602 if (inst->passwd_file) {
603 radlog(L_ERR, "rlm_mschap: SMB password file is no longer supported in this module. Use rlm_passwd module instead");
609 * Create the dynamic translation.
611 xlat_name = cf_section_name2(conf);
612 if (xlat_name == NULL)
613 xlat_name = cf_section_name1(conf);
615 inst->xlat_name = strdup(xlat_name);
616 xlat_register(xlat_name, mschap_xlat, inst);
623 * add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
624 * attribute to reply packet
626 static void add_reply(VALUE_PAIR** vp, unsigned char ident,
627 const char* name, const char* value, int len)
629 VALUE_PAIR *reply_attr;
630 reply_attr = pairmake(name, "", T_OP_EQ);
632 DEBUG(" rlm_mschap: Failed to create attribute %s: %s\n", name, librad_errstr);
636 reply_attr->strvalue[0] = ident;
637 memcpy(reply_attr->strvalue + 1, value, len);
638 reply_attr->length = len + 1;
639 pairadd(vp, reply_attr);
643 * Add MPPE attributes to the reply.
645 static void mppe_add_reply(VALUE_PAIR **vp,
646 const char* name, const char* value, int len)
648 VALUE_PAIR *reply_attr;
649 reply_attr = pairmake(name, "", T_OP_EQ);
651 DEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, librad_errstr);
655 memcpy(reply_attr->strvalue, value, len);
656 reply_attr->length = len;
657 pairadd(vp, reply_attr);
662 * Do the MS-CHAP stuff.
664 * This function is here so that all of the MS-CHAP related
665 * authentication is in one place, and we can perhaps later replace
666 * it with code to call winbindd, or something similar.
668 static int do_mschap(rlm_mschap_t *inst,
669 REQUEST *request, VALUE_PAIR *password,
670 uint8_t *challenge, uint8_t *response,
673 int do_ntlm_auth = 0;
674 uint8_t calculated[24];
675 VALUE_PAIR *vp = NULL;
678 * If we have ntlm_auth configured, use it unless told
681 if (inst->ntlm_auth) do_ntlm_auth = 1;
684 * If we have an ntlm_auth configuration, then we may
687 vp = pairfind(request->config_items,
688 PW_MS_CHAP_USE_NTLM_AUTH);
689 if (vp) do_ntlm_auth = vp->lvalue;
692 * No ntlm_auth configured, attribute to tell us to
693 * use it exists, and we're told to use it. We don't
696 if (!inst->ntlm_auth && do_ntlm_auth) {
697 DEBUG2(" rlm_mschap: Asked to use ntlm_auth, but it was not configured in the mschap{} section.");
702 * Do normal authentication.
706 * No password: can't do authentication.
709 DEBUG2(" rlm_mschap: FAILED: No NT/LM-Password. Cannot perform authentication.");
713 smbdes_mschap(password->strvalue, challenge, calculated);
714 if (memcmp(response, calculated, 24) != 0) {
719 * If the password exists, and is an NT-Password,
720 * then calculate the hash of the NT hash. Doing this
721 * here minimizes work for later.
723 if (password && (password->attribute == PW_NT_PASSWORD)) {
724 md4_calc(nthashhash, password->strvalue, 16);
726 memset(nthashhash, 0, 16);
728 } else { /* run ntlm_auth */
732 memset(nthashhash, 0, 16);
735 * Run the program, and expect that we get 16
737 result = radius_exec_program(inst->ntlm_auth, request,
739 buffer, sizeof(buffer),
742 DEBUG2(" rlm_mschap: External script failed.");
747 * Parse the answer as an nthashhash.
749 * ntlm_auth currently returns:
750 * NT_KEY: 000102030405060708090a0b0c0d0e0f
752 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
753 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: expecting NT_KEY");
758 * Check the length. It should be at least 32,
759 * with an LF at the end.
761 if (strlen(buffer + 8) < 32) {
762 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has unexpected length");
767 * Update the NT hash hash, from the NT key.
769 if (hex2bin(buffer + 8, nthashhash, 16) != 16) {
770 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has non-hex values");
780 * Data for the hashes.
782 static const uint8_t SHSpad1[40] =
783 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
784 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
785 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
786 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
788 static const uint8_t SHSpad2[40] =
789 { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
790 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
791 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
792 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
794 static const uint8_t magic1[27] =
795 { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
796 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
797 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
799 static const uint8_t magic2[84] =
800 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
801 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
802 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
803 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
804 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
805 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
806 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
807 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
808 0x6b, 0x65, 0x79, 0x2e };
810 static const uint8_t magic3[84] =
811 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
812 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
813 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
814 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
815 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
816 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
817 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
818 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
819 0x6b, 0x65, 0x79, 0x2e };
822 static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response,
829 SHA1Update(&Context,nt_hashhash,16);
830 SHA1Update(&Context,nt_response,24);
831 SHA1Update(&Context,magic1,27);
832 SHA1Final(digest,&Context);
834 memcpy(masterkey,digest,16);
838 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
839 int keylen,int issend)
854 SHA1Update(&Context,masterkey,16);
855 SHA1Update(&Context,SHSpad1,40);
856 SHA1Update(&Context,s,84);
857 SHA1Update(&Context,SHSpad2,40);
858 SHA1Final(digest,&Context);
860 memcpy(sesskey,digest,keylen);
864 static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response,
865 uint8_t *sendkey,uint8_t *recvkey)
867 uint8_t masterkey[16];
869 mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
871 mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
872 mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
876 * Generate MPPE keys.
878 static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response,
879 uint8_t *sendkey,uint8_t *recvkey)
884 mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
887 * dictionary.microsoft defines these attributes as
888 * 'encrypt=2'. The functions in src/lib/radius.c will
889 * take care of encrypting/decrypting them as appropriate,
890 * so that we don't have to.
892 memcpy (sendkey, enckey1, 16);
893 memcpy (recvkey, enckey2, 16);
898 * mschap_authorize() - authorize user if we can authenticate
899 * it later. Add Auth-Type attribute if present in module
900 * configuration (usually Auth-Type must be "MS-CHAP")
902 static int mschap_authorize(void * instance, REQUEST *request)
904 #define inst ((rlm_mschap_t *)instance)
905 VALUE_PAIR *challenge = NULL, *response = NULL;
907 const char *authtype_name = "MS-CHAP";
909 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
911 return RLM_MODULE_NOOP;
914 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
916 response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE);
919 * Nothing we recognize. Don't do anything.
922 DEBUG2(" rlm_mschap: Found MS-CHAP-Challenge, but no MS-CHAP-Response.");
923 return RLM_MODULE_NOOP;
927 * Choose MS-CHAP, or whatever else they told us to use.
929 if (inst->auth_type) {
930 authtype_name = inst->auth_type;
933 DEBUG2(" rlm_mschap: Found MS-CHAP attributes. Setting 'Auth-Type = %s'", authtype_name);
936 * Set Auth-Type to MS-CHAP. The authentication code
937 * will take care of turning clear-text passwords into
940 pairdelete(&request->config_items, PW_AUTHTYPE);
941 vp = pairmake("Auth-Type", authtype_name, T_OP_EQ);
942 rad_assert(vp != NULL);
943 pairadd(&request->config_items, vp);
945 return RLM_MODULE_OK;
950 * mschap_authenticate() - authenticate user based on given
951 * attributes and configuration.
952 * We will try to find out password in configuration
953 * or in configured passwd file.
954 * If one is found we will check paraneters given by NAS.
956 * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
958 * PAP: PW_PASSWORD or
959 * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
960 * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
961 * In case of password mismatch or locked account we MAY return
962 * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
963 * If MS-CHAP2 succeeds we MUST return
966 static int mschap_authenticate(void * instance, REQUEST *request)
968 #define inst ((rlm_mschap_t *)instance)
969 VALUE_PAIR *challenge = NULL, *response = NULL;
970 VALUE_PAIR *password = NULL;
971 VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
972 VALUE_PAIR *username;
973 VALUE_PAIR *reply_attr;
974 uint8_t nthashhash[16];
975 uint8_t msch2resp[42];
976 char *username_string;
980 * Find the SMB-Account-Ctrl attribute, or the
981 * SMB-Account-Ctrl-Text attribute.
983 smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL);
985 password = pairfind(request->config_items,
986 PW_SMB_ACCOUNT_CTRL_TEXT);
988 smb_ctrl = pairmake("SMB-Account-CTRL", "0", T_OP_SET);
989 pairadd(&request->config_items, smb_ctrl);
990 smb_ctrl->lvalue = pdb_decode_acct_ctrl(password->strvalue);
995 * We're configured to do MS-CHAP authentication.
996 * and account control information exists. Enforce it.
1000 * Password is not required.
1002 if ((smb_ctrl->lvalue & ACB_PWNOTREQ) != 0) {
1003 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says no password is required.");
1004 return RLM_MODULE_OK;
1009 * Decide how to get the passwords.
1011 password = pairfind(request->config_items, PW_PASSWORD);
1014 * We need an LM-Password.
1016 lm_password = pairfind(request->config_items, PW_LM_PASSWORD);
1021 if ((lm_password->length == 16) ||
1022 ((lm_password->length == 32) &&
1023 (hex2bin(lm_password->strvalue,
1024 lm_password->strvalue, 16) == 16))) {
1025 DEBUG2(" rlm_mschap: Found LM-Password");
1026 lm_password->length = 16;
1029 radlog(L_ERR, "rlm_mschap: Invalid LM-Password");
1033 } else if (!password) {
1034 DEBUG2(" rlm_mschap: No User-Password configured. Cannot create LM-Password.");
1036 } else { /* there is a configured User-Password */
1037 lm_password = pairmake("LM-Password", "", T_OP_EQ);
1039 radlog(L_ERR, "No memory");
1041 smbdes_lmpwdhash(password->strvalue,
1042 lm_password->strvalue);
1043 lm_password->length = 16;
1044 pairadd(&request->config_items, lm_password);
1049 * We need an NT-Password.
1051 nt_password = pairfind(request->config_items, PW_NT_PASSWORD);
1053 if ((nt_password->length == 16) ||
1054 ((nt_password->length == 32) &&
1055 (hex2bin(nt_password->strvalue,
1056 nt_password->strvalue, 16) == 16))) {
1057 DEBUG2(" rlm_mschap: Found NT-Password");
1058 nt_password->length = 16;
1061 radlog(L_ERR, "rlm_mschap: Invalid NT-Password");
1064 } else if (!password) {
1065 DEBUG2(" rlm_mschap: No User-Password configured. Cannot create NT-Password.");
1067 } else { /* there is a configured User-Password */
1068 nt_password = pairmake("NT-Password", "", T_OP_EQ);
1070 radlog(L_ERR, "No memory");
1071 return RLM_MODULE_FAIL;
1073 ntpwdhash(nt_password->strvalue, password->strvalue);
1074 nt_password->length = 16;
1075 pairadd(&request->config_items, nt_password);
1079 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
1081 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request");
1082 return RLM_MODULE_REJECT;
1086 * We also require an MS-CHAP-Response.
1088 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
1091 * MS-CHAP-Response, means MS-CHAPv1
1097 * MS-CHAPv1 challenges are 8 octets.
1099 if (challenge->length < 8) {
1100 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1101 return RLM_MODULE_INVALID;
1105 * Responses are 50 octets.
1107 if (response->length < 50) {
1108 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1109 return RLM_MODULE_INVALID;
1113 * We are doing MS-CHAP. Calculate the MS-CHAP
1116 if (response->strvalue[1] & 0x01) {
1117 DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with NT-Password");
1118 password = nt_password;
1121 DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with LM-Password");
1122 password = lm_password;
1127 * Do the MS-CHAP authentication.
1129 if (do_mschap(inst, request, password, challenge->strvalue,
1130 response->strvalue + offset, nthashhash) < 0) {
1131 DEBUG2(" rlm_mschap: MS-CHAP-Response is incorrect.");
1132 add_reply(&request->reply->vps, *response->strvalue,
1133 "MS-CHAP-Error", "E=691 R=1", 9);
1134 return RLM_MODULE_REJECT;
1139 } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE)) != NULL) {
1140 uint8_t mschapv1_challenge[16];
1143 * MS-CHAPv2 challenges are 16 octets.
1145 if (challenge->length < 16) {
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 also require a User-Name
1161 username = pairfind(request->packet->vps, PW_USER_NAME);
1163 radlog(L_AUTH, "rlm_mschap: We require a User-Name for MS-CHAPv2");
1164 return RLM_MODULE_INVALID;
1169 * with_ntdomain_hack moved here
1171 if ((username_string = strchr(username->strvalue, '\\')) != NULL) {
1172 if (inst->with_ntdomain_hack) {
1175 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1176 username_string = username->strvalue;
1179 username_string = username->strvalue;
1183 * The old "mschapv2" function has been moved to
1186 * MS-CHAPv2 takes some additional data to create an
1187 * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1189 challenge_hash(response->strvalue + 2, /* peer challenge */
1190 challenge->strvalue, /* our challenge */
1191 username_string, /* user name */
1192 mschapv1_challenge); /* resulting challenge */
1194 DEBUG2(" rlm_mschap: Told to do MS-CHAPv2 for %s with NT-Password",
1197 if (do_mschap(inst, request, nt_password, mschapv1_challenge,
1198 response->strvalue + 26, nthashhash) < 0) {
1199 DEBUG2(" rlm_mschap: FAILED: MS-CHAP2-Response is incorrect");
1200 add_reply(&request->reply->vps, *response->strvalue,
1201 "MS-CHAP-Error", "E=691 R=1", 9);
1202 return RLM_MODULE_REJECT;
1206 * Get the NT-hash-hash, if necessary
1211 auth_response(username_string, /* without the domain */
1212 nthashhash, /* nt-hash-hash */
1213 response->strvalue + 26, /* peer response */
1214 response->strvalue + 2, /* peer challenge */
1215 challenge->strvalue, /* our challenge */
1216 msch2resp); /* calculated MPPE key */
1217 add_reply( &request->reply->vps, *response->strvalue,
1218 "MS-CHAP2-Success", msch2resp, 42);
1221 } else { /* Neither CHAPv1 or CHAPv2 response: die */
1222 radlog(L_AUTH, "rlm_mschap: No MS-CHAP response found");
1223 return RLM_MODULE_INVALID;
1227 * We have a CHAP response, but the account may be
1228 * disabled. Reject the user with the same error code
1229 * we use when their password is invalid.
1233 * Account is disabled.
1235 * They're found, but they don't exist, so we
1236 * return 'not found'.
1238 if (((smb_ctrl->lvalue & ACB_DISABLED) != 0) ||
1239 ((smb_ctrl->lvalue & ACB_NORMAL) == 0)) {
1240 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is disabled, or is not a normal account.");
1241 add_reply( &request->reply->vps, *response->strvalue,
1242 "MS-CHAP-Error", "E=691 R=1", 9);
1243 return RLM_MODULE_NOTFOUND;
1247 * User is locked out.
1249 if ((smb_ctrl->lvalue & ACB_AUTOLOCK) != 0) {
1250 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is locked out.");
1251 add_reply( &request->reply->vps, *response->strvalue,
1252 "MS-CHAP-Error", "E=647 R=0", 9);
1253 return RLM_MODULE_USERLOCK;
1257 /* now create MPPE attributes */
1258 if (inst->use_mppe) {
1259 uint8_t mppe_sendkey[34];
1260 uint8_t mppe_recvkey[34];
1263 DEBUG2("rlm_mschap: adding MS-CHAPv1 MPPE keys");
1264 memset(mppe_sendkey, 0, 32);
1266 memcpy(mppe_sendkey, lm_password->strvalue, 8);
1270 * According to RFC 2548 we
1271 * should send NT hash. But in
1272 * practice it doesn't work.
1273 * Instead, we should send nthashhash
1275 * This is an error on RFC 2548.
1278 * do_mschap cares to zero nthashhash if NT hash
1281 memcpy(mppe_sendkey + 8,
1283 mppe_add_reply(&request->reply->vps,
1284 "MS-CHAP-MPPE-Keys",
1286 } else if (chap == 2) {
1287 DEBUG2("rlm_mschap: adding MS-CHAPv2 MPPE keys");
1288 mppe_chap2_gen_keys128(nthashhash,
1289 response->strvalue + 26,
1290 mppe_sendkey, mppe_recvkey);
1292 mppe_add_reply(&request->reply->vps,
1295 mppe_add_reply(&request->reply->vps,
1300 reply_attr = pairmake("MS-MPPE-Encryption-Policy",
1301 (inst->require_encryption)? "0x00000002":"0x00000001",
1303 rad_assert(reply_attr != NULL);
1304 pairadd(&request->reply->vps, reply_attr);
1305 reply_attr = pairmake("MS-MPPE-Encryption-Types",
1306 (inst->require_strong)? "0x00000004":"0x00000006",
1308 rad_assert(reply_attr != NULL);
1309 pairadd(&request->reply->vps, reply_attr);
1311 } /* else we weren't asked to use MPPE */
1313 return RLM_MODULE_OK;
1317 module_t rlm_mschap = {
1319 RLM_TYPE_THREAD_SAFE, /* type */
1320 NULL, /* initialize */
1321 mschap_instantiate, /* instantiation */
1323 mschap_authenticate, /* authenticate */
1324 mschap_authorize, /* authorize */
1325 NULL, /* pre-accounting */
1326 NULL, /* accounting */
1327 NULL, /* checksimul */
1328 NULL, /* pre-proxy */
1329 NULL, /* post-proxy */
1330 NULL /* post-auth */
1332 mschap_detach, /* detach */