#include <freeradius-devel/ident.h>
RCSID("$Id$")
#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/modules.h>
#include <ctype.h>
#include "config.h"
#include <gdbm.h>
#include <time.h>
#ifdef NEEDS_GDBM_SYNC
# define GDBM_SYNCOPT GDBM_SYNC
#else
# define GDBM_SYNCOPT 0
#endif
#ifdef GDBM_NOLOCK
#define GDBM_COUNTER_OPTS (GDBM_SYNCOPT | GDBM_NOLOCK)
#else
#define GDBM_COUNTER_OPTS (GDBM_SYNCOPT)
#endif
#ifndef HAVE_GDBM_FDESC
#define gdbm_fdesc(foo) (-1)
#endif
#define UNIQUEID_MAX_LEN 32
typedef struct rlm_caching_t {
char *filename;
char *key;
char *post_auth;
char *cache_ttl_str;
int cache_ttl;
int hit_ratio;
int cache_rejects;
int cache_size;
uint32_t cache_queries;
uint32_t cache_hits;
GDBM_FILE gdbm;
#ifdef HAVE_PTHREAD_H
pthread_mutex_t mutex;
#endif
} rlm_caching_t;
#define MAX_RECORD_LEN 750
#define MAX_AUTH_TYPE 32
#define show_hit_ratio \
if (data->hit_ratio && (data->cache_queries % data->hit_ratio) == 0) \
radlog(L_INFO, "rlm_caching: Cache Queries: %7d, Cache Hits: %7d, Hit Ratio: %.2f%%", \
data->cache_queries,data->cache_hits,hit_ratio)
typedef struct rlm_caching_data {
time_t creation;
char data[MAX_RECORD_LEN];
char auth_type[MAX_AUTH_TYPE];
int len;
} rlm_caching_data;
#ifndef HAVE_PTHREAD_H
#define pthread_mutex_lock(a)
#define pthread_mutex_unlock(a)
#define pthread_mutex_init(a,b)
#define pthread_mutex_destroy(a)
#endif
static const CONF_PARSER module_config[] = {
{ "filename", PW_TYPE_STRING_PTR, offsetof(rlm_caching_t,filename), NULL, NULL },
{ "key", PW_TYPE_STRING_PTR, offsetof(rlm_caching_t,key), NULL, "%{Acct-Unique-Session-Id}" },
{ "post-auth", PW_TYPE_STRING_PTR, offsetof(rlm_caching_t,post_auth), NULL, NULL },
{ "cache-ttl", PW_TYPE_STRING_PTR, offsetof(rlm_caching_t,cache_ttl_str), NULL, "1d" },
{ "cache-size", PW_TYPE_INTEGER, offsetof(rlm_caching_t,cache_size), NULL, "1000" },
{ "hit-ratio", PW_TYPE_INTEGER, offsetof(rlm_caching_t,hit_ratio), NULL, "0" },
{ "cache-rejects", PW_TYPE_BOOLEAN, offsetof(rlm_caching_t,cache_rejects), NULL, "yes" },
{ NULL, -1, 0, NULL, NULL }
};
static int caching_detach(void *instance);
static int find_ttl(char *ttl)
{
unsigned len = 0;
char last = 's';
if (isdigit((int) ttl[0])){
len = strlen(ttl);
if (len == 0)
return -1;
last = ttl[len - 1];
if (!isalpha((int) last))
last = 's';
len = atoi(ttl);
DEBUG("rlm_caching::find_ttl: num=%d, last=%c",len,last);
}
switch (last){
case 's':
default:
break;
case 'm':
len *= 60;
break;
case 'h':
len *= 3600;
break;
case 'd':
len *= 86400;
break;
case 'w':
len *= 604800;
break;
}
DEBUG("rlm_caching::find_ttl: Returning '%d'",len);
return len;
}
static int caching_instantiate(CONF_SECTION *conf, void **instance)
{
rlm_caching_t *data;
int cache_size;
data = rad_malloc(sizeof(*data));
if (!data) {
radlog(L_ERR, "rlm_caching: rad_malloc() failed.");
return -1;
}
memset(data, 0, sizeof(*data));
if (cf_section_parse(conf, data, module_config) < 0) {
free(data);
return -1;
}
cache_size = data->cache_size;
if (data->key == NULL) {
radlog(L_ERR, "rlm_caching: 'key' must be set.");
caching_detach(data);
return -1;
}
if (data->cache_ttl_str == NULL) {
radlog(L_ERR, "rlm_caching: 'cache-ttl' must be set.");
caching_detach(data);
return -1;
}
else {
data->cache_ttl = find_ttl(data->cache_ttl_str);
if (data->cache_ttl == 0) {
radlog(L_ERR, "rlm_caching: 'cache-ttl' is invalid.");
caching_detach(data);
return -1;
}
}
if (data->filename == NULL) {
radlog(L_ERR, "rlm_caching: 'filename' must be set.");
caching_detach(data);
return -1;
}
data->gdbm = gdbm_open(data->filename, sizeof(int),
GDBM_WRCREAT | GDBM_COUNTER_OPTS, 0600, NULL);
if (data->gdbm == NULL) {
radlog(L_ERR, "rlm_caching: Failed to open file %s: %s",
data->filename, strerror(errno));
caching_detach(data);
return -1;
}
if (gdbm_setopt(data->gdbm, GDBM_CACHESIZE, &cache_size, sizeof(int)) == -1)
radlog(L_ERR, "rlm_caching: Failed to set cache size");
pthread_mutex_init(&data->mutex, NULL);
*instance = data;
return 0;
}
static int caching_postauth(void *instance, REQUEST *request)
{
rlm_caching_t *data = (rlm_caching_t *)instance;
char key[MAX_STRING_LEN];
datum key_datum;
datum data_datum;
VALUE_PAIR *reply_vp;
VALUE_PAIR *auth_type;
rlm_caching_data cache_data;
int count = 0;
int ret = 0;
int size = 0;
int rcode = 0;
if (pairfind(request->packet->vps, PW_CACHE_NO_CACHING) != NULL){
DEBUG("rlm_caching: Cache-No-Caching is set. Returning NOOP");
return RLM_MODULE_NOOP;
}
if ((auth_type = pairfind(request->config_items, PW_AUTH_TYPE)) != NULL){
DEBUG("rlm_caching: Found Auth-Type, value: '%s'",auth_type->vp_strvalue);
if (strcmp(auth_type->vp_strvalue,"Reject") == 0 && data->cache_rejects == 0){
DEBUG("rlm_caching: No caching of Rejects. Returning NOOP");
return RLM_MODULE_NOOP;
}
if (strlen(auth_type->vp_strvalue) > MAX_AUTH_TYPE - 1){
DEBUG("rlm_caching: Auth-Type value too large");
return RLM_MODULE_NOOP;
}
}
else{
DEBUG("rlm_caching: No Auth-Type found. Returning NOOP");
return RLM_MODULE_NOOP;
}
reply_vp = request->reply->vps;
if (reply_vp == NULL) {
DEBUG("rlm_caching: The Request does not contain any reply attributes");
return RLM_MODULE_NOOP;
}
if (!radius_xlat(key,sizeof(key), data->key, request, NULL)){
radlog(L_ERR, "rlm_caching: xlat on key '%s' failed.",data->key);
return RLM_MODULE_FAIL;
}
memset(&cache_data,0,sizeof(rlm_caching_data));
cache_data.creation = time(NULL);
strcpy(cache_data.auth_type,auth_type->vp_strvalue);
size = MAX_RECORD_LEN;
while(reply_vp) {
if (size <= 1){
DEBUG("rlm_caching: Not enough space.");
return RLM_MODULE_NOOP;
}
ret = vp_prints(cache_data.data + count,size,reply_vp);
if (ret == 0) {
DEBUG("rlm_caching: Record is too large, will not store it.");
return RLM_MODULE_NOOP;
}
count += (ret + 1);
size -= (ret + 1);
DEBUG("rlm_caching: VP=%s,VALUE=%s,length=%d,cache record length=%d, space left=%d",
reply_vp->name,reply_vp->vp_strvalue,ret,count,size);
reply_vp = reply_vp->next;
}
cache_data.len = count;
DEBUG("rlm_caching: Storing cache for Key='%s'",key);
data_datum.dptr = (rlm_caching_data *) &cache_data;
data_datum.dsize = sizeof(rlm_caching_data);
key_datum.dptr = (char *) key;
key_datum.dsize = strlen(key);
pthread_mutex_lock(&data->mutex);
rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
pthread_mutex_unlock(&data->mutex);
if (rcode < 0) {
radlog(L_ERR, "rlm_caching: Failed storing data to %s: %s",
data->filename, gdbm_strerror(gdbm_errno));
return RLM_MODULE_FAIL;
}
DEBUG("rlm_caching: New value stored successfully.");
return RLM_MODULE_OK;
}
static int caching_authorize(void *instance, REQUEST *request)
{
rlm_caching_t *data = (rlm_caching_t *) instance;
char key[MAX_STRING_LEN];
datum key_datum;
datum data_datum;
rlm_caching_data cache_data;
VALUE_PAIR *reply_item;
VALUE_PAIR *item;
char *tmp;
int len = 0;
int delete_cache = 0;
float hit_ratio = 0.0;
instance = instance;
request = request;
if (pairfind(request->packet->vps, PW_CACHE_NO_CACHING) != NULL){
DEBUG("rlm_caching: Cache-No-Caching is set. Returning NOOP");
return RLM_MODULE_NOOP;
}
if (pairfind(request->packet->vps, PW_CACHE_DELETE_CACHE) != NULL){
DEBUG("rlm_caching: Found Cache-Delete-Cache. Will delete record if found");
delete_cache = 1;
}
if (!radius_xlat(key,sizeof(key), data->key, request, NULL)){
radlog(L_ERR, "rlm_caching: xlat on key '%s' failed.",data->key);
return RLM_MODULE_FAIL;
}
key_datum.dptr = key;
key_datum.dsize = strlen(key);
DEBUG("rlm_caching: Searching the database for key '%s'",key);
pthread_mutex_lock(&data->mutex);
data_datum = gdbm_fetch(data->gdbm, key_datum);
pthread_mutex_unlock(&data->mutex);
data->cache_queries++;
if (data_datum.dptr != NULL){
DEBUG("rlm_caching: Key Found.");
data->cache_hits++;
hit_ratio = (float)data->cache_hits / data->cache_queries;
hit_ratio *= 100.0;
memcpy(&cache_data, data_datum.dptr, sizeof(rlm_caching_data));
free(data_datum.dptr);
if (delete_cache == 0 && cache_data.creation + data->cache_ttl <= time(NULL)){
DEBUG("rlm_caching: Cache entry has expired");
DEBUG("rlm_caching: Cache Queries: %7d, Cache Hits: %7d, Hit Ratio: %.2f%%",
data->cache_queries,data->cache_hits,hit_ratio);
show_hit_ratio;
delete_cache = 1;
}
if (delete_cache){
DEBUG("rlm_caching: Deleting record");
pthread_mutex_lock(&data->mutex);
gdbm_delete(data->gdbm, key_datum);
pthread_mutex_unlock(&data->mutex);
return RLM_MODULE_NOOP;
}
tmp = cache_data.data;
if (tmp){
pairfree(&request->reply->vps);
while(tmp && len < cache_data.len){
reply_item = NULL;
if (userparse(tmp, &reply_item) > 0 && reply_item != NULL)
pairadd(&request->reply->vps, reply_item);
len += (strlen(tmp) + 1);
DEBUG("rlm_caching: VP='%s',VALUE='%s',lenth='%d',cache record length='%d'",
reply_item->name,reply_item->vp_strvalue,reply_item->length,len);
tmp = cache_data.data + len;
}
}
else{
DEBUG("rlm_caching: No reply items found. Returning NOOP");
return RLM_MODULE_NOOP;
}
if (cache_data.auth_type){
DEBUG("rlm_caching: Adding Auth-Type '%s'",cache_data.auth_type);
if ((item = pairfind(request->config_items, PW_AUTH_TYPE)) == NULL){
item = pairmake("Auth-Type", cache_data.auth_type, T_OP_SET);
pairadd(&request->config_items, item);
}
else{
strcmp(item->vp_strvalue, cache_data.auth_type);
item->length = strlen(cache_data.auth_type);
}
}
if (data->post_auth){
DEBUG("rlm_caching: Adding Post-Auth-Type '%s'",data->post_auth);
if ((item = pairfind(request->config_items, PW_POST_AUTH_TYPE)) == NULL){
item = pairmake("Post-Auth-Type", data->post_auth, T_OP_SET);
pairadd(&request->config_items, item);
}
else{
strcmp(item->vp_strvalue, data->post_auth);
item->length = strlen(data->post_auth);
}
}
item = pairmake("Cache-No-Caching", "YES", T_OP_EQ);
pairadd(&request->packet->vps, item);
DEBUG("rlm_caching: Cache Queries: %7d, Cache Hits: %7d, Hit Ratio: %.2f%%",
data->cache_queries,data->cache_hits,hit_ratio);
show_hit_ratio;
return RLM_MODULE_OK;
}
else{
DEBUG("rlm_caching: Could not find the requested key in the database.");
DEBUG("rlm_caching: Cache Queries: %7d, Cache Hits: %7d, Hit Ratio: %.2f%%",
data->cache_queries,data->cache_hits,hit_ratio);
show_hit_ratio;
}
return RLM_MODULE_NOOP;
}
static int caching_detach(void *instance)
{
rlm_caching_t *data = (rlm_caching_t *) instance;
if (data->gdbm)
gdbm_close(data->gdbm);
pthread_mutex_destroy(&data->mutex);
free(instance);
return 0;
}
module_t rlm_caching = {
RLM_MODULE_INIT,
"Caching",
RLM_TYPE_THREAD_SAFE,
caching_instantiate,
caching_detach,
{
NULL,
caching_authorize,
NULL,
NULL,
NULL,
NULL,
NULL,
caching_postauth
},
};