/*
 * Asterisk -- A telephony toolkit for Linux.
 *
 * Copyright (C) 1999-2005, Digium, Inc.
 *
 * Mark Spencer <markster@digium.com>  - Asterisk Author
 * Matthew Boehm <mboehm@cytelcom.com> - MySQL RealTime Driver Author
 *
 * res_config_mysql.c <mysql plugin for RealTime configuration engine>
 *
 * v2.0   - (10-07-05) - mutex_lock fixes (bug #4973, comment #0034602)
 *
 * v1.9   - (08-19-05) - Added support to correctly honor the family database specified
 *                       in extconfig.conf (bug #4973)
 *
 * v1.8   - (04-21-05) - Modified return values of update_mysql to better indicate
 *                       what really happened.
 *
 * v1.7   - (01-28-05) - Fixed non-initialization of ast_category struct
 *                       in realtime_multi_mysql function which caused segfault. 
 *
 * v1.6   - (00-00-00) - Skipped to bring comments into sync with version number in CVS.
 *
 * v1.5.1 - (01-26-05) - Added better(?) locking stuff
 *
 * v1.5   - (01-26-05) - Brought up to date with new config.h changes (bug #3406)
 *                     - Added in extra locking provided by georg (bug #3248)
 *
 * v1.4   - (12-02-04) - Added realtime_multi_mysql function
 *                        This function will return an ast_config with categories,
 *                        unlike standard realtime_mysql which only returns
 *                        a linked list of ast_variables
 *
 * v1.3   - (12-01-04) - Added support other operators
 *                       Ex: =, !=, LIKE, NOT LIKE, RLIKE, etc...
 *
 * v1.2   - (11-DD-04) - Added reload. Updated load and unload.
 *                       Code beautification (doc/CODING-GUIDELINES)
 */

#include <asterisk/channel.h>
#include <asterisk/logger.h>
#include <asterisk/config.h>
#include <asterisk/module.h>
#include <asterisk/lock.h>
#include <asterisk/options.h>
#include <asterisk/cli.h>
#include <asterisk/utils.h>
#include <stdlib.h>
#include <string.h>
#include <mysql.h>
#include <mysql_version.h>
#include <errmsg.h>

static char *res_config_mysql_desc = "MySQL RealTime Configuration Driver";

AST_MUTEX_DEFINE_STATIC(mysql_lock);
#define RES_CONFIG_MYSQL_CONF "res_mysql.conf"
MYSQL         mysql;
static char   dbhost[50];
static char   dbuser[50];
static char   dbpass[50];
static char   dbname[50];
static char   dbsock[50];
static int    dbport;
static int    connected;
static time_t connect_time;

static int parse_config(void);
static int mysql_reconnect(const char *database);
static int realtime_mysql_status(int fd, int argc, char **argv);

STANDARD_LOCAL_USER;

LOCAL_USER_DECL;

static char cli_realtime_mysql_status_usage[] =
"Usage: realtime mysql status\n"
"       Shows connection information for the MySQL RealTime driver\n";

static struct ast_cli_entry cli_realtime_mysql_status = {
        { "realtime", "mysql", "status", NULL }, realtime_mysql_status,
        "Shows connection information for the MySQL RealTime driver", cli_realtime_mysql_status_usage, NULL };

static struct ast_variable *realtime_mysql(const char *database, const char *table, va_list ap)
{
	MYSQL_RES *result;
	MYSQL_ROW row;
	MYSQL_FIELD *fields;
	int numFields, i;
	char sql[256];
	char *stringp;
	char *chunk;
	char *op;
	const char *newparam, *newval;
	struct ast_variable *var=NULL, *prev=NULL;

	if(!table) {
		ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
		return NULL;
	}

	/* Get the first parameter and first value in our list of passed paramater/value pairs */
	newparam = va_arg(ap, const char *);
	newval = va_arg(ap, const char *);
	if(!newparam || !newval)  {
		ast_log(LOG_WARNING, "MySQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
		mysql_close(&mysql);
		return NULL;
	}

	/* Create the first part of the query using the first parameter/value pairs we just extracted
	   If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */

	if(!strchr(newparam, ' ')) op = " ="; else op = "";

	snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op, newval);
	while((newparam = va_arg(ap, const char *))) {
		newval = va_arg(ap, const char *);
		if(!strchr(newparam, ' ')) op = " ="; else op = "";
		snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s '%s'", newparam, op, newval);
	}
	va_end(ap);

	ast_log(LOG_DEBUG, "MySQL RealTime: Retrieve SQL: %s\n", sql);

	/* We now have our complete statement; Lets connect to the server and execute it. */
	ast_mutex_lock(&mysql_lock);
	if(!mysql_reconnect(database)) {
		ast_mutex_unlock(&mysql_lock);
		return NULL;
	}

	if(mysql_real_query(&mysql, sql, strlen(sql))) {
		ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database. Check debug for more info.\n");
		ast_log(LOG_DEBUG, "MySQL RealTime: Query: %s\n", sql);
		ast_log(LOG_DEBUG, "MySQL RealTime: Query Failed because: %s\n", mysql_error(&mysql));
		ast_mutex_unlock(&mysql_lock);
		return NULL;
	}

	if((result = mysql_store_result(&mysql))) {
		numFields = mysql_num_fields(result);
		fields = mysql_fetch_fields(result);

		while((row = mysql_fetch_row(result))) {
			for(i = 0; i < numFields; i++) {
				stringp = row[i];
				while(stringp) {
					chunk = strsep(&stringp, ";");
					if(chunk && !ast_strlen_zero(ast_strip(chunk))) {
						if(prev) {
							prev->next = ast_variable_new(fields[i].name, chunk);
							if (prev->next) {
								prev = prev->next;
							}
						} else {
							prev = var = ast_variable_new(fields[i].name, chunk);
						}
					}
				}
			}
		}
	} else {                                
		ast_log(LOG_WARNING, "MySQL RealTime: Could not find any rows in table %s.\n", table);
	}

	ast_mutex_unlock(&mysql_lock);
	mysql_free_result(result);

	return var;
}

static struct ast_config *realtime_multi_mysql(const char *database, const char *table, va_list ap)
{
	MYSQL_RES *result;
	MYSQL_ROW row;
	MYSQL_FIELD *fields;
	int numFields, i;
	char sql[256];
	const char *initfield = NULL;
	char *stringp;
	char *chunk;
	char *op;
	const char *newparam, *newval;
	struct ast_realloca ra;
	struct ast_variable *var=NULL;
	struct ast_config *cfg = NULL;
	struct ast_category *cat = NULL;

	if(!table) {
		ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
		return NULL;
	}
	
	memset(&ra, 0, sizeof(ra));

	cfg = ast_config_new();
	if (!cfg) {
		/* If I can't alloc memory at this point, why bother doing anything else? */
		ast_log(LOG_WARNING, "Out of memory!\n");
		return NULL;
	}

	/* Get the first parameter and first value in our list of passed paramater/value pairs */
	newparam = va_arg(ap, const char *);
	newval = va_arg(ap, const char *);
	if(!newparam || !newval)  {
		ast_log(LOG_WARNING, "MySQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
		mysql_close(&mysql);
		return NULL;
	}

	initfield = ast_strdupa(newparam);
	if(initfield && (op = strchr(initfield, ' '))) {
		*op = '\0';
	}

	/* Create the first part of the query using the first parameter/value pairs we just extracted
	   If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */

	if(!strchr(newparam, ' ')) op = " ="; else op = "";

	snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op, newval);
	while((newparam = va_arg(ap, const char *))) {
		newval = va_arg(ap, const char *);
		if(!strchr(newparam, ' ')) op = " ="; else op = "";
		snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s '%s'", newparam, op, newval);
	}

	if(initfield) {
		snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " ORDER BY %s", initfield);
	}

	va_end(ap);

	ast_log(LOG_DEBUG, "MySQL RealTime: Retrieve SQL: %s\n", sql);

	/* We now have our complete statement; Lets connect to the server and execute it. */
	ast_mutex_lock(&mysql_lock);
	if(!mysql_reconnect(database)) {
		ast_mutex_unlock(&mysql_lock);
		return NULL;
	}

	if(mysql_real_query(&mysql, sql, strlen(sql))) {
		ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database. Check debug for more info.\n");
		ast_log(LOG_DEBUG, "MySQL RealTime: Query: %s\n", sql);
		ast_log(LOG_DEBUG, "MySQL RealTime: Query Failed because: %s\n", mysql_error(&mysql));
		ast_mutex_unlock(&mysql_lock);
		return NULL;
	}

	if((result = mysql_store_result(&mysql))) {
		numFields = mysql_num_fields(result);
		fields = mysql_fetch_fields(result);

		while((row = mysql_fetch_row(result))) {
			var = NULL;
			cat = ast_category_new("");
			if(!cat) {
				ast_log(LOG_WARNING, "Out of memory!\n");
				continue;
			}
			for(i = 0; i < numFields; i++) {
				stringp = row[i];
				while(stringp) {
					chunk = strsep(&stringp, ";");
					if(chunk && !ast_strlen_zero(ast_strip(chunk))) {
						if(initfield && !strcmp(initfield, fields[i].name)) {
							ast_category_rename(cat, chunk);
						}
						var = ast_variable_new(fields[i].name, chunk);
						ast_variable_append(cat, var);
					}
				}
			}
			ast_category_append(cfg, cat);
		}
	} else {
		ast_log(LOG_WARNING, "MySQL RealTime: Could not find any rows in table %s.\n", table);
	}

	ast_mutex_unlock(&mysql_lock);
	mysql_free_result(result);

	return cfg;
}

static int update_mysql(const char *database, const char *table, const char *keyfield, const char *lookup, va_list ap)
{
	my_ulonglong numrows;
	char sql[256];
	const char *newparam, *newval;

	if(!table) {
		ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
               return -1;
	}

	/* Get the first parameter and first value in our list of passed paramater/value pairs */
	newparam = va_arg(ap, const char *);
	newval = va_arg(ap, const char *);
	if(!newparam || !newval)  {
		ast_log(LOG_WARNING, "MySQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
		mysql_close(&mysql);
               return -1;
	}

	/* Create the first part of the query using the first parameter/value pairs we just extracted
	   If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */

	snprintf(sql, sizeof(sql), "UPDATE %s SET %s = '%s'", table, newparam, newval);
	while((newparam = va_arg(ap, const char *))) {
		newval = va_arg(ap, const char *);
		snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ", %s = '%s'", newparam, newval);
	}
	va_end(ap);
	snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " WHERE %s = '%s'", keyfield, lookup);

	ast_log(LOG_DEBUG,"MySQL RealTime: Update SQL: %s\n", sql);

	/* We now have our complete statement; Lets connect to the server and execute it. */
	ast_mutex_lock(&mysql_lock);
	if(!mysql_reconnect(database)) {
		ast_mutex_unlock(&mysql_lock);
		return -1;
	}

	if(mysql_real_query(&mysql, sql, strlen(sql))) {
		ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database. Check debug for more info.\n");
		ast_log(LOG_DEBUG, "MySQL RealTime: Query: %s\n", sql);
		ast_log(LOG_DEBUG, "MySQL RealTime: Query Failed because: %s\n", mysql_error(&mysql));
		ast_mutex_unlock(&mysql_lock);
		return -1;
	}

	numrows = mysql_affected_rows(&mysql);
	ast_mutex_unlock(&mysql_lock);

	ast_log(LOG_DEBUG,"MySQL RealTime: Updated %llu rows on table: %s\n", numrows, table);

	/* From http://dev.mysql.com/doc/mysql/en/mysql-affected-rows.html
	 * An integer greater than zero indicates the number of rows affected
	 * Zero indicates that no records were updated
	 * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
	*/

	if(numrows >= 0)
		return (int)numrows;

	return -1;
}

static struct ast_config *config_mysql(const char *database, const char *table, const char *file, struct ast_config *cfg)
{
	MYSQL_RES *result;
	MYSQL_ROW row;
	my_ulonglong num_rows;
	struct ast_config *new;
	struct ast_variable *cur_v, *new_v;
	struct ast_category *cur_cat, *new_cat;
	char sql[250] = "";
	char last[80] = "";
	int cat_started = 0;
	int var_started = 0;
	int last_cat_metric = 0;

	last[0] = '\0';

	if(!file || !strcmp(file, RES_CONFIG_MYSQL_CONF)) {
		ast_log(LOG_WARNING, "MySQL RealTime: Cannot configure myself.\n");
		return NULL;
	}

	snprintf(sql, sizeof(sql), "SELECT category, var_name, var_val, cat_metric FROM %s WHERE filename='%s' and commented=0 ORDER BY filename, cat_metric desc, var_metric asc, category, var_name, var_val, id", table, file);

	ast_log(LOG_DEBUG, "MySQL RealTime: Static SQL: %s\n", sql);

	/* We now have our complete statement; Lets connect to the server and execute it. */
	ast_mutex_lock(&mysql_lock);
	if(!mysql_reconnect(database)) {
		ast_mutex_unlock(&mysql_lock);
		return NULL;
	}

	if(mysql_real_query(&mysql, sql, strlen(sql))) {
		ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database. Check debug for more info.\n");
		ast_log(LOG_DEBUG, "MySQL RealTime: Query: %s\n", sql);
		ast_log(LOG_DEBUG, "MySQL RealTime: Query Failed because: %s\n", mysql_error(&mysql));
		ast_mutex_unlock(&mysql_lock);
		return NULL;
	}

	if((result = mysql_store_result(&mysql))) {
		num_rows = mysql_num_rows(result);
		ast_log(LOG_DEBUG, "MySQL RealTime: Found %llu rows.\n", num_rows);

		/* There might exist a better way to access the column names other than counting,
                   but I believe that would require another loop that we don't need. */

		while((row = mysql_fetch_row(result))) {
			if(!strcmp(row[1], "#include")) {
				if (!ast_config_internal_load(row[2], cfg)) {
					mysql_free_result(result);
					ast_mutex_unlock(&mysql_lock);
					return NULL;
				}
				continue;
			}

			if(strcmp(last, row[0]) || last_cat_metric != atoi(row[3])) {
				cur_cat = ast_category_new(row[0]);
				if (!cur_cat) {
					ast_log(LOG_WARNING, "Out of memory!\n");
					break;
				}
				strcpy(last, row[0]);
				last_cat_metric = atoi(row[3]);
				ast_category_append(cfg, cur_cat);
			}
			new_v = ast_variable_new(row[1], row[2]);
			ast_variable_append(cur_cat, new_v);
		}
	} else {
		ast_log(LOG_WARNING, "MySQL RealTime: Could not find config '%s' in database.\n", file);
	}

	mysql_free_result(result);
	ast_mutex_unlock(&mysql_lock);

	return cfg;
}

static struct ast_config_engine mysql_engine = {
	.name = "mysql",
	.load_func = config_mysql,
	.realtime_func = realtime_mysql,
	.realtime_multi_func = realtime_multi_mysql,
	.update_func = update_mysql
};

int load_module (void)
{
	parse_config();

	ast_mutex_lock(&mysql_lock);

	if(!mysql_reconnect(NULL)) {
		ast_log(LOG_WARNING, "MySQL RealTime: Couldn't establish connection. Check debug.\n");
		ast_log(LOG_DEBUG, "MySQL RealTime: Cannot Connect: %s\n", mysql_error(&mysql));
	}

	ast_config_engine_register(&mysql_engine);
	if(option_verbose) {
		ast_verbose("MySQL RealTime driver loaded.\n");
	}
	ast_cli_register(&cli_realtime_mysql_status);

	ast_mutex_unlock(&mysql_lock);

	return 0;
}

int unload_module (void)
{
	/* Aquire control before doing anything to the module itself. */
	ast_mutex_lock(&mysql_lock);

	mysql_close(&mysql);
	ast_cli_unregister(&cli_realtime_mysql_status);
	ast_config_engine_deregister(&mysql_engine);
	if(option_verbose) {
		ast_verbose("MySQL RealTime unloaded.\n");
	}

	STANDARD_HANGUP_LOCALUSERS;

	/* Unlock so something else can destroy the lock. */
	ast_mutex_unlock(&mysql_lock);

	return 0;
}

int reload (void)
{
	/* Aquire control before doing anything to the module itself. */
	ast_mutex_lock(&mysql_lock);

	mysql_close(&mysql);
	connected = 0;
	parse_config();

	if(!mysql_reconnect(NULL)) {
		ast_log(LOG_WARNING, "MySQL RealTime: Couldn't establish connection. Check debug.\n");
		ast_log(LOG_DEBUG, "MySQL RealTime: Cannot Connect: %s\n", mysql_error(&mysql));
	}

	ast_verbose(VERBOSE_PREFIX_2 "MySQL RealTime reloaded.\n");

	/* Done reloading. Release lock so others can now use driver. */
	ast_mutex_unlock(&mysql_lock);

	return 0;
}

int parse_config (void)
{
	struct ast_config *config;
	char *s;

	config = ast_config_load(RES_CONFIG_MYSQL_CONF);

	if(config) {
		if(!(s=ast_variable_retrieve(config, "general", "dbuser"))) {
			ast_log(LOG_WARNING, "MySQL RealTime: No database user found, using 'asterisk' as default.\n");
			strncpy(dbuser, "asterisk", sizeof(dbuser) - 1);
		} else {
			strncpy(dbuser, s, sizeof(dbuser) - 1);
		}

		if(!(s=ast_variable_retrieve(config, "general", "dbpass"))) {
                        ast_log(LOG_WARNING, "MySQL RealTime: No database password found, using 'asterisk' as default.\n");
                        strncpy(dbpass, "asterisk", sizeof(dbpass) - 1);
                } else {
                        strncpy(dbpass, s, sizeof(dbpass) - 1);
                }

		if(!(s=ast_variable_retrieve(config, "general", "dbhost"))) {
                        ast_log(LOG_WARNING, "MySQL RealTime: No database host found, using localhost via socket.\n");
			dbhost[0] = '\0';
                } else {
                        strncpy(dbhost, s, sizeof(dbhost) - 1);
                }

		if(!(s=ast_variable_retrieve(config, "general", "dbname"))) {
                        ast_log(LOG_WARNING, "MySQL RealTime: No database name found, using 'asterisk' as default.\n");
			strncpy(dbname, "asterisk", sizeof(dbname) - 1);
                } else {
                        strncpy(dbname, s, sizeof(dbname) - 1);
                }

		if(!(s=ast_variable_retrieve(config, "general", "dbport"))) {
                        ast_log(LOG_WARNING, "MySQL RealTime: No database port found, using 3306 as default.\n");
			dbport = 3306;
                } else {
			dbport = atoi(s);
                }

		if(dbhost && !(s=ast_variable_retrieve(config, "general", "dbsock"))) {
                        ast_log(LOG_WARNING, "MySQL RealTime: No database socket found, using '/tmp/mysql.sock' as default.\n");
                        strncpy(dbsock, "/tmp/mysql.sock", sizeof(dbsock) - 1);
                } else {
                        strncpy(dbsock, s, sizeof(dbsock) - 1);
                }
	}
	ast_config_destroy(config);

	if(dbhost) {
		ast_log(LOG_DEBUG, "MySQL RealTime Host: %s\n", dbhost);
		ast_log(LOG_DEBUG, "MySQL RealTime Port: %i\n", dbport);
	} else {
		ast_log(LOG_DEBUG, "MySQL RealTime Socket: %s\n", dbsock);
	}
	ast_log(LOG_DEBUG, "MySQL RealTime User: %s\n", dbuser);
	ast_log(LOG_DEBUG, "MySQL RealTime Password: %s\n", dbpass);

	return 1;
}

char *description (void)
{
	return res_config_mysql_desc;
}

int usecount (void)
{
	/* Try and get a lock. If unsuccessful, than that means another thread is using the mysql object. */
	if(ast_mutex_trylock(&mysql_lock)) {
		ast_log(LOG_DEBUG, "MySQL RealTime: Module usage count is 1.\n");
		return 1;
	}
	ast_mutex_unlock(&mysql_lock);
	return 0;
}

char *key ()
{
	return ASTERISK_GPL_KEY;
}

static int mysql_reconnect(const char *database)
{
	char my_database[50];

	if(!database || ast_strlen_zero(database))
		ast_copy_string(my_database, dbname, sizeof(my_database));
	else
		ast_copy_string(my_database, database, sizeof(my_database));

	/* mutex lock should have been locked before calling this function. */

	if((!connected) && (dbhost || dbsock) && dbuser && dbpass && my_database) {
		if(!mysql_init(&mysql)) {
			ast_log(LOG_WARNING, "MySQL RealTime: Insufficient memory to allocate MySQL resource.\n");
			connected = 0;
			return 0;
		}
		if(mysql_real_connect(&mysql, dbhost, dbuser, dbpass, my_database, dbport, dbsock, 0)) {
			ast_log(LOG_DEBUG, "MySQL RealTime: Successfully connected to database.\n");
			connected = 1;
			connect_time = time(NULL);
			return 1;
		} else {
			ast_log(LOG_ERROR, "MySQL RealTime: Failed to connect database server %s on %s. Check debug for more info.\n", dbname, dbhost);
			ast_log(LOG_DEBUG, "MySQL RealTime: Cannot Connect: %s\n", mysql_error(&mysql));
			connected = 0;
			return 0;
		}
	} else {
		if(mysql_ping(&mysql) != 0) {
			connected = 0;
			ast_log(LOG_ERROR, "MySQL RealTime: Failed to reconnect. Check debug for more info.\n");
			ast_log(LOG_DEBUG, "MySQL RealTime: Server Error: %s\n", mysql_error(&mysql));
			return 0;
		}

		connected = 1;

		if(mysql_select_db(&mysql, my_database) != 0) {
			ast_log(LOG_WARNING, "MySQL RealTime: Unable to select database: %s. Still Connected.\n", my_database);
			ast_log(LOG_DEBUG, "MySQL RealTime: Database Select Failed: %s\n", mysql_error(&mysql));
			return 0;
		}

		ast_log(LOG_DEBUG, "MySQL RealTime: Everything is fine.\n");
		return 1;
	}
}

static int realtime_mysql_status(int fd, int argc, char **argv)
{
	char status[256], status2[100] = "";
	int ctime = time(NULL) - connect_time;

	if(mysql_reconnect(NULL)) {
		if(dbhost) {
			snprintf(status, 255, "Connected to %s@%s, port %d", dbname, dbhost, 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, dbhost);
		}

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

		if (ctime > 31536000) {
			ast_cli(fd, "%s%s for %d years, %d days, %d hours, %d minutes, %d seconds.\n", status, status2, ctime / 31536000, (ctime % 31536000) / 86400, (ctime % 86400) / 3600, (ctime % 3600) / 60, ctime % 60);
		} else if (ctime > 86400) {
			ast_cli(fd, "%s%s for %d days, %d hours, %d minutes, %d seconds.\n", status, status2, ctime / 86400, (ctime % 86400) / 3600, (ctime % 3600) / 60, ctime % 60);
		} else if (ctime > 3600) {
			ast_cli(fd, "%s%s for %d hours, %d minutes, %d seconds.\n", status, status2, ctime / 3600, (ctime % 3600) / 60, ctime % 60);
		} else if (ctime > 60) {
			ast_cli(fd, "%s%s for %d minutes, %d seconds.\n", status, status2, ctime / 60, ctime % 60);
		} else {
			ast_cli(fd, "%s%s for %d seconds.\n", status, status2, ctime);
		}

		return RESULT_SUCCESS;
	} else {
		return RESULT_FAILURE;
	}
}
