/*
* 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 <waga@sic.shibaura-it.ac.jp> */
#include "autoconf.h"
#include "libradius.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#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 */
};
syntax highlighted by Code2HTML, v. 0.9.1