/*
 *   This program is is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or (at
 *   your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

/**
 * $Id: e289bc264db5b0842fda1d4ad7aa54df15432bd9 $
 * @file rlm_ippool.c
 * @brief Allocates an IPv4 address from a pool stored in a GDBM database.
 *
 * @copyright 2000,2006  The FreeRADIUS server project
 * @copyright 2002  Kostas Kalevras <kkalev@noc.ntua.gr>
 */
RCSID("$Id: e289bc264db5b0842fda1d4ad7aa54df15432bd9 $")

#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/modules.h>
#include <freeradius-devel/rad_assert.h>

#include "config.h"
#include <ctype.h>

#ifdef WITH_DHCP
#include <freeradius-devel/dhcp.h>
#endif

#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 a structure for our module configuration.
 *
 *	These variables do not need to be in a structure, but it's
 *	a lot cleaner to do so, and a pointer to the structure can
 *	be used as the instance handle.
 */
typedef struct rlm_ippool_t {
	char const	*filename;
	char const	*ip_index;
	char const	*name;
	char const	*key;

	fr_ipaddr_t	range_start_addr;
	fr_ipaddr_t	range_stop_addr;
	fr_ipaddr_t	netmask_addr;
	uint32_t	range_start;
	uint32_t	range_stop;
	uint32_t	netmask;

	uint32_t	max_timeout;
	uint32_t	cache_size;
	bool		override;
	GDBM_FILE	gdbm;
	GDBM_FILE	ip;
#ifdef HAVE_PTHREAD_H
	pthread_mutex_t op_mutex;
#endif
} rlm_ippool_t;

#ifndef HAVE_PTHREAD_H
/*
 *	This is easier than ifdef's throughout the code.
 */
#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", FR_CONF_OFFSET(PW_TYPE_FILE_OUTPUT | PW_TYPE_DEPRECATED, rlm_ippool_t, filename), NULL },
	{ "filename", FR_CONF_OFFSET(PW_TYPE_FILE_OUTPUT | PW_TYPE_REQUIRED, rlm_ippool_t, filename), NULL },

	{ "ip-index", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_ippool_t, ip_index), NULL },
	{ "ip_index", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, rlm_ippool_t, ip_index), NULL },

	{ "key", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED | PW_TYPE_XLAT, rlm_ippool_t, key), "%{NAS-IP-Address} %{NAS-Port}" },

	{ "range-start", FR_CONF_OFFSET(PW_TYPE_IPV4_ADDR | PW_TYPE_DEPRECATED, rlm_ippool_t, range_start_addr), NULL },
	{ "range_start", FR_CONF_OFFSET(PW_TYPE_IPV4_ADDR, rlm_ippool_t, range_start_addr), "0" },

	{ "range-stop", FR_CONF_OFFSET(PW_TYPE_IPV4_ADDR | PW_TYPE_DEPRECATED, rlm_ippool_t, range_stop_addr), NULL },
	{ "range_stop", FR_CONF_OFFSET(PW_TYPE_IPV4_ADDR, rlm_ippool_t, range_stop_addr), "0" },

	{ "netmask", FR_CONF_OFFSET(PW_TYPE_IPV4_ADDR, rlm_ippool_t, netmask_addr), "0" },

	{ "cache-size", FR_CONF_OFFSET(PW_TYPE_INTEGER | PW_TYPE_DEPRECATED, rlm_ippool_t, cache_size), NULL },
	{ "cache_size", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_ippool_t, cache_size), "1000" },

	{ "override", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_ippool_t, override), "no" },

	{ "maximum-timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER | PW_TYPE_DEPRECATED, rlm_ippool_t, max_timeout), NULL },
	{ "maximum_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_ippool_t, max_timeout), "0" },
	CONF_PARSER_TERMINATOR
};

/*
 *	Do any per-module initialization that is separate to each
 *	configured instance of the module.  e.g. set up connections
 *	to external databases, read configuration files, set up
 *	dictionary entries, etc.
 *
 *	If configuration information is given in the config section
 *	that must be referenced in later calls, store a handle to it
 *	in *instance otherwise put a null pointer there.
 */
static int mod_instantiate(CONF_SECTION *conf, void *instance)
{
	rlm_ippool_t	*inst = instance;
	int		cache_size;
	ippool_info	entry;
	ippool_key	key;
	datum		key_datum;
	datum		data_datum;

	char const	*cli = "0";
	char const	*pool_name = NULL;

	int		rcode;
	uint32_t	i, j;
	uint32_t	or_result;
	char		str[32];
	char		init_str[17];

	/*
	 *  Add the ip pool name
	 */
	inst->name = NULL;
	pool_name = cf_section_name2(conf);
	if (pool_name != NULL) {
		inst->name = talloc_typed_strdup(inst, pool_name);
	}

	cache_size = inst->cache_size;

	rad_assert(inst->filename && *inst->filename);
	rad_assert(inst->ip_index && *inst->ip_index);

	inst->range_start = htonl(*((uint32_t *)(&(inst->range_start_addr.ipaddr.ip4addr))));
	inst->range_stop = htonl(*((uint32_t *)(&(inst->range_stop_addr.ipaddr.ip4addr))));
	inst->netmask = htonl(*((uint32_t *)(&(inst->netmask_addr.ipaddr.ip4addr))));
	if (inst->range_start == 0 || inst->range_stop == 0 || \
	    inst->range_start >= inst->range_stop ) {
		cf_log_err_cs(conf, "Invalid data range");
		return -1;
	}

	{
		char *file;

		memcpy(&file, &inst->filename, sizeof(file));
		inst->gdbm = gdbm_open(file, sizeof(int),
				       GDBM_WRCREAT | GDBM_IPPOOL_OPTS, 0600, NULL);
	}

	if (!inst->gdbm) {
		ERROR("rlm_ippool: Failed to open file %s: %s", inst->filename, fr_syserror(errno));

		return -1;
	}

	{
		char *file;

		memcpy(&file, &inst->ip_index, sizeof(file));
		inst->ip = gdbm_open(file, sizeof(int),
				     GDBM_WRCREAT | GDBM_IPPOOL_OPTS, 0600, NULL);
	}

	if (!inst->ip) {
		ERROR("rlm_ippool: Failed to open file %s: %s", inst->ip_index, fr_syserror(errno));

		return -1;
	}

	if (gdbm_setopt(inst->gdbm, GDBM_CACHESIZE, &cache_size, sizeof(int)) == -1) {
		ERROR("rlm_ippool: Failed to set cache size");
	}

	if (gdbm_setopt(inst->ip, GDBM_CACHESIZE, &cache_size, sizeof(int)) == -1) {
		ERROR("rlm_ippool: Failed to set cache size");
	}

	pthread_mutex_init(&inst->op_mutex, NULL);

	key_datum = gdbm_firstkey(inst->gdbm);
	if (key_datum.dptr) {
		free(key_datum.dptr);
		return 0;
	}

	/*
	 *  If the database does not exist initialize it.
	 *  We set the nas/port pairs to not existent values and
	 *  active = 0
	 */
	DEBUG("rlm_ippool: Initializing database");
	for (i = inst->range_start, j=~0; i <= inst->range_stop; i++, j--){
		/*
		 *  Net and Broadcast addresses are excluded
		 */
		or_result = i | inst->netmask;
		if (~inst->netmask != 0 && (or_result == inst->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(inst->gdbm, key_datum, data_datum, GDBM_REPLACE);
		if (rcode < 0) {
			ERROR("rlm_ippool: Failed storing data to %s: %s", inst->filename, gdbm_strerror(gdbm_errno));
			gdbm_close(inst->gdbm);
			gdbm_close(inst->ip);
			return -1;
		}
	}

	return 0;
}

/** Decrease allocated count from the ip index
 *
 */
static int decrease_allocated_count(rlm_ippool_t *inst, REQUEST *request, ippool_info *entry, datum *save_datum)
{
	datum		data_datum;
	datum		key_datum;
	int		num;


	key_datum.dptr = (char *) &(entry->ipaddr);
	key_datum.dsize = sizeof(uint32_t);
	data_datum = gdbm_fetch(inst->ip, key_datum);
	if (!data_datum.dptr) {
		return 0;
	}
	memcpy(&num, data_datum.dptr, sizeof(int));
	free(data_datum.dptr);
	if (num > 0){
		int	rcode;

		num--;

		RDEBUG("Allocated count now: %i", num);
		data_datum.dptr = (char *) &num;
		data_datum.dsize = sizeof(int);
		rcode = gdbm_store(inst->ip, key_datum, data_datum, GDBM_REPLACE);
		if (rcode < 0) {
			RDEBUG("Failed storing data to %s: %s", inst->ip_index, gdbm_strerror(gdbm_errno));
			return -1;
		}
		if ((num > 0) && entry->extra == 1){
			/*
			 * We are doing MPPP and we still have nas/port entries referencing
			 * this ip. Delete this entry so that eventually we only keep one
			 * reference to this ip.
			 */
			gdbm_delete(inst->gdbm, *save_datum);
		}
	}

	return 0;
}


/*
 *	Check for an Accounting-Stop
 *	If we find one and we have allocated an IP to this nas/port combination, deallocate it.
 */
static rlm_rcode_t CC_HINT(nonnull) mod_accounting(void *instance, REQUEST *request)
{
	rlm_ippool_t	*inst = instance;

	datum		key_datum;
	ippool_key	key;
	datum		data_datum;
	ippool_info	entry;
	datum		save_datum;

	int		rcode;
	VALUE_PAIR	*vp;

	char		str[32];
	uint8_t		key_str[17];
	char		hex_str[35];
	char		xlat_str[MAX_STRING_LEN];
	int		ret;

	vp = fr_pair_find_by_num(request->packet->vps, PW_ACCT_STATUS_TYPE, 0, TAG_ANY);
	if (!vp) {
		RDEBUG2("Could not find account status type in packet");
		return RLM_MODULE_INVALID;
	}

	switch (vp->vp_integer) {
	case PW_STATUS_STOP:
	{
		FR_MD5_CTX md5_context;
		if (radius_xlat(xlat_str, sizeof(xlat_str), request, inst->key, NULL, NULL) < 0){
			return RLM_MODULE_FAIL;
		}

		fr_md5_init(&md5_context);
		fr_md5_update(&md5_context, (uint8_t *)xlat_str, strlen(xlat_str));
		fr_md5_final(key_str, &md5_context);
		fr_md5_destroy(&md5_context);

		key_str[16] = '\0';
		fr_bin2hex(hex_str, key_str, 16);
		hex_str[32] = '\0';

		RDEBUG2("MD5 on 'key' directive maps to: %s", hex_str);
		memcpy(key.key, key_str, 16);
		break;
	}

	default:
		/* We don't care about any other accounting packet */
		RDEBUG2("This is not an Accounting-Stop");

		return RLM_MODULE_NOOP;
	}

	RDEBUG2("Searching for an entry for key: '%s'", xlat_str);
	key_datum.dptr = (char *) &key;
	key_datum.dsize = sizeof(ippool_key);

	pthread_mutex_lock(&inst->op_mutex);
	data_datum = gdbm_fetch(inst->gdbm, key_datum);
	if (data_datum.dptr == NULL) {
		pthread_mutex_unlock(&inst->op_mutex);
		RDEBUG2("Entry not found");

		return RLM_MODULE_NOTFOUND;
	}

	/*
	 *   If the entry was found set active to zero
	 */
	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 the reference to the entry
	 */
	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(inst->gdbm, key_datum, data_datum, GDBM_REPLACE);
	if (rcode < 0) {
		pthread_mutex_unlock(&inst->op_mutex);
		REDEBUG("Failed storing data to %s: %s", inst->filename, gdbm_strerror(gdbm_errno));

		return RLM_MODULE_FAIL;
	}

	/*
	 *  Decrease allocated count from the ip index
	 */
	ret = decrease_allocated_count(inst, request, &entry, &save_datum);
	pthread_mutex_unlock(&inst->op_mutex);
	if (ret < 0) {
		return RLM_MODULE_FAIL;
	}

	return RLM_MODULE_OK;
}

static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request)
{
	rlm_ippool_t *inst = instance;

	datum		key_datum;
	ippool_key	key;
	datum		nextkey;
	datum		data_datum;
	ippool_info	entry;
	datum		save_datum;

	int		delete = 0;
	bool		found = false;
	int		mppp = 0;
	int		extra = 0;
	int		rcode;
	int		num = 0;

	VALUE_PAIR	*vp;
	char const	*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;

#ifdef WITH_DHCP
	bool dhcp = false;
#endif
	int		attr_ipaddr = PW_FRAMED_IP_ADDRESS;
	int		attr_ipmask = PW_FRAMED_IP_NETMASK;
	int		vendor_ipaddr = 0;

	/*
	 *  Check if Pool-Name attribute exists. If it exists check our name and
	 *  run only if they match
	 */
	vp = fr_pair_find_by_num(request->config, PW_POOL_NAME, 0, TAG_ANY);
	if (vp != NULL){
		if (!inst->name || (strcmp(inst->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;
	}

	/*
	 *  Find the caller id
	 */
	vp = fr_pair_find_by_num(request->packet->vps, PW_CALLING_STATION_ID, 0, TAG_ANY);
	if (vp != NULL) {
		cli = vp->vp_strvalue;
	}

#ifdef WITH_DHCP
	if (request->listener->type == RAD_LISTEN_DHCP) {
		dhcp = 1;
		attr_ipaddr = PW_DHCP_YOUR_IP_ADDRESS;
		vendor_ipaddr = DHCP_MAGIC_VENDOR;
		attr_ipmask = PW_DHCP_SUBNET_MASK;
	}
#endif

	if (radius_xlat(xlat_str, sizeof(xlat_str), request, inst->key, NULL, NULL) < 0){
		return RLM_MODULE_FAIL;
	}

	fr_md5_init(&md5_context);
	fr_md5_update(&md5_context, (uint8_t *)xlat_str, strlen(xlat_str));
	fr_md5_final(key_str, &md5_context);
	fr_md5_destroy(&md5_context);
	key_str[16] = '\0';
	fr_bin2hex(hex_str, key_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(&inst->op_mutex);
	data_datum = gdbm_fetch(inst->gdbm, key_datum);
	if (data_datum.dptr != NULL){
		/*
		 *  If there is a corresponding entry in the database with active=1 it is stale.
		 *  Set active to zero
		 */
		found = true;
		memcpy(&entry, data_datum.dptr, sizeof(ippool_info));
		free(data_datum.dptr);

		if (entry.active){
			int ret;
			RDEBUG("Found a stale entry for ip: %s",ip_ntoa(str,entry.ipaddr));
			entry.active = 0;
			entry.timestamp = 0;
			entry.timeout = 0;

			/*
			 *  Save the reference to the entry
			 */
			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(inst->gdbm, key_datum, data_datum, GDBM_REPLACE);
			if (rcode < 0) {
				REDEBUG("Failed storing data to %s: %s", inst->filename, gdbm_strerror(gdbm_errno));
				pthread_mutex_unlock(&inst->op_mutex);
				return RLM_MODULE_FAIL;
			}

			/*
			 *  Decrease allocated count for the ip
			 */
			ret = decrease_allocated_count(inst, request, &entry, &save_datum);
			pthread_mutex_unlock(&inst->op_mutex);
			if (ret < 0) {
				return RLM_MODULE_FAIL;
			}
		}
	}

	pthread_mutex_unlock(&inst->op_mutex);

	/*
	 *  If there is a Framed-IP-Address (or Dhcp-Your-IP-Address)
	 *  attribute in the reply, check for override
	 */
	if (fr_pair_find_by_num(request->reply->vps, attr_ipaddr, vendor_ipaddr, TAG_ANY) != NULL) {
		RDEBUG("Found IP address attribute in reply attribute list");
		if (!inst->override) {
			RDEBUG("override is set to no. Return NOOP");
			return RLM_MODULE_NOOP;
		}

		RDEBUG("Override supplied IP address");
		fr_pair_delete_by_num(&request->reply->vps, attr_ipaddr, vendor_ipaddr, TAG_ANY);
	}

	/*
	 *  Walk through the database searching for an active=0 entry.
	 *  We search twice. Once to see if we have an active entry with the same caller_id
	 *  so that MPPP can work ok and then once again to find a free entry.
	 */
	pthread_mutex_lock(&inst->op_mutex);
	key_datum.dptr = NULL;
	if (cli != NULL){
		key_datum = gdbm_firstkey(inst->gdbm);
		while (key_datum.dptr) {
			data_datum = gdbm_fetch(inst->gdbm, key_datum);
			if (data_datum.dptr){
				memcpy(&entry,data_datum.dptr, sizeof(ippool_info));
				free(data_datum.dptr);
				/*
				* If we find an entry for the same caller-id with active=1
				* then we use that for multilink (MPPP) to work properly.
				*/
				if (strcmp(entry.cli,cli) == 0 && entry.active){
					mppp = 1;
					break;
				}
			}

			nextkey = gdbm_nextkey(inst->gdbm, key_datum);
			free(key_datum.dptr);
			key_datum = nextkey;
		}
	}

	if (!key_datum.dptr){
		key_datum = gdbm_firstkey(inst->gdbm);
		while(key_datum.dptr){
			data_datum = gdbm_fetch(inst->gdbm, key_datum);
			if (data_datum.dptr){
				memcpy(&entry,data_datum.dptr, sizeof(ippool_info));
				free(data_datum.dptr);

				/*
				 * Find an entry with active == 0
				 * or an entry that has expired
				 */
				if (entry.active == 0 || (entry.timestamp && ((entry.timeout &&
				request->timestamp >= (entry.timestamp + entry.timeout)) ||
				(inst->max_timeout && request->timestamp >= (entry.timestamp + inst->max_timeout))))){
					datum tmp;

					tmp.dptr = (char *) &entry.ipaddr;
					tmp.dsize = sizeof(uint32_t);
					data_datum = gdbm_fetch(inst->ip, tmp);

					/*
					 * If we find an entry in the ip index and the number is zero (meaning
					 * that we haven't allocated the same ip address to another nas/port pair)
					 * or if we don't find an entry then delete the session entry so
					 * that we can change the key
					 * Else we don't delete the session entry since we haven't yet deallocated the
					 * corresponding ip address and we continue our search.
					 */

					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(inst->gdbm, key_datum);
			free(key_datum.dptr);
			key_datum = nextkey;
		}
	}
	/*
	 * If we have found a free entry set active to 1 then add a Framed-IP-Address attribute to
	 * the reply
	 * We keep the operation mutex locked until after we have set the corresponding entry active
	 */
	if (key_datum.dptr){
		if (found && !mppp){
			/*
			 * Found == 1 means we have the nas/port combination entry in our database
			 * We exchange the ip address between the nas/port entry and the free entry
			 * Afterwards we will save the free ip address to the nas/port entry.
			 * That is:
			 *  ---------------------------------------------
			 *  - NAS/PORT Entry  |||| Free Entry  ||| Time
			 *  -    IP1		 IP2(Free)    BEFORE
			 *  -    IP2(Free)	   IP1	  AFTER
			 *  ---------------------------------------------
			 *
			 * We only do this if we are NOT doing 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(inst->gdbm, key_datum_tmp);
			if (data_datum_tmp.dptr != NULL){

				rcode = gdbm_store(inst->gdbm, key_datum, data_datum_tmp, GDBM_REPLACE);
				free(data_datum_tmp.dptr);
				if (rcode < 0) {
					REDEBUG("Failed storing data to %s: %s", inst->filename, gdbm_strerror(gdbm_errno));
						pthread_mutex_unlock(&inst->op_mutex);
					return RLM_MODULE_FAIL;
				}
			}
		} else{
			/*
			 * We have not found the nas/port combination
			 */
			if (delete) {
				/*
				 *  Delete the entry so that we can change the key
				 *  All is well. We delete one entry and we add one entry
				 */
				gdbm_delete(inst->gdbm, key_datum);
			} else{
				/*
				 *  We are doing MPPP. (mppp should be 1)
				 *  We don't do anything.
				 *  We will create an extra not needed entry in the database in this case
				 *  but we don't really care since we always also use the ip_index database
				 *  when we search for a free entry.
				 *  We will also delete that entry on the accounting section so that we only
				 *  have one nas/port entry referencing each ip
				 */
				if (mppp) {
					extra = 1;
				}
				if (!mppp) {
					REDEBUG("mppp is not one. Please report this behaviour");
				}
			}
		}
		free(key_datum.dptr);
		entry.active = 1;
		entry.timestamp = request->timestamp;
		if ((vp = fr_pair_find_by_num(request->reply->vps, PW_SESSION_TIMEOUT, 0, TAG_ANY)) != NULL) {
			entry.timeout = (time_t) vp->vp_integer;
#ifdef WITH_DHCP
			if (dhcp) {
				vp = radius_pair_create(request->reply, &request->reply->vps,
						       PW_DHCP_IP_ADDRESS_LEASE_TIME, DHCP_MAGIC_VENDOR);
				vp->vp_integer = entry.timeout;
				fr_pair_delete_by_num(&request->reply->vps, PW_SESSION_TIMEOUT, 0, TAG_ANY);
			}
#endif
		} 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);

		RDEBUG2("Allocating ip to key: '%s'",hex_str);
		rcode = gdbm_store(inst->gdbm, key_datum, data_datum, GDBM_REPLACE);
		if (rcode < 0) {
			REDEBUG("Failed storing data to %s: %s", inst->filename, gdbm_strerror(gdbm_errno));
				pthread_mutex_unlock(&inst->op_mutex);
			return RLM_MODULE_FAIL;
		}

		/* Increase the ip index count */
		key_datum.dptr = (char *) &entry.ipaddr;
		key_datum.dsize = sizeof(uint32_t);
		data_datum = gdbm_fetch(inst->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 *) &num;
		data_datum.dsize = sizeof(int);
		rcode = gdbm_store(inst->ip, key_datum, data_datum, GDBM_REPLACE);
		if (rcode < 0) {
			REDEBUG("Failed storing data to %s: %s", inst->ip_index, gdbm_strerror(gdbm_errno));
			pthread_mutex_unlock(&inst->op_mutex);
			return RLM_MODULE_FAIL;
		}
		pthread_mutex_unlock(&inst->op_mutex);

		RDEBUG("Allocated ip %s to client key: %s",ip_ntoa(str,entry.ipaddr),hex_str);
		vp = radius_pair_create(request->reply, &request->reply->vps,
				       attr_ipaddr, vendor_ipaddr);
		vp->vp_ipaddr = entry.ipaddr;

		/*
		 *	If there is no Framed-Netmask attribute in the
		 *	reply, add one
		 */
		if (fr_pair_find_by_num(request->reply->vps, attr_ipmask, vendor_ipaddr, TAG_ANY) == NULL) {
			vp = radius_pair_create(request->reply, &request->reply->vps,
					       attr_ipmask, vendor_ipaddr);
			vp->vp_ipaddr = ntohl(inst->netmask);
		}

	}
	else{
		pthread_mutex_unlock(&inst->op_mutex);
		RDEBUG("No available ip addresses in pool");
		return RLM_MODULE_NOTFOUND;
	}

	return RLM_MODULE_OK;
}

static int mod_detach(void *instance)
{
	rlm_ippool_t *inst = instance;

	gdbm_close(inst->gdbm);
	gdbm_close(inst->ip);
	pthread_mutex_destroy(&inst->op_mutex);
	return 0;
}

/*
 *	The module name should be the only globally exported symbol.
 *	That is, everything else should be 'static'.
 *
 *	If the module needs to temporarily modify it's instantiation
 *	data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
 *	The server will then take care of ensuring that the module
 *	is single-threaded.
 */
extern module_t rlm_ippool;
module_t rlm_ippool = {
	.magic		= RLM_MODULE_INIT,
	.name		= "ippool",
	.type		= RLM_TYPE_THREAD_SAFE,
	.inst_size	= sizeof(rlm_ippool_t),
	.config		= module_config,
	.instantiate	= mod_instantiate,
	.detach		= mod_detach,
	.methods = {

		[MOD_ACCOUNTING]	= mod_accounting,
		[MOD_POST_AUTH]		= mod_post_auth
	},
};
