#include "autoconf.h"
#include "libradius.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "radiusd.h"
#include "modules.h"
#include "conffile.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
static const char rcsid[] = "$Id: rlm_pap.c,v 1.15.2.2.2.5 2007/04/08 06:31:38 aland Exp $";
typedef struct rlm_pap_t {
const char *name;
char *scheme;
int sch;
char norm_passwd;
int auto_header;
} rlm_pap_t;
static CONF_PARSER module_config[] = {
{ "encryption_scheme", PW_TYPE_STRING_PTR, offsetof(rlm_pap_t,scheme), NULL, "crypt" },
{ "auto_header", PW_TYPE_BOOLEAN, offsetof(rlm_pap_t,auto_header), NULL, "no" },
{ NULL, -1, 0, NULL, NULL }
};
static const LRAD_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 LRAD_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 },
{ "{x-nthash}", PW_NT_PASSWORD },
{ "{ns-mta-md5}", PW_NS_MTA_MD5_PASSWORD },
{ NULL, 0 }
};
static int pap_detach(void *instance)
{
rlm_pap_t *inst = (rlm_pap_t *) instance;
free((char *)inst->scheme);
free(inst);
return 0;
}
static int pap_instantiate(CONF_SECTION *conf, void **instance)
{
rlm_pap_t *inst;
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 = lrad_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;
}
*instance = inst;
inst->name = cf_section_name2(conf);
if (!inst->name) {
inst->name = cf_section_name1(conf);
}
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); x >>= 8;
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(VALUE_PAIR *vp, int min_length)
{
int decoded;
char buffer[64];
if ((size_t) min_length >= sizeof(buffer)) return;
if (vp->length >= (2 * min_length)) {
decoded = lrad_hex2bin(vp->strvalue, buffer, vp->length >> 1);
if (decoded == (vp->length >> 1)) {
DEBUG2("rlm_pap: Normalizing %s from hex encoding", vp->name);
memcpy(vp->strvalue, buffer, decoded);
vp->length = decoded;
return;
}
}
if ((vp->length * 3) >= ((min_length * 4))) {
decoded = base64_decode(vp->strvalue, buffer);
if (decoded >= min_length) {
DEBUG2("rlm_pap: Normalizing %s from base64 encoding", vp->name);
memcpy(vp->strvalue, 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;
int user_pw = FALSE;
VALUE_PAIR *vp, *next;
VALUE_PAIR *cleartext_pw = NULL;
for (vp = request->config_items; vp != NULL; vp = next) {
next = vp->next;
switch (vp->attribute) {
case PW_USER_PASSWORD:
user_pw = TRUE;
found_pw = TRUE;
if (!inst->auto_header ||
(vp->strvalue[0] != '{')) {
break;
}
case PW_PASSWORD_WITH_HEADER:
{
int attr;
uint8_t *p, *q;
char buffer[64];
VALUE_PAIR *new_vp;
found_pw = TRUE;
q = vp->strvalue;
p = strchr(q + 1, '}');
if (!p) {
break;
}
if ((size_t) (p - q) > sizeof(buffer)) break;
memcpy(buffer, q, p - q + 1);
buffer[p - q + 1] = '\0';
attr = lrad_str2int(header_names, buffer, 0);
if (!attr) {
DEBUG2("rlm_pap: Found unknown header {%s}: Not doing anything", buffer);
break;
}
new_vp = paircreate(attr, PW_TYPE_STRING);
if (!new_vp) break;
strcpy(new_vp->strvalue, p + 1);
new_vp->length = strlen(new_vp->strvalue);
pairadd(&request->config_items, new_vp);
pairdelete(&request->config_items, PW_USER_PASSWORD);
}
break;
case PW_CLEARTEXT_PASSWORD:
cleartext_pw = vp;
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(vp, 16);
found_pw = TRUE;
break;
case PW_SHA_PASSWORD:
case PW_SSHA_PASSWORD:
normify(vp, 20);
found_pw = TRUE;
break;
case PW_PROXY_TO_REALM:
{
REALM *realm = realm_find(vp->strvalue, 0);
if (realm &&
(realm->ipaddr != htonl(INADDR_NONE))) {
return RLM_MODULE_NOOP;
}
break;
}
case PW_AUTH_TYPE:
auth_type = TRUE;
if ((vp->lvalue == 254) ||
(vp->lvalue == 4)) {
found_pw = 1;
}
break;
default:
break;
}
}
if (!found_pw) {
DEBUG("rlm_pap: WARNING! No \"known good\" password found for the user. Authentication may fail because of this.");
return RLM_MODULE_NOOP;
}
if (cleartext_pw && !user_pw) {
vp = paircreate(PW_USER_PASSWORD, PW_TYPE_STRING);
if (!vp) return RLM_MODULE_FAIL;
memcpy(vp, cleartext_pw, sizeof(*vp));
vp->next = NULL;
pairadd(&request->config_items, vp);
}
if (auth_type) {
DEBUG2("rlm_pap: Found existing Auth-Type, not changing it.");
return RLM_MODULE_NOOP;
}
if (!request->password ||
(request->password->attribute != PW_USER_PASSWORD)) {
if (request->packet->code == PW_ACCESS_CHALLENGE) {
return RLM_MODULE_NOOP;
}
DEBUG2("rlm_pap: No clear-text password in the request. Not performing PAP.");
return RLM_MODULE_NOOP;
}
vp = paircreate(PW_AUTH_TYPE, PW_TYPE_INTEGER);
if (!vp) return RLM_MODULE_FAIL;
pairparsevalue(vp, inst->name);
pairadd(&request->config_items, vp);
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];
MD5_CTX md5_context;
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){
radlog(L_AUTH, "rlm_pap: Attribute \"Password\" is required for authentication.");
return RLM_MODULE_INVALID;
}
if (request->password->attribute != PW_USER_PASSWORD) {
radlog(L_AUTH, "rlm_pap: Attribute \"User-Password\" is required for authentication. Cannot use \"%s\".", request->password->name);
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;
}
DEBUG("rlm_pap: login attempt with password %s",
request->password->strvalue);
if ((inst->sch == PAP_ENC_AUTO) || inst->auto_header) {
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:
DEBUG("rlm_pap: 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;
}
scheme = inst->sch;
}
switch (scheme) {
case PAP_ENC_CLEAR:
do_clear:
DEBUG("rlm_pap: Using clear text password \"%s\".", vp->strvalue);
if (strcmp((char *) vp->strvalue,
(char *) request->password->strvalue) != 0){
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: CLEAR TEXT password check failed");
goto make_msg;
}
done:
DEBUG("rlm_pap: User authenticated successfully");
return RLM_MODULE_OK;
break;
case PAP_ENC_CRYPT:
do_crypt:
DEBUG("rlm_pap: Using CRYPT encryption.");
if (lrad_crypt_check((char *) request->password->strvalue,
(char *) 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:
DEBUG("rlm_pap: Using MD5 encryption.");
normify(vp, 16);
if (vp->length != 16) {
DEBUG("rlm_pap: Configured MD5 password has incorrect length");
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured MD5 password has incorrect length");
goto make_msg;
}
MD5Init(&md5_context);
MD5Update(&md5_context, request->password->strvalue,
request->password->length);
MD5Final(digest, &md5_context);
if (memcmp(digest, vp->strvalue, 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:
DEBUG("rlm_pap: Using SMD5 encryption.");
normify(vp, 16);
if (vp->length <= 16) {
DEBUG("rlm_pap: Configured SMD5 password has incorrect length");
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured SMD5 password has incorrect length");
goto make_msg;
}
MD5Init(&md5_context);
MD5Update(&md5_context, request->password->strvalue,
request->password->length);
MD5Update(&md5_context, &vp->strvalue[16], vp->length - 16);
MD5Final(digest, &md5_context);
if (memcmp(digest, vp->strvalue, 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:
DEBUG("rlm_pap: Using SHA1 encryption.");
normify(vp, 20);
if (vp->length != 20) {
DEBUG("rlm_pap: Configured SHA1 password has incorrect length");
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured SHA1 password has incorrect length");
goto make_msg;
}
SHA1Init(&sha1_context);
SHA1Update(&sha1_context, request->password->strvalue,
request->password->length);
SHA1Final(digest,&sha1_context);
if (memcmp(digest, vp->strvalue, 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:
DEBUG("rlm_pap: Using SSHA encryption.");
normify(vp, 20);
if (vp->length <= 20) {
DEBUG("rlm_pap: Configured SSHA password has incorrect length");
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured SHA password has incorrect length");
goto make_msg;
}
SHA1Init(&sha1_context);
SHA1Update(&sha1_context, request->password->strvalue,
request->password->length);
SHA1Update(&sha1_context, &vp->strvalue[20], vp->length - 20);
SHA1Final(digest,&sha1_context);
if (memcmp(digest, vp->strvalue, 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:
DEBUG("rlm_pap: Using NT encryption.");
normify(vp, 16);
if (vp->length != 16) {
DEBUG("rlm_pap: Configured NT-Password has incorrect length");
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured NT-Password has incorrect length");
goto make_msg;
}
sprintf(buff2,"%%{mschap:NT-Hash %s}",
request->password->strvalue);
if (!radius_xlat(digest,sizeof(digest),buff2,request,NULL)){
DEBUG("rlm_pap: mschap xlat failed");
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: mschap xlat failed");
goto make_msg;
}
if ((lrad_hex2bin(digest, digest, 16) != vp->length) ||
(memcmp(digest, vp->strvalue, 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:
DEBUG("rlm_pap: Using LM encryption.");
normify(vp, 16);
if (vp->length != 16) {
DEBUG("rlm_pap: Configured LM-Password has incorrect length");
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured LM-Password has incorrect length");
goto make_msg;
}
sprintf(buff2,"%%{mschap:LM-Hash %s}",
request->password->strvalue);
if (!radius_xlat(digest,sizeof(digest),buff2,request,NULL)){
DEBUG("rlm_pap: mschap xlat failed");
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: mschap xlat failed");
goto make_msg;
}
if ((lrad_hex2bin(digest, digest, 16) != vp->length) ||
(memcmp(digest, vp->strvalue, vp->length) != 0)) {
snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: LM password check failed");
make_msg:
DEBUG("rlm_pap: 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:
DEBUG("rlm_pap: Using NT-MTA-MD5 password");
if (vp->length != 64) {
DEBUG("rlm_pap: 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 (lrad_hex2bin(vp->strvalue, buff, 32) != 16) {
DEBUG("rlm_pap: 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->strvalue) >= (sizeof(buff2) - 2 - 2 * 32)) {
DEBUG("rlm_pap: 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->strvalue[32], 32);
p += 32;
*(p++) = 89;
strcpy(p, request->password->strvalue);
p += strlen(p);
*(p++) = 247;
memcpy(p, &vp->strvalue[32], 32);
p += 32;
MD5Init(&md5_context);
MD5Update(&md5_context, buff2, p - buff2);
MD5Final(digest, &md5_context);
}
if (memcmp(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;
}
DEBUG("rlm_pap: No password configured for the user. Cannot do authentication");
return RLM_MODULE_FAIL;
}
module_t rlm_pap = {
"PAP",
0,
NULL,
pap_instantiate,
{
pap_authenticate,
pap_authorize,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
},
pap_detach,
NULL,
};