/**
	mod_kzvhd - Kibble's MySql IP Virtual Domains
	Copyright (C) 2003  James A C Kibblewhite [ ApRoXiTy ]

	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
*/

/** modules/mod_kzvhd/mod_kzvhd.c */
/** /usr/apache/bin/httpd -k start */
/** /home/kibble/source/compiled/httpd-2.0.46 */
/** /home/ftp/nts-graphics.com/logs */

/**
# I think we'll use this in the httpd.conf if the module is statically linked
<IfModule mod_kzvhd.c>
        kSQLServer      	localhost
        kSQLDatabase    	vhost_database
        kSQLUsername    	username
        kSQLPassword    	password

        kSQLTable       	vhost_table
        kSQLColumnVPath 	vhost_path
        kSQLColumnVHost 	vhost_domain
        kSQLDefaultHost 	aproxity.com
        kSQLPrefixPath		/home/ftp
        kSQLCacheTimeOut	180
</IfModule>

*/

#include "ap_mmn.h"
#include "apr_buckets.h"
#include "util_filter.h"
#include "apr.h"
#include "apr_strings.h"
#include "apr_lib.h"
#include "apr_uri.h"
#include "ap_config.h"
#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "http_main.h"
#include "http_protocol.h"
#include "http_request.h"
#include "util_script.h"

#include "ap_config_auto.h"

#ifdef HAVE_STDDEF_H
#include <stddef.h>
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <fcntl.h>

#include <mysql.h>

/** Required for the time_t structures */
#include <sys/types.h>

/**
 * This module
 */
module AP_MODULE_DECLARE_DATA kzvhd_module;

typedef struct {
	char			*vhost;
	char			*vpath;
	int			vepoch;
} kzvhd_cached;

/**
 * This modules per-server configuration structure.
 */
typedef struct {
	char			*kSQLServer_Conf;
	char			*kSQLDatabase_Conf;
	char			*kSQLUsername_Conf;
	char			*kSQLPassword_Conf;
	char			*kSQLTable_Conf;
	char			*kSQLColumnVPath_Conf;
	char			*kSQLColumnVHost_Conf;
	char			*kSQLDefaultHost_Conf;
	char			*kSQLPrefixPath_Conf;
	char			*kSQLCacheTimeOut_Conf;
	apr_array_header_t	*vcache;
	MYSQL			*mysql;
} kzvhd_config_rec;

static void* kzvhd_create_server_config(apr_pool_t *p, server_rec *s) {
	kzvhd_config_rec *kzvhd = (kzvhd_config_rec*) apr_pcalloc(p, sizeof(kzvhd_config_rec));
	kzvhd->vcache		= apr_array_make(p, 0, sizeof(kzvhd_cached));
	return kzvhd;
}

static void *kzvhd_merge_server_config(apr_pool_t *p, void *parentv, void *childv) {
	kzvhd_config_rec *parent	= (kzvhd_config_rec*) parentv;
	kzvhd_config_rec *child		= (kzvhd_config_rec*) childv;
	kzvhd_config_rec *new		= (kzvhd_config_rec*) apr_pcalloc(p, sizeof(kzvhd_config_rec));

	new->kSQLServer_Conf		= (child->kSQLServer_Conf ? child->kSQLServer_Conf : parent->kSQLServer_Conf);
	new->kSQLDatabase_Conf		= (child->kSQLDatabase_Conf ? child->kSQLDatabase_Conf : parent->kSQLDatabase_Conf);
	new->kSQLUsername_Conf		= (child->kSQLUsername_Conf ? child->kSQLUsername_Conf : parent->kSQLUsername_Conf);
	new->kSQLPassword_Conf		= (child->kSQLPassword_Conf ? child->kSQLPassword_Conf : parent->kSQLPassword_Conf);
	new->kSQLTable_Conf		= (child->kSQLTable_Conf ? child->kSQLTable_Conf : parent->kSQLTable_Conf);
	new->kSQLColumnVPath_Conf	= (child->kSQLColumnVPath_Conf ? child->kSQLColumnVPath_Conf : parent->kSQLColumnVPath_Conf);
	new->kSQLColumnVHost_Conf	= (child->kSQLColumnVHost_Conf ? child->kSQLColumnVHost_Conf : parent->kSQLColumnVHost_Conf);
	new->kSQLDefaultHost_Conf	= (child->kSQLDefaultHost_Conf ? child->kSQLDefaultHost_Conf : parent->kSQLDefaultHost_Conf);
	new->kSQLPrefixPath_Conf	= (child->kSQLPrefixPath_Conf ? child->kSQLPrefixPath_Conf : parent->kSQLPrefixPath_Conf);
	new->kSQLCacheTimeOut_Conf	= (child->kSQLCacheTimeOut_Conf ? child->kSQLCacheTimeOut_Conf : parent->kSQLCacheTimeOut_Conf);
	/** new->vcache			= apr_array_copy(p, parent->vcache); */
	/** new->vcache			= apr_array_append(p, new->vcache, child->vcache); */
	new->vcache			= (child->vcache ? child->vcache : parent->vcache);
	new->mysql			= NULL;		/* set this to NULL, so ensure we won't use it again */

	return new;
}

static const char* kzvhd_field(cmd_parms *parms, void *mconfig, const char *arg) {
	ptrdiff_t pos = (ptrdiff_t) parms->info;
	kzvhd_config_rec *kzvhd = (kzvhd_config_rec*) ap_get_module_config(parms->server->module_config, &kzvhd_module);

	switch (pos) {
		case 0:
			kzvhd->kSQLServer_Conf		= apr_pstrdup(parms->pool, arg);
			break;
		case 1:
			kzvhd->kSQLDatabase_Conf	= apr_pstrdup(parms->pool, arg);
			break;
		case 2:
			kzvhd->kSQLUsername_Conf	= apr_pstrdup(parms->pool, arg);
			break;
		case 3:
			kzvhd->kSQLPassword_Conf	= apr_pstrdup(parms->pool, arg);
			break;
		case 4:
			kzvhd->kSQLTable_Conf		= apr_pstrdup(parms->pool, arg);
			break;
		case 5:
			kzvhd->kSQLColumnVPath_Conf	= apr_pstrdup(parms->pool, arg);
			break;
		case 6:
			kzvhd->kSQLColumnVHost_Conf	= apr_pstrdup(parms->pool, arg);
			break;
		case 7:
			kzvhd->kSQLDefaultHost_Conf	= apr_pstrdup(parms->pool, arg);
			break;
		case 8:
			kzvhd->kSQLPrefixPath_Conf	= apr_pstrdup(parms->pool, arg);
			break;
		case 9:
			kzvhd->kSQLCacheTimeOut_Conf	= apr_pstrdup(parms->pool, arg);
			break;
	}

	return NULL;
}

static int kzvhd_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) {	
	ap_add_version_component(pconf, "mod_kzvhd/0.0.1");
	return DECLINED;
}

static char* get_path(request_rec *r, const char *host, kzvhd_config_rec *kzvhd) {
	MYSQL_RES	*res;
	MYSQL_ROW	row;
	char		*qstr;
	char		*rstr;

	if (!kzvhd->mysql) {
		if (!(kzvhd->mysql = mysql_init(NULL))) {
			ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r->server, "kzvhd: get_path: unable to allocate MYSQL connection.");
			return NULL;
		}
		/** We do not support SSL connections or any other type... just standard connection for now */
		if (!mysql_real_connect(kzvhd->mysql, kzvhd->kSQLServer_Conf, kzvhd->kSQLUsername_Conf, kzvhd->kSQLPassword_Conf, kzvhd->kSQLDatabase_Conf, 3306, NULL, 0)) {
			ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r->server, "kzvhd: get_path: unable to connect to database: %s.", mysql_error(kzvhd->mysql));
			mysql_close(kzvhd->mysql);
			kzvhd->mysql = NULL;
			return NULL;
		}
	}

	/** Here's where we build our SQL Statement */
	qstr = apr_psprintf(r->pool, "SELECT %s FROM %s WHERE %s = '%s'", kzvhd->kSQLColumnVPath_Conf, kzvhd->kSQLTable_Conf, kzvhd->kSQLColumnVHost_Conf, host);

	if (mysql_real_query(kzvhd->mysql, qstr, strlen(qstr))) {
		ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r->server, "kzvhd: get_path: %s / %s", mysql_error(kzvhd->mysql), host);
		mysql_close(kzvhd->mysql);
		kzvhd->mysql = NULL;
		return NULL;
	}

	if (!(res = mysql_store_result(kzvhd->mysql))) {
		ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r->server, "kzvhd: get_path: %s / %s", mysql_error(kzvhd->mysql), host);
		mysql_close(kzvhd->mysql);
		kzvhd->mysql = NULL;
		return NULL;
	}

	switch (mysql_num_rows(res)) {
	  case 1:
	  	break;
	  case 0:
		ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r->server, "kzvhd: get_path: no results for %s", host);
		mysql_free_result(res);
		mysql_close(kzvhd->mysql);
		kzvhd->mysql = NULL;
		return NULL;
		break;
	  default:
		ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r->server, "kzvhd: get_path: %s has more than 1 server row, failing.", host);
		mysql_free_result(res);
		mysql_close(kzvhd->mysql);
		kzvhd->mysql = NULL;
		return NULL;
		break;
	}

	if (!(row = mysql_fetch_row(res))) {
		ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r->server, "kzvhd: get_path: %s / %s", mysql_error(kzvhd->mysql), host);
		mysql_free_result(res);
		mysql_close(kzvhd->mysql);
		kzvhd->mysql = NULL;
		return NULL;
	}

	rstr = apr_pstrdup(r->pool, row[0]);

	/** Don't forget to free your results variable...
	 * and clean things up or you could cause a mess,
	 * like many many threads and get errors like this:
	 * "server seems busy, (you may need to increase StartServers, or Min/MaxSpareServers)" 
	 *
	 * And setting persistent connections aren't really a good idea either... */
	mysql_free_result(res);	
	mysql_close(kzvhd->mysql);
	kzvhd->mysql = NULL;

	return rstr;
}

static int kzvhd_translate_name(request_rec *r) {

        kzvhd_config_rec *kzvhd = (kzvhd_config_rec*) ap_get_module_config(r->server->module_config, &kzvhd_module);
        kzvhd_cached *vcache    = (kzvhd_cached*)kzvhd->vcache->elts;        
        kzvhd_cached *vcacheArr;

        const char      *host;
        int             fvCache = 0;
        int             iCount;
        time_t          vCtime;

	/** I think this was for a 1.3 work around */
	if (r->uri[0] != '/') {
		ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "kzvhd_translate_name: declined %s no leading `/'", r->uri);
		r->uri = apr_psprintf(r->pool, "/%s", r->uri); /** This line should fix the problem, but I can't be arsed */
		return DECLINED;
	}

	if (!(host = apr_table_get(r->headers_in, "Host"))) {
		if ((host == "") || (host == NULL)) {
			ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "kzvhd_translate_name: no host found (non HTTP/1.1 request, no default set) %s", host);
			return DECLINED;
		} else {
			host = kzvhd->kSQLDefaultHost_Conf;
		}
	}

	/**	We got the host, and we have an array(kzvhd_cached[]), empty or with data in it...?
		Now we roll thru the array and check for old records with a matching host.
		If the host doesn't exist in the array structure, we then add a new record to the array
		with the current epoch time. If the record exists, check the epoch time the the time out variable
		obtained from the httpd.conf and if it is still within the params, then just use the record
		saved in the array structure to save from connecting to the mysql database. If the record has expired,
		look up in the mysql database and refresh that record in the array structure. The data will be updated
		when the module calls 'kzvhd_merge_server_config'. All threads that was created will have a new data structure.
		I do not know how memory intensive this will become thou ...? Any help on this would be great !! */

	/** Get current epoch time */
	(void)time(&vCtime);

	for (iCount = 0; iCount < kzvhd->vcache->nelts; iCount++) {
		/** ((kzvhd_cached*)kzvhd->vcache->elts)[iCount] = vcacheArr; */
		/** ("%s\n", vcache[iCount]); */
	}

	if (fvCache == 0) {
		/** do look up and add new record to array structure */
		if ((int)kzvhd->vcache->nelts == 0) {
			iCount = (int)kzvhd->vcache->nelts;
		} else {
			iCount = (int)kzvhd->vcache->nelts + 1;
		}	
	
                vcacheArr->vhost = apr_pstrdup(r->pool, host);
                vcacheArr->vpath = get_path(r, host, kzvhd);
                vcacheArr->vepoch = (int)vCtime + (int)atoi(kzvhd->kSQLCacheTimeOut_Conf);

		(kzvhd_cached *)ap_push_array(kzvhd->vcache) = ap_pstrdup(r->pool, vcacheArr);

                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "kzvhd_translate_name: review:\nvhost: %s\nvpath: %s\nvepoch: %d", vcacheArr->vhost, vcacheArr->vpath, vcacheArr->vepoch);
	}

	/* if ((kzvhd->vhost_path == "") || (kzvhd->vhost_path == NULL)) {
		if (!(kzvhd->vhost_path = get_path(r, host, kzvhd))) {
			ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "kzvhd_translate_name: no host found in database for %s", host);
			return DECLINED;
		}
	} */

	/** Need to check for leading and trailing slashes, so no mistakes can be made */
	/** ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, "kzvhd_translate_name: translated path: %s%s%s",
		kzvhd->kSQLPrefixPath_Conf ? kzvhd->kSQLPrefixPath_Conf : "", kzvhd->vhost_path, r->uri); */

	/** You can set any environment variables here */
	apr_table_set(r->subprocess_env, "KMIPVD_HOST", host);

	/** r->filename = apr_psprintf(r->pool, "%s%s%s", kzvhd->kSQLPrefixPath_Conf ? kzvhd->kSQLPrefixPath_Conf : "", kzvhd->vhost_path, r->uri); */

	return DECLINED;
}

/** <==-------[ Standard Stuff ... ]--------==> */
static const command_rec kzvhd_commands[] = {
	AP_INIT_TAKE1(	"kSQLServer",		kzvhd_field,	(void *)0,	RSRC_CONF,	"MySql Server IP/Host. kzvhd conf error"),
	AP_INIT_TAKE1(	"kSQLDatabase",		kzvhd_field,	(void *)1,	RSRC_CONF,	"MySql Database. kzvhd conf error"),
	AP_INIT_TAKE1(	"kSQLUsername",		kzvhd_field,	(void *)2,	RSRC_CONF,	"MySql Username. kzvhd conf error"),
	AP_INIT_TAKE1(	"kSQLPassword",		kzvhd_field,	(void *)3,	RSRC_CONF,	"MySql Password. kzvhd conf error"),
	AP_INIT_TAKE1(	"kSQLTable",		kzvhd_field,	(void *)4,	RSRC_CONF,	"MySql Table. kzvhd conf error"),
	AP_INIT_TAKE1(	"kSQLColumnVPath",	kzvhd_field,	(void *)5,	RSRC_CONF,	"MySql vConf. kzvhd conf error"),
	AP_INIT_TAKE1(	"kSQLColumnVHost",	kzvhd_field,	(void *)6,	RSRC_CONF,	"MySql vHost. kzvhd conf error"),
	AP_INIT_TAKE1(	"kSQLDefaultHost",	kzvhd_field,	(void *)7,	RSRC_CONF,	"MySql Default vHost. kzvhd conf error"),
	AP_INIT_TAKE1(	"kSQLPrefixPath",	kzvhd_field,	(void *)8,	RSRC_CONF,	"vHost Prefix Path. kzvhd conf error"),
	AP_INIT_TAKE1(	"kSQLCacheTimeOut",	kzvhd_field,	(void *)9,	RSRC_CONF,	"Time out for expired cached data. kzvhd conf error"),
	{ NULL }
};

static void register_hooks(apr_pool_t *p) {
	/** This is where the old _init routines get registered */
	ap_hook_post_config(kzvhd_post_config, NULL, NULL, APR_HOOK_MIDDLE);	
	/** Translate the URI into a filename */
	ap_hook_translate_name(kzvhd_translate_name, NULL, NULL, APR_HOOK_MIDDLE);
}

AP_DECLARE_DATA module kzvhd_module = {
	STANDARD20_MODULE_STUFF,
	NULL,					/** create per-directory config structure */
	NULL,					/** merge per-directory config structures */
	kzvhd_create_server_config,		/** create per-server config structure */
	kzvhd_merge_server_config,		/** merge per-server config structures */
	kzvhd_commands,				/** command apr_table_t */
	register_hooks				/** register hooks */
};


	/** for (iCount = 0; iCount < kzvhd->vcache->nelts; iCount++) {
		ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "kzvhd_translate_name: cached_review:\nvhost: %s\nvpath: %s\nvepoch: %d", vcache[iCount].vhost, vcache[iCount].vpath, vcache[iCount].vepoch);
		if (strcmp(vcache[iCount].vhost, host)) {
			fvCache = 1;
			ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "kzvhd_translate_name: record located");
			if ((int)vctime > vcache[iCount].vepoch) { */
				/** do look up and reset record */
				/** ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "kzvhd_translate_name: record expired, updating record"); */
				/* vcache[iCount].vpath = get_path(r, host, kzvhd);
				vcache[iCount].vepoch = (int)vctime + (int)atoi(kzvhd->kSQLCacheTimeOut_Conf); */
			/** } else { */
				/** vcache[iCount].vpath */
				/** ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "kzvhd_translate_name: record retrieved, using cached data"); */
			/** }
		} else {
			fvCache = 0;
		}
	} */