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"
61 static const char rcsid[] = "$Id$";
63 static const char *letters = "0123456789ABCDEF";
66 * hex2bin converts hexadecimal strings into binary
68 static int hex2bin (const char *szHex, unsigned char* szBin, int len)
73 for (i = 0; i < len; i++) {
74 if( !(c1 = memchr(letters, toupper((int) szHex[i << 1]), 16)) ||
75 !(c2 = memchr(letters, toupper((int) szHex[(i << 1) + 1]), 16)))
77 szBin[i] = ((c1-letters)<<4) + (c2-letters);
83 * bin2hex creates hexadecimal presentation
86 static void bin2hex (const unsigned char *szBin, char *szHex, int len)
89 for (i = 0; i < len; i++) {
90 szHex[i<<1] = letters[szBin[i] >> 4];
91 szHex[(i<<1) + 1] = letters[szBin[i] & 0x0F];
96 /* Allowable account control bits */
97 #define ACB_DISABLED 0x0001 /* 1 = User account disabled */
98 #define ACB_HOMDIRREQ 0x0002 /* 1 = Home directory required */
99 #define ACB_PWNOTREQ 0x0004 /* 1 = User password not required */
100 #define ACB_TEMPDUP 0x0008 /* 1 = Temporary duplicate account */
101 #define ACB_NORMAL 0x0010 /* 1 = Normal user account */
102 #define ACB_MNS 0x0020 /* 1 = MNS logon user account */
103 #define ACB_DOMTRUST 0x0040 /* 1 = Interdomain trust account */
104 #define ACB_WSTRUST 0x0080 /* 1 = Workstation trust account */
105 #define ACB_SVRTRUST 0x0100 /* 1 = Server trust account */
106 #define ACB_PWNOEXP 0x0200 /* 1 = User password does not expire */
107 #define ACB_AUTOLOCK 0x0400 /* 1 = Account auto locked */
109 static int pdb_decode_acct_ctrl(const char *p)
115 * Check if the account type bits have been encoded after the
116 * NT password (in the form [NDHTUWSLXI]).
119 if (*p != '[') return 0;
121 for (p++; *p && !finished; p++) {
123 case 'N': /* 'N'o password. */
124 acct_ctrl |= ACB_PWNOTREQ;
127 case 'D': /* 'D'isabled. */
128 acct_ctrl |= ACB_DISABLED ;
131 case 'H': /* 'H'omedir required. */
132 acct_ctrl |= ACB_HOMDIRREQ;
135 case 'T': /* 'T'emp account. */
136 acct_ctrl |= ACB_TEMPDUP;
139 case 'U': /* 'U'ser account (normal). */
140 acct_ctrl |= ACB_NORMAL;
143 case 'M': /* 'M'NS logon user account. What is this? */
144 acct_ctrl |= ACB_MNS;
147 case 'W': /* 'W'orkstation account. */
148 acct_ctrl |= ACB_WSTRUST;
151 case 'S': /* 'S'erver account. */
152 acct_ctrl |= ACB_SVRTRUST;
155 case 'L': /* 'L'ocked account. */
156 acct_ctrl |= ACB_AUTOLOCK;
159 case 'X': /* No 'X'piry on password */
160 acct_ctrl |= ACB_PWNOEXP;
163 case 'I': /* 'I'nterdomain trust account. */
164 acct_ctrl |= ACB_DOMTRUST;
167 case ' ': /* ignore spaces */
185 * ntpwdhash converts Unicode password to 16-byte NT hash
188 static void ntpwdhash (char *szHash, const char *szPassword)
190 char szUnicodePass[513];
195 * NT passwords are unicode. Convert plain text password
196 * to unicode by inserting a zero every other byte
198 nPasswordLen = strlen(szPassword);
199 for (i = 0; i < nPasswordLen; i++) {
200 szUnicodePass[i << 1] = szPassword[i];
201 szUnicodePass[(i << 1) + 1] = 0;
204 /* Encrypt Unicode password to a 16-byte MD4 hash */
205 md4_calc(szHash, szUnicodePass, (nPasswordLen<<1) );
210 * challenge_hash() is used by mschap2() and auth_response()
211 * implements RFC2759 ChallengeHash()
212 * generates 64 bit challenge
214 static void challenge_hash( const char *peer_challenge,
215 const char *auth_challenge,
216 const char *user_name, char *challenge )
222 SHA1Update(&Context, peer_challenge, 16);
223 SHA1Update(&Context, auth_challenge, 16);
224 SHA1Update(&Context, user_name, strlen(user_name));
225 SHA1Final(hash, &Context);
226 memcpy(challenge, hash, 8);
230 * auth_response() generates MS-CHAP v2 SUCCESS response
231 * according to RFC 2759 GenerateAuthenticatorResponse()
232 * returns 42-octet response string
234 static void auth_response(const char *username,
235 const char *nt_hash_hash,
237 char *peer_challenge, char *auth_challenge,
241 const char magic1[39] =
242 {0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
243 0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
244 0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
245 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74};
247 const char magic2[41] =
248 {0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
249 0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
250 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
251 0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
258 SHA1Update(&Context, nt_hash_hash, 16);
259 SHA1Update(&Context, ntresponse, 24);
260 SHA1Update(&Context, magic1, 39);
261 SHA1Final(digest, &Context);
262 challenge_hash(peer_challenge, auth_challenge, username, challenge);
264 SHA1Update(&Context, digest, 20);
265 SHA1Update(&Context, challenge, 8);
266 SHA1Update(&Context, magic2, 41);
267 SHA1Final(digest, &Context);
270 * Encode the value of 'Digest' as "S=" followed by
271 * 40 ASCII hexadecimal digits and return it in
272 * AuthenticatorResponse.
274 * "S=0123456789ABCDEF0123456789ABCDEF01234567"
278 bin2hex(digest, response + 2, 20);
282 typedef struct rlm_mschap_t {
284 int require_encryption;
286 int with_ntdomain_hack; /* this should be in another module */
289 char *auth_type; /* I don't think this is needed... */
295 * Does dynamic translation of strings.
297 * Pulls NT-Response, LM-Response, or Challenge from MSCHAP
300 static int mschap_xlat(void *instance, REQUEST *request,
301 char *fmt, char *out, size_t outlen,
302 RADIUS_ESCAPE_STRING func)
305 uint8_t *data = NULL;
307 VALUE_PAIR *user_name;
308 VALUE_PAIR *chap_challenge, *response;
309 rlm_mschap_t *inst = instance;
311 chap_challenge = response = NULL;
313 func = func; /* -Wunused */
316 * Challenge means MS-CHAPv1 challenge, or
317 * hash of MS-CHAPv2 challenge, and peer challenge.
319 if (strcasecmp(fmt, "Challenge") == 0) {
320 chap_challenge = pairfind(request->packet->vps,
321 PW_MSCHAP_CHALLENGE);
322 if (!chap_challenge) {
323 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request.");
328 * MS-CHAP-Challenges are 8 octets,
331 if (chap_challenge->length == 8) {
332 DEBUG2(" mschap1: %02x", chap_challenge->strvalue[0]);
333 data = chap_challenge->strvalue;
337 * MS-CHAP-Challenges are 16 octets,
340 } else if (chap_challenge->length == 16) {
341 char *username_string;
343 DEBUG2(" mschap2: %02x", chap_challenge->strvalue[0]);
344 response = pairfind(request->packet->vps,
345 PW_MSCHAP2_RESPONSE);
347 DEBUG2(" rlm_mschap: MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge.");
352 * Responses are 50 octets.
354 if (response->length < 50) {
355 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
359 user_name = pairfind(request->packet->vps,
362 DEBUG2(" rlm_mschap: User-Name is required to calculateMS-CHAPv1 Challenge.");
367 * with_ntdomain_hack moved here, too.
369 if ((username_string = strchr(user_name->strvalue, '\\')) != NULL) {
370 if (inst->with_ntdomain_hack) {
373 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
374 username_string = user_name->strvalue;
377 username_string = user_name->strvalue;
381 * Get the MS-CHAPv1 challenge
382 * from the MS-CHAPv2 peer challenge,
383 * our challenge, and the user name.
385 challenge_hash(response->strvalue + 2,
386 chap_challenge->strvalue,
387 user_name->strvalue, buffer);
391 DEBUG2(" rlm_mschap: Invalid MS-CHAP challenge length");
396 * Get the MS-CHAPv1 response, or the MS-CHAPv2
399 } else if (strcasecmp(fmt, "NT-Response") == 0) {
400 response = pairfind(request->packet->vps,
402 if (!response) response = pairfind(request->packet->vps,
403 PW_MSCHAP2_RESPONSE);
405 DEBUG2(" rlm_mschap: No MS-CHAP-Response or MS-CHAP2-Response was found in the request.");
410 * For MS-CHAPv1, the NT-Response exists only
411 * if the second octet says so.
413 if ((response->attribute == PW_MSCHAP_RESPONSE) &&
414 ((response->strvalue[1] & 0x01) == 0)) {
415 DEBUG2(" rlm_mschap: No NT-Response in MS-CHAP-Response");
420 * MS-CHAP-Response and MS-CHAP2-Response have
421 * the NT-Response at the same offset, and are
424 data = response->strvalue + 26;
428 * LM-Response is deprecated, and exists only
429 * in MS-CHAPv1, and not often there.
431 } else if (strcasecmp(fmt, "LM-Response") == 0) {
432 response = pairfind(request->packet->vps,
435 DEBUG2(" rlm_mschap: No MS-CHAP-Response was found in the request.");
440 * For MS-CHAPv1, the NT-Response exists only
441 * if the second octet says so.
443 if ((response->strvalue[1] & 0x01) != 0) {
444 DEBUG2(" rlm_mschap: No LM-Response in MS-CHAP-Response");
447 data = response->strvalue + 2;
451 * Pull the NT-Domain out of the User-Name, if it exists.
453 } else if (strcasecmp(fmt, "NT-Domain") == 0) {
456 user_name = pairfind(request->packet->vps, PW_USER_NAME);
458 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
462 p = strchr(user_name->strvalue, '\\');
464 DEBUG2(" rlm_mschap: No NT-Domain was found in the User-Name.");
469 * Hack. This is simpler than the alternatives.
472 strNcpy(out, user_name->strvalue, outlen);
478 * Pull the User-Name out of the User-Name...
480 } else if (strcasecmp(fmt, "User-Name") == 0) {
483 user_name = pairfind(request->packet->vps, PW_USER_NAME);
485 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
489 p = strchr(user_name->strvalue, '\\');
491 p++; /* skip the backslash */
493 p = user_name->strvalue; /* use the whole User-Name */
496 strNcpy(out, p, outlen);
500 DEBUG2(" rlm_mschap: Unknown expansion string \"%s\"",
505 if (outlen == 0) return 0; /* nowhere to go, don't do anything */
508 * Didn't set anything: this is bad.
511 DEBUG2(" rlm_mschap: Failed to do anything intelligent");
516 * Check the output length.
518 if (outlen < ((data_len * 2) + 1)) {
519 data_len = (outlen - 1) / 2;
525 for (i = 0; i < data_len; i++) {
526 sprintf(out + (2 * i), "%02x", data[i]);
528 out[data_len * 2] = '\0';
534 static CONF_PARSER module_config[] = {
536 * Cache the password by default.
538 { "use_mppe", PW_TYPE_BOOLEAN,
539 offsetof(rlm_mschap_t,use_mppe), NULL, "yes" },
540 { "require_encryption", PW_TYPE_BOOLEAN,
541 offsetof(rlm_mschap_t,require_encryption), NULL, "no" },
542 { "require_strong", PW_TYPE_BOOLEAN,
543 offsetof(rlm_mschap_t,require_strong), NULL, "no" },
544 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
545 offsetof(rlm_mschap_t,with_ntdomain_hack), NULL, "no" },
546 { "passwd", PW_TYPE_STRING_PTR,
547 offsetof(rlm_mschap_t, passwd_file), NULL, NULL },
548 { "authtype", PW_TYPE_STRING_PTR,
549 offsetof(rlm_mschap_t, auth_type), NULL, NULL },
550 { "ntlm_auth", PW_TYPE_STRING_PTR,
551 offsetof(rlm_mschap_t, ntlm_auth), NULL, NULL },
553 { NULL, -1, 0, NULL, NULL } /* end the list */
557 * deinstantiate module, free all memory allocated during
558 * mschap_instantiate()
560 static int mschap_detach(void *instance){
561 #define inst ((rlm_mschap_t *)instance)
562 if (inst->passwd_file) free(inst->passwd_file);
563 if (inst->auth_type) free(inst->auth_type);
564 if (inst->ntlm_auth) free(inst->ntlm_auth);
565 if (inst->xlat_name) {
566 xlat_unregister(inst->xlat_name, mschap_xlat);
567 free(inst->xlat_name);
575 * Create instance for our module. Allocate space for
576 * instance structure and read configuration parameters
578 static int mschap_instantiate(CONF_SECTION *conf, void **instance)
580 const char *xlat_name;
583 inst = *instance = rad_malloc(sizeof(*inst));
587 memset(inst, 0, sizeof(*inst));
589 if (cf_section_parse(conf, inst, module_config) < 0) {
595 * This module used to support SMB Password files, but it
596 * made it too complicated. If the user tries to
597 * configure an SMB Password file, then die, with an
600 if (inst->passwd_file) {
601 radlog(L_ERR, "rlm_mschap: SMB password file is no longer supported in this module. Use rlm_passwd module instead");
607 * Create the dynamic translation.
609 xlat_name = cf_section_name2(conf);
610 if (xlat_name == NULL)
611 xlat_name = cf_section_name1(conf);
613 inst->xlat_name = strdup(xlat_name);
614 xlat_register(xlat_name, mschap_xlat, inst);
621 * add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
622 * attribute to reply packet
624 static void add_reply(VALUE_PAIR** vp, unsigned char ident,
625 const char* name, const char* value, int len)
627 VALUE_PAIR *reply_attr;
628 reply_attr = pairmake(name, "", T_OP_EQ);
630 DEBUG(" rlm_mschap: Failed to create attribute %s: %s\n", name, librad_errstr);
634 reply_attr->strvalue[0] = ident;
635 memcpy(reply_attr->strvalue + 1, value, len);
636 reply_attr->length = len + 1;
637 pairadd(vp, reply_attr);
641 * Add MPPE attributes to the reply.
643 static void mppe_add_reply(VALUE_PAIR **vp,
644 const char* name, const char* value, int len)
646 VALUE_PAIR *reply_attr;
647 reply_attr = pairmake(name, "", T_OP_EQ);
649 DEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, librad_errstr);
653 memcpy(reply_attr->strvalue, value, len);
654 reply_attr->length = len;
655 pairadd(vp, reply_attr);
660 * Do the MS-CHAP stuff.
662 * This function is here so that all of the MS-CHAP related
663 * authentication is in one place, and we can perhaps later replace
664 * it with code to call winbindd, or something similar.
666 static int do_mschap(rlm_mschap_t *inst,
667 REQUEST *request, VALUE_PAIR *password,
668 uint8_t *challenge, uint8_t *response,
671 int do_ntlm_auth = 0;
672 uint8_t calculated[24];
673 VALUE_PAIR *ntlm_auth = NULL;
676 * If we have an ntlm_auth configuration, then we may
679 ntlm_auth = pairfind(request->config_items,
680 PW_MS_CHAP_USE_NTLM_AUTH);
681 if (ntlm_auth) do_ntlm_auth = ntlm_auth->lvalue;
684 * No ntlm_auth configured, attribute to tell us to
685 * use it exists, and we're told to use it. We don't
688 if (!inst->ntlm_auth && ntlm_auth &&
689 (ntlm_auth->lvalue != 0)) {
690 DEBUG2(" rlm_mschap: Asked to use ntlm_auth, but it was not configured in the mschap{} section.");
695 * Do normal authentication.
699 * No password: can't do authentication.
702 DEBUG2(" rlm_mschap: FAILED: No NT/LM-Password. Cannot perform authentication.");
706 lrad_mschap(password->strvalue, challenge, calculated);
707 if (memcmp(response, calculated, 24) != 0) {
712 * If the password exists, and is an NT-Password,
713 * then calculate the hash of the NT hash. Doing this
714 * here minimizes work for later.
716 if (password && (password->attribute == PW_NT_PASSWORD)) {
717 md4_calc(nthashhash, password->strvalue, 16);
719 memset(nthashhash, 0, 16);
721 } else { /* run ntlm_auth */
725 memset(nthashhash, 0, 16);
728 * Run the program, and expect that we get 16
730 result = radius_exec_program(inst->ntlm_auth, request,
732 buffer, sizeof(buffer),
735 DEBUG2(" rlm_mschap: External script failed.");
740 * Parse the answer as an nthashhash.
742 * ntlm_auth currently returns:
743 * NT_KEY: 000102030405060708090a0b0c0d0e0f
745 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
746 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: expecting NT_KEY");
751 * Check the length. It should be at least 32,
752 * with an LF at the end.
754 if (strlen(buffer + 8) < 32) {
755 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has unexpected length");
760 * Update the NT hash hash, from the NT key.
762 if (hex2bin(buffer + 8, nthashhash, 16) != 16) {
763 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has non-hex values");
773 * Data for the hashes.
775 static const uint8_t SHSpad1[40] =
776 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
777 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
778 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
779 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
781 static const uint8_t SHSpad2[40] =
782 { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
783 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
784 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
785 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
787 static const uint8_t magic1[27] =
788 { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
789 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
790 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
792 static const uint8_t magic2[84] =
793 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
794 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
795 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
796 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
797 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
798 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
799 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
800 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
801 0x6b, 0x65, 0x79, 0x2e };
803 static const uint8_t magic3[84] =
804 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
805 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
806 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
807 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
808 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
809 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
810 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
811 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
812 0x6b, 0x65, 0x79, 0x2e };
815 static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response,
822 SHA1Update(&Context,nt_hashhash,16);
823 SHA1Update(&Context,nt_response,24);
824 SHA1Update(&Context,magic1,27);
825 SHA1Final(digest,&Context);
827 memcpy(masterkey,digest,16);
831 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
832 int keylen,int issend)
847 SHA1Update(&Context,masterkey,16);
848 SHA1Update(&Context,SHSpad1,40);
849 SHA1Update(&Context,s,84);
850 SHA1Update(&Context,SHSpad2,40);
851 SHA1Final(digest,&Context);
853 memcpy(sesskey,digest,keylen);
857 static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response,
858 uint8_t *sendkey,uint8_t *recvkey)
860 uint8_t masterkey[16];
862 mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
864 mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
865 mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
869 * Generate MPPE keys.
871 static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response,
872 uint8_t *sendkey,uint8_t *recvkey)
877 mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
880 * dictionary.microsoft defines these attributes as
881 * 'encrypt=2'. The functions in src/lib/radius.c will
882 * take care of encrypting/decrypting them as appropriate,
883 * so that we don't have to.
885 memcpy (sendkey, enckey1, 16);
886 memcpy (recvkey, enckey2, 16);
891 * mschap_authorize() - authorize user if we can authenticate
892 * it later. Add Auth-Type attribute if present in module
893 * configuration (usually Auth-Type must be "MS-CHAP")
895 static int mschap_authorize(void * instance, REQUEST *request)
897 #define inst ((rlm_mschap_t *)instance)
898 VALUE_PAIR *challenge = NULL, *response = NULL;
900 const char *authtype_name = "MS-CHAP";
902 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
904 return RLM_MODULE_NOOP;
907 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
909 response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE);
912 * Nothing we recognize. Don't do anything.
915 DEBUG2(" rlm_mschap: Found MS-CHAP-Challenge, but no MS-CHAP-Response.");
916 return RLM_MODULE_NOOP;
920 * Choose MS-CHAP, or whatever else they told us to use.
922 if (inst->auth_type) {
923 authtype_name = inst->auth_type;
926 DEBUG2(" rlm_mschap: Found MS-CHAP attributes. Setting 'Auth-Type = %s'", authtype_name);
929 * Set Auth-Type to MS-CHAP. The authentication code
930 * will take care of turning clear-text passwords into
933 pairdelete(&request->config_items, PW_AUTHTYPE);
934 vp = pairmake("Auth-Type", authtype_name, T_OP_EQ);
935 rad_assert(vp != NULL);
936 pairadd(&request->config_items, vp);
938 return RLM_MODULE_OK;
943 * mschap_authenticate() - authenticate user based on given
944 * attributes and configuration.
945 * We will try to find out password in configuration
946 * or in configured passwd file.
947 * If one is found we will check paraneters given by NAS.
949 * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
951 * PAP: PW_PASSWORD or
952 * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
953 * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
954 * In case of password mismatch or locked account we MAY return
955 * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
956 * If MS-CHAP2 succeeds we MUST return
959 static int mschap_authenticate(void * instance, REQUEST *request)
961 #define inst ((rlm_mschap_t *)instance)
962 VALUE_PAIR *challenge = NULL, *response = NULL;
963 VALUE_PAIR *password = NULL;
964 VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
965 VALUE_PAIR *username;
966 VALUE_PAIR *reply_attr;
967 uint8_t nthashhash[16];
968 uint8_t msch2resp[42];
969 char *username_string;
973 * Find the SMB-Account-Ctrl attribute, or the
974 * SMB-Account-Ctrl-Text attribute.
976 smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL);
978 password = pairfind(request->config_items,
979 PW_SMB_ACCOUNT_CTRL_TEXT);
981 smb_ctrl = pairmake("SMB-Account-CTRL", "0", T_OP_SET);
982 pairadd(&request->config_items, smb_ctrl);
983 smb_ctrl->lvalue = pdb_decode_acct_ctrl(password->strvalue);
988 * We're configured to do MS-CHAP authentication.
989 * and account control information exists. Enforce it.
993 * Password is not required.
995 if ((smb_ctrl->lvalue & ACB_PWNOTREQ) != 0) {
996 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says no password is required.");
997 return RLM_MODULE_OK;
1002 * Decide how to get the passwords.
1004 password = pairfind(request->config_items, PW_PASSWORD);
1007 * We need an LM-Password.
1009 lm_password = pairfind(request->config_items, PW_LM_PASSWORD);
1014 if ((lm_password->length == 16) ||
1015 ((lm_password->length == 32) &&
1016 (hex2bin(lm_password->strvalue,
1017 lm_password->strvalue, 16) == 16))) {
1018 DEBUG2(" rlm_mschap: Found LM-Password");
1019 lm_password->length = 16;
1022 radlog(L_ERR, "rlm_mschap: Invalid LM-Password");
1026 } else if (!password) {
1027 DEBUG2(" rlm_mschap: No User-Password configured. Cannot create LM-Password.");
1029 } else { /* there is a configured User-Password */
1030 lm_password = pairmake("LM-Password", "", T_OP_EQ);
1032 radlog(L_ERR, "No memory");
1034 lrad_lmpwdhash(password->strvalue,
1035 lm_password->strvalue);
1036 lm_password->length = 16;
1037 pairadd(&request->config_items, lm_password);
1042 * We need an NT-Password.
1044 nt_password = pairfind(request->config_items, PW_NT_PASSWORD);
1046 if ((nt_password->length == 16) ||
1047 ((nt_password->length == 32) &&
1048 (hex2bin(nt_password->strvalue,
1049 nt_password->strvalue, 16) == 16))) {
1050 DEBUG2(" rlm_mschap: Found NT-Password");
1051 nt_password->length = 16;
1054 radlog(L_ERR, "rlm_mschap: Invalid NT-Password");
1057 } else if (!password) {
1058 DEBUG2(" rlm_mschap: No User-Password configured. Cannot create NT-Password.");
1060 } else { /* there is a configured User-Password */
1061 nt_password = pairmake("NT-Password", "", T_OP_EQ);
1063 radlog(L_ERR, "No memory");
1065 ntpwdhash(nt_password->strvalue, password->strvalue);
1066 nt_password->length = 16;
1067 pairadd(&request->config_items, nt_password);
1071 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
1073 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request");
1074 return RLM_MODULE_REJECT;
1078 * We also require an MS-CHAP-Response.
1080 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
1083 * MS-CHAP-Response, means MS-CHAPv1
1089 * MS-CHAPv1 challenges are 8 octets.
1091 if (challenge->length < 8) {
1092 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1093 return RLM_MODULE_INVALID;
1097 * Responses are 50 octets.
1099 if (response->length < 50) {
1100 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1101 return RLM_MODULE_INVALID;
1105 * We are doing MS-CHAP. Calculate the MS-CHAP
1108 if (response->strvalue[1] & 0x01) {
1109 DEBUG2(" rlm_mschap: doing MS-CHAPv1 with NT-Password");
1110 password = nt_password;
1113 DEBUG2(" rlm_mschap: doing MS-CHAPv1 with LM-Password");
1114 password = lm_password;
1119 * Do the MS-CHAP authentication.
1121 if (do_mschap(inst, request, password, challenge->strvalue,
1122 response->strvalue + offset, nthashhash) < 0) {
1123 DEBUG2(" rlm_mschap: MS-CHAP-Response is incorrect.");
1124 add_reply(&request->reply->vps, *response->strvalue,
1125 "MS-CHAP-Error", "E=691 R=1", 9);
1126 return RLM_MODULE_REJECT;
1131 } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE)) != NULL) {
1132 uint8_t mschapv1_challenge[16];
1135 * MS-CHAPv2 challenges are 16 octets.
1137 if (challenge->length < 16) {
1138 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1139 return RLM_MODULE_INVALID;
1143 * Responses are 50 octets.
1145 if (response->length < 50) {
1146 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1147 return RLM_MODULE_INVALID;
1151 * We also require a User-Name
1153 username = pairfind(request->packet->vps, PW_USER_NAME);
1155 radlog(L_AUTH, "rlm_mschap: We require a User-Name for MS-CHAPv2");
1156 return RLM_MODULE_INVALID;
1161 * with_ntdomain_hack moved here
1163 if ((username_string = strchr(username->strvalue, '\\')) != NULL) {
1164 if (inst->with_ntdomain_hack) {
1167 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1168 username_string = username->strvalue;
1171 username_string = username->strvalue;
1175 * The old "mschapv2" function has been moved to
1178 * MS-CHAPv2 takes some additional data to create an
1179 * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1181 challenge_hash(response->strvalue + 2, /* peer challenge */
1182 challenge->strvalue, /* our challenge */
1183 username_string, /* user name */
1184 mschapv1_challenge); /* resulting challenge */
1186 DEBUG2(" rlm_mschap: doing MS-CHAPv2 with NT-Password");
1188 if (do_mschap(inst, request, nt_password, mschapv1_challenge,
1189 response->strvalue + 26, nthashhash) < 0) {
1190 DEBUG2(" rlm_mschap: FAILED: MS-CHAP2-Response is incorrect");
1191 add_reply(&request->reply->vps, *response->strvalue,
1192 "MS-CHAP-Error", "E=691 R=1", 9);
1193 return RLM_MODULE_REJECT;
1197 * Get the NT-hash-hash, if necessary
1202 auth_response(username_string, /* without the domain */
1203 nthashhash, /* nt-hash-hash */
1204 response->strvalue + 26, /* peer response */
1205 response->strvalue + 2, /* peer challenge */
1206 challenge->strvalue, /* our challenge */
1207 msch2resp); /* calculated MPPE key */
1208 add_reply( &request->reply->vps, *response->strvalue,
1209 "MS-CHAP2-Success", msch2resp, 42);
1212 } else { /* Neither CHAPv1 or CHAPv2 response: die */
1213 radlog(L_AUTH, "rlm_mschap: No MS-CHAP response found");
1214 return RLM_MODULE_INVALID;
1218 * We have a CHAP response, but the account may be
1219 * disabled. Reject the user with the same error code
1220 * we use when their password is invalid.
1224 * Account is disabled.
1226 * They're found, but they don't exist, so we
1227 * return 'not found'.
1229 if (((smb_ctrl->lvalue & ACB_DISABLED) != 0) ||
1230 ((smb_ctrl->lvalue & ACB_NORMAL) == 0)) {
1231 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is disabled, or is not a normal account.");
1232 add_reply( &request->reply->vps, *response->strvalue,
1233 "MS-CHAP-Error", "E=691 R=1", 9);
1234 return RLM_MODULE_NOTFOUND;
1238 * User is locked out.
1240 if ((smb_ctrl->lvalue & ACB_AUTOLOCK) != 0) {
1241 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is locked out.");
1242 add_reply( &request->reply->vps, *response->strvalue,
1243 "MS-CHAP-Error", "E=647 R=0", 9);
1244 return RLM_MODULE_USERLOCK;
1248 /* now create MPPE attributes */
1249 if (inst->use_mppe) {
1250 uint8_t mppe_sendkey[34];
1251 uint8_t mppe_recvkey[34];
1254 DEBUG2("rlm_mschap: adding MS-CHAPv1 MPPE keys");
1255 memset(mppe_sendkey, 0, 32);
1257 memcpy(mppe_sendkey, lm_password->strvalue, 8);
1262 * According to RFC 2548 we
1263 * should send NT hash. But in
1264 * practice it doesn't work.
1265 * Instead, we should send nthashhash
1267 * This is an error on RFC 2548.
1269 memcpy(mppe_sendkey + 8,
1271 mppe_add_reply(&request->reply->vps,
1272 "MS-CHAP-MPPE-Keys",
1276 } else if (chap == 2) {
1277 DEBUG2("rlm_mschap: adding MS-CHAPv2 MPPE keys");
1278 mppe_chap2_gen_keys128(nthashhash,
1279 response->strvalue + 26,
1280 mppe_sendkey, mppe_recvkey);
1282 mppe_add_reply(&request->reply->vps,
1285 mppe_add_reply(&request->reply->vps,
1290 reply_attr = pairmake("MS-MPPE-Encryption-Policy",
1291 (inst->require_encryption)? "0x00000002":"0x00000001",
1293 rad_assert(reply_attr != NULL);
1294 pairadd(&request->reply->vps, reply_attr);
1295 reply_attr = pairmake("MS-MPPE-Encryption-Types",
1296 (inst->require_strong)? "0x00000004":"0x00000006",
1298 rad_assert(reply_attr != NULL);
1299 pairadd(&request->reply->vps, reply_attr);
1301 } /* else we weren't asked to use MPPE */
1303 return RLM_MODULE_OK;
1307 module_t rlm_mschap = {
1309 RLM_TYPE_THREAD_SAFE, /* type */
1310 NULL, /* initialize */
1311 mschap_instantiate, /* instantiation */
1313 mschap_authenticate, /* authenticate */
1314 mschap_authorize, /* authorize */
1315 NULL, /* pre-accounting */
1316 NULL, /* accounting */
1317 NULL, /* checksimul */
1318 NULL, /* pre-proxy */
1319 NULL, /* post-proxy */
1320 NULL /* post-auth */
1322 mschap_detach, /* detach */