#include <freeradius-devel/ident.h>
RCSID("$Id$")
#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/modules.h>
#include <ctype.h>
#include "../../include/md5.h"
#include "../../include/sha1.h"
#define PAP_ENC_INVALID -1
#define PAP_ENC_CLEAR 0
#define PAP_ENC_CRYPT 1
#define PAP_ENC_MD5 2
#define PAP_ENC_SHA1 3
#define PAP_ENC_NT 4
#define PAP_ENC_LM 5
#define PAP_ENC_SMD5 6
#define PAP_ENC_SSHA 7
#define PAP_ENC_NS_MTA_MD5 8
#define PAP_ENC_AUTO 9
#define PAP_MAX_ENC 9
typedef struct rlm_pap_t {
const char *name;
char *scheme;
int sch;
char norm_passwd;
int auto_header;
int auth_type;
} rlm_pap_t;
static const CONF_PARSER module_config[] = {
{ "encryption_scheme", PW_TYPE_STRING_PTR, offsetof(rlm_pap_t,scheme), NULL, "auto" },
{ "auto_header", PW_TYPE_BOOLEAN, offsetof(rlm_pap_t,auto_header), NULL, "no" },
{ NULL, -1, 0, NULL, NULL }
};
static const FR_NAME_NUMBER schemes[] = {
{ "clear", PAP_ENC_CLEAR },
{ "crypt", PAP_ENC_CRYPT },
{ "md5", PAP_ENC_MD5 },
{ "sha1", PAP_ENC_SHA1 },
{ "nt", PAP_ENC_NT },
{ "lm", PAP_ENC_LM },
{ "smd5", PAP_ENC_SMD5 },
{ "ssha", PAP_ENC_SSHA },
{ "auto", PAP_ENC_AUTO },
{ NULL, PAP_ENC_INVALID }
};
static const FR_NAME_NUMBER header_names[] = {
{ "{clear}", PW_CLEARTEXT_PASSWORD },
{ "{cleartext}", PW_CLEARTEXT_PASSWORD },
{ "{md5}", PW_MD5_PASSWORD },
{ "{smd5}", PW_SMD5_PASSWORD },
{ "{crypt}", PW_CRYPT_PASSWORD },
{ "{sha}", PW_SHA_PASSWORD },
{ "{ssha}", PW_SSHA_PASSWORD },
{ "{nt}", PW_NT_PASSWORD },
{ "{nthash}", PW_NT_PASSWORD },
{ "{x-nthash}", PW_NT_PASSWORD },
{ "{ns-mta-md5}", PW_NS_MTA_MD5_PASSWORD },
{ "{x- orcllmv}", PW_LM_PASSWORD },
{ "{X- ORCLNTV}", PW_NT_PASSWORD },
{ NULL, 0 }
};
static int pap_detach(void *instance)
{
rlm_pap_t *inst = (rlm_pap_t *) instance;
free(inst);
return 0;
}
static int pap_instantiate(CONF_SECTION *conf, void **instance)
{
rlm_pap_t *inst;
DICT_VALUE *dval;
inst = rad_malloc(sizeof(*inst));
if (!inst) {
return -1;
}
memset(inst, 0, sizeof(*inst));
if (cf_section_parse(conf, inst, module_config) < 0) {
pap_detach(inst);
return -1;
}
if (inst->scheme == NULL || strlen(inst->scheme) == 0){
radlog(L_ERR, "rlm_pap: No scheme defined");
pap_detach(inst);
return -1;
}
inst->sch = fr_str2int(schemes, inst->scheme, PAP_ENC_INVALID);
if (inst->sch == PAP_ENC_INVALID) {
radlog(L_ERR, "rlm_pap: Unknown scheme \"%s\"", inst->scheme);
pap_detach(inst);
return -1;
}
inst->name = cf_section_name2(conf);
if (!inst->name) {
inst->name = cf_section_name1(conf);
}
dval = dict_valbyname(PW_AUTH_TYPE, inst->name);
if (dval) {
inst->auth_type = dval->value;
} else {
inst->auth_type = 0;
}
*instance = inst;
return 0;
}
static int decode_it(const char *src, uint8_t *dst)
{
int i;
unsigned int x = 0;
for(i = 0; i < 4; i++) {
if (src[i] >= 'A' && src[i] <= 'Z')
x = (x << 6) + (unsigned int)(src[i] - 'A' + 0);
else if (src[i] >= 'a' && src[i] <= 'z')
x = (x << 6) + (unsigned int)(src[i] - 'a' + 26);
else if(src[i] >= '0' && src[i] <= '9')
x = (x << 6) + (unsigned int)(src[i] - '0' + 52);
else if(src[i] == '+')
x = (x << 6) + 62;
else if (src[i] == '/')
x = (x << 6) + 63;
else if (src[i] == '=')
x = (x << 6);
else return 0;
}
dst[2] = (unsigned char)(x & 255); x >>= 8;
dst[1] = (unsigned char)(x & 255); x >>= 8;
dst[0] = (unsigned char)(x & 255);
return 1;
}
static int base64_decode (const char *src, uint8_t *dst)
{
int length, equals;
int i, num;
uint8_t last[3];
length = equals = 0;
while (src[length] && src[length] != '=') length++;
while (src[length + equals] == '=') equals++;
num = (length + equals) / 4;
for (i = 0; i < num - 1; i++) {
if (!decode_it(src, dst)) return 0;
src += 4;
dst += 3;
}
decode_it(src, last);
for (i = 0; i < (3 - equals); i++) {
dst[i] = last[i];
}
return (num * 3) - equals;
}
static void normify(REQUEST *request, VALUE_PAIR *vp, size_t min_length)
{
size_t decoded;
uint8_t buffer[64];
if (min_length >= sizeof(buffer)) return;
if (vp->length >= (2 * min_length)) {
decoded = fr_hex2bin(vp->vp_strvalue, buffer, vp->length >> 1);
if (decoded == (vp->length >> 1)) {
RDEBUG2("Normalizing %s from hex encoding", vp->name);
memcpy(vp->vp_octets, buffer, decoded);
vp->length = decoded;
return;
}
}
if ((vp->length * 3) >= ((min_length * 4))) {
decoded = base64_decode(vp->vp_strvalue, buffer);
if (decoded >= min_length) {
RDEBUG2("Normalizing %s from base64 encoding", vp->name);
memcpy(vp->vp_octets, buffer, decoded);
vp->length = decoded;
return;
}
}
}
static int pap_authorize(void *instance, REQUEST *request)
{
rlm_pap_t *inst = instance;
int auth_type = FALSE;
int found_pw = FALSE;
VALUE_PAIR *vp, *next;
for (vp = request->config_items; vp != NULL; vp = next) {
next = vp->next;
switch (vp->attribute) {
case PW_USER_PASSWORD:
found_pw = TRUE;
if (!inst->auto_header ||
(vp->vp_strvalue[0] != '{')) {
break;
}
case PW_PASSWORD_WITH_HEADER:
{
int attr;
char *p, *q;
char buffer[128];
VALUE_PAIR *new_vp;
found_pw = TRUE;
redo:
q = vp->vp_strvalue;
p = strchr(q + 1, '}');
if (!p) {
int decoded;
if (pairfind(request->config_items, PW_USER_PASSWORD) ||
pairfind(request->config_items, PW_CLEARTEXT_PASSWORD)) {
RDEBUG("Config already contains \"known good\" password. Ignoring Password-With-Header");
break;
}
decoded = base64_decode(vp->vp_strvalue, buffer);
if ((decoded > 0) && (buffer[0] == '{') &&
(strchr(buffer, '}') != NULL)) {
memcpy(vp->vp_octets, buffer, decoded);
vp->length = decoded;
goto redo;
}
RDEBUG("Failed to decode Password-With-Header = \"%s\"", vp->vp_strvalue);
break;
}
if ((size_t) (p - q) > sizeof(buffer)) break;
memcpy(buffer, q, p - q + 1);
buffer[p - q + 1] = '\0';
attr = fr_str2int(header_names, buffer, 0);
if (!attr) {
RDEBUG2("Found unknown header {%s}: Not doing anything", buffer);
break;
}
new_vp = radius_paircreate(request,
&request->config_items,
attr, PW_TYPE_STRING);
new_vp->length = vp->length;
new_vp->length -= (p - q + 1);
memcpy(new_vp->vp_strvalue, p + 1, new_vp->length);
pairdelete(&request->config_items, PW_USER_PASSWORD);
}
break;
case PW_CLEARTEXT_PASSWORD:
case PW_CRYPT_PASSWORD:
case PW_NS_MTA_MD5_PASSWORD:
found_pw = TRUE;
break;
case PW_MD5_PASSWORD:
case PW_SMD5_PASSWORD:
case PW_NT_PASSWORD:
case PW_LM_PASSWORD:
normify(request, vp, 16);
found_pw = TRUE;
break;
case PW_SHA_PASSWORD:
case PW_SSHA_PASSWORD:
normify(request, vp, 20);
found_pw = TRUE;
break;
case PW_PROXY_TO_REALM:
{
REALM *realm = realm_find(vp->vp_strvalue);
if (realm && realm->auth_pool) {
return RLM_MODULE_NOOP;
}
break;
}
case PW_AUTH_TYPE:
auth_type = TRUE;
if ((vp->vp_integer == 254) ||
(vp->vp_integer == 4)) {
found_pw = 1;
}
break;
default:
break;
}
}
if (!found_pw) {
if (pairfind(request->config_items, PW_REALM) ||
(pairfind(request->config_items, PW_PROXY_TO_REALM))) {
return RLM_MODULE_NOOP;
}
vp = pairfind(request->packet->vps, PW_EAP_TYPE);
if (vp &&
((vp->vp_integer == 13) ||
(vp->vp_integer == 21) ||
(vp->vp_integer == 25))) {
return RLM_MODULE_NOOP;
}
RDEBUG("WARNING! No \"known good\" password found for the user. Authentication may fail because of this.");
return RLM_MODULE_NOOP;
}
if (auth_type) {
RDEBUG2("WARNING: Auth-Type already set. Not setting to PAP");
return RLM_MODULE_NOOP;
}
if (!request->password ||
(request->password->attribute != PW_USER_PASSWORD)) {
if (request->packet->code == PW_ACCESS_CHALLENGE) {
return RLM_MODULE_NOOP;
}
RDEBUG2("No clear-text password in the request. Not performing PAP.");
return RLM_MODULE_NOOP;
}
if (inst->auth_type) {
vp = radius_paircreate(request, &request->config_items,
PW_AUTH_TYPE, PW_TYPE_INTEGER);
vp->vp_integer = inst->auth_type;
}
return RLM_MODULE_UPDATED;
}
static int pap_authenticate(void *instance, REQUEST *request)
{
rlm_pap_t *inst = instance;
VALUE_PAIR *vp;
VALUE_PAIR *module_fmsg_vp;
char module_fmsg[MAX_STRING_LEN];
FR_MD5_CTX md5_context;
fr_SHA1_CTX sha1_context;
uint8_t digest[40];
char buff[MAX_STRING_LEN];
char buff2[MAX_STRING_LEN + 50];
int scheme = PAP_ENC_INVALID;
if (!request->password ||
(request->password->attribute != PW_USER_PASSWORD)) {
RDEBUG("ERROR: You set 'Auth-Type = PAP' for a request that does not contain a User-Password attribute!");
return RLM_MODULE_INVALID;
}
if (request->password->length == 0) {
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: empty password supplied");
module_fmsg_vp = pairmake("Module-Failure-Message", module_fmsg, T_OP_EQ);
pairadd(&request->packet->vps, module_fmsg_vp);
return RLM_MODULE_INVALID;
}
RDEBUG("login attempt with password \"%s\"",
request->password->vp_strvalue);
if (inst->sch == PAP_ENC_AUTO) {
for (vp = request->config_items; vp != NULL; vp = vp->next) {
switch (vp->attribute) {
case PW_USER_PASSWORD:
case PW_CLEARTEXT_PASSWORD:
goto do_clear;
case PW_CRYPT_PASSWORD:
goto do_crypt;
case PW_MD5_PASSWORD:
goto do_md5;
case PW_SHA_PASSWORD:
goto do_sha;
case PW_NT_PASSWORD:
goto do_nt;
case PW_LM_PASSWORD:
goto do_lm;
case PW_SMD5_PASSWORD:
goto do_smd5;
case PW_SSHA_PASSWORD:
goto do_ssha;
case PW_NS_MTA_MD5_PASSWORD:
goto do_ns_mta_md5;
default:
break;
}
}
fail:
RDEBUG("No password configured for the user. Cannot do authentication");
return RLM_MODULE_FAIL;
} else {
vp = NULL;
if (inst->sch == PAP_ENC_CRYPT) {
vp = pairfind(request->config_items, PW_CRYPT_PASSWORD);
}
if (!vp) {
vp = pairfind(request->config_items, PW_USER_PASSWORD);
if (!vp) goto fail;
}
}
switch (scheme) {
case PAP_ENC_CLEAR:
do_clear:
RDEBUG("Using clear text password \"%s\"",
vp->vp_strvalue);
if ((vp->length != request->password->length) ||
(rad_digest_cmp(vp->vp_strvalue,
request->password->vp_strvalue,
vp->length) != 0)) {
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: CLEAR TEXT password check failed");
goto make_msg;
}
done:
RDEBUG("User authenticated successfully");
return RLM_MODULE_OK;
break;
case PAP_ENC_CRYPT:
do_crypt:
RDEBUG("Using CRYPT password \"%s\"",
vp->vp_strvalue);
if (fr_crypt_check(request->password->vp_strvalue,
vp->vp_strvalue) != 0) {
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: CRYPT password check failed");
goto make_msg;
}
goto done;
break;
case PW_MD5_PASSWORD:
do_md5:
RDEBUG("Using MD5 encryption.");
normify(request, vp, 16);
if (vp->length != 16) {
RDEBUG("Configured MD5 password has incorrect length");
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured MD5 password has incorrect length");
goto make_msg;
}
fr_MD5Init(&md5_context);
fr_MD5Update(&md5_context, request->password->vp_octets,
request->password->length);
fr_MD5Final(digest, &md5_context);
if (rad_digest_cmp(digest, vp->vp_octets, vp->length) != 0) {
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: MD5 password check failed");
goto make_msg;
}
goto done;
break;
case PW_SMD5_PASSWORD:
do_smd5:
RDEBUG("Using SMD5 encryption.");
normify(request, vp, 16);
if (vp->length <= 16) {
RDEBUG("Configured SMD5 password has incorrect length");
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured SMD5 password has incorrect length");
goto make_msg;
}
fr_MD5Init(&md5_context);
fr_MD5Update(&md5_context, request->password->vp_octets,
request->password->length);
fr_MD5Update(&md5_context, &vp->vp_octets[16], vp->length - 16);
fr_MD5Final(digest, &md5_context);
if (rad_digest_cmp(digest, vp->vp_octets, 16) != 0) {
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: SMD5 password check failed");
goto make_msg;
}
goto done;
break;
case PW_SHA_PASSWORD:
do_sha:
RDEBUG("Using SHA1 encryption.");
normify(request, vp, 20);
if (vp->length != 20) {
RDEBUG("Configured SHA1 password has incorrect length");
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured SHA1 password has incorrect length");
goto make_msg;
}
fr_SHA1Init(&sha1_context);
fr_SHA1Update(&sha1_context, request->password->vp_octets,
request->password->length);
fr_SHA1Final(digest,&sha1_context);
if (rad_digest_cmp(digest, vp->vp_octets, vp->length) != 0) {
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: SHA1 password check failed");
goto make_msg;
}
goto done;
break;
case PW_SSHA_PASSWORD:
do_ssha:
RDEBUG("Using SSHA encryption.");
normify(request, vp, 20);
if (vp->length <= 20) {
RDEBUG("Configured SSHA password has incorrect length");
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured SHA password has incorrect length");
goto make_msg;
}
fr_SHA1Init(&sha1_context);
fr_SHA1Update(&sha1_context, request->password->vp_octets,
request->password->length);
fr_SHA1Update(&sha1_context, &vp->vp_octets[20], vp->length - 20);
fr_SHA1Final(digest,&sha1_context);
if (rad_digest_cmp(digest, vp->vp_octets, 20) != 0) {
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: SSHA password check failed");
goto make_msg;
}
goto done;
break;
case PW_NT_PASSWORD:
do_nt:
RDEBUG("Using NT encryption.");
normify(request, vp, 16);
if (vp->length != 16) {
RDEBUG("Configured NT-Password has incorrect length");
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured NT-Password has incorrect length");
goto make_msg;
}
strlcpy(buff2, "%{mschap:NT-Hash %{User-Password}}", sizeof(buff2));
if (!radius_xlat(digest, sizeof(digest),buff2,request,NULL)){
RDEBUG("mschap xlat failed");
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: mschap xlat failed");
goto make_msg;
}
if ((fr_hex2bin(digest, digest, 16) != vp->length) ||
(rad_digest_cmp(digest, vp->vp_octets, vp->length) != 0)) {
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: NT password check failed");
goto make_msg;
}
goto done;
break;
case PW_LM_PASSWORD:
do_lm:
RDEBUG("Using LM encryption.");
normify(request, vp, 16);
if (vp->length != 16) {
RDEBUG("Configured LM-Password has incorrect length");
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured LM-Password has incorrect length");
goto make_msg;
}
strlcpy(buff2, "%{mschap:LM-Hash %{User-Password}}", sizeof(buff2));
if (!radius_xlat(digest,sizeof(digest),buff2,request,NULL)){
RDEBUG("mschap xlat failed");
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: mschap xlat failed");
goto make_msg;
}
if ((fr_hex2bin(digest, digest, 16) != vp->length) ||
(rad_digest_cmp(digest, vp->vp_octets, vp->length) != 0)) {
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: LM password check failed");
make_msg:
RDEBUG("Passwords don't match");
module_fmsg_vp = pairmake("Module-Failure-Message",
module_fmsg, T_OP_EQ);
pairadd(&request->packet->vps, module_fmsg_vp);
return RLM_MODULE_REJECT;
}
goto done;
break;
case PAP_ENC_NS_MTA_MD5:
do_ns_mta_md5:
RDEBUG("Using NT-MTA-MD5 password");
if (vp->length != 64) {
RDEBUG("Configured NS-MTA-MD5-Password has incorrect length");
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured NS-MTA-MD5-Password has incorrect length");
goto make_msg;
}
if (fr_hex2bin(vp->vp_strvalue, buff, 32) != 16) {
RDEBUG("Configured NS-MTA-MD5-Password has invalid value");
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured NS-MTA-MD5-Password has invalid value");
goto make_msg;
}
if (strlen(request->password->vp_strvalue) >= (sizeof(buff2) - 2 - 2 * 32)) {
RDEBUG("Configured password is too long");
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: password is too long");
goto make_msg;
}
{
char *p = buff2;
memcpy(p, &vp->vp_octets[32], 32);
p += 32;
*(p++) = 89;
strcpy(p, request->password->vp_strvalue);
p += strlen(p);
*(p++) = 247;
memcpy(p, &vp->vp_octets[32], 32);
p += 32;
fr_MD5Init(&md5_context);
fr_MD5Update(&md5_context, (uint8_t *) buff2,
p - buff2);
fr_MD5Final(digest, &md5_context);
}
if (rad_digest_cmp(digest, buff, 16) != 0) {
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: NS-MTA-MD5 password check failed");
goto make_msg;
}
goto done;
default:
break;
}
RDEBUG("No password configured for the user. Cannot do authentication");
return RLM_MODULE_FAIL;
}
module_t rlm_pap = {
RLM_MODULE_INIT,
"PAP",
RLM_TYPE_CHECK_CONFIG_SAFE | RLM_TYPE_HUP_SAFE,
pap_instantiate,
pap_detach,
{
pap_authenticate,
pap_authorize,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
},
};