/*
 * Asterisk -- A telephony toolkit for Linux.
 *
 * MySQL Query Interface
 * 
 * Copyright(C) 2004, Rob Gagnon
 * Rob Gagnon <rgagnon@networkip.net>
 *
 * based on code by:
 * James Sharp <jsharp@psychoses.org>
 *
 * This program is free software, distributed under the terms of
 * the GNU General Public License.
 *
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <errno.h>
#include <asterisk/config.h>
#include <asterisk/options.h>
#include <asterisk/channel.h>
#include <asterisk/acl.h>
#include <asterisk/chan_sip.h>
#include <asterisk/qint.h>
#include <asterisk/module.h>
#include <asterisk/logger.h>
#include <asterisk/cli.h>
#include "../asterisk.h"

#include <stdio.h>
#include <string.h>

#include <stdlib.h>
#include <unistd.h>
#include <time.h>

#include <mysql/mysql.h>
#include <mysql/errmsg.h>

static char *desc = "MySQL Query Interface";
// Interface will be registered with this name, and the section
// in the config file will use this for its parameters
// ie:  "[mysql]"
static char *name = "mysql";

// Config file name (should remain qint.conf no matter the interface type
// create a section within the file, with your specific section
static char *config = "qint.conf";

// registrar name to use when extensions are found
static char *registrar = "qint";

//ACL stuff
#define AST_SENSE_DENY                    0
#define AST_SENSE_ALLOW                   1

//SIP stuff
static int globalnat = 0;
static int globaldtmfmode = SIP_DTMF_RFC2833;
static int capability = AST_FORMAT_ULAW | AST_FORMAT_ALAW | AST_FORMAT_GSM | AST_FORMAT_H263;

//MYSQL stuff
static ast_mutex_t db_lock = AST_MUTEX_INITIALIZER;
static MYSQL *mysql;
static char hostname[80] = "localhost";
static char dbname[80] = "asterisk";
static char dbuser[80] = "root";
static char password[80] = "";
static char dbsock[256] = "";
static int dbport = 0;

//Statistical Stuff
static int connected = 0;
static time_t connect_time = 0;
static int records = 0;
static int totalrecords = 0;

//Flags for hooking handlers
struct qint_flags {
	int extension_lookup;
	int sip_find_user;
	int sip_find_peer;
	int sip_update_peer;
	int vm_find_user;
	int end_of_call;
};
static struct qint_flags flags = { 0, 0, 0, 0, 0, 0 };

//CLI stuff
static char qint_status_help[] =
"Usage: show qint mysql status\n"
"       Shows current connection status for qint_mysql\n";

static int
handle_qint_status(int fd, int argc, char *argv[])
{
	if (connected) {
		char status[256], status2[100] = "";
		int ctime = time(NULL) - connect_time;
		int years = 0;
		int days = 0;
		int hours = 0;
		int minutes = 0;
		int seconds = 0;

		years = ctime / 31536000;
		ctime -= years*31536000;
		days = ctime / 86400;
		ctime -= days*86400;
		hours = ctime / 3600;
		ctime -= hours*3600;
		minutes = ctime / 60;
		ctime -= minutes*60;
		seconds = ctime;
		
		if (dbport)
			snprintf(status, 255, "Connected to %s@%s, port %d", dbname, hostname, dbport);
		else if (dbsock)
			snprintf(status, 255, "Connected to %s on socket file %s", dbname, dbsock);
		else
			snprintf(status, 255, "Connected to %s@%s", dbname, hostname);

		if (dbuser && *dbuser)
			snprintf(status2, 99, " with username %s", dbuser);

		ast_cli(fd, "%s%s for", status, status2);
		if (years) ast_cli(fd, " %d years", years);
		if (days) ast_cli(fd, " %d days", days);
		if (hours) ast_cli(fd, " %d hours", hours);
		if (minutes) ast_cli(fd, " %d minutes", minutes);
		if (seconds) ast_cli(fd, " %d seconds", seconds);

		if (records == totalrecords)
			ast_cli(fd, "\n  Wrote %d records since last restart.\n", totalrecords);
		else
			ast_cli(fd, "\n  Wrote %d records since last restart and %d records since last reconnect.\n", totalrecords, records);
		return RESULT_SUCCESS;
	} else {
		ast_cli(fd, "Not currently connected to a MySQL server.\n");
		return RESULT_FAILURE;
	}
}

static struct ast_cli_entry qint_status_cli =
	{ { "show", "qint", "mysql", "status", NULL },
	handle_qint_status, "Show connection status of qint_mysql",
	qint_status_help, NULL };

// ***************************************************************************
// This has been modified by Michael Shuler (mike@bwsys.net) to support
// pattern matching.
//
static int mysql_ext_lookup(struct ast_exten *e, struct ast_channel *chan, char *context, char *exten, int priority, char *callerid, int action, int *status, char **data)
{
	if (!e) 
	{
		// not valid pointer coming in
		return QINT_ERROR;
	}

	// set default return status
	if (!*status) 
		*status = STATUS_NO_EXTENSION;

	if (mysql && (!exten || strlen(exten) < 128)) 
	{
		char query[512];
		char *dbcontext;
		char *dbexten;
		int NumOfRows;

		MYSQL_RES *MySQLResultSet;
		MYSQL_ROW MySQLCurRow;			// Cur is short for Current

		dbcontext = alloca(strlen(context) * 2 + 1);
		mysql_real_escape_string(mysql, dbcontext, context, strlen(context));
		dbexten = alloca(strlen(exten) * 2 + 1);
		mysql_real_escape_string(mysql, dbexten, exten, strlen(exten));
		snprintf(query, sizeof(query), "SELECT extension, application, args FROM extensions WHERE context = \"%s\" AND (extension = \"%s\" OR extension LIKE \"\\_%%\") AND priority = \"%d\" ORDER BY extension ASC;", dbcontext, dbexten, priority);
		ast_log(LOG_DEBUG, "Query for extension match: %s\n", query);
	
		ast_log(LOG_DEBUG, "Context to match: %s\tExten to match: %s\tPriority to match: %d\n", dbcontext, dbexten, priority);

		// Lock the DB (need to come up with a non-locking system to improve performance)
		ast_mutex_lock(&db_lock);
	
		// Run the query	
		if(mysql_query(mysql, query) != 0)
		{
			ast_log(LOG_ERROR, "Extension lookup query failed: %s\n", mysql_error(mysql));
			return(QINT_ERROR);
		}

		// Get the result set back from the DB
		if((MySQLResultSet = mysql_store_result(mysql)) == NULL) 
		{
			ast_log(LOG_ERROR, "Unable to store query results: %s\n", mysql_error(mysql));
			
			// Unlock the DB
			ast_mutex_unlock(&db_lock);
		
			errno = ENODATA;
			return(QINT_ERROR);
		}
			
		// See how many rows we got back
		if((NumOfRows = mysql_num_rows(MySQLResultSet)) == 0)
		{
			// Got nothing back, all done.
			ast_log(LOG_DEBUG, "Found %d extension records\n", NumOfRows);
		
			// Free the result set memory		
			mysql_free_result(MySQLResultSet);
		
			// Unlock the DB
			ast_mutex_unlock(&db_lock);
			
			errno = ENODATA;
			return(QINT_ERROR);
		}

		ast_log(LOG_DEBUG, "Found %d extension records\n", NumOfRows);
	
		// Compare each row returned from the DB.  
		// NOTE: because we made the ORDER BY ASC anything starting with a '_'
		// will appear twords the end instead of at the start of the records).
		// This allows us to apply exact extension matches first and then patten matches.
		while((MySQLCurRow = mysql_fetch_row(MySQLResultSet)) != NULL) 
		{
			// 0 = extension
			// 1 = application
			// 2 = args
		
			ast_log(LOG_DEBUG, "Testing for match: %s and %s\n", dbexten, MySQLCurRow[0]);
		
			// Check if we have a match (NOTE: this function also handels pattern matching)
			if(ast_extension_match(MySQLCurRow[0], dbexten) == 1)
			{
				ast_log(LOG_DEBUG, "Found match: %s and %s\n", dbexten, MySQLCurRow[0]);

				strncpy(e->app, MySQLCurRow[1], sizeof(e->app));
				e->data = strdup(MySQLCurRow[2]);
	
				// record found
				*status = STATUS_SUCCESS;

				// change commas to pipes in args
				char *start;
				for (start = e->data; *start; start++) 
				{
					if (*start == ',') 
						*start = '|';
				}
	
				strncpy(e->exten, exten, sizeof(e->exten));
				// maybe todo later... dont need statements now since memset
				// zeroed everything, but just left here as a reminder
				// e->matchcid = 0;
				// e->cidmatch[0] = '\0';
				e->priority = priority;
				e->datad = free;
				e->registrar = registrar;
				e->temponly = 1;

				// All done going through the records, free the memory		
				mysql_free_result(MySQLResultSet);
		
				// Unlock the DB
				ast_mutex_unlock(&db_lock);
			
				// All done, we got our match
				return(QINT_OK);
			}
			
		}
	
		// All done going through the records, free the memory		
		mysql_free_result(MySQLResultSet);
		
		// Unlock the DB
		ast_mutex_unlock(&db_lock);
	}

	// Nothing matched, all done.
	// !!!!!! ROB, I'M NOT SURE IF THIS IS THE PROPER WAY TO RETURN - FEEL FREE TO FIX :)  !!!!!
	errno = ENODATA;
	return(QINT_ERROR);
}

// ***************************************************************************

static int
mysql_sip_update_peer(char *peer, struct sockaddr_in *sin, char *username, int expiry)
{
	if (mysql && (!peer || strlen(peer) < 128)) {
		char query[512];
		char *name;
		char *uname;
		time_t nowtime;
		name = alloca(strlen(peer) * 2 + 1);
		uname = alloca(strlen(username) * 2 + 1);
		time(&nowtime);
		mysql_real_escape_string(mysql, name, peer, strlen(peer));
		mysql_real_escape_string(mysql, uname, username, strlen(username));
		snprintf(query, sizeof(query), "UPDATE sipfriends SET ipaddr=\"%s\", port=\"%d\", regseconds=\"%ld\", username=\"%s\" WHERE name=\"%s\"", 
			inet_ntoa(sin->sin_addr), ntohs(sin->sin_port), nowtime + expiry, uname, name);
		ast_mutex_lock(&db_lock);
		if (mysql_real_query(mysql, query, strlen(query))) 
			ast_log(LOG_WARNING, "Unable to update database\n");
			
		ast_mutex_unlock(&db_lock);
	}
	return QINT_OK;
}

static struct ast_ha *
mysql_getacl(char *name, struct ast_ha *ha)
{
	char	pattern[50] = ""; //only needs 42, but just being safe
	char	ipaddr[21] = "";
	char	netmask[21] = "";
	int	sense = 0;

	if (mysql && (!name || strlen(name) < 128)) {
		char	query[512] = "";
		int	numfields, x;
		char	*username = NULL;
		MYSQL_RES	*result;
		MYSQL_FIELD	*fields;
		MYSQL_ROW	rowval;

		if (name) {
			username = alloca(strlen(name) * 2 + 1);
			mysql_real_escape_string(mysql, username, name, strlen(name));
		}
		snprintf(query, sizeof(query), "SELECT * FROM sipacl WHERE name=\"%s\";",
			username); 
		ast_mutex_lock(&db_lock);
		mysql_query(mysql, query);
		if ((result = mysql_store_result(mysql))) {
			while ((rowval = mysql_fetch_row(result))) {
				numfields = mysql_num_fields(result);
				fields = mysql_fetch_fields(result);
				for (x=0;x<numfields;x++) {
					if (rowval[x]) {
						if (!strcasecmp(fields[x].name, "sense")) {
							if (sscanf(rowval[x], "%i", &sense) != 1)
								sense = AST_SENSE_DENY;
						} else if (!strcasecmp(fields[x].name, "ipaddr")) {
							strncpy(ipaddr, rowval[x], sizeof(ipaddr) - 1);
						} else if (!strcasecmp(fields[x].name, "netmask")) {
							strncpy(netmask, rowval[x], sizeof(netmask) - 1);
						}
					} // end if (rowval[x])
				} //end for

				// Add to ACL
				snprintf(pattern, sizeof(pattern), "%s/%s", ipaddr, netmask);
				if (sense == AST_SENSE_ALLOW) {
					ha = ast_append_ha("permit", pattern, ha);
				}
				else {
					ha = ast_append_ha("deny", pattern, ha);
				}

				// reset values for next trip around
				sense = 0;
				ipaddr[0] = '\0';
				netmask[0] = '\0';
			} // end while
			mysql_free_result(result);
			result = NULL;
		} // end if (result = mysql...)
		ast_mutex_unlock(&db_lock);
	}

	return (ha);
}

static int
mysql_sip_find_peer(struct sip_peer *p, char *peer, struct sockaddr_in *sin)
{
	int success = 0;
	int nat = globalnat;
	int dtmfmode = globaldtmfmode;
	int codecflags = capability;
	
	if (!p) {
		// not valid pointer coming in
		return QINT_ERROR;
	}

	if (mysql && (!peer || strlen(peer) < 128)) {
		char query[512];
		char *name = NULL;
		int numfields, x;
		int port;
		time_t regseconds, nowtime;
		MYSQL_RES *result;
		MYSQL_FIELD *fields;
		MYSQL_ROW rowval;
		if (peer) {
			name = alloca(strlen(peer) * 2 + 1);
			mysql_real_escape_string(mysql, name, peer, strlen(peer));
		}
		if (sin)
			snprintf(query, sizeof(query),
				"SELECT * FROM sipfriends WHERE ipaddr=\"%s\" AND port=\"%d\""
				" AND (type=\"%d\" OR type=\"%d\");",
				inet_ntoa(sin->sin_addr), ntohs(sin->sin_port),
				SIP_TYPE_PEER, SIP_TYPE_FRIEND);
		else
			snprintf(query, sizeof(query),
				"SELECT * FROM sipfriends WHERE name=\"%s\""
				" AND (type=\"%d\" OR type=\"%d\");",
				name, SIP_TYPE_PEER, SIP_TYPE_FRIEND);
		ast_mutex_lock(&db_lock);
		mysql_query(mysql, query);
		if ((result = mysql_store_result(mysql))) {
			if ((rowval = mysql_fetch_row(result))) {
				numfields = mysql_num_fields(result);
				fields = mysql_fetch_fields(result);
				success = 1;
				for (x=0;x<numfields;x++) {
					if (rowval[x]) {
						if (!strcasecmp(fields[x].name, "secret")) {
							strncpy(p->secret, rowval[x], sizeof(p->secret));
						} else if (!strcasecmp(fields[x].name, "name")) {
							strncpy(p->name, rowval[x], sizeof(p->name) - 1);
						} else if (!strcasecmp(fields[x].name, "context")) {
							strncpy(p->context, rowval[x], sizeof(p->context) - 1);
						} else if (!strcasecmp(fields[x].name, "username")) {
							strncpy(p->username, rowval[x], sizeof(p->username) - 1);
						} else if (!strcasecmp(fields[x].name, "ipaddr")) {
							inet_aton(rowval[x], &(p->addr.sin_addr));
						} else if (!strcasecmp(fields[x].name, "mailbox")) {
							strncpy(p->mailbox, rowval[x], sizeof(p->mailbox) - 1);
						} else if (!strcasecmp(fields[x].name, "port")) {
							if (sscanf(rowval[x], "%i", &port) != 1)
								port = 0;
							p->addr.sin_port = htons(port);
						} else if (!strcasecmp(fields[x].name, "nat")) {
							if (sscanf(rowval[x], "%i", &nat) != 1)
								nat = globalnat;
						} else if (!strcasecmp(fields[x].name, "dtmfmode")) {
							if (sscanf(rowval[x], "%i", &dtmfmode) != 1)
								dtmfmode = globaldtmfmode;
						} else if (!strcasecmp(fields[x].name, "regseconds")) {
							if (sscanf(rowval[x], "%li", &regseconds) != 1)
								regseconds = 0;
						} else if (!strcasecmp(fields[x].name, "codecflags")) {
							if (sscanf(rowval[x], "%i", &codecflags) != 1)
								codecflags = capability;
						}
					}
				}
				time(&nowtime);
				if (nowtime > regseconds) 
					memset(&(p->addr), 0, sizeof(p->addr));
			}
			mysql_free_result(result);
			result = NULL;
		}
		ast_mutex_unlock(&db_lock);
	}

	if (!success) {
		errno = ENODATA;
		return QINT_ERROR;
	} else {
		p->dynamic = 1;
		p->capability = codecflags;
		if (p->capability == 0) {
			ast_log(LOG_WARNING, "codecflags for peer: %s set to zero (none)\n",
				peer);
		}
		p->nat = (nat ? -1 : 0);

		// Validate dtmfmode
		switch (dtmfmode) {
			case SIP_DTMF_RFC2833:
			case SIP_DTMF_INBAND:
			case SIP_DTMF_INFO:
				break;
			default:
				dtmfmode = globaldtmfmode;
				break;
		} //end switch
		p->dtmfmode = dtmfmode;

		p->insecure = 1;
		p->expire = -1;
		p->temponly = 1;

		//go get the acl if there is one
		//Uses p->name here, and not "peer" because "peer" could be NULL
		//on input to this function
		p->ha = mysql_getacl(p->name, p->ha);
	}
	return QINT_OK;
}

static int
mysql_sip_find_user(struct sip_user *u, char *user)
{
	// struct sip_user *u;
	int success = 0;
	int canreinvite = REINVITE_INVITE;
	int nat = globalnat;
	int dtmfmode = globaldtmfmode;
	int codecflags = capability;
	int restrictcid = 0;
	int amaflags = 0;
	char cidname[21] = "";
	char cidnumber[21] = "";

	//check we got a valid pointer
	if (!u) {
		return QINT_ERROR;
	}

	if (mysql && (!user || strlen(user) < 128)) {
		char query[512];
		char *name;
		int numfields, x;

		MYSQL_RES *result;
		MYSQL_FIELD *fields;
		MYSQL_ROW rowval;
		name = alloca(strlen(user) * 2 + 1);
		mysql_real_escape_string(mysql, name, user, strlen(user));
		snprintf(query, sizeof(query),
			"SELECT * FROM sipfriends WHERE name=\"%s\""
			" AND (type=\"%d\" OR type=\"%d\");",
			name, SIP_TYPE_USER, SIP_TYPE_FRIEND);
		ast_mutex_lock(&db_lock);
		mysql_query(mysql, query);
		if ((result = mysql_store_result(mysql))) {
			if ((rowval = mysql_fetch_row(result))) {
				numfields = mysql_num_fields(result);
				fields = mysql_fetch_fields(result);
				success = 1;
				for (x=0;x<numfields;x++) {
					if (rowval[x]) {
						if (!strcasecmp(fields[x].name, "secret")) {
							strncpy(u->secret, rowval[x], sizeof(u->secret) - 1);
						} else if (!strcasecmp(fields[x].name, "context")) {
							strncpy(u->context, rowval[x], sizeof(u->context) - 1);
						} else if (!strcasecmp(fields[x].name, "cidname")) {
							strncpy(cidname, rowval[x], sizeof(cidname) - 1);
						} else if (!strcasecmp(fields[x].name, "cidnumber")) {
							strncpy(cidnumber, rowval[x], sizeof(cidnumber) - 1);
						} else if (!strcasecmp(fields[x].name, "accountcode")) {
							strncpy(u->accountcode, rowval[x], sizeof(u->accountcode) - 1);
						} else if (!strcasecmp(fields[x].name, "nat")) {
							if (sscanf(rowval[x], "%i", &nat) != 1)
								nat = globalnat;
						} else if (!strcasecmp(fields[x].name, "dtmfmode")) {
							if (sscanf(rowval[x], "%i", &dtmfmode) != 1)
								dtmfmode = globaldtmfmode;
						} else if (!strcasecmp(fields[x].name, "canreinvite")) {
							if (sscanf(rowval[x], "%i", &canreinvite) != 1)
								canreinvite = 0;
						} else if (!strcasecmp(fields[x].name, "restrictcid")) {
							if (sscanf(rowval[x], "%i", &restrictcid) != 1)
								restrictcid = 0;
						} else if (!strcasecmp(fields[x].name, "amaflags")) {
							if (sscanf(rowval[x], "%i", &amaflags) != 1)
								amaflags = 0;
						} else if (!strcasecmp(fields[x].name, "codecflags")) {
							if (sscanf(rowval[x], "%i", &codecflags) != 1)
								codecflags = capability;
						}
					}
				}
			}
			mysql_free_result(result);
			result = NULL;
		}
		ast_mutex_unlock(&db_lock);
	}

	if (!success) {
		// not found
		errno = ENODATA;
		return QINT_ERROR;
	} else {
		strncpy(u->name, user, sizeof(u->name) - 1);
		u->temponly = 1;

		// Validate codec flags...
		u->capability = codecflags;
		if (u->capability == 0) {
			ast_log(LOG_WARNING, "codecflags for user: %s set to zero (none)\n",
				user);
		}

		// Validate dtmfmode
		switch (dtmfmode) {
			case SIP_DTMF_RFC2833:
			case SIP_DTMF_INBAND:
			case SIP_DTMF_INFO:
				break;
			default:
				dtmfmode = globaldtmfmode;
				break;
		} //end switch
		u->dtmfmode = dtmfmode;

		// Validate amaflags
		switch (amaflags) {
			case AST_CDR_OMIT:
			case AST_CDR_BILLING:
			case AST_CDR_DOCUMENTATION:
				break;
			default:
				amaflags = 0;
				break;
		} //end switch
		u->amaflags = amaflags;

		// double-check true/false fields from database
		// DB uses 1/0 for true/false.  Internally, we use -1/0
		u->canreinvite = (canreinvite ? -1 : 0);
		u->nat = (nat ? -1 : 0);
		u->restrictcid = (restrictcid ? -1 : 0);

		snprintf(u->callerid,sizeof(u->callerid),"\"%s\" <%s>",
			cidname, cidnumber);

		// setup auth methods
		if (!strlen(u->methods)) {
			if (strlen(u->secret)) 
				strncpy(u->methods, "md5,plaintext", sizeof(u->methods) - 1);
			else if (strlen(u->md5secret))
		        	strncpy(u->methods, "md5", sizeof(u->methods) - 1);
		}

		//go get ACL if there is one
		u->ha = mysql_getacl(user, u->ha);
	}
	return QINT_OK;
}

static void apply_options(struct ast_vm_user *vmu, char *options)
{
	/* Destructively Parse options and apply */
	char *stringp = ast_strdupa(options);
	char *s;
	char *var, *value;
	while((s = strsep(&stringp, "|"))) {
		value = s;
		if ((var = strsep(&value, "=")) && value) {
			if (!strcasecmp(var, "attach")) {
				if (ast_true(value))
					vmu->attach = 1;
				else
					vmu->attach = 0;
			} else if (!strcasecmp(var, "serveremail")) {
				strncpy(vmu->serveremail, value, sizeof(vmu->serveremail) - 1);
			} else if (!strcasecmp(var, "tz")) {
				strncpy(vmu->zonetag, value, sizeof(vmu->zonetag) - 1);
			}
		}
	}
	
}

static int
mysql_vm_find_user(struct ast_vm_user *ivm, char *context, char *mailbox)
{
	MYSQL_RES *result;
	MYSQL_ROW rowval;
	MYSQL_FIELD *fields;
	int numFields, i;
	char query[240];
	char options[160] = "";

	//check we got a valid pointer
	if (!ivm) {
		return QINT_ERROR;
	}

	ivm->attach=-1;
	if (mailbox) {
		strncpy(ivm->mailbox, mailbox, sizeof(ivm->mailbox) - 1);
	}
	if (context) {
		strncpy(ivm->context, context, sizeof(ivm->context) - 1);
	}

	if (*ivm->context) {
		snprintf(query, sizeof(query), "SELECT password,fullname,email,pager,options FROM users WHERE context='%s' AND mailbox='%s'", context, mailbox);
	} else {
		snprintf(query, sizeof(query), "SELECT password,fullname,email,pager,options FROM users WHERE mailbox='%s'", mailbox);
	}
	ast_mutex_lock(&db_lock);
	mysql_query(mysql, query);
	if ((result=mysql_store_result(mysql))!=NULL) {
		if ((rowval=mysql_fetch_row(result))!=NULL) {
			numFields=mysql_num_fields(result);
			fields=mysql_fetch_fields(result);
			for (i=0; i<numFields; i++) {
				if (rowval[i]) {
					if (!strcmp(fields[i].name, "password")) {
						strncpy(ivm->password, rowval[i], sizeof(ivm->password) - 1);
					} else if (!strcmp(fields[i].name, "fullname")) {
						strncpy(ivm->fullname, rowval[i], sizeof(ivm->fullname) - 1);
					} else if (!strcmp(fields[i].name, "email")) {
						strncpy(ivm->email, rowval[i], sizeof(ivm->email) - 1);
					} else if (!strcmp(fields[i].name, "pager")) {
						strncpy(ivm->pager, rowval[i], sizeof(ivm->pager) - 1);
					} else if (!strcmp(fields[i].name, "options")) {
						strncpy(options, rowval[i], sizeof(options) - 1);
						apply_options(ivm, options);
					}
				}
			}
			mysql_free_result(result);
			ast_mutex_unlock(&db_lock);
			return(QINT_OK);
		} else {
			mysql_free_result(result);
			ast_mutex_unlock(&db_lock);
			errno = ENODATA;
			return(QINT_ERROR);
		}
	}
	ast_mutex_unlock(&db_lock);
	errno = ENODATA;
	return(QINT_ERROR);
}

static int
mysql_vm_change_password(struct ast_vm_user *vmu, char *password)
{
	char query[400];

	if (*vmu->context) {
		snprintf(query, sizeof(query), "UPDATE users SET password='%s' WHERE context='%s' AND mailbox='%s' AND password='%s'", password, vmu->context, vmu->mailbox, vmu->password);
	} else {
		snprintf(query, sizeof(query), "UPDATE users SET password='%s' WHERE mailbox='%s' AND password='%s'", password, vmu->mailbox, vmu->password);
	}
	ast_mutex_lock(&db_lock);
	mysql_query(mysql, query);
	strcpy(vmu->password, password);
	ast_mutex_unlock(&db_lock);

	return(QINT_OK);
}

static int
mysql_vm_reset_password(char *context, char *mailbox, char *password)
{
	char query[320];

	if (context) {
		snprintf(query, sizeof(query), "UPDATE users SET password='%s' WHERE context='%s' AND mailbox='%s'", password, context, mailbox);
	} else {
		snprintf(query, sizeof(query), "UPDATE users SET password='%s' WHERE mailbox='%s'", password, mailbox);
	}
	ast_mutex_lock(&db_lock);
	mysql_query(mysql, query);
	ast_mutex_unlock(&db_lock);

	return(QINT_OK);
}

static int
mysql_end_of_call(struct ast_channel *chan) {
	ast_log(LOG_NOTICE, "<><><>END OF CALL<><><>\n");
	ast_log(LOG_NOTICE, "   Name: %s\n", chan->name);
	if (chan->cdr) {
		ast_log(LOG_NOTICE, " cdr->billsec: %d\n", chan->cdr->billsec);
	}
	return(QINT_OK);
}

/////// INSERT CODE FOR OTHER API CALLS ABOVE THIS LINE //////

// Connect to the database.  For other database engines, just change the
// interior of this function.  Return 0 if connect fails, -1 if good
static int
db_connect(void) {
	char *dbsocket = NULL;

	// so we can send null to real_connect if actual socket is empty string
	if (*dbsock) dbsocket = dbsock;

	mysql = mysql_init(NULL);
	
	if (!mysql_real_connect(mysql, hostname, dbuser, password, dbname, dbport, dbsocket, 0)) {
		ast_log(LOG_ERROR, "Failed to connect to mysql database %s on %s.\n",
		dbname, hostname);
		connected = 0;
		records = 0;
	} else {
		ast_log(LOG_DEBUG,"Successfully connected to MySQL database.\n");
		connected = 1;
		records = 0;
		connect_time = time(NULL);
	}
	//clear the username and password for security whether we connected or not
	memset(dbuser, 0, sizeof(dbuser));
	memset(password, 0, sizeof(password));

	if (mysql)
		return(-1);
	else
		return(0);
}

// Disconnect from the database.  For other databases, just change the
// interior of this function
static void
db_disconnect(void) {
	if (mysql) {
		mysql_close(mysql);
		mysql = NULL;
		connected = 0;
		records = 0;
	}
}

static int
my_unload_module(void)
{ 
	ast_cli_unregister(&qint_status_cli);

	// can use 0 here to signify unregister all query types
	// registered for name
	ast_mutex_lock(&db_lock);
	qint_unregister_all(name);

	db_disconnect();

	ast_mutex_unlock(&db_lock);
	return 0;
}

static void
my_log_register_fail(int res, char *s) {
	if (res) {
		ast_log(LOG_WARNING, "Unable to register %s\n", s);
	}
}

static int
my_load_module(void)
{
	int res = 0;
	struct ast_config *cfg = NULL;
	struct ast_variable *var = NULL;
	char *handlername = NULL;

	*hostname = 0;
	*dbname = 0;
	*dbuser = 0;
	*dbsock = 0;
	*password = 0;

	cfg = ast_load(config);
	if (!cfg) {
		ast_log(LOG_WARNING, "Unable to load config for %s Query Interface: %s\n",
				name, config);
		return 0;
	}

	var = ast_variable_browse(cfg, name);
	if (!var) {
		/* nothing configured */
		ast_destroy(cfg);
		return 0;
	}

	while (var) {
		if (!strcasecmp(var->name, "hostname")) {
			strncpy(hostname, var->value, sizeof(hostname)-1);
		}
		else if (!strcasecmp(var->name, "dbname")) {
			strncpy(dbname, var->value, sizeof(dbname)-1);
		}
		else if (!strcasecmp(var->name, "user")) {
			strncpy(dbuser, var->value, sizeof(dbuser)-1);
		}
		else if (!strcasecmp(var->name, "sock")) {
			strncpy(dbsock, var->value, sizeof(dbsock)-1);
		}
		else if (!strcasecmp(var->name, "password")) {
			strncpy(password, var->value, sizeof(password)-1);
		}
		else if (!strcasecmp(var->name, "port")) {
			if (sscanf(var->value, "%d", &dbport) < 1) {
				ast_log(LOG_WARNING,"Bad port number in [%s].  Using default\n",
					name);
				dbport = 0;
			}
		}
		else if (!strcasecmp(var->name, "extensionlookup")) {
			flags.extension_lookup = ast_true(var->value);
		}
		else if (!strcasecmp(var->name, "sipfinduser")) {
			flags.sip_find_user = ast_true(var->value);
		}
		else if (!strcasecmp(var->name, "sipfindpeer")) {
			flags.sip_find_peer = ast_true(var->value);
		}
		else if (!strcasecmp(var->name, "sipupdatepeer")) {
			flags.sip_update_peer = ast_true(var->value);
		}
		else if (!strcasecmp(var->name, "vmfinduser")) {
			flags.vm_find_user = ast_true(var->value);
		}
		else if (!strcasecmp(var->name, "endofcall")) {
			flags.end_of_call = ast_true(var->value);
		}
		var = var->next;
	}
	ast_destroy(cfg);

	// Connect to the database
	if (!db_connect()) return(0);

	// Enable the handlers per the flags in the config file
	if (flags.extension_lookup) {
		handlername = "MySQL Extension Lookup Handler";
		res = qint_register(name, handlername, QINT_EXTENSION_LOOKUP,
					mysql_ext_lookup);
		my_log_register_fail(res, handlername);
	}

	if (flags.sip_find_user) {
		handlername = "MySQL SIP User Lookup Handler";
		res = qint_register(name, handlername, QINT_SIP_FIND_USER,
					mysql_sip_find_user);
		my_log_register_fail(res, handlername);
	}

	if (flags.sip_find_peer) {
		handlername = "MySQL SIP Peer Lookup Handler";
		res = qint_register(name, handlername, QINT_SIP_FIND_PEER,
					mysql_sip_find_peer);
		my_log_register_fail(res, handlername);
	}

	if (flags.sip_update_peer) {
		handlername = "MySQL SIP Peer Update Handler";
		res = qint_register(name, handlername, QINT_SIP_UPDATE_PEER,
					mysql_sip_update_peer);
		my_log_register_fail(res, handlername);
	}

	if (flags.vm_find_user) {
		handlername = "MySQL VM User Lookup Handler";
		res = qint_register(name, handlername, QINT_VM_FIND_USER,
					mysql_vm_find_user);
		my_log_register_fail(res, handlername);

		handlername = "MySQL VM Password Reset Handler";
		res = qint_register(name, handlername, QINT_VM_RESET_PASSWORD,
					mysql_vm_reset_password);
		my_log_register_fail(res, handlername);

		handlername = "MySQL VM Password Change Handler";
		res = qint_register(name, handlername, QINT_VM_CHANGE_PASSWORD,
					mysql_vm_change_password);
		my_log_register_fail(res, handlername);
	}

	if (flags.end_of_call) {
		handlername = "MySQL End of Call Handler";
		res = qint_register(name, handlername, QINT_END_OF_CALL,
					mysql_end_of_call);
		my_log_register_fail(res, handlername);
	}

	ast_cli_register(&qint_status_cli);

	return res;
}

char *
description(void)
{
	return desc;
}

int
load_module(void)
{
	return my_load_module();
}

int
unload_module(void)
{
	return my_unload_module();
}

int
reload(void)
{
	my_unload_module();
	return my_load_module();
}

int
usecount(void)
{
	/* Simplistic use count */
	if (ast_mutex_trylock(&db_lock)) {
		return 1;
	} else {
		ast_mutex_unlock(&db_lock);
		return 0;
	}
}

char *
key()
{
	return ASTERISK_GPL_KEY;
}
