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 (unsigned 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 )
221 unsigned char hash[20];
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 unsigned char *nt_hash_hash,
238 unsigned char *ntresponse,
239 char *peer_challenge, char *auth_challenge,
243 const unsigned 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 unsigned 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,
257 unsigned char digest[20];
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 */
296 * Does dynamic translation of strings.
298 * Pulls NT-Response, LM-Response, or Challenge from MSCHAP
301 static int mschap_xlat(void *instance, REQUEST *request,
302 char *fmt, char *out, size_t outlen,
303 RADIUS_ESCAPE_STRING func)
306 uint8_t *data = NULL;
308 VALUE_PAIR *user_name;
309 VALUE_PAIR *chap_challenge, *response;
310 rlm_mschap_t *inst = instance;
312 chap_challenge = response = NULL;
314 func = func; /* -Wunused */
317 * Challenge means MS-CHAPv1 challenge, or
318 * hash of MS-CHAPv2 challenge, and peer challenge.
320 if (strcasecmp(fmt, "Challenge") == 0) {
321 chap_challenge = pairfind(request->packet->vps,
322 PW_MSCHAP_CHALLENGE);
323 if (!chap_challenge) {
324 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request.");
329 * MS-CHAP-Challenges are 8 octets,
332 if (chap_challenge->length == 8) {
333 DEBUG2(" mschap1: %02x", chap_challenge->strvalue[0]);
334 data = chap_challenge->strvalue;
338 * MS-CHAP-Challenges are 16 octets,
341 } else if (chap_challenge->length == 16) {
342 char *username_string;
344 DEBUG2(" mschap2: %02x", chap_challenge->strvalue[0]);
345 response = pairfind(request->packet->vps,
346 PW_MSCHAP2_RESPONSE);
348 DEBUG2(" rlm_mschap: MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge.");
353 * Responses are 50 octets.
355 if (response->length < 50) {
356 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
360 user_name = pairfind(request->packet->vps,
363 DEBUG2(" rlm_mschap: User-Name is required to calculateMS-CHAPv1 Challenge.");
368 * with_ntdomain_hack moved here, too.
370 if ((username_string = strchr(user_name->strvalue, '\\')) != NULL) {
371 if (inst->with_ntdomain_hack) {
374 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
375 username_string = user_name->strvalue;
378 username_string = user_name->strvalue;
382 * Get the MS-CHAPv1 challenge
383 * from the MS-CHAPv2 peer challenge,
384 * our challenge, and the user name.
386 challenge_hash(response->strvalue + 2,
387 chap_challenge->strvalue,
388 username_string, buffer);
392 DEBUG2(" rlm_mschap: Invalid MS-CHAP challenge length");
397 * Get the MS-CHAPv1 response, or the MS-CHAPv2
400 } else if (strcasecmp(fmt, "NT-Response") == 0) {
401 response = pairfind(request->packet->vps,
403 if (!response) response = pairfind(request->packet->vps,
404 PW_MSCHAP2_RESPONSE);
406 DEBUG2(" rlm_mschap: No MS-CHAP-Response or MS-CHAP2-Response was found in the request.");
411 * For MS-CHAPv1, the NT-Response exists only
412 * if the second octet says so.
414 if ((response->attribute == PW_MSCHAP_RESPONSE) &&
415 ((response->strvalue[1] & 0x01) == 0)) {
416 DEBUG2(" rlm_mschap: No NT-Response in MS-CHAP-Response");
421 * MS-CHAP-Response and MS-CHAP2-Response have
422 * the NT-Response at the same offset, and are
425 data = response->strvalue + 26;
429 * LM-Response is deprecated, and exists only
430 * in MS-CHAPv1, and not often there.
432 } else if (strcasecmp(fmt, "LM-Response") == 0) {
433 response = pairfind(request->packet->vps,
436 DEBUG2(" rlm_mschap: No MS-CHAP-Response was found in the request.");
441 * For MS-CHAPv1, the NT-Response exists only
442 * if the second octet says so.
444 if ((response->strvalue[1] & 0x01) != 0) {
445 DEBUG2(" rlm_mschap: No LM-Response in MS-CHAP-Response");
448 data = response->strvalue + 2;
452 * Pull the NT-Domain out of the User-Name, if it exists.
454 } else if (strcasecmp(fmt, "NT-Domain") == 0) {
457 user_name = pairfind(request->packet->vps, PW_USER_NAME);
459 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
464 * First check to see if this is a host/ style User-Name
465 * (a la Kerberos host principal)
467 if (strncmp(user_name->strvalue, "host/", 5) == 0) {
469 * If we're getting a User-Name formatted in this way,
470 * it's likely due to PEAP. The Windows Domain will be
471 * the first domain component following the hostname,
472 * or the machine name itself if only a hostname is supplied
474 p = strchr(user_name->strvalue, '.');
476 DEBUG2(" rlm_mschap: setting NT-Domain to same as machine name");
477 strNcpy(out, user_name->strvalue + 5, outlen);
479 p++; /* skip the period */
482 * use the same hack as below
483 * only if another period was found
486 strNcpy(out, p, outlen);
490 p = strchr(user_name->strvalue, '\\');
492 DEBUG2(" rlm_mschap: No NT-Domain was found in the User-Name.");
497 * Hack. This is simpler than the alternatives.
500 strNcpy(out, user_name->strvalue, outlen);
507 * Pull the User-Name out of the User-Name...
509 } else if (strcasecmp(fmt, "User-Name") == 0) {
512 user_name = pairfind(request->packet->vps, PW_USER_NAME);
514 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
519 * First check to see if this is a host/ style User-Name
520 * (a la Kerberos host principal)
522 if (strncmp(user_name->strvalue, "host/", 5) == 0) {
524 * If we're getting a User-Name formatted in this way,
525 * it's likely due to PEAP. When authenticating this against
526 * a Domain, Windows will expect the User-Name to be in the
527 * format of hostname$, the SAM version of the name, so we
528 * have to convert it to that here. We do so by stripping
529 * off the first 5 characters (host/), and copying everything
530 * from that point to the first period into a string and appending
533 p = strchr(user_name->strvalue, '.');
535 * use the same hack as above
536 * only if a period was found
539 snprintf(out, outlen, "%s$", user_name->strvalue + 5);
542 p = strchr(user_name->strvalue, '\\');
544 p++; /* skip the backslash */
546 p = user_name->strvalue; /* use the whole User-Name */
548 strNcpy(out, p, outlen);
554 DEBUG2(" rlm_mschap: Unknown expansion string \"%s\"",
559 if (outlen == 0) return 0; /* nowhere to go, don't do anything */
562 * Didn't set anything: this is bad.
565 DEBUG2(" rlm_mschap: Failed to do anything intelligent");
570 * Check the output length.
572 if (outlen < ((data_len * 2) + 1)) {
573 data_len = (outlen - 1) / 2;
579 for (i = 0; i < data_len; i++) {
580 sprintf(out + (2 * i), "%02x", data[i]);
582 out[data_len * 2] = '\0';
588 static CONF_PARSER module_config[] = {
590 * Cache the password by default.
592 { "use_mppe", PW_TYPE_BOOLEAN,
593 offsetof(rlm_mschap_t,use_mppe), NULL, "yes" },
594 { "require_encryption", PW_TYPE_BOOLEAN,
595 offsetof(rlm_mschap_t,require_encryption), NULL, "no" },
596 { "require_strong", PW_TYPE_BOOLEAN,
597 offsetof(rlm_mschap_t,require_strong), NULL, "no" },
598 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
599 offsetof(rlm_mschap_t,with_ntdomain_hack), NULL, "no" },
600 { "passwd", PW_TYPE_STRING_PTR,
601 offsetof(rlm_mschap_t, passwd_file), NULL, NULL },
602 { "ntlm_auth", PW_TYPE_STRING_PTR,
603 offsetof(rlm_mschap_t, ntlm_auth), NULL, NULL },
605 { NULL, -1, 0, NULL, NULL } /* end the list */
609 * deinstantiate module, free all memory allocated during
610 * mschap_instantiate()
612 static int mschap_detach(void *instance){
613 #define inst ((rlm_mschap_t *)instance)
614 free(inst->passwd_file);
615 free(inst->ntlm_auth);
616 if (inst->xlat_name) {
617 xlat_unregister(inst->xlat_name, mschap_xlat);
618 free(inst->xlat_name);
626 * Create instance for our module. Allocate space for
627 * instance structure and read configuration parameters
629 static int mschap_instantiate(CONF_SECTION *conf, void **instance)
631 const char *xlat_name;
634 inst = *instance = rad_malloc(sizeof(*inst));
638 memset(inst, 0, sizeof(*inst));
640 if (cf_section_parse(conf, inst, module_config) < 0) {
646 * This module used to support SMB Password files, but it
647 * made it too complicated. If the user tries to
648 * configure an SMB Password file, then die, with an
651 if (inst->passwd_file) {
652 radlog(L_ERR, "rlm_mschap: SMB password file is no longer supported in this module. Use rlm_passwd module instead");
658 * Create the dynamic translation.
660 xlat_name = cf_section_name2(conf);
661 if (xlat_name == NULL)
662 xlat_name = cf_section_name1(conf);
664 inst->xlat_name = strdup(xlat_name);
665 xlat_register(xlat_name, mschap_xlat, inst);
672 * add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
673 * attribute to reply packet
675 static void add_reply(VALUE_PAIR** vp, unsigned char ident,
676 const char* name, const char* value, int len)
678 VALUE_PAIR *reply_attr;
679 reply_attr = pairmake(name, "", T_OP_EQ);
681 DEBUG(" rlm_mschap: Failed to create attribute %s: %s\n", name, librad_errstr);
685 reply_attr->strvalue[0] = ident;
686 memcpy(reply_attr->strvalue + 1, value, len);
687 reply_attr->length = len + 1;
688 pairadd(vp, reply_attr);
692 * Add MPPE attributes to the reply.
694 static void mppe_add_reply(VALUE_PAIR **vp,
695 const char* name, const char* value, int len)
697 VALUE_PAIR *reply_attr;
698 reply_attr = pairmake(name, "", T_OP_EQ);
700 DEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, librad_errstr);
704 memcpy(reply_attr->strvalue, value, len);
705 reply_attr->length = len;
706 pairadd(vp, reply_attr);
711 * Do the MS-CHAP stuff.
713 * This function is here so that all of the MS-CHAP related
714 * authentication is in one place, and we can perhaps later replace
715 * it with code to call winbindd, or something similar.
717 static int do_mschap(rlm_mschap_t *inst,
718 REQUEST *request, VALUE_PAIR *password,
719 uint8_t *challenge, uint8_t *response,
722 int do_ntlm_auth = 0;
723 uint8_t calculated[24];
724 VALUE_PAIR *vp = NULL;
727 * If we have ntlm_auth configured, use it unless told
730 if (inst->ntlm_auth) do_ntlm_auth = 1;
733 * If we have an ntlm_auth configuration, then we may
736 vp = pairfind(request->config_items,
737 PW_MS_CHAP_USE_NTLM_AUTH);
738 if (vp) do_ntlm_auth = vp->lvalue;
741 * No ntlm_auth configured, attribute to tell us to
742 * use it exists, and we're told to use it. We don't
745 if (!inst->ntlm_auth && do_ntlm_auth) {
746 DEBUG2(" rlm_mschap: Asked to use ntlm_auth, but it was not configured in the mschap{} section.");
751 * Do normal authentication.
755 * No password: can't do authentication.
758 DEBUG2(" rlm_mschap: FAILED: No NT/LM-Password. Cannot perform authentication.");
762 smbdes_mschap(password->strvalue, challenge, calculated);
763 if (memcmp(response, calculated, 24) != 0) {
768 * If the password exists, and is an NT-Password,
769 * then calculate the hash of the NT hash. Doing this
770 * here minimizes work for later.
772 if (password && (password->attribute == PW_NT_PASSWORD)) {
773 md4_calc(nthashhash, password->strvalue, 16);
775 memset(nthashhash, 0, 16);
777 } else { /* run ntlm_auth */
781 memset(nthashhash, 0, 16);
784 * Run the program, and expect that we get 16
786 result = radius_exec_program(inst->ntlm_auth, request,
788 buffer, sizeof(buffer),
791 DEBUG2(" rlm_mschap: External script failed.");
796 * Parse the answer as an nthashhash.
798 * ntlm_auth currently returns:
799 * NT_KEY: 000102030405060708090a0b0c0d0e0f
801 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
802 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: expecting NT_KEY");
807 * Check the length. It should be at least 32,
808 * with an LF at the end.
810 if (strlen(buffer + 8) < 32) {
811 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has unexpected length");
816 * Update the NT hash hash, from the NT key.
818 if (hex2bin(buffer + 8, nthashhash, 16) != 16) {
819 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has non-hex values");
829 * Data for the hashes.
831 static const uint8_t SHSpad1[40] =
832 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
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 };
837 static const uint8_t SHSpad2[40] =
838 { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
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 };
843 static const uint8_t magic1[27] =
844 { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
845 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
846 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
848 static const uint8_t magic2[84] =
849 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
850 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
851 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
852 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
853 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
854 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
855 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
856 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
857 0x6b, 0x65, 0x79, 0x2e };
859 static const uint8_t magic3[84] =
860 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
861 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
862 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
863 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
864 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
865 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
866 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
867 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
868 0x6b, 0x65, 0x79, 0x2e };
871 static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response,
878 SHA1Update(&Context,nt_hashhash,16);
879 SHA1Update(&Context,nt_response,24);
880 SHA1Update(&Context,magic1,27);
881 SHA1Final(digest,&Context);
883 memcpy(masterkey,digest,16);
887 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
888 int keylen,int issend)
903 SHA1Update(&Context,masterkey,16);
904 SHA1Update(&Context,SHSpad1,40);
905 SHA1Update(&Context,s,84);
906 SHA1Update(&Context,SHSpad2,40);
907 SHA1Final(digest,&Context);
909 memcpy(sesskey,digest,keylen);
913 static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response,
914 uint8_t *sendkey,uint8_t *recvkey)
916 uint8_t masterkey[16];
918 mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
920 mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
921 mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
925 * Generate MPPE keys.
927 static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response,
928 uint8_t *sendkey,uint8_t *recvkey)
933 mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
936 * dictionary.microsoft defines these attributes as
937 * 'encrypt=2'. The functions in src/lib/radius.c will
938 * take care of encrypting/decrypting them as appropriate,
939 * so that we don't have to.
941 memcpy (sendkey, enckey1, 16);
942 memcpy (recvkey, enckey2, 16);
947 * mschap_authorize() - authorize user if we can authenticate
948 * it later. Add Auth-Type attribute if present in module
949 * configuration (usually Auth-Type must be "MS-CHAP")
951 static int mschap_authorize(void * instance, REQUEST *request)
953 #define inst ((rlm_mschap_t *)instance)
954 VALUE_PAIR *challenge = NULL;
955 VALUE_PAIR *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->xlat_name, T_OP_EQ);
983 rad_assert(vp != NULL);
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_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->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_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 (hex2bin(lm_password->strvalue,
1067 lm_password->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 User-Password configured. Cannot create LM-Password.");
1079 } else { /* there is a configured User-Password */
1080 lm_password = pairmake("LM-Password", "", T_OP_EQ);
1082 radlog(L_ERR, "No memory");
1084 smbdes_lmpwdhash(password->strvalue,
1085 lm_password->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 (hex2bin(nt_password->strvalue,
1099 nt_password->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 User-Password configured. Cannot create NT-Password.");
1110 } else { /* there is a configured User-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->strvalue, password->strvalue);
1117 nt_password->length = 16;
1118 pairadd(&request->config_items, nt_password);
1122 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
1124 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request");
1125 return RLM_MODULE_REJECT;
1129 * We also require an MS-CHAP-Response.
1131 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
1134 * MS-CHAP-Response, means MS-CHAPv1
1140 * MS-CHAPv1 challenges are 8 octets.
1142 if (challenge->length < 8) {
1143 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1144 return RLM_MODULE_INVALID;
1148 * Responses are 50 octets.
1150 if (response->length < 50) {
1151 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1152 return RLM_MODULE_INVALID;
1156 * We are doing MS-CHAP. Calculate the MS-CHAP
1159 if (response->strvalue[1] & 0x01) {
1160 DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with NT-Password");
1161 password = nt_password;
1164 DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with LM-Password");
1165 password = lm_password;
1170 * Do the MS-CHAP authentication.
1172 if (do_mschap(inst, request, password, challenge->strvalue,
1173 response->strvalue + offset, nthashhash) < 0) {
1174 DEBUG2(" rlm_mschap: MS-CHAP-Response is incorrect.");
1175 add_reply(&request->reply->vps, *response->strvalue,
1176 "MS-CHAP-Error", "E=691 R=1", 9);
1177 return RLM_MODULE_REJECT;
1182 } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE)) != NULL) {
1183 uint8_t mschapv1_challenge[16];
1186 * MS-CHAPv2 challenges are 16 octets.
1188 if (challenge->length < 16) {
1189 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1190 return RLM_MODULE_INVALID;
1194 * Responses are 50 octets.
1196 if (response->length < 50) {
1197 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1198 return RLM_MODULE_INVALID;
1202 * We also require a User-Name
1204 username = pairfind(request->packet->vps, PW_USER_NAME);
1206 radlog(L_AUTH, "rlm_mschap: We require a User-Name for MS-CHAPv2");
1207 return RLM_MODULE_INVALID;
1212 * with_ntdomain_hack moved here
1214 if ((username_string = strchr(username->strvalue, '\\')) != NULL) {
1215 if (inst->with_ntdomain_hack) {
1218 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1219 username_string = username->strvalue;
1222 username_string = username->strvalue;
1226 * The old "mschapv2" function has been moved to
1229 * MS-CHAPv2 takes some additional data to create an
1230 * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1232 challenge_hash(response->strvalue + 2, /* peer challenge */
1233 challenge->strvalue, /* our challenge */
1234 username_string, /* user name */
1235 mschapv1_challenge); /* resulting challenge */
1237 DEBUG2(" rlm_mschap: Told to do MS-CHAPv2 for %s with NT-Password",
1240 if (do_mschap(inst, request, nt_password, mschapv1_challenge,
1241 response->strvalue + 26, nthashhash) < 0) {
1242 DEBUG2(" rlm_mschap: FAILED: MS-CHAP2-Response is incorrect");
1243 add_reply(&request->reply->vps, *response->strvalue,
1244 "MS-CHAP-Error", "E=691 R=1", 9);
1245 return RLM_MODULE_REJECT;
1249 * Get the NT-hash-hash, if necessary
1254 auth_response(username_string, /* without the domain */
1255 nthashhash, /* nt-hash-hash */
1256 response->strvalue + 26, /* peer response */
1257 response->strvalue + 2, /* peer challenge */
1258 challenge->strvalue, /* our challenge */
1259 msch2resp); /* calculated MPPE key */
1260 add_reply( &request->reply->vps, *response->strvalue,
1261 "MS-CHAP2-Success", msch2resp, 42);
1264 } else { /* Neither CHAPv1 or CHAPv2 response: die */
1265 radlog(L_AUTH, "rlm_mschap: No MS-CHAP response found");
1266 return RLM_MODULE_INVALID;
1270 * We have a CHAP response, but the account may be
1271 * disabled. Reject the user with the same error code
1272 * we use when their password is invalid.
1276 * Account is disabled.
1278 * They're found, but they don't exist, so we
1279 * return 'not found'.
1281 if (((smb_ctrl->lvalue & ACB_DISABLED) != 0) ||
1282 ((smb_ctrl->lvalue & ACB_NORMAL) == 0)) {
1283 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is disabled, or is not a normal account.");
1284 add_reply( &request->reply->vps, *response->strvalue,
1285 "MS-CHAP-Error", "E=691 R=1", 9);
1286 return RLM_MODULE_NOTFOUND;
1290 * User is locked out.
1292 if ((smb_ctrl->lvalue & ACB_AUTOLOCK) != 0) {
1293 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is locked out.");
1294 add_reply( &request->reply->vps, *response->strvalue,
1295 "MS-CHAP-Error", "E=647 R=0", 9);
1296 return RLM_MODULE_USERLOCK;
1300 /* now create MPPE attributes */
1301 if (inst->use_mppe) {
1302 uint8_t mppe_sendkey[34];
1303 uint8_t mppe_recvkey[34];
1306 DEBUG2("rlm_mschap: adding MS-CHAPv1 MPPE keys");
1307 memset(mppe_sendkey, 0, 32);
1309 memcpy(mppe_sendkey, lm_password->strvalue, 8);
1313 * According to RFC 2548 we
1314 * should send NT hash. But in
1315 * practice it doesn't work.
1316 * Instead, we should send nthashhash
1318 * This is an error on RFC 2548.
1321 * do_mschap cares to zero nthashhash if NT hash
1324 memcpy(mppe_sendkey + 8,
1326 mppe_add_reply(&request->reply->vps,
1327 "MS-CHAP-MPPE-Keys",
1329 } else if (chap == 2) {
1330 DEBUG2("rlm_mschap: adding MS-CHAPv2 MPPE keys");
1331 mppe_chap2_gen_keys128(nthashhash,
1332 response->strvalue + 26,
1333 mppe_sendkey, mppe_recvkey);
1335 mppe_add_reply(&request->reply->vps,
1338 mppe_add_reply(&request->reply->vps,
1343 reply_attr = pairmake("MS-MPPE-Encryption-Policy",
1344 (inst->require_encryption)? "0x00000002":"0x00000001",
1346 rad_assert(reply_attr != NULL);
1347 pairadd(&request->reply->vps, reply_attr);
1348 reply_attr = pairmake("MS-MPPE-Encryption-Types",
1349 (inst->require_strong)? "0x00000004":"0x00000006",
1351 rad_assert(reply_attr != NULL);
1352 pairadd(&request->reply->vps, reply_attr);
1354 } /* else we weren't asked to use MPPE */
1356 return RLM_MODULE_OK;
1360 module_t rlm_mschap = {
1362 RLM_TYPE_THREAD_SAFE, /* type */
1363 NULL, /* initialize */
1364 mschap_instantiate, /* instantiation */
1366 mschap_authenticate, /* authenticate */
1367 mschap_authorize, /* authorize */
1368 NULL, /* pre-accounting */
1369 NULL, /* accounting */
1370 NULL, /* checksimul */
1371 NULL, /* pre-proxy */
1372 NULL, /* post-proxy */
1373 NULL /* post-auth */
1375 mschap_detach, /* detach */