/*
 *  rlm_sqlippool.c     rlm_sqlippool - FreeRADIUS SQL IP Pool Module
 *
 * Version:     $Id: rlm_sqlippool.c,v 1.3.2.6 2007/07/17 18:46:32 pnixon Exp $
 *
 *   This program 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Copyright 2002  Globe.Net Communications Limited
 * Copyright 2006  The FreeRADIUS server project
 * Copyright 2006  Suntel Communications
 */

#include "autoconf.h"
#include "libradius.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <netinet/in.h>

#include "radiusd.h"
#include "modules.h"
#include "conffile.h"
#include "modpriv.h"

#include <rlm_sql.h>

static const char rcsid[] = "$Id: rlm_sqlippool.c,v 1.3.2.6 2007/07/17 18:46:32 pnixon Exp $";



/*
 *	Define a structure for our module configuration.
 */
typedef struct rlm_sqlippool_t {
	char *sql_instance_name;

	int lease_duration;

	SQL_INST *sql_inst;

	char *pool_name;

	/* We ended up removing the init 
	   queries so that its up to user
	   to create the db structure and put the required
	   information in there			
	*/
				/* Allocation sequence */
	char *allocate_begin;	/* SQL query to begin */
	char *allocate_clear;	/* SQL query to clear an IP */
	char *allocate_find;	/* SQL query to find an unused IP */
	char *allocate_update;	/* SQL query to mark an IP as used */
	char *allocate_commit;	/* SQL query to commit */
	char *allocate_rollback; /* SQL query to rollback */

				/* Start sequence */
	char *start_begin;	/* SQL query to begin */
	char *start_update;	/* SQL query to update an IP entry */
	char *start_commit;	/* SQL query to commit */
	char *start_rollback;	/* SQL query to rollback */

				/* Alive sequence */
	char *alive_begin;	/* SQL query to begin */
	char *alive_update;	/* SQL query to update an IP entry */
	char *alive_commit;	/* SQL query to commit */
	char *alive_rollback;	/* SQL query to rollback */

				/* Stop sequence */
	char *stop_begin;	/* SQL query to begin */
	char *stop_clear;	/* SQL query to clear an IP */
	char *stop_commit;	/* SQL query to commit */
	char *stop_rollback;	/* SQL query to rollback */

				/* On sequence */
	char *on_begin;		/* SQL query to begin */
	char *on_clear;		/* SQL query to clear an entire NAS */
	char *on_commit;	/* SQL query to commit */
	char *on_rollback;	/* SQL query to rollback */

				/* Off sequence */
	char *off_begin;	/* SQL query to begin */
	char *off_clear;	/* SQL query to clear an entire NAS */
	char *off_commit;	/* SQL query to commit */
	char *off_rollback;	/* SQL query to rollback */
	
#ifdef HAVE_PTHREAD_H
	pthread_mutex_t dlock;
	long owner;
#endif	

} rlm_sqlippool_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


/*
 *	A mapping of configuration file names to internal variables.
 *
 *	Note that the string is dynamically allocated, so it MUST
 *	be freed.  When the configuration file parse re-reads the string,
 *	it free's the old one, and strdup's the new one, placing the pointer
 *	to the strdup'd string into 'config.string'.  This gets around
 *	buffer over-flows.
 */
static CONF_PARSER module_config[] = {
  {"sql-instance-name",PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,sql_instance_name), NULL, "sql"},

  { "lease-duration", PW_TYPE_INTEGER, offsetof(rlm_sqlippool_t,lease_duration), NULL, "86400"},

  { "pool-name"	    , PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t, pool_name), NULL, ""},

  { "allocate-begin", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,allocate_begin), NULL, "BEGIN" },
  { "allocate-clear", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,allocate_clear), NULL, "" },
  { "allocate-find", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,allocate_find), NULL, "" },
  { "allocate-update", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,allocate_update), NULL, "" },
  { "allocate-commit", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,allocate_commit), NULL, "COMMIT" },
  { "allocate-rollback", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,allocate_rollback), NULL, "ROLLBACK" },

  { "start-begin", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,start_begin), NULL, "BEGIN" },
  { "start-update", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,start_update), NULL, "" },
  { "start-commit", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,start_commit), NULL, "COMMIT" },
  { "start-rollback", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,start_rollback), NULL, "ROLLBACK" },

  { "alive-begin", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,alive_begin), NULL, "BEGIN" },
  { "alive-update", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,alive_update), NULL, "" },
  { "alive-commit", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,alive_commit), NULL, "COMMIT" },
  { "alive-rollback", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,alive_rollback), NULL, "ROLLBACK" },

  { "stop-begin", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,stop_begin), NULL, "BEGIN" },
  { "stop-clear", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,stop_clear), NULL, "" },
  { "stop-commit", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,stop_commit), NULL, "COMMIT" },
  { "stop-rollback", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,stop_rollback), NULL, "ROLLBACK" },

  { "on-begin", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,on_begin), NULL, "BEGIN" },
  { "on-clear", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,on_clear), NULL, "" },
  { "on-commit", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,on_commit), NULL, "COMMIT" },
  { "on-rollback", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,on_rollback), NULL, "ROLLBACK" },

  { "off-begin", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,off_begin), NULL, "BEGIN" },
  { "off-clear", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,off_clear), NULL, "" },
  { "off-commit", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,off_commit), NULL, "COMMIT" },
  { "off-rollback", PW_TYPE_STRING_PTR, offsetof(rlm_sqlippool_t,off_rollback), NULL, "ROLLBACK" },

  { NULL, -1, 0, NULL, NULL }
};

/*
 *	Replace %<whatever> in a string.
 *
 *	%P	pool_name
 *	%I	param
 *	%J	lease_duration
 *
 */
static int sqlippool_expand(char * out, int outlen, const char * fmt, void * instance, char * param, int param_len)
{
	rlm_sqlippool_t * data = (rlm_sqlippool_t *) instance;
	char *q;
	const char *p;
	char tmp[40]; /* For temporary storing of integers */
	int openbraces;

	openbraces = 0;
	q = out;
	for (p = fmt; *p ; p++) {
		int freespace;
		int c;

		/* Calculate freespace in output */
		freespace = outlen - (q - out);
		if (freespace <= 1)
			break;

		c = *p;
		if (c != '%' && c != '$' && c != '\\') {
			/*
			 * We check if we're inside an open brace.  If we are
			 * then we assume this brace is NOT literal, but is
			 * a closing brace and apply it 
			 */
			if((c == '}') && openbraces) {
				openbraces--;
				continue;
			}
			*q++ = *p;
			continue;
		}

		if (*++p == '\0')
			break;

		if (c == '\\') {
			switch(*p) {
			case '\\':
				*q++ = '\\';
				break;
			case 't':
				*q++ = '\t';
				break;
			case 'n':
				*q++ = '\n';
				break;
			default:
				*q++ = c;
				*q++ = *p;
				break;
			}
		}
		else if (c == '%') {
			switch(*p) {
			case '%':
				*q++ = *p;
				break;
			case 'P': /* pool name */
				strNcpy(q, data->pool_name, freespace); 
				q += strlen(q);
				break;
			case 'I': /* IP address */
				if (param && param_len > 0) {
					if (param_len > freespace) {
						strNcpy(q, param, freespace);
						q += strlen(q);
					}
					else {
						memcpy(q, param, param_len);
						q += param_len;
					}
				}
				break;
			case 'J': /* lease duration */
				sprintf(tmp, "%d", data->lease_duration);
				strNcpy(q, tmp, freespace); 
				q += strlen(q);
				break;
			default:
				*q++ = '%';
				*q++ = *p;
				break;
			}
		}
	}
	*q = '\0';

#if 0
	DEBUG2("sqlippool_expand: '%s'", out);
#endif

	return strlen(out);
}

/*
 * Query the database executing a command with no result rows
 */
static int sqlippool_command(const char * fmt, SQLSOCK * sqlsocket, void * instance, REQUEST * request, char * param, int param_len)
{
	rlm_sqlippool_t * data = (rlm_sqlippool_t *) instance;
	char expansion[MAX_STRING_LEN * 4];
	char query[MAX_STRING_LEN * 4];

	sqlippool_expand(expansion, sizeof(expansion), fmt, instance, param, param_len);

	/*
	 * Do an xlat on the provided string
	 */
	if (request) {
		char sqlusername[MAX_STRING_LEN];

		if(sql_set_user(data->sql_inst, request, sqlusername, NULL) < 0) {
			return RLM_MODULE_FAIL;
		}

		if (!radius_xlat(query, sizeof(query), expansion, request, NULL)) {
			radlog(L_ERR, "sqlippool_command: xlat failed.");
			return 0;
		}
	}
	else {
		strcpy(query, expansion);
	}

#if 0
	DEBUG2("sqlippool_command: '%s'", query);
#endif
	if (rlm_sql_query(sqlsocket, data->sql_inst, query)){
		radlog(L_ERR, "sqlippool_command: database query error");
		return 0;
	}

	(data->sql_inst->module->sql_finish_query)(sqlsocket, data->sql_inst->config);
	return 0;
}

/*
 * Query the database expecting a single result row
 */
static int sqlippool_query1(char * out, int outlen, const char * fmt, SQLSOCK * sqlsocket, void * instance, REQUEST * request, char * param, int param_len)
{
	rlm_sqlippool_t * data = (rlm_sqlippool_t *) instance;
	char expansion[MAX_STRING_LEN * 4];
	char query[MAX_STRING_LEN * 4];
	SQL_ROW row;
	int r;

	sqlippool_expand(expansion, sizeof(expansion), fmt, instance, param, param_len);

	/*
	 * Do an xlat on the provided string
	 */
	if (request) {
		char sqlusername[MAX_STRING_LEN];

		if(sql_set_user(data->sql_inst, request, sqlusername, NULL) < 0) {
			return RLM_MODULE_FAIL;
		}

		if (!radius_xlat(query, sizeof(query), expansion, request, NULL)) {
			radlog(L_ERR, "sqlippool_command: xlat failed.");
			out[0] = '\0';
			return 0;
		}
	}
	else {
		strcpy(query, expansion);
	}

#if 0
	DEBUG2("sqlippool_query1: '%s'", query);
#endif
	if (rlm_sql_select_query(sqlsocket, data->sql_inst, query)){
		radlog(L_ERR, "sqlippool_query1: database query error");
		out[0] = '\0';
		return 0;
	}

	r = rlm_sql_fetch_row(sqlsocket, data->sql_inst);
	(data->sql_inst->module->sql_finish_select_query)(sqlsocket, data->sql_inst->config);

	if (r) {
		DEBUG("sqlippool_query1: SQL query did not succeed");
		out[0] = '\0';
		return 0;
	}

	row = sqlsocket->row;
	if (row == NULL) {
		DEBUG("sqlippool_query1: SQL query did not return any results");
		out[0] = '\0';
		return 0;
	}

	if (row[0] == NULL){
		DEBUG("sqlippool_query1: row[0] returned NULL");
		out[0] = '\0';
		return 0;
	}

	r = strlen(row[0]);
	if (r >= outlen){
		DEBUG("sqlippool_query1: insufficient string space");
		out[0] = '\0';
		return 0;
	}

	strncpy(out, row[0], r);
	out[r] = '\0';

	return r;
}

static int sqlippool_initialize_sql(void * instance)
{

	rlm_sqlippool_t * data = (rlm_sqlippool_t *) instance;

	SQLSOCK * sqlsocket;

	sqlsocket = sql_get_socket(data->sql_inst);
	if (sqlsocket == NULL) {
		DEBUG("rlm_sqlippool: cannot allocate sql connection for initialization sequence");
		return 0;
	}

	return 1;
}

/*
 *	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 sqlippool_instantiate(CONF_SECTION * conf, void ** instance)
{
	rlm_sqlippool_t * data;
	char * pool_name = NULL;

	/*
	 *	Set up a storage area for instance data
	 */
	data = rad_malloc(sizeof(*data));
	memset(data, 0, sizeof(*data));

	/*
	 *	If the configuration parameters can't be parsed, then
	 *	fail.
	 */
	if (cf_section_parse(conf, data, module_config) < 0) {
		free(data);
		return -1;
	}

	if (data->sql_instance_name == NULL || strlen(data->sql_instance_name) == 0) {
		radlog(L_ERR, "rlm_sqlippool: the 'sql-instance-name' variable must be set.");
		free(data);
		exit(0);
	}

	/*
	 *	Check that all the queries are in place
	 */

	if (data->allocate_clear == NULL || strlen(data->allocate_clear) == 0) {
		radlog(L_ERR, "rlm_sqlippool: the 'allocate-clear' statement must be set.");
		free(data);
		exit(0);
	}

	if (data->allocate_find == NULL || strlen(data->allocate_find) == 0) {
		radlog(L_ERR, "rlm_sqlippool: the 'allocate_find' statement must be set.");
		free(data);
		exit(0);
	}

	if (data->allocate_update == NULL || strlen(data->allocate_update) == 0) {
		radlog(L_ERR, "rlm_sqlippool: the 'allocate_update' statement must be set.");
		free(data);
		exit(0);
	}

	if (data->start_update == NULL || strlen(data->start_update) == 0) {
		radlog(L_ERR, "rlm_sqlippool: the 'start-update' statement must be set.");
		free(data);
		exit(0);
	}

	if (data->alive_update == NULL || strlen(data->alive_update) == 0) {
		radlog(L_ERR, "rlm_sqlippool: the 'alive-update' statement must be set.");
		free(data);
		exit(0);
	}

	if (data->stop_clear == NULL || strlen(data->stop_clear) == 0) {
		radlog(L_ERR, "rlm_sqlippool: the 'stop-clear' statement must be set.");
		free(data);
		exit(0);
	}

	if (data->on_clear == NULL || strlen(data->on_clear) == 0) {
		radlog(L_ERR, "rlm_sqlippool: the 'on-clear' statement must be set.");
		free(data);
		exit(0);
	}

	if (data->off_clear == NULL || strlen(data->off_clear) == 0) {
		radlog(L_ERR, "rlm_sqlippool: the 'off-clear' statement must be set.");
		free(data);
		exit(0);
	}

	pool_name = cf_section_name2(conf);
	if (pool_name != NULL)
		data->pool_name = strdup(pool_name);
	else
		data->pool_name = strdup("ippool");

	if ( !(data->sql_inst = (SQL_INST *) (find_module_instance(data->sql_instance_name))->insthandle) )
	{
		radlog(L_ERR, "sqlippool_instantiate: failed to find sql instance named %s", data->sql_instance_name);
		free(data);
		exit(0);
	}

	sqlippool_initialize_sql(data);
	pthread_mutex_init(&data->dlock, NULL);
	*instance = data;
	return 0;
}

/*
 *	Allocate an IP number from the pool.
 */
static int sqlippool_postauth(void *instance, REQUEST * request)
{
	rlm_sqlippool_t * data = (rlm_sqlippool_t *) instance;
	char allocation[MAX_STRING_LEN];
	int allocation_len;
	uint32_t ip_allocation;
	VALUE_PAIR * vp;
	SQLSOCK * sqlsocket;
	long self = (long) pthread_self();

	/*
	 * If there is a Framed-IP-Address attribute in the reply do nothing
	 */
	if (pairfind(request->reply->vps, PW_FRAMED_IP_ADDRESS) != NULL) {
		DEBUG("rlm_sqlippool: Framed-IP-Address already exists");
		return RLM_MODULE_NOOP;
	}

	if ((vp = pairfind(request->config_items, PW_POOL_NAME)) != NULL) {
		DEBUG("Value Of the Pool-Name is [%s] and its [%i] Chars \n", vp->strvalue, vp->length);
		pthread_mutex_lock(&data->dlock);
		strNcpy(data->pool_name, vp->strvalue, (vp->length + 1));
		pthread_mutex_unlock(&data->dlock);	
	}
	else {
		DEBUG("rlm_sqlippool: missing pool_name");
		return RLM_MODULE_NOOP;
	}
	
	if (pairfind(request->packet->vps, PW_NAS_IP_ADDRESS) == NULL) {
		DEBUG("rlm_sqlippool: unknown NAS-IP-Address");
		return RLM_MODULE_NOOP;
	}

	if (pairfind(request->packet->vps, PW_NAS_PORT) == NULL) {
		DEBUG("rlm_sqlippool: unknown NAS-Port");
		return RLM_MODULE_NOOP;
	}

	sqlsocket = sql_get_socket(data->sql_inst);
	if (sqlsocket == NULL) {
		DEBUG("rlm_sqlippool: cannot allocate sql connection");
		return RLM_MODULE_NOOP;
	}

	/*
	 * BEGIN
	 */
	sqlippool_command(data->allocate_begin, sqlsocket, instance, request,
			  (char *) NULL, 0);

	/*
	 * CLEAR
	 */
	sqlippool_command(data->allocate_clear, sqlsocket, instance, request,
			  (char *) NULL, 0);

	/*
	 * FIND
	 */
	allocation_len = sqlippool_query1(allocation, sizeof(allocation),
					  data->allocate_find, sqlsocket, instance, request,
					  (char *) NULL, 0);
	radlog(L_INFO,"rlm_sqlippool: ip=[%s] len=%d", allocation, allocation_len);

	if (allocation_len == 0)
	{	
		/*
		 * COMMIT
		 */
		sqlippool_command(data->allocate_commit, sqlsocket, instance, request,
				  (char *) NULL, 0);

		DEBUG("rlm_sqlippool: IP number could not be allocated.");
		sql_release_socket(data->sql_inst, sqlsocket);
		return RLM_MODULE_NOTFOUND;
	}

	ip_allocation = ip_addr(allocation);
	if (ip_allocation == INADDR_NONE)
	{
		/*
		 * COMMIT
		 */
		sqlippool_command(data->allocate_commit, sqlsocket, instance, request,
				  (char *) NULL, 0);

		DEBUG("rlm_sqlippool: Invalid IP number [%s] returned from database query.", allocation);
		sql_release_socket(data->sql_inst, sqlsocket);
		return RLM_MODULE_NOOP;
	}

	/*
	 * UPDATE
	 */
	sqlippool_command(data->allocate_update, sqlsocket, instance, request,
			  allocation, allocation_len);

	DEBUG("rlm_sqlippool: Allocated IP %s [%08x]", allocation, ip_allocation);

	if ((vp = paircreate(PW_FRAMED_IP_ADDRESS, PW_TYPE_IPADDR)) == NULL) {
		radlog(L_ERR|L_CONS, "no memory");
		sql_release_socket(data->sql_inst, sqlsocket);
		return RLM_MODULE_NOOP;
	}
	vp->lvalue = ip_allocation;
	pairadd(&request->reply->vps, vp);

	/*
	 * COMMIT
	 */
	sqlippool_command(data->allocate_commit, sqlsocket, instance, request,
			  (char *) NULL, 0);

	sql_release_socket(data->sql_inst, sqlsocket);
	return RLM_MODULE_OK;
}

static int sqlippool_accounting_start(void * instance, REQUEST * request)
{
	rlm_sqlippool_t * data = (rlm_sqlippool_t *) instance;
	SQLSOCK * sqlsocket;

	if (pairfind(request->packet->vps, PW_NAS_PORT) == NULL) {
		DEBUG("rlm_ippool: Could not find port number in packet.");
		return RLM_MODULE_NOOP;
	}

	if (pairfind(request->packet->vps, PW_NAS_IP_ADDRESS) == NULL) {
		DEBUG("rlm_ippool: Could not find nas information in packet.");
		return RLM_MODULE_NOOP;
	}

	sqlsocket = sql_get_socket(data->sql_inst);
	if (sqlsocket == NULL) {
		DEBUG("rlm_sqlippool: cannot allocate sql connection");
		return RLM_MODULE_NOOP;
	}

	/*
	 * BEGIN
	 */
	sqlippool_command(data->start_begin, sqlsocket, instance, request,
			  (char *) NULL, 0);

	/*
	 * UPDATE
	 */
	sqlippool_command(data->start_update, sqlsocket, instance, request,
			  (char *) NULL, 0);

	/*
	 * COMMIT
	 */
	sqlippool_command(data->start_commit, sqlsocket, instance, request,
			  (char *) NULL, 0);

	sql_release_socket(data->sql_inst, sqlsocket);

	return RLM_MODULE_OK;
}

static int sqlippool_accounting_alive(void * instance, REQUEST * request)
{
	rlm_sqlippool_t * data = (rlm_sqlippool_t *) instance;
	SQLSOCK * sqlsocket;

	if (pairfind(request->packet->vps, PW_NAS_PORT) == NULL) {
		DEBUG("rlm_ippool: Could not find port number in packet.");
		return RLM_MODULE_NOOP;
	}

	if (pairfind(request->packet->vps, PW_NAS_IP_ADDRESS) == NULL) {
		DEBUG("rlm_ippool: Could not find nas information in packet.");
		return RLM_MODULE_NOOP;
	}

	sqlsocket = sql_get_socket(data->sql_inst);
	if (sqlsocket == NULL) {
		DEBUG("rlm_sqlippool: cannot allocate sql connection");
		return RLM_MODULE_NOOP;
	}

	/*
	 * BEGIN
	 */
	sqlippool_command(data->alive_begin, sqlsocket, instance, request,
			  (char *) NULL, 0);

	/*
	 * UPDATE
	 */
	sqlippool_command(data->alive_update, sqlsocket, instance, request,
			  (char *) NULL, 0);

	/*
	 * COMMIT
	 */
	sqlippool_command(data->alive_commit, sqlsocket, instance, request,
			  (char *) NULL, 0);

	sql_release_socket(data->sql_inst, sqlsocket);

	return RLM_MODULE_OK;
}

static int sqlippool_accounting_stop(void * instance, REQUEST * request)
{
	rlm_sqlippool_t * data = (rlm_sqlippool_t *) instance;
	SQLSOCK * sqlsocket;

	if (pairfind(request->packet->vps, PW_NAS_PORT) == NULL) {
		DEBUG("rlm_ippool: Could not find port number in packet.");
		return RLM_MODULE_NOOP;
	}

	if (pairfind(request->packet->vps, PW_NAS_IP_ADDRESS) == NULL) {
		DEBUG("rlm_ippool: Could not find nas information in packet.");
		return RLM_MODULE_NOOP;
	}

	sqlsocket = sql_get_socket(data->sql_inst);
	if (sqlsocket == NULL) {
		DEBUG("rlm_sqlippool: cannot allocate sql connection");
		return RLM_MODULE_NOOP;
	}

	/*
	 * BEGIN
	 */
	sqlippool_command(data->stop_begin, sqlsocket, instance, request,
			  (char *) NULL, 0);

	/*
	 * CLEAR
	 */
	sqlippool_command(data->stop_clear, sqlsocket, instance, request,
			  (char *) NULL, 0);

	/*
	 * COMMIT
	 */
	sqlippool_command(data->stop_commit, sqlsocket, instance, request,
			  (char *) NULL, 0);

	sql_release_socket(data->sql_inst, sqlsocket);

	return RLM_MODULE_OK;
}

static int sqlippool_accounting_on(void * instance, REQUEST * request)
{
	rlm_sqlippool_t * data = (rlm_sqlippool_t *) instance;
	SQLSOCK * sqlsocket;

	if (pairfind(request->packet->vps, PW_NAS_IP_ADDRESS) == NULL) {
		DEBUG("rlm_ippool: Could not find nas information in packet.");
		return RLM_MODULE_NOOP;
	}

	sqlsocket = sql_get_socket(data->sql_inst);
	if (sqlsocket == NULL) {
		DEBUG("rlm_sqlippool: cannot allocate sql connection");
		return RLM_MODULE_NOOP;
	}

	/*
	 * BEGIN
	 */
	sqlippool_command(data->on_begin, sqlsocket, instance, request,
			  (char *) NULL, 0);

	/*
	 * CLEAR
	 */
	sqlippool_command(data->on_clear, sqlsocket, instance, request,
			  (char *) NULL, 0);

	/*
	 * COMMIT
	 */
	sqlippool_command(data->on_commit, sqlsocket, instance, request,
			  (char *) NULL, 0);

	sql_release_socket(data->sql_inst, sqlsocket);

	return RLM_MODULE_OK;
}

static int sqlippool_accounting_off(void * instance, REQUEST * request)
{
	rlm_sqlippool_t * data = (rlm_sqlippool_t *) instance;
	SQLSOCK * sqlsocket;

	if (pairfind(request->packet->vps, PW_NAS_IP_ADDRESS) == NULL) {
		DEBUG("rlm_ippool: Could not find nas information in packet.");
		return RLM_MODULE_NOOP;
	}

	sqlsocket = sql_get_socket(data->sql_inst);
	if (sqlsocket == NULL) {
		DEBUG("rlm_sqlippool: cannot allocate sql connection");
		return RLM_MODULE_NOOP;
	}

	/*
	 * BEGIN
	 */
	sqlippool_command(data->off_begin, sqlsocket, instance, request,
			  (char *) NULL, 0);

	/*
	 * CLEAR
	 */
	sqlippool_command(data->off_clear, sqlsocket, instance, request,
			  (char *) NULL, 0);

	/*
	 * COMMIT
	 */
	sqlippool_command(data->off_commit, sqlsocket, instance, request,
			  (char *) NULL, 0);

	sql_release_socket(data->sql_inst, sqlsocket);

	return RLM_MODULE_OK;
}

/*
 *	Check for an Accounting-Stop
 *	If we find one and we have allocated an IP to this nas/port combination, deallocate it.	
 */
static int sqlippool_accounting(void * instance, REQUEST * request)
{
	VALUE_PAIR * vp;
	int acct_status_type;

	vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE);
	if (vp == NULL) {
		DEBUG("rlm_sqlippool: Could not find account status type in packet.");
		return RLM_MODULE_NOOP;
	}
	acct_status_type = vp->lvalue;

	switch (acct_status_type) {
	case PW_STATUS_START:
		return sqlippool_accounting_start(instance, request);

	case PW_STATUS_ALIVE:
		return sqlippool_accounting_alive(instance, request);

	case PW_STATUS_STOP:
		return sqlippool_accounting_stop(instance, request);

	case PW_STATUS_ACCOUNTING_ON:
		return sqlippool_accounting_on(instance, request);

	case PW_STATUS_ACCOUNTING_OFF:
		return sqlippool_accounting_off(instance, request);

	default:
		/* We don't care about any other accounting packet */
		return RLM_MODULE_NOOP;
	}
}

static int sqlippool_detach(void *instance)
{
	rlm_sqlippool_t * data = (rlm_sqlippool_t *) instance;

	free(data->sql_instance_name);
	free(data->pool_name);

	free(data->allocate_begin);
	free(data->allocate_clear);
	free(data->allocate_find);
	free(data->allocate_update);
	free(data->allocate_commit);
	free(data->allocate_rollback);

	free(data->start_begin);
	free(data->start_update);
	free(data->start_commit);
	free(data->start_rollback);

	free(data->alive_begin);
	free(data->alive_update);
	free(data->alive_commit);
	free(data->alive_rollback);

	free(data->stop_begin);
	free(data->stop_clear);
	free(data->stop_commit);
	free(data->stop_rollback);

	free(data->on_begin);
	free(data->on_clear);
	free(data->on_commit);
	free(data->on_rollback);

	free(data->off_begin);
	free(data->off_clear);
	free(data->off_commit);
	free(data->off_rollback);

	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.
 */
module_t rlm_sqlippool = {
	"SQL IP Pool",	
	RLM_TYPE_THREAD_SAFE,		/* type */
	NULL,				/* initialization */
	sqlippool_instantiate,		/* instantiation */
	{
		NULL,			/* authentication */
		NULL,			/* authorization */
		NULL,			/* preaccounting */
		sqlippool_accounting,	/* accounting */
		NULL,			/* checksimul */
		NULL,			/* pre-proxy */
		NULL,			/* post-proxy */
		sqlippool_postauth	/* post-auth */
	},
	sqlippool_detach,		/* detach */
	NULL,				/* destroy */
};


syntax highlighted by Code2HTML, v. 0.9.1