/* * rlm_mschap.c * * Version: $Id: rlm_mschap.c,v 1.59.2.3.2.5 2007/07/04 13:25:21 aland Exp $ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Copyright 2000,2001 The FreeRADIUS server project */ /* * mschap.c MS-CHAP module * * This implements MS-CHAP, as described in RFC 2548 * * http://www.freeradius.org/rfc/rfc2548.txt * */ /* * If you have any questions on NTLM (Samba) passwords * support, LM authentication and MS-CHAP v2 support * please contact * * Vladimir Dubrovin vlad@sandy.ru * aka * ZARAZA 3APA3A@security.nnov.ru */ /* MPPE support from Takahiro Wagatsuma */ #include "autoconf.h" #include "libradius.h" #include #include #include #include #include "radiusd.h" #include "modules.h" #include "md4.h" #include "md5.h" #include "sha1.h" #include "rad_assert.h" #include "smbdes.h" static const char rcsid[] = "$Id: rlm_mschap.c,v 1.59.2.3.2.5 2007/07/04 13:25:21 aland Exp $"; static const char *letters = "0123456789ABCDEF"; /* * hex2bin converts hexadecimal strings into binary */ static int hex2bin (const char *szHex, unsigned char* szBin, int len) { char * c1, * c2; int i; for (i = 0; i < len; i++) { if( !(c1 = memchr(letters, toupper((int) szHex[i << 1]), 16)) || !(c2 = memchr(letters, toupper((int) szHex[(i << 1) + 1]), 16))) break; szBin[i] = ((c1-letters)<<4) + (c2-letters); } return i; } /* * bin2hex creates hexadecimal presentation * of binary data */ static void bin2hex (const unsigned char *szBin, char *szHex, int len) { int i; for (i = 0; i < len; i++) { szHex[i<<1] = letters[szBin[i] >> 4]; szHex[(i<<1) + 1] = letters[szBin[i] & 0x0F]; } } /* Allowable account control bits */ #define ACB_DISABLED 0x0001 /* 1 = User account disabled */ #define ACB_HOMDIRREQ 0x0002 /* 1 = Home directory required */ #define ACB_PWNOTREQ 0x0004 /* 1 = User password not required */ #define ACB_TEMPDUP 0x0008 /* 1 = Temporary duplicate account */ #define ACB_NORMAL 0x0010 /* 1 = Normal user account */ #define ACB_MNS 0x0020 /* 1 = MNS logon user account */ #define ACB_DOMTRUST 0x0040 /* 1 = Interdomain trust account */ #define ACB_WSTRUST 0x0080 /* 1 = Workstation trust account */ #define ACB_SVRTRUST 0x0100 /* 1 = Server trust account */ #define ACB_PWNOEXP 0x0200 /* 1 = User password does not expire */ #define ACB_AUTOLOCK 0x0400 /* 1 = Account auto locked */ static int pdb_decode_acct_ctrl(const char *p) { int acct_ctrl = 0; int finished = 0; /* * Check if the account type bits have been encoded after the * NT password (in the form [NDHTUWSLXI]). */ if (*p != '[') return 0; for (p++; *p && !finished; p++) { switch (*p) { case 'N': /* 'N'o password. */ acct_ctrl |= ACB_PWNOTREQ; break; case 'D': /* 'D'isabled. */ acct_ctrl |= ACB_DISABLED ; break; case 'H': /* 'H'omedir required. */ acct_ctrl |= ACB_HOMDIRREQ; break; case 'T': /* 'T'emp account. */ acct_ctrl |= ACB_TEMPDUP; break; case 'U': /* 'U'ser account (normal). */ acct_ctrl |= ACB_NORMAL; break; case 'M': /* 'M'NS logon user account. What is this? */ acct_ctrl |= ACB_MNS; break; case 'W': /* 'W'orkstation account. */ acct_ctrl |= ACB_WSTRUST; break; case 'S': /* 'S'erver account. */ acct_ctrl |= ACB_SVRTRUST; break; case 'L': /* 'L'ocked account. */ acct_ctrl |= ACB_AUTOLOCK; break; case 'X': /* No 'X'piry on password */ acct_ctrl |= ACB_PWNOEXP; break; case 'I': /* 'I'nterdomain trust account. */ acct_ctrl |= ACB_DOMTRUST; break; case ' ': /* ignore spaces */ break; case ':': case '\n': case '\0': case ']': default: finished = 1; break; } } return acct_ctrl; } /* * ntpwdhash converts Unicode password to 16-byte NT hash * with MD4 */ static void ntpwdhash (unsigned char *szHash, const char *szPassword) { char szUnicodePass[513]; int nPasswordLen; int i; /* * NT passwords are unicode. Convert plain text password * to unicode by inserting a zero every other byte */ nPasswordLen = strlen(szPassword); for (i = 0; i < nPasswordLen; i++) { szUnicodePass[i << 1] = szPassword[i]; szUnicodePass[(i << 1) + 1] = 0; } /* Encrypt Unicode password to a 16-byte MD4 hash */ md4_calc(szHash, szUnicodePass, (nPasswordLen<<1) ); } /* * challenge_hash() is used by mschap2() and auth_response() * implements RFC2759 ChallengeHash() * generates 64 bit challenge */ static void challenge_hash( const char *peer_challenge, const char *auth_challenge, const char *user_name, char *challenge ) { SHA1_CTX Context; unsigned char hash[20]; SHA1Init(&Context); SHA1Update(&Context, peer_challenge, 16); SHA1Update(&Context, auth_challenge, 16); SHA1Update(&Context, user_name, strlen(user_name)); SHA1Final(hash, &Context); memcpy(challenge, hash, 8); } /* * auth_response() generates MS-CHAP v2 SUCCESS response * according to RFC 2759 GenerateAuthenticatorResponse() * returns 42-octet response string */ static void auth_response(const char *username, const unsigned char *nt_hash_hash, unsigned char *ntresponse, char *peer_challenge, char *auth_challenge, char *response) { SHA1_CTX Context; const unsigned char magic1[39] = {0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67, 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74}; const unsigned char magic2[41] = {0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B, 0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F, 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E, 0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F, 0x6E}; char challenge[8]; unsigned char digest[20]; SHA1Init(&Context); SHA1Update(&Context, nt_hash_hash, 16); SHA1Update(&Context, ntresponse, 24); SHA1Update(&Context, magic1, 39); SHA1Final(digest, &Context); challenge_hash(peer_challenge, auth_challenge, username, challenge); SHA1Init(&Context); SHA1Update(&Context, digest, 20); SHA1Update(&Context, challenge, 8); SHA1Update(&Context, magic2, 41); SHA1Final(digest, &Context); /* * Encode the value of 'Digest' as "S=" followed by * 40 ASCII hexadecimal digits and return it in * AuthenticatorResponse. * For example, * "S=0123456789ABCDEF0123456789ABCDEF01234567" */ response[0] = 'S'; response[1] = '='; bin2hex(digest, response + 2, 20); } typedef struct rlm_mschap_t { int use_mppe; int require_encryption; int require_strong; int with_ntdomain_hack; /* this should be in another module */ char *passwd_file; char *xlat_name; char *ntlm_auth; char *auth_type; } rlm_mschap_t; /* * Does dynamic translation of strings. * * Pulls NT-Response, LM-Response, or Challenge from MSCHAP * attributes. */ static int mschap_xlat(void *instance, REQUEST *request, char *fmt, char *out, size_t outlen, RADIUS_ESCAPE_STRING func) { int i, data_len; uint8_t *data = NULL; uint8_t buffer[32]; VALUE_PAIR *user_name; VALUE_PAIR *chap_challenge, *response; rlm_mschap_t *inst = instance; chap_challenge = response = NULL; func = func; /* -Wunused */ /* * Challenge means MS-CHAPv1 challenge, or * hash of MS-CHAPv2 challenge, and peer challenge. */ if (strcasecmp(fmt, "Challenge") == 0) { chap_challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE); if (!chap_challenge) { DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request."); return 0; } /* * MS-CHAP-Challenges are 8 octets, * for MS-CHAPv2 */ if (chap_challenge->length == 8) { DEBUG2(" mschap1: %02x", chap_challenge->strvalue[0]); data = chap_challenge->strvalue; data_len = 8; /* * MS-CHAP-Challenges are 16 octets, * for MS-CHAPv2. */ } else if (chap_challenge->length == 16) { char *username_string; DEBUG2(" mschap2: %02x", chap_challenge->strvalue[0]); response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE); if (!response) { DEBUG2(" rlm_mschap: MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge."); return 0; } /* * Responses are 50 octets. */ if (response->length < 50) { radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format."); return 0; } user_name = pairfind(request->packet->vps, PW_USER_NAME); if (!user_name) { DEBUG2(" rlm_mschap: User-Name is required to calculateMS-CHAPv1 Challenge."); return 0; } /* * with_ntdomain_hack moved here, too. */ if ((username_string = strchr(user_name->strvalue, '\\')) != NULL) { if (inst->with_ntdomain_hack) { username_string++; } else { DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?"); username_string = user_name->strvalue; } } else { username_string = user_name->strvalue; } /* * Get the MS-CHAPv1 challenge * from the MS-CHAPv2 peer challenge, * our challenge, and the user name. */ challenge_hash(response->strvalue + 2, chap_challenge->strvalue, username_string, buffer); data = buffer; data_len = 8; } else { DEBUG2(" rlm_mschap: Invalid MS-CHAP challenge length"); return 0; } /* * Get the MS-CHAPv1 response, or the MS-CHAPv2 * response. */ } else if (strcasecmp(fmt, "NT-Response") == 0) { response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE); if (!response) response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE); if (!response) { DEBUG2(" rlm_mschap: No MS-CHAP-Response or MS-CHAP2-Response was found in the request."); return 0; } /* * For MS-CHAPv1, the NT-Response exists only * if the second octet says so. */ if ((response->attribute == PW_MSCHAP_RESPONSE) && ((response->strvalue[1] & 0x01) == 0)) { DEBUG2(" rlm_mschap: No NT-Response in MS-CHAP-Response"); return 0; } /* * MS-CHAP-Response and MS-CHAP2-Response have * the NT-Response at the same offset, and are * the same length. */ data = response->strvalue + 26; data_len = 24; /* * LM-Response is deprecated, and exists only * in MS-CHAPv1, and not often there. */ } else if (strcasecmp(fmt, "LM-Response") == 0) { response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE); if (!response) { DEBUG2(" rlm_mschap: No MS-CHAP-Response was found in the request."); return 0; } /* * For MS-CHAPv1, the NT-Response exists only * if the second octet says so. */ if ((response->strvalue[1] & 0x01) != 0) { DEBUG2(" rlm_mschap: No LM-Response in MS-CHAP-Response"); return 0; } data = response->strvalue + 2; data_len = 24; /* * Pull the NT-Domain out of the User-Name, if it exists. */ } else if (strcasecmp(fmt, "NT-Domain") == 0) { char *p, *q; user_name = pairfind(request->packet->vps, PW_USER_NAME); if (!user_name) { DEBUG2(" rlm_mschap: No User-Name was found in the request."); return 0; } /* * First check to see if this is a host/ style User-Name * (a la Kerberos host principal) */ if (strncmp(user_name->strvalue, "host/", 5) == 0) { /* * If we're getting a User-Name formatted in this way, * it's likely due to PEAP. The Windows Domain will be * the first domain component following the hostname, * or the machine name itself if only a hostname is supplied */ p = strchr(user_name->strvalue, '.'); if (!p) { DEBUG2(" rlm_mschap: setting NT-Domain to same as machine name"); strNcpy(out, user_name->strvalue + 5, outlen); } else { p++; /* skip the period */ q = strchr(p, '.'); /* * use the same hack as below * only if another period was found */ if (q) *q = '\0'; strNcpy(out, p, outlen); if (q) *q = '.'; } } else { p = strchr(user_name->strvalue, '\\'); if (!p) { DEBUG2(" rlm_mschap: No NT-Domain was found in the User-Name."); return 0; } /* * Hack. This is simpler than the alternatives. */ *p = '\0'; strNcpy(out, user_name->strvalue, outlen); *p = '\\'; } return strlen(out); /* * Pull the User-Name out of the User-Name... */ } else if (strcasecmp(fmt, "User-Name") == 0) { char *p; user_name = pairfind(request->packet->vps, PW_USER_NAME); if (!user_name) { DEBUG2(" rlm_mschap: No User-Name was found in the request."); return 0; } /* * First check to see if this is a host/ style User-Name * (a la Kerberos host principal) */ if (strncmp(user_name->strvalue, "host/", 5) == 0) { /* * If we're getting a User-Name formatted in this way, * it's likely due to PEAP. When authenticating this against * a Domain, Windows will expect the User-Name to be in the * format of hostname$, the SAM version of the name, so we * have to convert it to that here. We do so by stripping * off the first 5 characters (host/), and copying everything * from that point to the first period into a string and appending * a $ to the end. */ p = strchr(user_name->strvalue, '.'); /* * use the same hack as above * only if a period was found */ if (p) *p = '\0'; snprintf(out, outlen, "%s$", user_name->strvalue + 5); if (p) *p = '.'; } else { p = strchr(user_name->strvalue, '\\'); if (p) { p++; /* skip the backslash */ } else { p = user_name->strvalue; /* use the whole User-Name */ } strNcpy(out, p, outlen); } return strlen(out); /* * Return the NT-Hash of the passed string */ } else if (strncasecmp(fmt, "NT-Hash ", 8) == 0) { char *p; p = fmt + 8; /* 7 is the length of 'NT-Hash' */ if ((p == '\0') || (outlen <= 32)) return 0; DEBUG("rlm_mschap: NT-Hash: %s",p); ntpwdhash(buffer,p); lrad_bin2hex(buffer, out, 16); out[32] = '\0'; DEBUG("rlm_mschap: NT-Hash: Result: %s",out); return 32; /* * Return the LM-Hash of the passed string */ } else if (strncasecmp(fmt, "LM-Hash ", 8) == 0) { char *p; p = fmt + 8; /* 7 is the length of 'LM-Hash' */ if ((p == '\0') || (outlen <= 32)) return 0; DEBUG("rlm_mschap: LM-Hash: %s",p); smbdes_lmpwdhash(p,buffer); lrad_bin2hex(buffer, out, 16); out[32] = '\0'; DEBUG("rlm_mschap: LM-Hash: Result: %s",out); return 32; } else { DEBUG2(" rlm_mschap: Unknown expansion string \"%s\"", fmt); return 0; } if (outlen == 0) return 0; /* nowhere to go, don't do anything */ /* * Didn't set anything: this is bad. */ if (!data) { DEBUG2(" rlm_mschap: Failed to do anything intelligent"); return 0; } /* * Check the output length. */ if (outlen < ((data_len * 2) + 1)) { data_len = (outlen - 1) / 2; } /* * */ for (i = 0; i < data_len; i++) { sprintf(out + (2 * i), "%02x", data[i]); } out[data_len * 2] = '\0'; return data_len * 2; } static CONF_PARSER module_config[] = { /* * Cache the password by default. */ { "use_mppe", PW_TYPE_BOOLEAN, offsetof(rlm_mschap_t,use_mppe), NULL, "yes" }, { "require_encryption", PW_TYPE_BOOLEAN, offsetof(rlm_mschap_t,require_encryption), NULL, "no" }, { "require_strong", PW_TYPE_BOOLEAN, offsetof(rlm_mschap_t,require_strong), NULL, "no" }, { "with_ntdomain_hack", PW_TYPE_BOOLEAN, offsetof(rlm_mschap_t,with_ntdomain_hack), NULL, "no" }, { "passwd", PW_TYPE_STRING_PTR, offsetof(rlm_mschap_t, passwd_file), NULL, NULL }, { "ntlm_auth", PW_TYPE_STRING_PTR, offsetof(rlm_mschap_t, ntlm_auth), NULL, NULL }, { NULL, -1, 0, NULL, NULL } /* end the list */ }; /* * deinstantiate module, free all memory allocated during * mschap_instantiate() */ static int mschap_detach(void *instance){ #define inst ((rlm_mschap_t *)instance) free(inst->passwd_file); free(inst->ntlm_auth); if (inst->xlat_name) { xlat_unregister(inst->xlat_name, mschap_xlat); } free(instance); return 0; #undef inst } /* * Create instance for our module. Allocate space for * instance structure and read configuration parameters */ static int mschap_instantiate(CONF_SECTION *conf, void **instance) { rlm_mschap_t *inst; inst = *instance = rad_malloc(sizeof(*inst)); if (!inst) { return -1; } memset(inst, 0, sizeof(*inst)); if (cf_section_parse(conf, inst, module_config) < 0) { free(inst); return -1; } /* * This module used to support SMB Password files, but it * made it too complicated. If the user tries to * configure an SMB Password file, then die, with an * error message. */ if (inst->passwd_file) { radlog(L_ERR, "rlm_mschap: SMB password file is no longer supported in this module. Use rlm_passwd module instead"); mschap_detach(inst); return -1; } /* * Create the dynamic translation. */ inst->xlat_name = cf_section_name2(conf); if (!inst->xlat_name) inst->xlat_name = cf_section_name1(conf); xlat_register(inst->xlat_name, mschap_xlat, inst); /* * For backwards compatibility */ if (!dict_valbyname(PW_AUTH_TYPE, inst->xlat_name)) { inst->auth_type = "MS-CHAP"; } else { inst->auth_type = inst->xlat_name; } return 0; } /* * add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error * attribute to reply packet */ static void add_reply(VALUE_PAIR** vp, unsigned char ident, const char* name, const char* value, int len) { VALUE_PAIR *reply_attr; reply_attr = pairmake(name, "", T_OP_EQ); if (!reply_attr) { DEBUG(" rlm_mschap: Failed to create attribute %s: %s\n", name, librad_errstr); return; } reply_attr->strvalue[0] = ident; memcpy(reply_attr->strvalue + 1, value, len); reply_attr->length = len + 1; pairadd(vp, reply_attr); } /* * Add MPPE attributes to the reply. */ static void mppe_add_reply(VALUE_PAIR **vp, const char* name, const char* value, int len) { VALUE_PAIR *reply_attr; reply_attr = pairmake(name, "", T_OP_EQ); if (!reply_attr) { DEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, librad_errstr); return; } memcpy(reply_attr->strvalue, value, len); reply_attr->length = len; pairadd(vp, reply_attr); } /* * Do the MS-CHAP stuff. * * This function is here so that all of the MS-CHAP related * authentication is in one place, and we can perhaps later replace * it with code to call winbindd, or something similar. */ static int do_mschap(rlm_mschap_t *inst, REQUEST *request, VALUE_PAIR *password, uint8_t *challenge, uint8_t *response, uint8_t *nthashhash) { int do_ntlm_auth = 0; uint8_t calculated[24]; VALUE_PAIR *vp = NULL; /* * If we have ntlm_auth configured, use it unless told * otherwise */ if (inst->ntlm_auth) do_ntlm_auth = 1; /* * If we have an ntlm_auth configuration, then we may * want to use it. */ vp = pairfind(request->config_items, PW_MS_CHAP_USE_NTLM_AUTH); if (vp) do_ntlm_auth = vp->lvalue; /* * No ntlm_auth configured, attribute to tell us to * use it exists, and we're told to use it. We don't * know what to do... */ if (!inst->ntlm_auth && do_ntlm_auth) { DEBUG2(" rlm_mschap: Asked to use ntlm_auth, but it was not configured in the mschap{} section."); return -1; } /* * Do normal authentication. */ if (!do_ntlm_auth) { /* * No password: can't do authentication. */ if (!password) { DEBUG2(" rlm_mschap: FAILED: No NT/LM-Password. Cannot perform authentication."); return -1; } smbdes_mschap(password->strvalue, challenge, calculated); if (memcmp(response, calculated, 24) != 0) { return -1; } /* * If the password exists, and is an NT-Password, * then calculate the hash of the NT hash. Doing this * here minimizes work for later. */ if (password && (password->attribute == PW_NT_PASSWORD)) { md4_calc(nthashhash, password->strvalue, 16); } else { memset(nthashhash, 0, 16); } } else { /* run ntlm_auth */ int result; char buffer[256]; memset(nthashhash, 0, 16); /* * Run the program, and expect that we get 16 */ result = radius_exec_program(inst->ntlm_auth, request, TRUE, /* wait */ buffer, sizeof(buffer), NULL, NULL); if (result != 0) { char *p; DEBUG2(" rlm_mschap: External script failed."); vp = pairmake("Module-Failure-Message", "", T_OP_EQ); if (!vp) { radlog(L_ERR, "No memory"); return -1; } p = strchr(buffer, '\n'); if (p) *p = '\0'; snprintf(vp->strvalue, sizeof(vp->strvalue), "rlm_mschap: %s", buffer); vp->length = strlen(vp->strvalue); pairadd(&request->packet->vps, vp); return -1; } /* * Parse the answer as an nthashhash. * * ntlm_auth currently returns: * NT_KEY: 000102030405060708090a0b0c0d0e0f */ if (memcmp(buffer, "NT_KEY: ", 8) != 0) { DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: expecting NT_KEY"); return -1; } /* * Check the length. It should be at least 32, * with an LF at the end. */ if (strlen(buffer + 8) < 32) { DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has unexpected length"); return -1; } /* * Update the NT hash hash, from the NT key. */ if (hex2bin(buffer + 8, nthashhash, 16) != 16) { DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has non-hex values"); return -1; } } return 0; } /* * Data for the hashes. */ static const uint8_t SHSpad1[40] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; static const uint8_t SHSpad2[40] = { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 }; static const uint8_t magic1[27] = { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 }; static const uint8_t magic2[84] = { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x2e }; static const uint8_t magic3[84] = { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79, 0x2e }; static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response, uint8_t *masterkey) { uint8_t digest[20]; SHA1_CTX Context; SHA1Init(&Context); SHA1Update(&Context,nt_hashhash,16); SHA1Update(&Context,nt_response,24); SHA1Update(&Context,magic1,27); SHA1Final(digest,&Context); memcpy(masterkey,digest,16); } static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey, int keylen,int issend) { uint8_t digest[20]; const uint8_t *s; SHA1_CTX Context; memset(digest,0,20); if(issend) { s = magic3; } else { s = magic2; } SHA1Init(&Context); SHA1Update(&Context,masterkey,16); SHA1Update(&Context,SHSpad1,40); SHA1Update(&Context,s,84); SHA1Update(&Context,SHSpad2,40); SHA1Final(digest,&Context); memcpy(sesskey,digest,keylen); } static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response, uint8_t *sendkey,uint8_t *recvkey) { uint8_t masterkey[16]; mppe_GetMasterKey(nt_hashhash,nt_response,masterkey); mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1); mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0); } /* * Generate MPPE keys. */ static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response, uint8_t *sendkey,uint8_t *recvkey) { uint8_t enckey1[16]; uint8_t enckey2[16]; mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2); /* * dictionary.microsoft defines these attributes as * 'encrypt=2'. The functions in src/lib/radius.c will * take care of encrypting/decrypting them as appropriate, * so that we don't have to. */ memcpy (sendkey, enckey1, 16); memcpy (recvkey, enckey2, 16); } /* * mschap_authorize() - authorize user if we can authenticate * it later. Add Auth-Type attribute if present in module * configuration (usually Auth-Type must be "MS-CHAP") */ static int mschap_authorize(void * instance, REQUEST *request) { #define inst ((rlm_mschap_t *)instance) VALUE_PAIR *challenge = NULL; VALUE_PAIR *response = NULL; VALUE_PAIR *vp; challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE); if (!challenge) { return RLM_MODULE_NOOP; } response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE); if (!response) response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE); /* * Nothing we recognize. Don't do anything. */ if (!response) { DEBUG2(" rlm_mschap: Found MS-CHAP-Challenge, but no MS-CHAP-Response."); return RLM_MODULE_NOOP; } DEBUG2(" rlm_mschap: Found MS-CHAP attributes. Setting 'Auth-Type = %s'", inst->xlat_name); /* * Set Auth-Type to MS-CHAP. The authentication code * will take care of turning clear-text passwords into * NT/LM passwords. */ vp = pairmake("Auth-Type", inst->auth_type, T_OP_EQ); if (!vp) return RLM_MODULE_FAIL; pairmove(&request->config_items, &vp); pairfree(&vp); /* may be NULL */ return RLM_MODULE_OK; #undef inst } /* * mschap_authenticate() - authenticate user based on given * attributes and configuration. * We will try to find out password in configuration * or in configured passwd file. * If one is found we will check paraneters given by NAS. * * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have * one of: * PAP: PW_PASSWORD or * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE * In case of password mismatch or locked account we MAY return * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2 * If MS-CHAP2 succeeds we MUST return * PW_MSCHAP2_SUCCESS */ static int mschap_authenticate(void * instance, REQUEST *request) { #define inst ((rlm_mschap_t *)instance) VALUE_PAIR *challenge = NULL; VALUE_PAIR *response = NULL; VALUE_PAIR *password = NULL; VALUE_PAIR *lm_password, *nt_password, *smb_ctrl; VALUE_PAIR *username; VALUE_PAIR *reply_attr; uint8_t nthashhash[16]; uint8_t msch2resp[42]; char *username_string; int chap = 0; /* * Find the SMB-Account-Ctrl attribute, or the * SMB-Account-Ctrl-Text attribute. */ smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL); if (!smb_ctrl) { password = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL_TEXT); if (password) { smb_ctrl = pairmake("SMB-Account-CTRL", "0", T_OP_SET); pairadd(&request->config_items, smb_ctrl); smb_ctrl->lvalue = pdb_decode_acct_ctrl(password->strvalue); } } /* * We're configured to do MS-CHAP authentication. * and account control information exists. Enforce it. */ if (smb_ctrl) { /* * Password is not required. */ if ((smb_ctrl->lvalue & ACB_PWNOTREQ) != 0) { DEBUG2(" rlm_mschap: SMB-Account-Ctrl says no password is required."); return RLM_MODULE_OK; } } /* * Decide how to get the passwords. */ password = pairfind(request->config_items, PW_PASSWORD); /* * We need an LM-Password. */ lm_password = pairfind(request->config_items, PW_LM_PASSWORD); if (lm_password) { /* * Allow raw octets. */ if ((lm_password->length == 16) || ((lm_password->length == 32) && (hex2bin(lm_password->strvalue, lm_password->strvalue, 16) == 16))) { DEBUG2(" rlm_mschap: Found LM-Password"); lm_password->length = 16; } else { radlog(L_ERR, "rlm_mschap: Invalid LM-Password"); lm_password = NULL; } } else if (!password) { DEBUG2(" rlm_mschap: No User-Password configured. Cannot create LM-Password."); } else { /* there is a configured User-Password */ lm_password = pairmake("LM-Password", "", T_OP_EQ); if (!lm_password) { radlog(L_ERR, "No memory"); } else { smbdes_lmpwdhash(password->strvalue, lm_password->strvalue); lm_password->length = 16; pairadd(&request->config_items, lm_password); } } /* * We need an NT-Password. */ nt_password = pairfind(request->config_items, PW_NT_PASSWORD); if (nt_password) { if ((nt_password->length == 16) || ((nt_password->length == 32) && (hex2bin(nt_password->strvalue, nt_password->strvalue, 16) == 16))) { DEBUG2(" rlm_mschap: Found NT-Password"); nt_password->length = 16; } else { radlog(L_ERR, "rlm_mschap: Invalid NT-Password"); nt_password = NULL; } } else if (!password) { DEBUG2(" rlm_mschap: No User-Password configured. Cannot create NT-Password."); } else { /* there is a configured User-Password */ nt_password = pairmake("NT-Password", "", T_OP_EQ); if (!nt_password) { radlog(L_ERR, "No memory"); return RLM_MODULE_FAIL; } else { ntpwdhash(nt_password->strvalue, password->strvalue); nt_password->length = 16; pairadd(&request->config_items, nt_password); } } challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE); if (!challenge) { DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request"); return RLM_MODULE_REJECT; } /* * We also require an MS-CHAP-Response. */ response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE); /* * MS-CHAP-Response, means MS-CHAPv1 */ if (response) { int offset; /* * MS-CHAPv1 challenges are 8 octets. */ if (challenge->length < 8) { radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format."); return RLM_MODULE_INVALID; } /* * Responses are 50 octets. */ if (response->length < 50) { radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format."); return RLM_MODULE_INVALID; } /* * We are doing MS-CHAP. Calculate the MS-CHAP * response */ if (response->strvalue[1] & 0x01) { DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with NT-Password"); password = nt_password; offset = 26; } else { DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with LM-Password"); password = lm_password; offset = 2; } /* * Do the MS-CHAP authentication. */ if (do_mschap(inst, request, password, challenge->strvalue, response->strvalue + offset, nthashhash) < 0) { DEBUG2(" rlm_mschap: MS-CHAP-Response is incorrect."); add_reply(&request->reply->vps, *response->strvalue, "MS-CHAP-Error", "E=691 R=1", 9); return RLM_MODULE_REJECT; } chap = 1; } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE)) != NULL) { uint8_t mschapv1_challenge[16]; /* * MS-CHAPv2 challenges are 16 octets. */ if (challenge->length < 16) { radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format."); return RLM_MODULE_INVALID; } /* * Responses are 50 octets. */ if (response->length < 50) { radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format."); return RLM_MODULE_INVALID; } /* * We also require a User-Name */ username = pairfind(request->packet->vps, PW_USER_NAME); if (!username) { radlog(L_AUTH, "rlm_mschap: We require a User-Name for MS-CHAPv2"); return RLM_MODULE_INVALID; } /* * with_ntdomain_hack moved here */ if ((username_string = strchr(username->strvalue, '\\')) != NULL) { if (inst->with_ntdomain_hack) { username_string++; } else { DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?"); username_string = username->strvalue; } } else { username_string = username->strvalue; } /* * The old "mschapv2" function has been moved to * here. * * MS-CHAPv2 takes some additional data to create an * MS-CHAPv1 challenge, and then does MS-CHAPv1. */ challenge_hash(response->strvalue + 2, /* peer challenge */ challenge->strvalue, /* our challenge */ username_string, /* user name */ mschapv1_challenge); /* resulting challenge */ DEBUG2(" rlm_mschap: Told to do MS-CHAPv2 for %s with NT-Password", username_string); if (do_mschap(inst, request, nt_password, mschapv1_challenge, response->strvalue + 26, nthashhash) < 0) { DEBUG2(" rlm_mschap: FAILED: MS-CHAP2-Response is incorrect"); add_reply(&request->reply->vps, *response->strvalue, "MS-CHAP-Error", "E=691 R=1", 9); return RLM_MODULE_REJECT; } /* * Get the NT-hash-hash, if necessary */ if (nt_password) { } auth_response(username_string, /* without the domain */ nthashhash, /* nt-hash-hash */ response->strvalue + 26, /* peer response */ response->strvalue + 2, /* peer challenge */ challenge->strvalue, /* our challenge */ msch2resp); /* calculated MPPE key */ add_reply( &request->reply->vps, *response->strvalue, "MS-CHAP2-Success", msch2resp, 42); chap = 2; } else { /* Neither CHAPv1 or CHAPv2 response: die */ radlog(L_AUTH, "rlm_mschap: No MS-CHAP response found"); return RLM_MODULE_INVALID; } /* * We have a CHAP response, but the account may be * disabled. Reject the user with the same error code * we use when their password is invalid. */ if (smb_ctrl) { /* * Account is disabled. * * They're found, but they don't exist, so we * return 'not found'. */ if (((smb_ctrl->lvalue & ACB_DISABLED) != 0) || ((smb_ctrl->lvalue & ACB_NORMAL) == 0)) { DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is disabled, or is not a normal account."); add_reply( &request->reply->vps, *response->strvalue, "MS-CHAP-Error", "E=691 R=1", 9); return RLM_MODULE_NOTFOUND; } /* * User is locked out. */ if ((smb_ctrl->lvalue & ACB_AUTOLOCK) != 0) { DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is locked out."); add_reply( &request->reply->vps, *response->strvalue, "MS-CHAP-Error", "E=647 R=0", 9); return RLM_MODULE_USERLOCK; } } /* now create MPPE attributes */ if (inst->use_mppe) { uint8_t mppe_sendkey[34]; uint8_t mppe_recvkey[34]; if (chap == 1){ DEBUG2("rlm_mschap: adding MS-CHAPv1 MPPE keys"); memset(mppe_sendkey, 0, 32); if (lm_password) { memcpy(mppe_sendkey, lm_password->strvalue, 8); } /* * According to RFC 2548 we * should send NT hash. But in * practice it doesn't work. * Instead, we should send nthashhash * * This is an error on RFC 2548. */ /* * do_mschap cares to zero nthashhash if NT hash * is not available. */ memcpy(mppe_sendkey + 8, nthashhash, 16); mppe_add_reply(&request->reply->vps, "MS-CHAP-MPPE-Keys", mppe_sendkey, 32); } else if (chap == 2) { DEBUG2("rlm_mschap: adding MS-CHAPv2 MPPE keys"); mppe_chap2_gen_keys128(nthashhash, response->strvalue + 26, mppe_sendkey, mppe_recvkey); mppe_add_reply(&request->reply->vps, "MS-MPPE-Recv-Key", mppe_recvkey, 16); mppe_add_reply(&request->reply->vps, "MS-MPPE-Send-Key", mppe_sendkey, 16); } reply_attr = pairmake("MS-MPPE-Encryption-Policy", (inst->require_encryption)? "0x00000002":"0x00000001", T_OP_EQ); rad_assert(reply_attr != NULL); pairadd(&request->reply->vps, reply_attr); reply_attr = pairmake("MS-MPPE-Encryption-Types", (inst->require_strong)? "0x00000004":"0x00000006", T_OP_EQ); rad_assert(reply_attr != NULL); pairadd(&request->reply->vps, reply_attr); } /* else we weren't asked to use MPPE */ return RLM_MODULE_OK; #undef inst } module_t rlm_mschap = { "MS-CHAP", RLM_TYPE_THREAD_SAFE, /* type */ NULL, /* initialize */ mschap_instantiate, /* instantiation */ { mschap_authenticate, /* authenticate */ mschap_authorize, /* authorize */ NULL, /* pre-accounting */ NULL, /* accounting */ NULL, /* checksimul */ NULL, /* pre-proxy */ NULL, /* post-proxy */ NULL /* post-auth */ }, mschap_detach, /* detach */ NULL, /* destroy */ };