/*
 * rlm_sql.c		SQL Module
 * 		Main SQL module file. Most ICRADIUS code is located in sql.c
 *
 * Version:	$Id: rlm_sql.c,v 1.131.2.8.2.5 2007/07/17 12:59:08 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 2000  The FreeRADIUS server project
 * Copyright 2000  Mike Machado <mike@innercite.com>
 * Copyright 2000  Alan DeKok <aland@ox.org>
 */

static const char rcsid[] =
	"$Id: rlm_sql.c,v 1.131.2.8.2.5 2007/07/17 12:59:08 pnixon Exp $";

#include "autoconf.h"

#include <stdio.h>
#include <sys/stat.h>
#include <stdlib.h>

#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "radiusd.h"
#include "modules.h"
#include "conffile.h"
#include "rlm_sql.h"
#include "rad_assert.h"

static char *allowed_chars = NULL;

static CONF_PARSER module_config[] = {
	{"driver",PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,sql_driver), NULL, "mysql"},
	{"server",PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,sql_server), NULL, "localhost"},
	{"port",PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,sql_port), NULL, ""},
	{"login", PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,sql_login), NULL, ""},
	{"password", PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,sql_password), NULL, ""},
	{"radius_db", PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,sql_db), NULL, "radius"},
	{"nas_table", PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,sql_nas_table), NULL, "nas"},
	{"sqltrace", PW_TYPE_BOOLEAN,
	 offsetof(SQL_CONFIG,sqltrace), NULL, "no"},
	{"sqltracefile", PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,tracefile), NULL, SQLTRACEFILE},
	{"readclients", PW_TYPE_BOOLEAN,
	 offsetof(SQL_CONFIG,do_clients), NULL, "no"},
	{"deletestalesessions", PW_TYPE_BOOLEAN,
	 offsetof(SQL_CONFIG,deletestalesessions), NULL, "no"},
	{"num_sql_socks", PW_TYPE_INTEGER,
	 offsetof(SQL_CONFIG,num_sql_socks), NULL, "5"},
	{"sql_user_name", PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,query_user), NULL, ""},
	{"default_user_profile", PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,default_profile), NULL, ""},
	{"query_on_not_found", PW_TYPE_BOOLEAN,
	 offsetof(SQL_CONFIG,query_on_not_found), NULL, "no"},
	{"authorize_check_query", PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,authorize_check_query), NULL, ""},
	{"authorize_reply_query", PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,authorize_reply_query), NULL, ""},
	{"authorize_group_check_query", PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,authorize_group_check_query), NULL, ""},
	{"authorize_group_reply_query", PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,authorize_group_reply_query), NULL, ""},
	{"accounting_onoff_query", PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,accounting_onoff_query), NULL, ""},
	{"accounting_update_query", PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,accounting_update_query), NULL, ""},
	{"accounting_update_query_alt", PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,accounting_update_query_alt), NULL, ""},
	{"accounting_start_query", PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,accounting_start_query), NULL, ""},
	{"accounting_start_query_alt", PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,accounting_start_query_alt), NULL, ""},
	{"accounting_stop_query", PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,accounting_stop_query), NULL, ""},
	{"accounting_stop_query_alt", PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,accounting_stop_query_alt), NULL, ""},
	{"group_membership_query", PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,groupmemb_query), NULL, ""},
	{"connect_failure_retry_delay", PW_TYPE_INTEGER,
	 offsetof(SQL_CONFIG,connect_failure_retry_delay), NULL, "60"},
	{"simul_count_query", PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,simul_count_query), NULL, ""},
	{"simul_verify_query", PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,simul_verify_query), NULL, ""},
	{"postauth_query", PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,postauth_query), NULL, ""},
	{"safe-characters", PW_TYPE_STRING_PTR,
	 offsetof(SQL_CONFIG,allowed_chars), NULL, 
	"@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /"},

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

/***********************************************************************
 * start of main routines
 ***********************************************************************/
static int rlm_sql_init(void) {

	/*
	 * FIXME:
	 * We should put the sqlsocket array here once
	 * the module code is reworked to not unload
	 * modules on HUP.  This way we can have
	 * persistant connections.  -jcarneal
	 */
	return 0;
}

/*
 *	Yucky prototype.
 */
static int generate_sql_clients(SQL_INST *inst);
static int sql_escape_func(char *out, int outlen, const char *in);

/*
 *	sql xlat function. Right now only SELECTs are supported. Only
 *	the first element of the SELECT result will be used.
 */
static int sql_xlat(void *instance, REQUEST *request,
		    char *fmt, char *out, size_t freespace,
		    RADIUS_ESCAPE_STRING func)
{
	SQLSOCK *sqlsocket;
	SQL_ROW row;
	SQL_INST *inst = instance;
	char querystr[MAX_QUERY_LEN];
	char sqlusername[MAX_STRING_LEN];
	int ret = 0;

	DEBUG("rlm_sql (%s): - sql_xlat", inst->config->xlat_name);
	/*
         * Add SQL-User-Name attribute just in case it is needed
         *  We could search the string fmt for SQL-User-Name to see if this is
         *  needed or not
         */
	sql_set_user(inst, request, sqlusername, NULL);
	/*
	 * Do an xlat on the provided string (nice recursive operation).
	 */
	if (!radius_xlat(querystr, sizeof(querystr), fmt, request, sql_escape_func)) {
		radlog(L_ERR, "rlm_sql (%s): xlat failed.",
		       inst->config->xlat_name);
		return 0;
	}

	query_log(request, inst,querystr);
	sqlsocket = sql_get_socket(inst);
	if (sqlsocket == NULL)
		return 0;
	if (rlm_sql_select_query(sqlsocket,inst,querystr)){
		radlog(L_ERR, "rlm_sql (%s): database query error, %s: %s",
		       inst->config->xlat_name,querystr,
		       (char *)(inst->module->sql_error)(sqlsocket, inst->config));
		sql_release_socket(inst,sqlsocket);
		return 0;
	}

	ret = rlm_sql_fetch_row(sqlsocket, inst);

	if (ret) {
		DEBUG("rlm_sql (%s): SQL query did not succeed",
		      inst->config->xlat_name);
		(inst->module->sql_finish_select_query)(sqlsocket, inst->config);
		sql_release_socket(inst,sqlsocket);
		return 0;
	}

	row = sqlsocket->row;
	if (row == NULL) {
		DEBUG("rlm_sql (%s): SQL query did not return any results",
		      inst->config->xlat_name);
		(inst->module->sql_finish_select_query)(sqlsocket, inst->config);
		sql_release_socket(inst,sqlsocket);
		return 0;
	}

	if (row[0] == NULL){
		DEBUG("rlm_sql (%s): row[0] returned NULL",
		      inst->config->xlat_name);
		(inst->module->sql_finish_select_query)(sqlsocket, inst->config);
		sql_release_socket(inst,sqlsocket);
		return 0;
	}
	ret = strlen(row[0]);
	if (ret > freespace){
		DEBUG("rlm_sql (%s): sql_xlat:: Insufficient string space",
		      inst->config->xlat_name);
		(inst->module->sql_finish_select_query)(sqlsocket, inst->config);
		sql_release_socket(inst,sqlsocket);
		return 0;
	}

	strncpy(out,row[0],ret);

	DEBUG("rlm_sql (%s): - sql_xlat finished",
	      inst->config->xlat_name);

	(inst->module->sql_finish_select_query)(sqlsocket, inst->config);
	sql_release_socket(inst,sqlsocket);
	return ret;
}

static int generate_sql_clients(SQL_INST *inst)
{
	SQLSOCK *sqlsocket;
	SQL_ROW row;
	char querystr[MAX_QUERY_LEN];
	RADCLIENT *c;
	char *netmask;
	unsigned int i = 0;
	
	DEBUG("rlm_sql (%s): - generate_sql_clients",inst->config->xlat_name);

	if (inst->config->sql_nas_table == NULL){
		radlog(L_ERR, "rlm_sql (%s): sql_nas_table is NULL.",inst->config->xlat_name);
		return -1;
	}
	snprintf(querystr,MAX_QUERY_LEN - 1,"SELECT * FROM %s",inst->config->sql_nas_table);

	DEBUG("rlm_sql (%s): Query: %s",inst->config->xlat_name,querystr);
	sqlsocket = sql_get_socket(inst);
	if (sqlsocket == NULL)
		return -1;
	if (rlm_sql_select_query(sqlsocket,inst,querystr)){
		radlog(L_ERR, "rlm_sql (%s): database query error, %s: %s",
			inst->config->xlat_name,querystr,
			(char *)(inst->module->sql_error)(sqlsocket, inst->config));
		sql_release_socket(inst,sqlsocket);
		return -1;
	}

	while(rlm_sql_fetch_row(sqlsocket, inst) == 0) {
		i++;
		row = sqlsocket->row;
		if (row == NULL)
			break;
/*
 * Format:
 * Row1	Row2	Row3		Row4	Row5	Row6	Row7		Row8
 *
 * id	nasname	shortname	type	ports	secret	community	description
 *
 */

		if (!row[0]){
			radlog(L_ERR, "rlm_sql (%s): No row id found on pass %d",inst->config->xlat_name,i);
			continue;
		}
		if (!row[1]){
			radlog(L_ERR, "rlm_sql (%s): No nasname found for row %s",inst->config->xlat_name,row[0]);
			continue;
		}
		if (strlen(row[1]) >= sizeof(c->longname)){
			radlog(L_ERR, "rlm_sql (%s): nasname of length %d is greater than the allowed maximum of %d",
				inst->config->xlat_name,strlen(row[1]),sizeof(c->longname) - 1);
			continue;
		}	
		
		if (!row[2]){
			radlog(L_ERR, "rlm_sql (%s): No short name found for row %s",inst->config->xlat_name,row[0]);
			continue;
		}
		if (strlen(row[2]) >= sizeof(c->shortname)){
			radlog(L_ERR, "rlm_sql (%s): shortname of length %d is greater than the allowed maximum of %d",
				inst->config->xlat_name,strlen(row[2]),sizeof(c->shortname) - 1);
			continue;
		}
		if (row[3] && strlen(row[3]) >= sizeof(c->nastype)){
			radlog(L_ERR, "rlm_sql (%s): nastype of length %d is greater than the allowed maximum of %d",
				inst->config->xlat_name,strlen(row[3]),sizeof(c->nastype) - 1);
			continue;
		}
		if (!row[5]){
			radlog(L_ERR, "rlm_sql (%s): No secret found for row %s",inst->config->xlat_name,row[0]);
			continue;
		}
		if (strlen(row[5]) >= sizeof(c->secret)){
			radlog(L_ERR, "rlm_sql (%s): secret of length %d is greater than the allowed maximum of %d",
				inst->config->xlat_name,strlen(row[5]),sizeof(c->secret) - 1);
			continue;
		}

		DEBUG("rlm_sql (%s): Read entry nasname=%s,shortname=%s,secret=%s",inst->config->xlat_name,
			row[1],row[2],row[5]);

		c = rad_malloc(sizeof(RADCLIENT));
		memset(c, 0, sizeof(RADCLIENT));

		c->netmask = ~0;
		netmask = strchr(row[1], '/');
		
		/*
		 *      Look for netmasks.
		 */
		c->netmask = ~0;
		if (netmask) {
			int mask_length;

			mask_length = atoi(netmask + 1);
			if ((mask_length < 0) || (mask_length > 32)) {
				radlog(L_ERR, "rlm_sql (%s): Invalid value '%s' for IP network mask for nasname %s.",
						inst->config->xlat_name, netmask + 1,row[1]);
				free(c);
				continue;
			}

			if (mask_length == 0) {
				c->netmask = 0;
			} else {
				c->netmask = ~0 << (32 - mask_length);
			}

			*netmask = '\0';
			c->netmask = htonl(c->netmask);
		}

		c->ipaddr = ip_getaddr(row[1]);
		if (c->ipaddr == INADDR_NONE) {
			radlog(L_CONS|L_ERR, "rlm_sql (%s): Failed to look up hostname %s",
					inst->config->xlat_name, row[1]);
			free(c);
			continue;
		}

		/*
		 *      Update the client name again...
		 */
		if (netmask) {
			*netmask = '/';
			c->ipaddr &= c->netmask;
			strcpy(c->longname, row[1]);
		} else {
			ip_hostname(c->longname, sizeof(c->longname),
					c->ipaddr);
		}

		strcpy((char *)c->secret, row[5]);
		strcpy(c->shortname, row[2]);
		if(row[3] != NULL)
			strcpy(c->nastype, row[3]);

		DEBUG("rlm_sql (%s): Adding client %s (%s) to clients list",inst->config->xlat_name,
			c->longname,c->shortname);

		c->next = mainconfig.clients;
		mainconfig.clients = c;

	}
	(inst->module->sql_finish_select_query)(sqlsocket, inst->config);
	sql_release_socket(inst, sqlsocket);

	return 0;
}


/*
 *	Translate the SQL queries.
 */
static int sql_escape_func(char *out, int outlen, const char *in)
{
	int len = 0;

	while (in[0]) {
		/*
		 *	Non-printable characters get replaced with their
		 *	mime-encoded equivalents.
		 */
		if ((in[0] < 32) ||
		    strchr(allowed_chars, *in) == NULL) {
			/*
			 *	Only 3 or less bytes available.
			 */
			if (outlen <= 3) {
				break;
			}

			snprintf(out, outlen, "=%02X", (unsigned char) in[0]);
			in++;
			out += 3;
			outlen -= 3;
			len += 3;
			continue;
		}

		/*
		 *	Only one byte left.
		 */
		if (outlen <= 1) {
			break;
		}

		/*
		 *	Allowed character.
		 */
		*out = *in;
		out++;
		in++;
		outlen--;
		len++;
	}
	*out = '\0';
	return len;
}

/*
 *	Set the SQL user name.
 *
 *	We don't call the escape function here. The resulting string
 *	will be escaped later in the queries xlat so we don't need to
 *	escape it twice. (it will make things wrong if we have an
 *	escape candidate character in the username)
 */
int sql_set_user(SQL_INST *inst, REQUEST *request, char *sqlusername, const char *username)
{
	VALUE_PAIR *vp=NULL;
	char tmpuser[MAX_STRING_LEN];

	tmpuser[0] = '\0';
	sqlusername[0]= '\0';

	/* Remove any user attr we added previously */
	pairdelete(&request->packet->vps, PW_SQL_USER_NAME);

	if (username != NULL) {
		strNcpy(tmpuser, username, MAX_STRING_LEN);
	} else if (strlen(inst->config->query_user)) {
		radius_xlat(tmpuser, sizeof(tmpuser), inst->config->query_user, request, NULL);
	} else {
		return 0;
	}

	if (*tmpuser) {
		strNcpy(sqlusername, tmpuser, MAX_STRING_LEN);
		DEBUG2("rlm_sql (%s): sql_set_user escaped user --> '%s'",
		       inst->config->xlat_name, sqlusername);
		vp = pairmake("SQL-User-Name", sqlusername, 0);
		if (vp == NULL) {
			radlog(L_ERR, "%s", librad_errstr);
			return -1;
		}

		pairadd(&request->packet->vps, vp);
		return 0;
	}
	return -1;
}

/*
 * sql groupcmp function. That way we can do group comparisons (in the users file for example)
 * with the group memberships reciding in sql
 * The group membership query should only return one element which is the username. The returned
 * username will then be checked with the passed check string.
 */

static int sql_groupcmp(void *instance, REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check,
			VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
{
	SQLSOCK *sqlsocket;
	SQL_ROW row;
	SQL_INST *inst = instance;
	char querystr[MAX_QUERY_LEN];
	char sqlusername[MAX_STRING_LEN];

	check_pairs = check_pairs;
	reply_pairs = reply_pairs;

	DEBUG("rlm_sql (%s): - sql_groupcmp", inst->config->xlat_name);
	if (!check || !check->strvalue || !check->length){
		DEBUG("rlm_sql (%s): sql_groupcmp: Illegal group name",
		      inst->config->xlat_name);
		return 1;
	}
	if (req == NULL){
		DEBUG("rlm_sql (%s): sql_groupcmp: NULL request",
		      inst->config->xlat_name);
		return 1;
	}
	if (inst->config->groupmemb_query[0] == 0)
		return 1;
	/*
	 * Set, escape, and check the user attr here
	 */
	if (sql_set_user(inst, req, sqlusername, NULL) < 0)
		return 1;
	if (!radius_xlat(querystr, sizeof(querystr), inst->config->groupmemb_query, req, sql_escape_func)){
		radlog(L_ERR, "rlm_sql (%s): xlat failed.",
		       inst->config->xlat_name);
		/* Remove the username we (maybe) added above */
		pairdelete(&req->packet->vps, PW_SQL_USER_NAME);
		return 1;
	}
	/* Remove the username we (maybe) added above */
	pairdelete(&req->packet->vps, PW_SQL_USER_NAME);

	sqlsocket = sql_get_socket(inst);
	if (sqlsocket == NULL)
		return 1;
	if ((inst->module->sql_select_query)(sqlsocket,inst->config,querystr) <0){
		radlog(L_ERR, "rlm_sql (%s): database query error, %s: %s",
		       inst->config->xlat_name,querystr,
		       (char *)(inst->module->sql_error)(sqlsocket,inst->config));
		sql_release_socket(inst,sqlsocket);
		return 1;
	}
	while (rlm_sql_fetch_row(sqlsocket, inst) == 0) {
		row = sqlsocket->row;
		if (row == NULL)
			break;
		if (row[0] == NULL){
			DEBUG("rlm_sql (%s): row[0] returned NULL",
			      inst->config->xlat_name);
			(inst->module->sql_finish_select_query)(sqlsocket, inst->config);
			sql_release_socket(inst, sqlsocket);
			return 1;
		}
		if (strcmp(row[0],check->strvalue) == 0){
			DEBUG("rlm_sql (%s): - sql_groupcmp finished: User belongs in group %s",
			      inst->config->xlat_name,
			      (char *)check->strvalue);
			(inst->module->sql_finish_select_query)(sqlsocket, inst->config);			sql_release_socket(inst, sqlsocket);
			return 0;
		}
	}

	(inst->module->sql_finish_select_query)(sqlsocket, inst->config);
	sql_release_socket(inst,sqlsocket);

	DEBUG("rlm_sql (%s): - sql_groupcmp finished: User does not belong in group %s",
	      inst->config->xlat_name, (char *)check->strvalue);

	return 1;
}


static int rlm_sql_detach(void *instance)
{
	SQL_INST *inst = instance;

	paircompare_unregister(PW_SQL_GROUP, sql_groupcmp);

	if (inst->config) {
		int i;

		if (inst->sqlpool) {
			sql_poolfree(inst);
		}

		if (inst->config->xlat_name) {
			xlat_unregister(inst->config->xlat_name,sql_xlat);
			free(inst->config->xlat_name);
		}

		/*
		 *	Free up dynamically allocated string pointers.
		 */
		for (i = 0; module_config[i].name != NULL; i++) {
			char **p;
			if (module_config[i].type != PW_TYPE_STRING_PTR) {
				continue;
			}

			/*
			 *	Treat 'config' as an opaque array of bytes,
			 *	and take the offset into it.  There's a
			 *      (char*) pointer at that offset, and we want
			 *	to point to it.
			 */
			p = (char **) (((char *)inst->config) + module_config[i].offset);
			if (!*p) { /* nothing allocated */
				continue;
			}
			free(*p);
			*p = NULL;
		}
		allowed_chars = NULL;
		free(inst->config);
		inst->config = NULL;
	}

	if (inst->handle) {
#if 0
		/*
		 *	FIXME: Call the modules 'destroy' function?
		 */
		lt_dlclose(inst->handle);	/* ignore any errors */
#endif
	}
	free(inst);

	return 0;
}
static int rlm_sql_instantiate(CONF_SECTION * conf, void **instance)
{
	SQL_INST *inst;
	char *xlat_name;

	inst = rad_malloc(sizeof(SQL_INST));
	memset(inst, 0, sizeof(SQL_INST));

	inst->config = rad_malloc(sizeof(SQL_CONFIG));
	memset(inst->config, 0, sizeof(SQL_CONFIG));

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

	xlat_name = cf_section_name2(conf);
	if (xlat_name == NULL)
		xlat_name = cf_section_name1(conf);
	if (xlat_name){
		inst->config->xlat_name = strdup(xlat_name);
		xlat_register(xlat_name, sql_xlat, inst);
	}

	if (inst->config->num_sql_socks > MAX_SQL_SOCKS) {
		radlog(L_ERR | L_CONS, "rlm_sql (%s): sql_instantiate: number of sqlsockets cannot exceed MAX_SQL_SOCKS, %d",
		       inst->config->xlat_name, MAX_SQL_SOCKS);
		rlm_sql_detach(inst);
		return -1;
	}

	/*
	 *	Sanity check for crazy people.
	 */
	if (strncmp(inst->config->sql_driver, "rlm_sql_", 8) != 0) {
		radlog(L_ERR, "rlm_sql (%s): \"%s\" is NOT an SQL driver!",
		       inst->config->xlat_name, inst->config->sql_driver);
		rlm_sql_detach(inst);
		return -1;
	}

	inst->handle = lt_dlopenext(inst->config->sql_driver);
	if (inst->handle == NULL) {
		radlog(L_ERR, "rlm_sql (%s): Could not link driver %s: %s",
		       inst->config->xlat_name, inst->config->sql_driver,
		       lt_dlerror());
		radlog(L_ERR, "rlm_sql (%s): Make sure it (and all its dependent libraries!) are in the search path of your system's ld.",
		       inst->config->xlat_name);
		rlm_sql_detach(inst);
		return -1;
	}

	inst->module = (rlm_sql_module_t *) lt_dlsym(inst->handle, inst->config->sql_driver);
	if (!inst->module) {
		radlog(L_ERR, "rlm_sql (%s): Could not link symbol %s: %s",
		       inst->config->xlat_name, inst->config->sql_driver,
		       lt_dlerror());
		rlm_sql_detach(inst);
		return -1;
	}

	radlog(L_INFO, "rlm_sql (%s): Driver %s (module %s) loaded and linked",
	       inst->config->xlat_name, inst->config->sql_driver,
	       inst->module->name);
	radlog(L_INFO, "rlm_sql (%s): Attempting to connect to %s@%s:%s/%s",
	       inst->config->xlat_name, inst->config->sql_login,
	       inst->config->sql_server, inst->config->sql_port,
	       inst->config->sql_db);

	if (sql_init_socketpool(inst) < 0) {
		rlm_sql_detach(inst);
		return -1;
	}
	paircompare_register(PW_SQL_GROUP, PW_USER_NAME, sql_groupcmp, inst);

	if (inst->config->do_clients){
		if (generate_sql_clients(inst) == -1){
			radlog(L_ERR, "rlm_sql (%s): generate_sql_clients() returned error",inst->config->xlat_name);
			rlm_sql_detach(inst);
			return -1;
		}
	}
	allowed_chars = inst->config->allowed_chars;

	*instance = inst;

	return RLM_MODULE_OK;
}

static int rlm_sql_destroy(void)
{
	return 0;
}


static int rlm_sql_authorize(void *instance, REQUEST * request)
{
	VALUE_PAIR *check_tmp = NULL;
	VALUE_PAIR *reply_tmp = NULL;
	VALUE_PAIR *user_profile = NULL;
	int     found = 0;
	SQLSOCK *sqlsocket;
	SQL_INST *inst = instance;
	char    querystr[MAX_QUERY_LEN];
	char	sqlusername[MAX_STRING_LEN];

	/*
	 *	They MUST have a user name to do SQL authorization.
	 */
	if ((request->username == NULL) ||
	    (request->username->length == 0)) {
		radlog(L_ERR, "rlm_sql (%s): zero length username not permitted\n", inst->config->xlat_name);
		return RLM_MODULE_INVALID;
	}

	/*
	 *	Set, escape, and check the user attr here.
	 */
	if (sql_set_user(inst, request, sqlusername, NULL) < 0)
		return RLM_MODULE_FAIL;
	radius_xlat(querystr, sizeof(querystr), inst->config->authorize_check_query, request, sql_escape_func);

	sqlsocket = sql_get_socket(inst);
	if (sqlsocket == NULL) {
		/* Remove the username we (maybe) added above */
		pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
		return RLM_MODULE_FAIL;
	}

	/*
	 *	After this point, ALL 'return's MUST release the SQL socket!
	 */

	found = sql_getvpdata(inst, sqlsocket, &check_tmp, querystr, PW_VP_USERDATA);
	/*
	 *      Find the entry for the user.
	 */
	if (found > 0) {
		radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_check_query, request, sql_escape_func);
		sql_getvpdata(inst, sqlsocket, &check_tmp, querystr, PW_VP_GROUPDATA);
		radius_xlat(querystr, sizeof(querystr), inst->config->authorize_reply_query, request, sql_escape_func);
		sql_getvpdata(inst, sqlsocket, &reply_tmp, querystr, PW_VP_USERDATA);
		radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_reply_query, request, sql_escape_func);
		sql_getvpdata(inst, sqlsocket, &reply_tmp, querystr, PW_VP_GROUPDATA);
	} else if (found < 0) {
		radlog(L_ERR, "rlm_sql (%s): SQL query error; rejecting user",
		       inst->config->xlat_name);
		sql_release_socket(inst, sqlsocket);
		/* Remove the username we (maybe) added above */
		pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
		pairfree(&check_tmp);
		return RLM_MODULE_FAIL;

	} else {
		radlog(L_DBG, "rlm_sql (%s): User %s not found in radcheck",
		       inst->config->xlat_name, sqlusername);

                /*
		 * We didn't find the user in radcheck, so we try looking
		 * for radgroupcheck entry
		 */
                radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_check_query, request, sql_escape_func);
                found = sql_getvpdata(inst, sqlsocket, &check_tmp, querystr, PW_VP_GROUPDATA);
                radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_reply_query, request, sql_escape_func);
                sql_getvpdata(inst, sqlsocket, &reply_tmp, querystr, PW_VP_GROUPDATA);
        }
	if (!found)
		radlog(L_DBG, "rlm_sql (%s): User %s not found in radgroupcheck",
		       inst->config->xlat_name, sqlusername);
	if (found || (!found && inst->config->query_on_not_found)){
		int def_found = 0;

		/*
	 	* Check for a default_profile or for a User-Profile.
		*/
		user_profile = pairfind(request->config_items, PW_USER_PROFILE);
		if (inst->config->default_profile[0] != 0 || user_profile != NULL){
			char *profile = inst->config->default_profile;

			if (user_profile != NULL)
				profile = user_profile->strvalue;
			if (profile && strlen(profile)){
				radlog(L_DBG, "rlm_sql (%s): Checking profile %s",
				       inst->config->xlat_name, profile);
				if (sql_set_user(inst, request, sqlusername, profile) < 0) {
					sql_release_socket(inst, sqlsocket);
					pairfree(&reply_tmp);
					pairfree(&check_tmp);
					return RLM_MODULE_FAIL;
				}
				radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_check_query,
									request, sql_escape_func);
				def_found = sql_getvpdata(inst, sqlsocket, &check_tmp, querystr, PW_VP_GROUPDATA);
				if (def_found)
					found = 1;
				radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_reply_query,
									request, sql_escape_func);
				sql_getvpdata(inst, sqlsocket, &reply_tmp, querystr, PW_VP_GROUPDATA);
			}
		}
	}

	/*
	 *	We don't need the SQL socket anymore.
	 */
	sql_release_socket(inst, sqlsocket);

	if (!found) {
		radlog(L_DBG, "rlm_sql (%s): User not found",
		       inst->config->xlat_name);
		/* Remove the username we (maybe) added above */
		pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
		pairfree(&reply_tmp);
		pairfree(&check_tmp);
		return RLM_MODULE_NOTFOUND;
	}

	/*
	 * Uncomment these lines for debugging
	 * Recompile, and run 'radiusd -X'
	 */

	/*
	DEBUG2("rlm_sql:  check items");
	vp_listdebug(check_tmp);
	DEBUG2("rlm_sql:  reply items");
	vp_listdebug(reply_tmp);
	*/

	if (paircmp(request, request->packet->vps, check_tmp, &reply_tmp) != 0) {
		radlog(L_INFO, "rlm_sql (%s): No matching entry in the database for request from user [%s]",
		       inst->config->xlat_name, sqlusername);
		/* Remove the username we (maybe) added above */
		pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
		pairfree(&reply_tmp);
		pairfree(&check_tmp);
		return RLM_MODULE_NOTFOUND;
	}

	pairxlatmove(request, &request->reply->vps, &reply_tmp);
	pairxlatmove(request, &request->config_items, &check_tmp);
	pairfree(&reply_tmp);
	pairfree(&check_tmp);

	/* Remove the username we (maybe) added above */
	pairdelete(&request->packet->vps, PW_SQL_USER_NAME);

	return RLM_MODULE_OK;
}

/*
 *	Accounting: save the account data to our sql table
 */
static int rlm_sql_accounting(void *instance, REQUEST * request) {

	SQLSOCK *sqlsocket = NULL;
	VALUE_PAIR *pair;
	SQL_INST *inst = instance;
	int	ret = RLM_MODULE_OK;
	int     numaffected = 0;
	int     acctstatustype = 0;
	char    querystr[MAX_QUERY_LEN];
	char    logstr[MAX_QUERY_LEN];
	char	sqlusername[MAX_STRING_LEN];

#ifdef CISCO_ACCOUNTING_HACK
	int     acctsessiontime = 0;
#endif

	memset(querystr, 0, MAX_QUERY_LEN);

	/*
	 * Find the Acct Status Type
	 */
	if ((pair = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE)) != NULL) {
		acctstatustype = pair->lvalue;
	} else {
		radius_xlat(logstr, sizeof(logstr), "packet has no accounting status type. [user '%{User-Name}', nas '%{NAS-IP-Address}']", request, NULL);
		radlog(L_ERR, "rlm_sql (%s) in sql_accounting: %s",
		       inst->config->xlat_name, logstr);
		return RLM_MODULE_INVALID;
	}

	switch (acctstatustype) {
			/*
			 * The Terminal server informed us that it was rebooted
			 * STOP all records from this NAS
			 */
		case PW_STATUS_ACCOUNTING_ON:
		case PW_STATUS_ACCOUNTING_OFF:
			radlog(L_INFO, "rlm_sql (%s): received Acct On/Off packet", inst->config->xlat_name);
			radius_xlat(querystr, sizeof(querystr), inst->config->accounting_onoff_query, request, sql_escape_func);
			query_log(request, inst, querystr);

			sqlsocket = sql_get_socket(inst);
			if (sqlsocket == NULL)
				return(RLM_MODULE_FAIL);
			if (*querystr) { /* non-empty query */
				if (rlm_sql_query(sqlsocket, inst, querystr)) {
					radlog(L_ERR, "rlm_sql (%s): Couldn't update SQL accounting for Acct On/Off packet - %s",
					       inst->config->xlat_name,
					       (char *)(inst->module->sql_error)(sqlsocket, inst->config));
					ret = RLM_MODULE_FAIL;
				}
				(inst->module->sql_finish_query)(sqlsocket, inst->config);
			}

			break;

			/*
			 * Got an update accounting packet
			 */
		case PW_STATUS_ALIVE:

			/*
			 * Set, escape, and check the user attr here
			 */
			sql_set_user(inst, request, sqlusername, NULL);

			radius_xlat(querystr, sizeof(querystr), inst->config->accounting_update_query, request, sql_escape_func);
			query_log(request, inst, querystr);

			sqlsocket = sql_get_socket(inst);
			if (sqlsocket == NULL)
				return(RLM_MODULE_FAIL);
			if (*querystr) { /* non-empty query */
				if (rlm_sql_query(sqlsocket, inst, querystr)) {
					radlog(L_ERR, "rlm_sql (%s): Couldn't update SQL accounting ALIVE record - %s",
					       inst->config->xlat_name,
					       (char *)(inst->module->sql_error)(sqlsocket, inst->config));
					ret = RLM_MODULE_FAIL;
				}
				else {
					numaffected = (inst->module->sql_affected_rows)(sqlsocket, inst->config);
					if (numaffected < 1) {

						/*
						 * If our update above didn't match anything
						 * we assume it's because we haven't seen a
						 * matching Start record.  So we have to
						 * insert this update rather than do an update
						 */
						radius_xlat(querystr, sizeof(querystr), inst->config->accounting_update_query_alt, request, sql_escape_func);
						query_log(request, inst, querystr);
						if (*querystr) { /* non-empty query */
							if (rlm_sql_query(sqlsocket, inst, querystr)) {
								radlog(L_ERR, "rlm_sql (%s): Couldn't insert SQL accounting ALIVE record - %s",
									   inst->config->xlat_name,
									   (char *)(inst->module->sql_error)(sqlsocket, inst->config));
								ret = RLM_MODULE_FAIL;
							}
							(inst->module->sql_finish_query)(sqlsocket, inst->config);
						}
					}
				}
				(inst->module->sql_finish_query)(sqlsocket, inst->config);
			}
			break;

			/*
			 * Got accounting start packet
			 */
		case PW_STATUS_START:

			/*
			 * Set, escape, and check the user attr here
			 */
			sql_set_user(inst, request, sqlusername, NULL);

			radius_xlat(querystr, sizeof(querystr), inst->config->accounting_start_query, request, sql_escape_func);
			query_log(request, inst, querystr);

			sqlsocket = sql_get_socket(inst);
			if (sqlsocket == NULL)
				return(RLM_MODULE_FAIL);
			if (*querystr) { /* non-empty query */
				if (rlm_sql_query(sqlsocket, inst, querystr)) {
					radlog(L_ERR, "rlm_sql (%s): Couldn't insert SQL accounting START record - %s",
					       inst->config->xlat_name,
					       (char *)(inst->module->sql_error)(sqlsocket, inst->config));

					/*
					 * We failed the insert above.  It's probably because
					 * the stop record came before the start.  We try
					 * our alternate query now (typically an UPDATE)
					 */
					radius_xlat(querystr, sizeof(querystr), inst->config->accounting_start_query_alt, request, sql_escape_func);
					query_log(request, inst, querystr);

					if (*querystr) { /* non-empty query */
						if (rlm_sql_query(sqlsocket, inst, querystr)) {
							radlog(L_ERR, "rlm_sql (%s): Couldn't update SQL accounting START record - %s",
							       inst->config->xlat_name,
							       (char *)(inst->module->sql_error)(sqlsocket, inst->config));
							ret = RLM_MODULE_FAIL;
						}
						(inst->module->sql_finish_query)(sqlsocket, inst->config);
					}
				}
				(inst->module->sql_finish_query)(sqlsocket, inst->config);
			}
			break;

			/*
			 * Got accounting stop packet
			 */
		case PW_STATUS_STOP:

			/*
			 * Set, escape, and check the user attr here
			 */
			sql_set_user(inst, request, sqlusername, NULL);

			radius_xlat(querystr, sizeof(querystr), inst->config->accounting_stop_query, request, sql_escape_func);
			query_log(request, inst, querystr);

			sqlsocket = sql_get_socket(inst);
			if (sqlsocket == NULL)
				return(RLM_MODULE_FAIL);
			if (*querystr) { /* non-empty query */
				if (rlm_sql_query(sqlsocket, inst, querystr)) {
					radlog(L_ERR, "rlm_sql (%s): Couldn't update SQL accounting STOP record - %s",
					       inst->config->xlat_name,
					       (char *)(inst->module->sql_error)(sqlsocket, inst->config));
					ret = RLM_MODULE_FAIL;
				}
				else {
					numaffected = (inst->module->sql_affected_rows)(sqlsocket, inst->config);
					if (numaffected < 1) {
						/*
						 * If our update above didn't match anything
						 * we assume it's because we haven't seen a
						 * matching Start record.  So we have to
						 * insert this stop rather than do an update
						 */
#ifdef CISCO_ACCOUNTING_HACK
					        /*
					         * If stop but zero session length AND no previous
					         * session found, drop it as in invalid packet
				        	 * This is to fix CISCO's aaa from filling our
				        	 * table with bogus crap
					         */
					        if ((pair = pairfind(request->packet->vps, PW_ACCT_SESSION_TIME)) != NULL)
					                acctsessiontime = pair->lvalue;

						if (acctsessiontime <= 0) {
							radius_xlat(logstr, sizeof(logstr), "stop packet with zero session length. [user '%{User-Name}', nas '%{NAS-IP-Address}']", request, NULL);
							radlog(L_ERR, "rlm_sql (%s) in sql_accounting: %s", inst->config->xlat_name, logstr);
							sql_release_socket(inst, sqlsocket);
							ret = RLM_MODULE_NOOP;
						}
#endif

						radius_xlat(querystr, sizeof(querystr), inst->config->accounting_stop_query_alt, request, sql_escape_func);
						query_log(request, inst, querystr);

						if (*querystr) { /* non-empty query */
							if (rlm_sql_query(sqlsocket, inst, querystr)) {
								radlog(L_ERR, "rlm_sql (%s): Couldn't insert SQL accounting STOP record - %s",
										inst->config->xlat_name,
										(char *)(inst->module->sql_error)(sqlsocket, inst->config));
								ret = RLM_MODULE_FAIL;
							}
							(inst->module->sql_finish_query)(sqlsocket, inst->config);
						}
					}
				}
				(inst->module->sql_finish_query)(sqlsocket, inst->config);
			}
			break;

			/*
			 *	Anything else is ignored.
			 */
		default:
			radlog(L_INFO, "rlm_sql (%s): Unsupported Acct-Status-Type = %d", inst->config->xlat_name, acctstatustype);
			return RLM_MODULE_NOOP;
			break;

	}

	sql_release_socket(inst, sqlsocket);

	return ret;
}


/*
 *        See if a user is already logged in. Sets request->simul_count to the
 *        current session count for this user.
 *
 *        Check twice. If on the first pass the user exceeds his
 *        max. number of logins, do a second pass and validate all
 *        logins by querying the terminal server (using eg. SNMP).
 */

static int rlm_sql_checksimul(void *instance, REQUEST * request) {
	SQLSOCK 	*sqlsocket;
	SQL_INST	*inst = instance;
	SQL_ROW		row;
	char		querystr[MAX_QUERY_LEN];
	char		sqlusername[MAX_STRING_LEN];
	int		check = 0;
        uint32_t        ipno = 0;
        char            *call_num = NULL;
	VALUE_PAIR      *vp;
	int		ret;
	uint32_t	nas_addr = 0;
	int		nas_port = 0;

	/* If simul_count_query is not defined, we don't do any checking */
	if (inst->config->simul_count_query[0] == 0) {
		return RLM_MODULE_NOOP;
	}

	if((request->username == NULL) || (request->username->length == 0)) {
		radlog(L_ERR, "rlm_sql (%s): Zero Length username not permitted\n", inst->config->xlat_name);
		return RLM_MODULE_INVALID;
	}


	if(sql_set_user(inst, request, sqlusername, NULL) < 0)
		return RLM_MODULE_FAIL;

	radius_xlat(querystr, sizeof(querystr), inst->config->simul_count_query, request, sql_escape_func);

	/* initialize the sql socket */
	sqlsocket = sql_get_socket(inst);
	if(sqlsocket == NULL)
		return RLM_MODULE_FAIL;

	if(rlm_sql_select_query(sqlsocket, inst, querystr)) {
		radlog(L_ERR, "rlm_sql (%s) sql_checksimul: Database query failed", inst->config->xlat_name);
		sql_release_socket(inst, sqlsocket);
		return RLM_MODULE_FAIL;
	}

	ret = rlm_sql_fetch_row(sqlsocket, inst);

	if (ret != 0) {
		(inst->module->sql_finish_select_query)(sqlsocket, inst->config);
		sql_release_socket(inst, sqlsocket);
		return RLM_MODULE_FAIL;
	}

	row = sqlsocket->row;
	if (row == NULL) {
		(inst->module->sql_finish_select_query)(sqlsocket, inst->config);
		sql_release_socket(inst, sqlsocket);
		return RLM_MODULE_FAIL;
	}

	request->simul_count = atoi(row[0]);
	(inst->module->sql_finish_select_query)(sqlsocket, inst->config);

	if(request->simul_count < request->simul_max) {
		sql_release_socket(inst, sqlsocket);
		return RLM_MODULE_OK;
	}

	/* Looks like too many sessions, so lets start verifying them */

	if (inst->config->simul_verify_query[0] == 0) {
		/* No verify query defined, so skip verify step and rely on count query only */
		sql_release_socket(inst, sqlsocket);
		return RLM_MODULE_OK;
	}

	radius_xlat(querystr, sizeof(querystr), inst->config->simul_verify_query, request, sql_escape_func);
	if(rlm_sql_select_query(sqlsocket, inst, querystr)) {
		radlog(L_ERR, "rlm_sql (%s): sql_checksimul: Database query error", inst->config->xlat_name);
		sql_release_socket(inst, sqlsocket);
		return RLM_MODULE_FAIL;
	}

        /*
         *      Setup some stuff, like for MPP detection.
         */
	request->simul_count = 0;

        if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS)) != NULL)
                ipno = vp->lvalue;
        if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID)) != NULL)
                call_num = vp->strvalue;


	while (rlm_sql_fetch_row(sqlsocket, inst) == 0) {
		row = sqlsocket->row;
		if (row == NULL)
			break;
		if (!row[2]){
			(inst->module->sql_finish_select_query)(sqlsocket, inst->config);
			sql_release_socket(inst, sqlsocket);
			DEBUG("rlm_sql (%s): Cannot zap stale entry. No username present in entry.", inst->config->xlat_name);
			return RLM_MODULE_FAIL;
		}
		if (!row[1]){
			(inst->module->sql_finish_select_query)(sqlsocket, inst->config);
			sql_release_socket(inst, sqlsocket);
			DEBUG("rlm_sql (%s): Cannot zap stale entry. No session id in entry.", inst->config->xlat_name);
			return RLM_MODULE_FAIL;
		}
		if (row[3])
			nas_addr = inet_addr(row[3]);
		if (row[4])
			nas_port = atoi(row[4]);

		check = rad_check_ts(nas_addr, nas_port, row[2], row[1]);

                /*
                 *      Failed to check the terminal server for
                 *      duplicate logins: Return an error.
                 */
		if (check < 0) {
			(inst->module->sql_finish_select_query)(sqlsocket, inst->config);
			sql_release_socket(inst, sqlsocket);
			DEBUG("rlm_sql (%s) rad_check_ts() failed.",
			      inst->config->xlat_name);
			return RLM_MODULE_FAIL;
		}

		if(check == 1) {
			++request->simul_count;

                        /*
                         *      Does it look like a MPP attempt?
                         */
                        if (row[5] && ipno && inet_addr(row[5]) == ipno)
                                request->simul_mpp = 2;
                        else if (row[6] && call_num &&
                                !strncmp(row[6],call_num,16))
                                request->simul_mpp = 2;
		}
		else {
                        /*
                         *      Stale record - zap it.
                         */
			uint32_t framed_addr = 0;
			char proto = 'P';

			if (row[5])
				framed_addr = inet_addr(row[5]);
			if (row[7])
				if (strcmp(row[7],"SLIP") == 0)
					proto = 'S';

			session_zap(request,
				    nas_addr,nas_port,row[2],row[1],
				    framed_addr, proto);
		}
	}

	(inst->module->sql_finish_select_query)(sqlsocket, inst->config);
	sql_release_socket(inst, sqlsocket);

	/* The Auth module apparently looks at request->simul_count, not the return value
	   of this module when deciding to deny a call for too many sessions */
	return RLM_MODULE_OK;

}

/*
 *	Execute postauth_query after authentication
 */
static int rlm_sql_postauth(void *instance, REQUEST *request) {
	SQLSOCK 	*sqlsocket = NULL;
	SQL_INST	*inst = instance;
	char		querystr[MAX_QUERY_LEN];
	char		sqlusername[MAX_STRING_LEN];

	DEBUG("rlm_sql (%s): Processing sql_postauth", inst->config->xlat_name);

	if(sql_set_user(inst, request, sqlusername, NULL) < 0)
		return RLM_MODULE_FAIL;

	/* If postauth_query is not defined, we stop here */
	if (inst->config->postauth_query[0] == '\0')
		return RLM_MODULE_NOOP;

	/* Expand variables in the query */
	memset(querystr, 0, MAX_QUERY_LEN);
	radius_xlat(querystr, sizeof(querystr), inst->config->postauth_query,
		    request, sql_escape_func);
	query_log(request, inst, querystr);
	DEBUG2("rlm_sql (%s) in sql_postauth: query is %s",
	       inst->config->xlat_name, querystr);

	/* Initialize the sql socket */
	sqlsocket = sql_get_socket(inst);
	if (sqlsocket == NULL)
		return RLM_MODULE_FAIL;

	/* Process the query */
	if (rlm_sql_query(sqlsocket, inst, querystr)) {
		radlog(L_ERR, "rlm_sql (%s) in sql_postauth: Database query error - %s",
		       inst->config->xlat_name,
		       (char *)(inst->module->sql_error)(sqlsocket, inst->config));
		sql_release_socket(inst, sqlsocket);
		return RLM_MODULE_FAIL;
	}
	(inst->module->sql_finish_query)(sqlsocket, inst->config);

	sql_release_socket(inst, sqlsocket);
	return RLM_MODULE_OK;
}

/* globally exported name */
module_t rlm_sql = {
	"SQL",
	RLM_TYPE_THREAD_SAFE,	/* type: reserved */
	rlm_sql_init,		/* initialization */
	rlm_sql_instantiate,	/* instantiation */
	{
		NULL,			/* authentication */
		rlm_sql_authorize,	/* authorization */
		NULL,			/* preaccounting */
		rlm_sql_accounting,	/* accounting */
		rlm_sql_checksimul,	/* checksimul */
		NULL,			/* pre-proxy */
		NULL,			/* post-proxy */
		rlm_sql_postauth	/* post-auth */
	},
	rlm_sql_detach,		/* detach */
	rlm_sql_destroy,	/* destroy */
};


syntax highlighted by Code2HTML, v. 0.9.1