/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000-2002 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * Portions of this software are based upon public domain software
 * originally written at the National Center for Supercomputing Applications,
 * University of Illinois, Urbana-Champaign.
 *
 * (c) 2002 Theo Zourzouvillys <theo@crazygreek.co.uk>
 * See http://linux-whore.org/ for more info.
 *
 */


/* We need this to be able to access the docroot. */
#define CORE_PRIVATE

#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "http_main.h"
#include "http_protocol.h"
#include "util_script.h"
#include <mysql/mysql.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/un.h>

typedef struct shapvh_cfg 
{

	MYSQL	* mysql;
	char 	* mysql_host;
	char 	* mysql_user;
	char 	* mysql_pass;
	char 	* mysql_dbname;
	char 	* mysql_defaultroot;
	char 	* mysql_defaultadmin;
	char 	* mysql_query;
	int		  shapvh_enabled;
	int		  mysqlok;
	int		  shapvh_visp;

} shapvh_cfg;

module MODULE_VAR_EXPORT shapvh_module;

static int shapvh_setup(server_rec * s)
{
	shapvh_cfg * cfg = ap_get_module_config(s->module_config, &shapvh_module);

	if (cfg->shapvh_enabled == 0)
	{
		ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL, "ShapVH Disbaled for '%s', but tried to connect to MySQL.", s->server_hostname);
		return;
	} 

	/* This should never ever ever happen */
	if (cfg->mysql == NULL)
	{
		ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, NULL, "MySQL handle is NULL!");
			return -1;
	}

	cfg->mysqlok = 0;
	if (!mysql_real_connect(cfg->mysql, cfg->mysql_host, cfg->mysql_user, cfg->mysql_pass, cfg->mysql_dbname, 0, NULL, 0))
	{
		ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, NULL, "Failed to connect to database: Error: %s\n", mysql_error(cfg->mysql));
		cfg->mysqlok = 0;
		return -1;
	}

	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, NULL, "ShapVH Connected to MySQL");
	cfg->mysqlok = 1;

	return 1;
}

static void * shapvh_create_server_config(pool *p, server_rec *s)
{
    shapvh_cfg * cfg = (shapvh_cfg *) ap_pcalloc(p, sizeof(shapvh_cfg));
	cfg->mysql = NULL;
	cfg->mysql = ap_palloc(p, sizeof(MYSQL));
	mysql_init(cfg->mysql);
	return (void *) cfg;
}

static void * shapvh_merge_server_config (pool *p, void *basev, void *overridesv)
{
	shapvh_cfg * new = (shapvh_cfg *) ap_pcalloc(p, sizeof(shapvh_cfg));
	shapvh_cfg * base = (shapvh_cfg *) basev;
	shapvh_cfg * overrides = (shapvh_cfg *) overridesv;

	new->mysql = ap_palloc(p, sizeof(MYSQL));
	mysql_init(new->mysql);

	new->mysqlok = 0;
	new->mysql_host = base->mysql_host;
	new->mysql_user = base->mysql_user;
	new->mysql_pass = base->mysql_pass;
	new->mysql_dbname = base->mysql_dbname;
	new->mysql_defaultroot = base->mysql_defaultroot;
	new->mysql_defaultadmin = base->mysql_defaultadmin;

	new->mysql_query    = (overrides->mysql_query == NULL) ? base->mysql_query : overrides->mysql_query;
	new->shapvh_enabled = (overrides->shapvh_enabled == 1) ? 1 : 0;
	new->shapvh_visp    = (overrides->shapvh_visp == 1)    ? 1 : 0;

    return new;
}

static int shapvh_translate(request_rec *r)
{
	char * query, buff[HUGE_STRING_LEN], * ptr, * hptr = (char *)r->hostname;
	shapvh_cfg * cfg = ap_get_module_config(r->server->module_config, &shapvh_module);
	core_server_config * conf = (core_server_config *) ap_get_module_config(r->server->module_config, &core_module);

	if (cfg->shapvh_enabled == 0)
		return DECLINED;
	else if (cfg->mysqlok == 0)
		shapvh_setup(r->server);

	ap_table_setn(r->subprocess_env, "SHAPVH_HOST", r->hostname);

	/* Hostnames *CANT* have \'s or "'"'s in, according to RFCs. */
	if (strstr(r->hostname, "'") != NULL || strstr(r->hostname, "\\") != NULL)
	{
		ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL, "Invalid charecter found in hostname: %s", r->hostname);
		conf->ap_document_root = ap_pstrdup(r->pool, cfg->mysql_defaultroot);
		r->server->server_uid = 1016;
		r->server->server_admin = ap_pstrdup(r->pool, cfg->mysql_defaultadmin);
		ap_table_setn(r->subprocess_env, "SHAPVH_ERR", "INVALID_CHAR");
		return DECLINED;
	}

	/* Tut tut - bad bad bad. */
	if (cfg->mysql_query == NULL)
	{
		ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, NULL, "ShapVH: MySQL query is NULL!");
		return DECLINED;
	}

	ptr = buff;

	/* If it's a virtual ISP (www.username.visp.domain) then we should extract the username and VISP */
	/* it shouldn't really be hardcoded to 'www.' as the prefix, maybe i'll change it. */

	if (cfg->shapvh_visp == 1)
	{
		/* Fast forward to just after the "www." */
		while (*hptr && (*hptr == 'w' || *hptr == '.'))
		{
			if (*hptr == '.')
			{
				*hptr++;
				break;
			}
			*hptr++;
		}

		/* Tut tut, bad client */
		if (!*hptr)
		{
			ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL, "Unknown VISP hostname: %s", r->hostname);
			conf->ap_document_root = ap_pstrdup(r->pool, cfg->mysql_defaultroot);
			r->server->server_uid = 1016;
			r->server->server_admin = ap_pstrdup(r->pool, cfg->mysql_defaultadmin);
			ap_table_setn(r->subprocess_env, "SHAPVH_ERR", "VISP_HOSTNAME_INVALID");
			return DECLINED;
		}

		/* Collect the username */
		while (*hptr && (isalpha(*hptr) || isdigit(*hptr)))
			*ptr++ = *hptr++;

		/* jump past the dot after the username */
		if (*hptr) {

			*hptr++;

		} else {
			/* moooo, bad. */
            ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL, "Unknown VISP hostname: %s", r->hostname);
            conf->ap_document_root = ap_pstrdup(r->pool, cfg->mysql_defaultroot);
            r->server->server_uid = 1016;
            r->server->server_admin = ap_pstrdup(r->pool, cfg->mysql_defaultadmin);
            ap_table_setn(r->subprocess_env, "SHAPVH_ERR", "VISP_HOSTNAME_INVALID");
            return DECLINED;

		}

		/* Add a terminating NULL */
		*ptr = '\0';
		ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, NULL, "User: %s, VISP: %s", buff, hptr);

		/* Create our query */
		query = ap_psprintf(r->pool, cfg->mysql_query, buff, hptr);
		ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, NULL, "MySQL Query: %s", query);

	} else {

		/* otherwise, it's a plain vhost lookup. */
		query = ap_psprintf(r->pool, cfg->mysql_query, r->hostname);

	}

	if (mysql_real_query(cfg->mysql, query, strlen(query)))
	{
		/* Eeek! query failed! */
		ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL, "Error in query '%s' %s", query, mysql_error(cfg->mysql));
		conf->ap_document_root = ap_pstrdup(r->pool, cfg->mysql_defaultroot);
		r->server->server_uid = 1016;
		r->server->server_admin = ap_pstrdup(r->pool, cfg->mysql_defaultadmin);
		ap_table_setn(r->subprocess_env, "SHAPVH_ERR", "QUERY_ERROR");
		return DECLINED;

	} else {

		/* YaY! we have data! */

		MYSQL_RES * res_set;
		MYSQL_ROW row;

		res_set = mysql_store_result(cfg->mysql);

		if ((row = mysql_fetch_row(res_set)))
		{

			conf->ap_document_root = ap_pstrdup(r->pool, row[0]);
			r->server->server_uid = atoi(row[1]);
			r->server->server_gid = atoi(row[3]);
			/* r->server->server_hostname = r->hostname; // Can't do this - where do we store the memory!? */
			r->server->server_admin = ap_pstrdup(r->pool, row[2]);


			/* things could always go wrong. lets make sure it doesn't cause any damage if they do! */
			if (r->server->server_uid < 1000 || r->server->server_gid < 1000)
			{
				ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL, "Invalid UID or GID Found: %d/%d, aborting.", r->server->server_uid, r->server->server_gid);
				conf->ap_document_root = ap_pstrdup(r->pool, cfg->mysql_defaultroot);
				r->server->server_admin = ap_pstrdup(r->pool, cfg->mysql_defaultadmin);
				r->server->server_uid = 1016;
				r->server->server_gid = 1001;
				ap_table_setn(r->subprocess_env, "SHAPVH_ERR", "INVALID_UIDGID");
				mysql_free_result(res_set);
				return DECLINED;
			}

		} else {

			conf->ap_document_root = ap_pstrdup(r->pool, cfg->mysql_defaultroot);
			r->server->server_uid = 1016;
			r->server->server_admin = ap_pstrdup(r->pool, cfg->mysql_defaultadmin);
			ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL, "Host '%s' not found", r->hostname);
			ap_table_setn(r->subprocess_env, "SHAPVH_ERR", "HOST_NOT_FOUND");
			mysql_free_result(res_set);
			return DECLINED;
		}

	}

	/* Now, this is kinda norty - but it will allow over filename translation handlers to do their stuff, with the right docroot. */
	return DECLINED;
}

/* Config stuff. */

static const char *set_host(cmd_parms *cmd, void *dummy, char *arg)
{
	shapvh_cfg * cfg = ap_get_module_config(cmd->server->module_config, &shapvh_module);
	cfg->mysql_host = arg;
	return NULL;
}
static const char *set_user(cmd_parms *cmd, void *dummy, char *arg)
{
	shapvh_cfg * cfg = ap_get_module_config(cmd->server->module_config, &shapvh_module);
	cfg->mysql_user = arg;
	return NULL;
}
static const char *set_pass(cmd_parms *cmd, void *dummy, char *arg)
{
	shapvh_cfg * cfg = ap_get_module_config(cmd->server->module_config, &shapvh_module);
	cfg->mysql_pass = arg;
	return NULL;
}
static const char *set_dbname(cmd_parms *cmd, void *dummy, char *arg)
{
	shapvh_cfg * cfg = ap_get_module_config(cmd->server->module_config, &shapvh_module);
	cfg->mysql_dbname = arg;
	return NULL;
}

static const char *set_defaultroot(cmd_parms *cmd, void *dummy, char *arg)
{
	shapvh_cfg * cfg = ap_get_module_config(cmd->server->module_config, &shapvh_module);
	cfg->mysql_defaultroot = arg;
	return NULL;
}
static const char *set_defaultadmin(cmd_parms *cmd, void *dummy, char *arg)
{
	shapvh_cfg * cfg = ap_get_module_config(cmd->server->module_config, &shapvh_module);
	cfg->mysql_defaultadmin = arg;
	return NULL;
}

static const char *set_shquery(cmd_parms *cmd, void *dummy, char *arg)
{
	shapvh_cfg * cfg = ap_get_module_config(cmd->server->module_config, &shapvh_module);
	cfg->mysql_query = arg;
	return NULL;
}

static const char *set_onoff(cmd_parms *cmd, void * mconfig, int flag)
{
	shapvh_cfg * cfg = (shapvh_cfg *) ap_get_module_config(cmd->server->module_config, &shapvh_module);
	cfg->shapvh_enabled = (flag ? 1 : 0);
	return NULL;
}

static const char *set_shvisp(cmd_parms *cmd, void * mconfig, int flag)
{
	shapvh_cfg * cfg = (shapvh_cfg *) ap_get_module_config(cmd->server->module_config, &shapvh_module);
	cfg->shapvh_visp = (flag ? 1 : 0);
	return NULL;
}

static const command_rec shapvh_cmds[] = {
	{ "ShapVHOn",			set_onoff,			NULL, RSRC_CONF, FLAG,		"Turn on Shared Apache Virtualhost on this Server" },
	{ "ShapVhHost", 		set_host, 			NULL, RSRC_CONF, TAKE1, 	"Set Hostname to connect to database"},
	{ "ShapVhUser", 		set_user, 			NULL, RSRC_CONF, TAKE1, 	"Set Username to connect to database"},
	{ "ShapVhPass", 		set_pass, 			NULL, RSRC_CONF, TAKE1, 	"Set Password to connect to database with"},
	{ "ShapVhDbName", 		set_dbname, 		NULL, RSRC_CONF, TAKE1, 	"Set Database To use"},
	{ "ShapVhDefaultRoot", 	set_defaultroot, 	NULL, RSRC_CONF, TAKE1, 	"Default root when vhost isn't foud"},
	{ "ShapVhDefaultAdmin",	set_defaultadmin, 	NULL, RSRC_CONF, TAKE1, 	"Default admin address when vhost isn't found."},
	{ "ShapVhQuery", 		set_shquery, 		NULL, RSRC_CONF, TAKE1, 	"The Select query.  Returns DocRoot, UID, and AdminAddress."},
	{ "ShapVhVISP", 		set_shvisp, 		NULL, RSRC_CONF, FLAG, 		"If this virtualhost is a VISP."},
	NULL,
};

module MODULE_VAR_EXPORT shapvh_module =
{
    STANDARD_MODULE_STUFF,
 	NULL,          				/* initializer */
    NULL,           			/* dir config creater */
    NULL,           			/* dir merger --- default is to override */
    shapvh_create_server_config,/* server config */
    shapvh_merge_server_config,	/* merge server configs */
    shapvh_cmds,	      		/* command table */
    NULL,			  			/* handlers */
    shapvh_translate,      		/* filename translation */
    NULL,           			/* check_user_id */
    NULL,           			/* check auth */
    NULL,           			/* check access */
    NULL,           			/* type_checker */
    NULL,           			/* fixups */
    NULL,          				/* logger */
    NULL,           			/* header parser */
    NULL,		       			/* child_init */
    NULL,           			/* child_exit */
    NULL           				/* post read-request */
};
