#include <freeradius-devel/ident.h>
RCSID("$Id$")
#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/modules.h>
#include <sys/stat.h>
#include <ctype.h>
#include <fcntl.h>
#include <limits.h>
struct fastuser_instance {
char *compat_mode;
int hash_reload;
int hashsize;
PAIR_LIST **hashtable;
PAIR_LIST *defaults;
PAIR_LIST *acctusers;
int stats;
char *usersfile;
char *acctusersfile;
time_t next_reload;
time_t lastusersload;
time_t lastacctusersload;
};
static int fallthrough(VALUE_PAIR *vp);
static int fastuser_buildhash(struct fastuser_instance *inst);
static int fastuser_getfile(struct fastuser_instance *inst, const char *filename,
PAIR_LIST **default_list, PAIR_LIST **pair_list,
int isacctfile);
static int fastuser_hash(const char *s, int hashtablesize);
static int fastuser_store(PAIR_LIST **hashtable, PAIR_LIST *entry, int idx);
static PAIR_LIST *fastuser_find(REQUEST *request, PAIR_LIST *user,
const char *username);
static void fastuser_tablestats(PAIR_LIST **hashtable, int size);
static const CONF_PARSER module_config[] = {
{ "usersfile", PW_TYPE_FILENAME,
offsetof(struct fastuser_instance,usersfile), NULL, "${raddbdir}/users_fast" },
{ "acctusersfile", PW_TYPE_FILENAME,
offsetof(struct fastuser_instance,acctusersfile), NULL, "${raddbdir}/acct_users" },
{ "hashsize", PW_TYPE_INTEGER,
offsetof(struct fastuser_instance,hashsize), NULL, "100000" },
{ "stats", PW_TYPE_BOOLEAN,
offsetof(struct fastuser_instance,stats), NULL, "no" },
{ "compat", PW_TYPE_STRING_PTR,
offsetof(struct fastuser_instance,compat_mode), NULL, "cistron" },
{ "hash_reload", PW_TYPE_INTEGER,
offsetof(struct fastuser_instance,hash_reload), NULL, "600" },
{ NULL, -1, 0, NULL, NULL }
};
static int fallthrough(VALUE_PAIR *vp)
{
VALUE_PAIR *tmp;
tmp = pairfind(vp, PW_FALL_THROUGH);
return tmp ? tmp->vp_integer : 0;
}
static int rad_check_return(VALUE_PAIR *list)
{
VALUE_PAIR *authtype;
authtype = pairfind(list, PW_AUTHTYPE);
if((authtype) && authtype->vp_integer == PW_AUTHTYPE_REJECT) {
DEBUG2("rad_check_return: Auth-Type is Reject");
return RLM_MODULE_REJECT;
}
return RLM_MODULE_UPDATED;
}
static int fastuser_buildhash(struct fastuser_instance *inst) {
int memsize=0;
int rcode, hashindex;
PAIR_LIST **newhash=NULL, **oldhash=NULL;
PAIR_LIST *newdefaults=NULL, *newacctusers, *cur=NULL;
PAIR_LIST *olddefaults=NULL, *oldacctusers=NULL;
struct stat statbuf;
int reloadusers = 1;
int reloadacctusers = 1;
memsize = sizeof(PAIR_LIST *) * inst->hashsize;
newhash = (PAIR_LIST **) rad_malloc(memsize);
memset((PAIR_LIST *)newhash, 0, memsize);
if ((stat(inst->acctusersfile, &statbuf) != -1)
&& (statbuf.st_mtime <= inst->lastacctusersload)) {
DEBUG2("rlm_fastusers: File %s was unchanged. Not reloading.",
inst->acctusersfile);
reloadacctusers = 0;
rcode = 0;
}
else
rcode = fastuser_getfile(inst, inst->acctusersfile, NULL, &newacctusers, 1);
if (rcode != 0) {
free(newhash);
radlog(L_ERR|L_CONS, "rlm_fastusers: Errors reading %s", inst->usersfile);
return -1;
}
if ((stat(inst->usersfile, &statbuf) != -1)
&& (statbuf.st_mtime <= inst->lastusersload)) {
DEBUG2("rlm_fastusers: File %s was unchanged. Not reloading.",
inst->usersfile);
reloadusers = 0;
rcode = 0;
free(newhash);
newhash = NULL;
}
else
rcode = fastuser_getfile(inst, inst->usersfile, &newdefaults, newhash, 0);
if (rcode != 0) {
free(newhash);
radlog(L_ERR|L_CONS, "rlm_fastusers: Errors reading %s", inst->usersfile);
return -1;
}
if (reloadusers) {
inst->lastusersload = time(NULL);
oldhash = inst->hashtable;
inst->hashtable = newhash;
olddefaults = inst->defaults;
inst->defaults = newdefaults;
if (oldhash) {
for(hashindex=0; hashindex<inst->hashsize; hashindex++) {
if(oldhash[hashindex]) {
cur = oldhash[hashindex];
pairlist_free(&cur);
}
}
free(oldhash);
}
pairlist_free(&olddefaults);
}
if (reloadacctusers) {
inst->lastacctusersload = time(NULL);
oldacctusers = inst->acctusers;
inst->acctusers = newacctusers;
pairlist_free(&oldacctusers);
}
if(inst->stats)
fastuser_tablestats(inst->hashtable, inst->hashsize);
return 0;
}
static int fastuser_getfile(struct fastuser_instance *inst, const char *filename,
PAIR_LIST **default_list, PAIR_LIST **pair_list,
int isacctfile) {
int rcode;
PAIR_LIST *users = NULL;
PAIR_LIST *entry=NULL, *next=NULL, *cur=NULL, *defaults=NULL, *lastdefault=NULL;
int compat_mode = FALSE;
VALUE_PAIR *vp=NULL;
int hashindex = 0;
int numdefaults = 0, numusers=0;
radlog(L_INFO, " fastusers: Reading %s", filename);
rcode = pairlist_read(filename, &users, 1);
if (rcode < 0) {
return -1;
}
if (strcmp(inst->compat_mode, "cistron") == 0) {
compat_mode = TRUE;
}
entry = users;
while (entry) {
if (compat_mode) {
DEBUG("[%s]:%d Cistron compatibility checks for entry %s ...",
filename, entry->lineno, entry->name);
}
for (vp = entry->check; vp != NULL; vp = vp->next) {
if (vp->operator != T_OP_EQ)
continue;
if (((vp->attribute & ~0xffff) != 0) ||
(vp->attribute < 0x100)) {
if (!compat_mode) {
DEBUG("[%s]:%d WARNING! Changing '%s =' to '%s =='\n\tfor comparing RADIUS attribute in check item list for user %s",
filename, entry->lineno, vp->name, vp->name, entry->name);
} else {
DEBUG("\tChanging '%s =' to '%s =='",
vp->name, vp->name);
}
vp->operator = T_OP_CMP_EQ;
continue;
}
if (compat_mode) {
if ((vp->attribute >= 0x100) &&
(vp->attribute <= 0xffff) &&
(vp->attribute != PW_HINT) &&
(vp->attribute != PW_HUNTGROUP_NAME)) {
DEBUG("\tChanging '%s =' to '%s +='",
vp->name, vp->name);
vp->operator = T_OP_ADD;
} else {
DEBUG("\tChanging '%s =' to '%s =='",
vp->name, vp->name);
vp->operator = T_OP_CMP_EQ;
}
}
}
for (vp = entry->reply; vp != NULL; vp = vp->next) {
if (!(vp->attribute & ~0xffff) &&
(vp->attribute > 0xff) &&
(vp->attribute > 1000)) {
log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
"\tfound in reply item list for user \"%s\".\n"
"\tThis attribute MUST go on the first line"
" with the other check items",
filename, entry->lineno, vp->name,
entry->name);
}
}
next = entry->next;
if(!isacctfile) {
if(strcmp(entry->name, "DEFAULT")==0) {
lastdefault = entry;
numdefaults++;
if(defaults) {
for(cur=defaults; cur->next; cur=cur->next);
cur->next = entry;
entry->next = NULL;
} else {
defaults = entry;
defaults->next = NULL;
}
} else {
numusers++;
hashindex = fastuser_hash(entry->name, inst->hashsize);
entry->lastdefault = lastdefault;
fastuser_store(pair_list, entry, hashindex);
}
}
entry = next;
}
if(!isacctfile && (default_list)) {
*default_list = defaults;
radlog(L_INFO, "rlm_fastusers: Loaded %d users and %d defaults",
numusers, numdefaults);
} else {
*pair_list = users;
}
return 0;
}
int fastuser_hash(const char *s, int hashtablesize) {
unsigned int hash = 0;
while (*s != '\0') {
hash = hash * 7907 + (unsigned char)*s++;
}
return (hash % hashtablesize);
}
static int fastuser_store(PAIR_LIST **hashtable, PAIR_LIST *new, int idx) {
PAIR_LIST *cur;
cur = hashtable[idx];
if(cur) {
while (cur->next != NULL)
cur=cur->next;
cur->next = new;
new->next = NULL;
} else {
new->next = hashtable[idx];
hashtable[idx] = new;
}
return 1;
}
static PAIR_LIST *fastuser_find(REQUEST *request, PAIR_LIST *user,
const char *username)
{
PAIR_LIST *cur=user;
int userfound = 0;
while((cur) && (!userfound)) {
if((strcmp(cur->name, username)==0) &&
paircompare(request, request->packet->vps, cur->check, &request->reply->vps) == 0) {
userfound = 1;
DEBUG2(" fastusers: Matched %s at %d", cur->name, cur->lineno);
} else {
cur = cur->next;
}
}
if(cur) {
return cur;
}
return (PAIR_LIST *)0;
}
static void fastuser_tablestats(PAIR_LIST **hashtable, int size) {
int i, count;
int countarray[256];
int toomany=0;
PAIR_LIST *cur;
memset(countarray, 0, sizeof(countarray));
for(i=0; i<size; i++) {
count = 0;
for(cur=hashtable[i]; cur; cur=cur->next) {
count++;
}
if(count<256) {
countarray[count]++;
} else {
toomany++;
}
}
for(i=0; i<256; i++)
if(countarray[i]) {
radlog(L_INFO, "rlm_fastusers: Hash buckets with %d users: %d",
i, countarray[i]);
}
if(toomany) {
radlog(L_INFO, "rlm_fastusers: Hash buckets with more than 256: %d",
toomany);
}
}
static int fastuser_instantiate(CONF_SECTION *conf, void **instance)
{
struct fastuser_instance *inst=0;
inst = 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;
}
inst->next_reload = time(NULL) + inst->hash_reload;
inst->hashtable = NULL;
inst->lastusersload = 0;
inst->lastacctusersload = 0;
if(fastuser_buildhash(inst) < 0) {
radlog(L_ERR, "rlm_fastusers: error building user hash. aborting");
return -1;
}
*instance = inst;
return 0;
}
static int fastuser_authorize(void *instance, REQUEST *request)
{
VALUE_PAIR *namepair;
VALUE_PAIR *check_tmp;
VALUE_PAIR *reply_tmp;
PAIR_LIST *user;
PAIR_LIST *curdefault;
const char *name;
int userfound=0;
int defaultfound=0;
int hashidx=0;
struct fastuser_instance *inst = instance;
if((inst->hash_reload) && (request->timestamp > inst->next_reload)) {
inst->next_reload = request->timestamp + inst->hash_reload;
radlog(L_INFO, "rlm_fastusers: Reloading fastusers hash");
if(fastuser_buildhash(inst) < 0) {
radlog(L_ERR, "rlm_fastusers: error building user hash. aborting");
return RLM_MODULE_FAIL;
}
}
namepair = request->username;
name = namepair ? (char *) namepair->vp_strvalue : "NONE";
hashidx = fastuser_hash(name, inst->hashsize);
user = inst->hashtable[hashidx];
if((user=fastuser_find(request, user, name))!=NULL) {
userfound = 1;
}
if((user) && (userfound) && (user->lastdefault == NULL)) {
DEBUG2("rlm_fastusers: user found before DEFAULT");
check_tmp = paircopy(user->check);
pairmove(&request->config_items, &check_tmp);
pairfree(&check_tmp);
reply_tmp = paircopy(user->reply);
pairmove(&request->reply->vps, &reply_tmp);
pairfree(&reply_tmp);
if(!fallthrough(user->reply)) {
pairdelete(&request->reply->vps, PW_FALL_THROUGH);
return(rad_check_return(user->check));
} else {
user=user->next;
user=fastuser_find(request, user, name);
}
}
DEBUG2("rlm_fastusers: checking defaults");
curdefault = inst->defaults;
while(curdefault) {
if(paircompare(request, request->packet->vps, curdefault->check,
&request->reply->vps) == 0) {
DEBUG2(" fastusers: Matched %s at %d",
curdefault->name, curdefault->lineno);
defaultfound = 1;
check_tmp = paircopy(curdefault->check);
pairmove(&request->config_items, &check_tmp);
pairfree(&check_tmp);
reply_tmp = paircopy(curdefault->reply);
pairmove(&request->reply->vps, &reply_tmp);
pairfree(&reply_tmp);
if (!fallthrough(curdefault->reply))
break;
}
while((userfound && (user) && (curdefault == user->lastdefault))) {
DEBUG2(" fastusers: found lastdefault at line %d",
curdefault->lineno);
check_tmp = paircopy(user->check);
pairmove(&request->config_items, &check_tmp);
pairfree(&check_tmp);
reply_tmp = paircopy(user->reply);
pairmove(&request->reply->vps, &reply_tmp);
pairfree(&reply_tmp);
if(!fallthrough(user->reply)) {
pairdelete(&request->reply->vps, PW_FALL_THROUGH);
return(rad_check_return(user->check));
}
user=user->next;
user=fastuser_find(request, user, name);
}
curdefault = curdefault->next;
}
if(userfound || defaultfound) {
pairdelete(&request->reply->vps, PW_FALL_THROUGH);
return(rad_check_return(request->config_items));
} else {
DEBUG2("rlm_fastusers: user not found");
return RLM_MODULE_NOTFOUND;
}
}
static int fastuser_authenticate(void *instance, REQUEST *request)
{
instance = instance;
request = request;
return RLM_MODULE_OK;
}
static int fastuser_preacct(void *instance, REQUEST *request)
{
VALUE_PAIR *namepair;
const char *name;
VALUE_PAIR *request_pairs;
VALUE_PAIR **config_pairs;
VALUE_PAIR *reply_pairs = NULL;
VALUE_PAIR *check_tmp;
VALUE_PAIR *reply_tmp;
PAIR_LIST *pl = NULL;
int found = 0;
struct fastuser_instance *inst = instance;
namepair = request->username;
name = namepair ? (char *) namepair->vp_strvalue : "NONE";
request_pairs = request->packet->vps;
config_pairs = &request->config_items;
for (pl = inst->acctusers; pl; pl = pl->next) {
if (strcmp(name, pl->name) && strcmp(pl->name, "DEFAULT"))
continue;
if (paircompare(request, request_pairs, pl->check, &reply_pairs) == 0) {
DEBUG2(" acct_users: Matched %s at %d",
pl->name, pl->lineno);
found = 1;
check_tmp = paircopy(pl->check);
reply_tmp = paircopy(pl->reply);
pairmove(&reply_pairs, &reply_tmp);
pairmove(config_pairs, &check_tmp);
pairfree(&reply_tmp);
pairfree(&check_tmp);
if (!fallthrough(pl->reply))
break;
}
}
if (!found)
return RLM_MODULE_NOOP;
pairfree(&reply_pairs);
return RLM_MODULE_OK;
}
static int fastuser_detach(void *instance)
{
struct fastuser_instance *inst = instance;
int hashindex;
PAIR_LIST *cur;
for(hashindex=0; hashindex<inst->hashsize; hashindex++) {
if(inst->hashtable[hashindex]) {
cur = inst->hashtable[hashindex];
pairlist_free(&cur);
}
}
free(inst->hashtable);
pairlist_free(&inst->defaults);
pairlist_free(&inst->acctusers);
return 0;
}
static int fastuser_accounting(void *instance UNUSED, REQUEST *request UNUSED)
{
return RLM_MODULE_FAIL;
}
module_t rlm_fastusers = {
RLM_MODULE_INIT,
"fastusers",
0,
fastuser_instantiate,
fastuser_detach,
{
fastuser_authenticate,
fastuser_authorize,
fastuser_preacct,
fastuser_accounting,
NULL,
NULL,
NULL,
NULL
},
};