#include <freeradius-devel/ident.h>
RCSID("$Id$")
#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/modules.h>
#include "config.h"
#include <ctype.h>
#include "../../include/md5.h"
#include <gdbm.h>
#ifdef NEEDS_GDBM_SYNC
# define GDBM_SYNCOPT GDBM_SYNC
#else
# define GDBM_SYNCOPT 0
#endif
#ifdef GDBM_NOLOCK
#define GDBM_IPPOOL_OPTS (GDBM_SYNCOPT | GDBM_NOLOCK)
#else
#define GDBM_IPPOOL_OPTS (GDBM_SYNCOPT)
#endif
#define MAX_NAS_NAME_SIZE 64
typedef struct rlm_ippool_t {
char *session_db;
char *ip_index;
char *name;
char *key;
uint32_t range_start;
uint32_t range_stop;
uint32_t netmask;
time_t max_timeout;
int cache_size;
int override;
GDBM_FILE gdbm;
GDBM_FILE ip;
#ifdef HAVE_PTHREAD_H
pthread_mutex_t op_mutex;
#endif
} rlm_ippool_t;
#ifndef HAVE_PTHREAD_H
#define pthread_mutex_init(_x, _y)
#define pthread_mutex_destroy(_x)
#define pthread_mutex_lock(_x)
#define pthread_mutex_unlock(_x)
#endif
typedef struct ippool_info {
uint32_t ipaddr;
char active;
char cli[32];
char extra;
time_t timestamp;
time_t timeout;
} ippool_info;
typedef struct ippool_key {
char key[16];
} ippool_key;
static const CONF_PARSER module_config[] = {
{ "session-db", PW_TYPE_STRING_PTR, offsetof(rlm_ippool_t,session_db), NULL, NULL },
{ "ip-index", PW_TYPE_STRING_PTR, offsetof(rlm_ippool_t,ip_index), NULL, NULL },
{ "key", PW_TYPE_STRING_PTR, offsetof(rlm_ippool_t,key), NULL, "%{NAS-IP-Address} %{NAS-Port}" },
{ "range-start", PW_TYPE_IPADDR, offsetof(rlm_ippool_t,range_start), NULL, "0" },
{ "range-stop", PW_TYPE_IPADDR, offsetof(rlm_ippool_t,range_stop), NULL, "0" },
{ "netmask", PW_TYPE_IPADDR, offsetof(rlm_ippool_t,netmask), NULL, "0" },
{ "cache-size", PW_TYPE_INTEGER, offsetof(rlm_ippool_t,cache_size), NULL, "1000" },
{ "override", PW_TYPE_BOOLEAN, offsetof(rlm_ippool_t,override), NULL, "no" },
{ "maximum-timeout", PW_TYPE_INTEGER, offsetof(rlm_ippool_t,max_timeout), NULL, "0" },
{ NULL, -1, 0, NULL, NULL }
};
static int ippool_instantiate(CONF_SECTION *conf, void **instance)
{
rlm_ippool_t *data;
int cache_size;
ippool_info entry;
ippool_key key;
datum key_datum;
datum data_datum;
const char *cli = "0";
const char *pool_name = NULL;
data = rad_malloc(sizeof(*data));
if (!data) {
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->session_db == NULL) {
radlog(L_ERR, "rlm_ippool: 'session-db' must be set.");
free(data);
return -1;
}
if (data->ip_index == NULL) {
radlog(L_ERR, "rlm_ippool: 'ip-index' must be set.");
free(data);
return -1;
}
data->range_start = htonl(data->range_start);
data->range_stop = htonl(data->range_stop);
data->netmask = htonl(data->netmask);
if (data->range_start == 0 || data->range_stop == 0 || \
data->range_start >= data->range_stop ) {
radlog(L_ERR, "rlm_ippool: Invalid configuration data given.");
free(data);
return -1;
}
data->gdbm = gdbm_open(data->session_db, sizeof(int),
GDBM_WRCREAT | GDBM_IPPOOL_OPTS, 0600, NULL);
if (data->gdbm == NULL) {
radlog(L_ERR, "rlm_ippool: Failed to open file %s: %s",
data->session_db, strerror(errno));
return -1;
}
data->ip = gdbm_open(data->ip_index, sizeof(int),
GDBM_WRCREAT | GDBM_IPPOOL_OPTS, 0600, NULL);
if (data->ip == NULL) {
radlog(L_ERR, "rlm_ippool: Failed to open file %s: %s",
data->ip_index, strerror(errno));
return -1;
}
if (gdbm_setopt(data->gdbm, GDBM_CACHESIZE, &cache_size, sizeof(int)) == -1)
radlog(L_ERR, "rlm_ippool: Failed to set cache size");
if (gdbm_setopt(data->ip, GDBM_CACHESIZE, &cache_size, sizeof(int)) == -1)
radlog(L_ERR, "rlm_ippool: Failed to set cache size");
key_datum = gdbm_firstkey(data->gdbm);
if (key_datum.dptr == NULL){
int rcode;
uint32_t i, j;
uint32_t or_result;
char str[32];
char init_str[17];
DEBUG("rlm_ippool: Initializing database");
for(i=data->range_start,j=~0;i<=data->range_stop;i++,j--){
or_result = i | data->netmask;
if (~data->netmask != 0 &&
(or_result == data->netmask ||
(~or_result == 0))) {
DEBUG("rlm_ippool: IP %s excluded",
ip_ntoa(str, ntohl(i)));
continue;
}
sprintf(init_str,"%016d",j);
DEBUG("rlm_ippool: Initialized bucket: %s",init_str);
memcpy(key.key, init_str,16);
key_datum.dptr = (char *) &key;
key_datum.dsize = sizeof(ippool_key);
entry.ipaddr = ntohl(i);
entry.active = 0;
entry.extra = 0;
entry.timestamp = 0;
entry.timeout = 0;
strcpy(entry.cli,cli);
data_datum.dptr = (char *) &entry;
data_datum.dsize = sizeof(ippool_info);
rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
if (rcode < 0) {
radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
data->session_db, gdbm_strerror(gdbm_errno));
gdbm_close(data->gdbm);
gdbm_close(data->ip);
free(data);
return -1;
}
}
}
else
free(key_datum.dptr);
data->name = NULL;
pool_name = cf_section_name2(conf);
if (pool_name != NULL)
data->name = strdup(pool_name);
pthread_mutex_init(&data->op_mutex, NULL);
*instance = data;
return 0;
}
static int ippool_accounting(void *instance, REQUEST *request)
{
rlm_ippool_t *data = (rlm_ippool_t *)instance;
datum key_datum;
datum data_datum;
datum save_datum;
int acctstatustype = 0;
int rcode;
ippool_info entry;
ippool_key key;
int num = 0;
VALUE_PAIR *vp;
char str[32];
uint8_t key_str[17];
char hex_str[35];
char xlat_str[MAX_STRING_LEN];
FR_MD5_CTX md5_context;
if ((vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE)) != NULL)
acctstatustype = vp->vp_integer;
else {
RDEBUG("Could not find account status type in packet. Return NOOP.");
return RLM_MODULE_NOOP;
}
switch(acctstatustype){
case PW_STATUS_STOP:
if (!radius_xlat(xlat_str,MAX_STRING_LEN,data->key, request, NULL)){
RDEBUG("xlat on the 'key' directive failed");
return RLM_MODULE_NOOP;
}
fr_MD5Init(&md5_context);
fr_MD5Update(&md5_context, xlat_str, strlen(xlat_str));
fr_MD5Final(key_str, &md5_context);
key_str[16] = '\0';
fr_bin2hex(key_str,hex_str,16);
hex_str[32] = '\0';
RDEBUG("MD5 on 'key' directive maps to: %s",hex_str);
memcpy(key.key,key_str,16);
break;
default:
RDEBUG("This is not an Accounting-Stop. Return NOOP.");
return RLM_MODULE_NOOP;
}
RDEBUG("Searching for an entry for key: '%s'",xlat_str);
key_datum.dptr = (char *) &key;
key_datum.dsize = sizeof(ippool_key);
pthread_mutex_lock(&data->op_mutex);
data_datum = gdbm_fetch(data->gdbm, key_datum);
if (data_datum.dptr != NULL){
memcpy(&entry, data_datum.dptr, sizeof(ippool_info));
free(data_datum.dptr);
RDEBUG("Deallocated entry for ip: %s",ip_ntoa(str,entry.ipaddr));
entry.active = 0;
entry.timestamp = 0;
entry.timeout = 0;
save_datum.dptr = key_datum.dptr;
save_datum.dsize = key_datum.dsize;
data_datum.dptr = (char *) &entry;
data_datum.dsize = sizeof(ippool_info);
rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
if (rcode < 0) {
radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
data->session_db, gdbm_strerror(gdbm_errno));
pthread_mutex_unlock(&data->op_mutex);
return RLM_MODULE_FAIL;
}
key_datum.dptr = (char *) &entry.ipaddr;
key_datum.dsize = sizeof(uint32_t);
data_datum = gdbm_fetch(data->ip, key_datum);
if (data_datum.dptr != NULL){
memcpy(&num, data_datum.dptr, sizeof(int));
free(data_datum.dptr);
if (num >0){
num--;
RDEBUG("num: %d",num);
data_datum.dptr = (char *) #
data_datum.dsize = sizeof(int);
rcode = gdbm_store(data->ip, key_datum, data_datum, GDBM_REPLACE);
if (rcode < 0) {
radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
data->ip_index, gdbm_strerror(gdbm_errno));
pthread_mutex_unlock(&data->op_mutex);
return RLM_MODULE_FAIL;
}
if (num >0 && entry.extra == 1){
gdbm_delete(data->gdbm,save_datum);
}
}
}
pthread_mutex_unlock(&data->op_mutex);
}
else{
pthread_mutex_unlock(&data->op_mutex);
RDEBUG("Entry not found");
}
return RLM_MODULE_OK;
}
static int ippool_postauth(void *instance, REQUEST *request)
{
rlm_ippool_t *data = (rlm_ippool_t *) instance;
int delete = 0;
int found = 0;
int mppp = 0;
int extra = 0;
int rcode;
int num = 0;
datum key_datum;
datum nextkey;
datum data_datum;
datum save_datum;
ippool_key key;
ippool_info entry;
VALUE_PAIR *vp;
char *cli = NULL;
char str[32];
uint8_t key_str[17];
char hex_str[35];
char xlat_str[MAX_STRING_LEN];
FR_MD5_CTX md5_context;
instance = instance;
request = request;
if ((vp = pairfind(request->config_items, PW_POOL_NAME)) != NULL){
if (data->name == NULL || (strcmp(data->name,vp->vp_strvalue) && strcmp(vp->vp_strvalue,"DEFAULT")))
return RLM_MODULE_NOOP;
} else {
RDEBUG("Could not find Pool-Name attribute.");
return RLM_MODULE_NOOP;
}
if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID)) != NULL)
cli = vp->vp_strvalue;
if (!radius_xlat(xlat_str,MAX_STRING_LEN,data->key, request, NULL)){
RDEBUG("xlat on the 'key' directive failed");
return RLM_MODULE_NOOP;
}
fr_MD5Init(&md5_context);
fr_MD5Update(&md5_context, xlat_str, strlen(xlat_str));
fr_MD5Final(key_str, &md5_context);
key_str[16] = '\0';
fr_bin2hex(key_str,hex_str,16);
hex_str[32] = '\0';
RDEBUG("MD5 on 'key' directive maps to: %s",hex_str);
memcpy(key.key,key_str,16);
RDEBUG("Searching for an entry for key: '%s'",hex_str);
key_datum.dptr = (char *) &key;
key_datum.dsize = sizeof(ippool_key);
pthread_mutex_lock(&data->op_mutex);
data_datum = gdbm_fetch(data->gdbm, key_datum);
if (data_datum.dptr != NULL){
found = 1;
memcpy(&entry, data_datum.dptr, sizeof(ippool_info));
free(data_datum.dptr);
if (entry.active){
RDEBUG("Found a stale entry for ip: %s",ip_ntoa(str,entry.ipaddr));
entry.active = 0;
entry.timestamp = 0;
entry.timeout = 0;
save_datum.dptr = key_datum.dptr;
save_datum.dsize = key_datum.dsize;
data_datum.dptr = (char *) &entry;
data_datum.dsize = sizeof(ippool_info);
rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
if (rcode < 0) {
radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
data->session_db, gdbm_strerror(gdbm_errno));
pthread_mutex_unlock(&data->op_mutex);
return RLM_MODULE_FAIL;
}
key_datum.dptr = (char *) &entry.ipaddr;
key_datum.dsize = sizeof(uint32_t);
data_datum = gdbm_fetch(data->ip, key_datum);
if (data_datum.dptr != NULL){
memcpy(&num, data_datum.dptr, sizeof(int));
free(data_datum.dptr);
if (num >0){
num--;
RDEBUG("num: %d",num);
data_datum.dptr = (char *) #
data_datum.dsize = sizeof(int);
rcode = gdbm_store(data->ip, key_datum, data_datum, GDBM_REPLACE);
if (rcode < 0) {
radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
data->ip_index, gdbm_strerror(gdbm_errno));
pthread_mutex_unlock(&data->op_mutex);
return RLM_MODULE_FAIL;
}
if (num >0 && entry.extra == 1){
gdbm_delete(data->gdbm,save_datum);
}
}
}
}
}
pthread_mutex_unlock(&data->op_mutex);
if (pairfind(request->reply->vps, PW_FRAMED_IP_ADDRESS) != NULL) {
RDEBUG("Found Framed-IP-Address attribute in reply attribute list.");
if (data->override)
{
RDEBUG("override is set to yes. Override the existing Framed-IP-Address attribute.");
pairdelete(&request->reply->vps, PW_FRAMED_IP_ADDRESS);
} else {
RDEBUG("override is set to no. Return NOOP.");
return RLM_MODULE_NOOP;
}
}
pthread_mutex_lock(&data->op_mutex);
key_datum.dptr = NULL;
if (cli != NULL){
key_datum = gdbm_firstkey(data->gdbm);
while(key_datum.dptr){
data_datum = gdbm_fetch(data->gdbm, key_datum);
if (data_datum.dptr){
memcpy(&entry,data_datum.dptr, sizeof(ippool_info));
free(data_datum.dptr);
if (strcmp(entry.cli,cli) == 0 && entry.active){
mppp = 1;
break;
}
}
nextkey = gdbm_nextkey(data->gdbm, key_datum);
free(key_datum.dptr);
key_datum = nextkey;
}
}
if (key_datum.dptr == NULL){
key_datum = gdbm_firstkey(data->gdbm);
while(key_datum.dptr){
data_datum = gdbm_fetch(data->gdbm, key_datum);
if (data_datum.dptr){
memcpy(&entry,data_datum.dptr, sizeof(ippool_info));
free(data_datum.dptr);
if (entry.active == 0 || (entry.timestamp && ((entry.timeout &&
request->timestamp >= (entry.timestamp + entry.timeout)) ||
(data->max_timeout && request->timestamp >= (entry.timestamp + data->max_timeout))))){
datum tmp;
tmp.dptr = (char *) &entry.ipaddr;
tmp.dsize = sizeof(uint32_t);
data_datum = gdbm_fetch(data->ip, tmp);
if (data_datum.dptr){
memcpy(&num,data_datum.dptr, sizeof(int));
free(data_datum.dptr);
if (num == 0){
delete = 1;
break;
}
}
else{
delete = 1;
break;
}
}
}
nextkey = gdbm_nextkey(data->gdbm, key_datum);
free(key_datum.dptr);
key_datum = nextkey;
}
}
if (key_datum.dptr){
if (found && !mppp){
datum key_datum_tmp;
datum data_datum_tmp;
ippool_key key_tmp;
memcpy(key_tmp.key,key_str,16);
key_datum_tmp.dptr = (char *) &key_tmp;
key_datum_tmp.dsize = sizeof(ippool_key);
data_datum_tmp = gdbm_fetch(data->gdbm, key_datum_tmp);
if (data_datum_tmp.dptr != NULL){
rcode = gdbm_store(data->gdbm, key_datum, data_datum_tmp, GDBM_REPLACE);
free(data_datum_tmp.dptr);
if (rcode < 0) {
radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
data->session_db, gdbm_strerror(gdbm_errno));
pthread_mutex_unlock(&data->op_mutex);
return RLM_MODULE_FAIL;
}
}
}
else{
if (delete){
gdbm_delete(data->gdbm, key_datum);
}
else{
if (mppp)
extra = 1;
if (!mppp)
radlog(L_ERR, "rlm_ippool: mppp is not one. Please report this behaviour.");
}
}
free(key_datum.dptr);
entry.active = 1;
entry.timestamp = request->timestamp;
if ((vp = pairfind(request->reply->vps, PW_SESSION_TIMEOUT)) != NULL)
entry.timeout = (time_t) vp->vp_integer;
else
entry.timeout = 0;
if (extra)
entry.extra = 1;
data_datum.dptr = (char *) &entry;
data_datum.dsize = sizeof(ippool_info);
memcpy(key.key, key_str, 16);
key_datum.dptr = (char *) &key;
key_datum.dsize = sizeof(ippool_key);
DEBUG2("rlm_ippool: Allocating ip to key: '%s'",hex_str);
rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
if (rcode < 0) {
radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
data->session_db, gdbm_strerror(gdbm_errno));
pthread_mutex_unlock(&data->op_mutex);
return RLM_MODULE_FAIL;
}
key_datum.dptr = (char *) &entry.ipaddr;
key_datum.dsize = sizeof(uint32_t);
data_datum = gdbm_fetch(data->ip, key_datum);
if (data_datum.dptr){
memcpy(&num,data_datum.dptr,sizeof(int));
free(data_datum.dptr);
} else
num = 0;
num++;
RDEBUG("num: %d",num);
data_datum.dptr = (char *) #
data_datum.dsize = sizeof(int);
rcode = gdbm_store(data->ip, key_datum, data_datum, GDBM_REPLACE);
if (rcode < 0) {
radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
data->ip_index, gdbm_strerror(gdbm_errno));
pthread_mutex_unlock(&data->op_mutex);
return RLM_MODULE_FAIL;
}
pthread_mutex_unlock(&data->op_mutex);
RDEBUG("Allocated ip %s to client key: %s",ip_ntoa(str,entry.ipaddr),hex_str);
vp = radius_paircreate(request, &request->reply->vps,
PW_FRAMED_IP_ADDRESS, PW_TYPE_IPADDR);
vp->vp_ipaddr = entry.ipaddr;
if (pairfind(request->reply->vps, PW_FRAMED_IP_NETMASK) == NULL) {
vp = radius_paircreate(request, &request->reply->vps,
PW_FRAMED_IP_NETMASK,
PW_TYPE_IPADDR);
vp->vp_ipaddr = ntohl(data->netmask);
}
}
else{
pthread_mutex_unlock(&data->op_mutex);
RDEBUG("No available ip addresses in pool.");
return RLM_MODULE_NOTFOUND;
}
return RLM_MODULE_OK;
}
static int ippool_detach(void *instance)
{
rlm_ippool_t *data = (rlm_ippool_t *) instance;
gdbm_close(data->gdbm);
gdbm_close(data->ip);
pthread_mutex_destroy(&data->op_mutex);
free(instance);
return 0;
}
module_t rlm_ippool = {
RLM_MODULE_INIT,
"ippool",
RLM_TYPE_THREAD_SAFE,
ippool_instantiate,
ippool_detach,
{
NULL,
NULL,
NULL,
ippool_accounting,
NULL,
NULL,
NULL,
ippool_postauth
},
};