/*
 * Copyright (c) 2004-2005 Sergey Lyubka <valenok@gmail.com>
 * All rights reserved
 *
 * "THE BEER-WARE LICENSE" (Revision 42):
 * Sergey Lyubka wrote this file.  As long as you retain this notice you
 * can do whatever you want with this stuff. If we meet some day, and you think
 * this stuff is worth it, you can buy me a beer in return.
 */

#include "shttpd_defs.h"
#include "wsmand-daemon.h"

/*
 * Configuration parameters setters
 */
static void
set_int(struct shttpd_ctx *ctx, void *ptr, const char *string)
{
	ctx = NULL;	/* Unused */
	* (int *) ptr = atoi(string);
}

static void
set_str(struct shttpd_ctx *ctx, void *ptr, const char *string)
{
	ctx = NULL;	/* Unused */
	* (char **) ptr = strdup(string);
}

static void
set_log_file(struct shttpd_ctx *ctx, void *ptr, const char *string)
{
	FILE	**fp = ptr;
	ctx = NULL;

	if ((*fp = fopen(string, "a")) == NULL)
		elog(E_FATAL, NULL, "cannot open log file %s: %s",
		    string, strerror(errno));
}

static int isbyte(int n) { return (n >= 0 && n <= 255); }

static void
set_acl(struct shttpd_ctx *ctx, void *ptr, const char *s)
{
	struct llhead	*head = ptr;
	struct acl	*acl;
	char		flag;
	int		len, a, b, c, d, n, mask;

	ctx = NULL;

	FOR_EACH_WORD_IN_LIST(s, len) {

		mask = 32;

		if (sscanf(s, "%c%d.%d.%d.%d%n",&flag,&a,&b,&c,&d,&n) != 5) {
			elog(E_FATAL, NULL, "[%s]: subnet must be "
			    "[+|-]x.x.x.x[/x]", s);
		} else if (flag != '+' && flag != '-') {
			elog(E_FATAL, NULL, "flag must be + or -: [%s]", s);
		} else if (!isbyte(a)||!isbyte(b)||!isbyte(c)||!isbyte(d)) {
			elog(E_FATAL, NULL, "bad ip address: [%s]", s);
		} else	if ((acl = malloc(sizeof(*acl))) == NULL) {
			elog(E_FATAL, NULL, "%s", "cannot malloc subnet");
		} else if (sscanf(s + n, "/%d", &mask) == 0) {
			/* Do nothing, no mask specified */
		} else if (mask < 0 || mask > 32) {
			elog(E_FATAL, NULL, "bad subnet mask: %d [%s]", n, s);
		}

		acl->ip = (a << 24) | (b << 16) | (c << 8) | d;
		acl->mask = mask ? 0xffffffffU << (32 - mask) : 0;
		acl->flag = flag;
		LL_TAIL(head, &acl->link);
	}
}

#ifndef NO_SSL
/*
 * Dynamically load SSL library. Set up ctx->ssl_ctx pointer.
 */
static void
set_ssl(struct shttpd_ctx *ctx, void *arg, const char *pem)
{
	SSL_CTX		*CTX;
	void		*lib;
	struct ssl_func	*fp;

	arg = NULL;	/* Unused */

	/* Load SSL library dynamically */
	if ((lib = dlopen(SSL_LIB, RTLD_LAZY)) == NULL)
		elog(E_FATAL, NULL, "set_ssl: cannot load %s", SSL_LIB);

	for (fp = ssl_sw; fp->name != NULL; fp++)
		if ((fp->ptr.v_void = dlsym(lib, fp->name)) == NULL)
			elog(E_FATAL, NULL,"set_ssl: cannot find %s", fp->name);

	/* Initialize SSL crap */
	static int ssl_library_initialized = 0;
	if(!ssl_library_initialized) {
		debug("Initialize SSL");
		SSL_library_init();
		ssl_library_initialized = 1;
	}
	if ((CTX = SSL_CTX_new(SSLv23_server_method())) == NULL)
		elog(E_FATAL, NULL, "SSL_CTX_new error");
        else if (wsmand_options_get_ssl_cert_file() && SSL_CTX_use_certificate_file(CTX, wsmand_options_get_ssl_cert_file(),SSL_FILETYPE_PEM) == 0)
		elog(E_FATAL, NULL, "cannot open %s : %s", pem, strerror(errno));
	else if (wsmand_options_get_ssl_key_file() && SSL_CTX_use_PrivateKey_file(CTX, wsmand_options_get_ssl_key_file(), SSL_FILETYPE_PEM) == 0)
		elog(E_FATAL, NULL, "cannot open %s : %s", pem, strerror(errno));
	ctx->ssl_ctx = CTX;
}
#endif /* NO_SSL */

static void
set_mime(struct shttpd_ctx *ctx, void *arg, const char *string)
{
	arg = NULL;
	set_mime_types(ctx, string);
}

#define	OFS(x)	offsetof(struct shttpd_ctx, x)
#define BOOL_OPT	"0|1"
const struct opt options[] = {
	{'d', "document_root", "Web root directory", set_str,
		OFS(document_root), "directory", NULL, OPT_DIR},
	{'i', "index_files", "Index files", set_str, OFS(index_files),
		"file_list", INDEX_FILES, OPT_ADVANCED},
	{'p', "listen_ports", "Listening ports", set_str,
		OFS(ports), "ports", LISTENING_PORTS, OPT_ADVANCED},
	{'D', "list_directories", "Directory listing", set_int,
		OFS(dirlist), BOOL_OPT, "1", OPT_BOOL | OPT_ADVANCED},
#ifndef NO_CGI
	{'c', "cgi_extensions", "CGI extensions", set_str,
		OFS(cgi_extensions), "ext_list", CGI_EXT, OPT_ADVANCED},
	{'C', "cgi_interpreter", "CGI interpreter", set_str,
		OFS(cgi_interpreter), "file", NULL, OPT_FILE | OPT_ADVANCED},
	{'V', "cgi_envvar", "CGI envir variables", set_str,
		OFS(cgi_vars), "X=Y,....", NULL, OPT_ADVANCED},
#endif /* NO_CGI */
#if !defined(NO_SSI)
	{'S', "ssi_extensions", "SSI extensions", set_str,
		OFS(ssi_extensions), "ext_list", SSI_EXT, OPT_ADVANCED},
#endif /* NO_SSI */
	{'N', "auth_realm", "Authentication realm", set_str,
		OFS(auth_realm), "auth_realm", REALM, OPT_ADVANCED},
	{'l', "access_log", "Access log file", set_log_file,
		OFS(access_log), "file", NULL, OPT_FILE | OPT_ADVANCED},
	{'e', "error_log", "Error log file", set_log_file,
		OFS(error_log), "file", NULL, OPT_FILE | OPT_ADVANCED},
	{'m', "mime_types", "Mime types file", set_mime,
		OFS(mime_file), "file", NULL, OPT_FILE | OPT_ADVANCED},
	{'P', "global_htpasswd", "Global passwords file", set_str,
		OFS(global_passwd_file), "file", NULL, OPT_FILE | OPT_ADVANCED},
#ifndef NO_SSL
	{'s', "ssl_certificate", "SSL certificate file", set_ssl,
		OFS(ssl_ctx), "pem_file", NULL, OPT_FILE | OPT_ADVANCED},
#endif /* NO_SSL */
	{'U', "put_auth", "PUT,DELETE auth file",set_str,
		OFS(put_auth_file), "file", NULL, OPT_FILE | OPT_ADVANCED},
	{'a', "aliases", "Aliases", set_str,
		OFS(aliases), "X=Y,...", NULL, OPT_ADVANCED},
	{'b', "io_buf_size", "IO buffer size", set_int, OFS(io_buf_size),
		"bytes", DFLT_IO_SIZ, OPT_INT | OPT_ADVANCED},
	{'x', "acl", "Allow/deny IP addresses/subnets", set_acl,
		OFS(acl), "acl_list", NULL, OPT_ADVANCED},
#ifdef _WIN32
	{'B', "auto_start", "Autostart with Windows", set_int,
		OFS(auto_start), BOOL_OPT, "1", OPT_BOOL},
#else
	{'I', "inetd_mode", "Inetd mode", set_int,
		OFS(inetd_mode), BOOL_OPT, NULL, OPT_BOOL	},
	{'u', "runtime_uid", "Run as user", set_str,
		OFS(uid), "user_name", NULL, 0		},
#endif /* _WIN32 */
	{0,   NULL, NULL, NULL, 0, NULL, NULL, 0	}
};

static const struct opt *
find_option(int sw, const char *name)
{
	const struct opt	*opt;

	for (opt = options; opt->sw != 0; opt++)
		if (sw == opt->sw || (name && strcmp(opt->name, name) == 0))
			return (opt);

	return (NULL);
}

static void
set_option(const struct opt *opt, const char *val, char **tmpvars)
{
	tmpvars += opt - options;

	if (*tmpvars != NULL)
		free(*tmpvars);

	*tmpvars = strdup(val);
}

/*
 * Initialize shttpd context
 */
static void
initialize_context(struct shttpd_ctx *ctx, const char *config_file,
		int argc, char *argv[], char **tmpvars)
{
	char			line[FILENAME_MAX], root[FILENAME_MAX],
					var[sizeof(line)], val[sizeof(line)];
	const char		*arg;
	size_t			i;
	const struct opt	*opt;
	FILE 			*fp;
	struct tm		*tm;

	current_time = time(NULL);
	tm = localtime(&current_time);
	tz_offset = 0;
#if 0
	tm->tm_gmtoff - 3600 * (tm->tm_isdst > 0 ? 1 : 0);
#endif

	(void) memset(ctx, 0, sizeof(*ctx));

	ctx->start_time = current_time;
	InitializeCriticalSection(&ctx->mutex);

	LL_INIT(&ctx->connections);
	LL_INIT(&ctx->mime_types);
	LL_INIT(&ctx->registered_uris);
	LL_INIT(&ctx->uri_auths);
	LL_INIT(&ctx->error_handlers);
	LL_INIT(&ctx->acl);

#if !defined(NO_SSI)
	LL_INIT(&ctx->ssi_funcs);
#endif /* NO_SSI */

	/* First pass: set the defaults */
	for (opt = options; opt->sw != 0; opt++)
		if (tmpvars[opt - options] == NULL && opt->def != NULL)
			tmpvars[opt - options] = strdup(opt->def);

	/* Second pass: load config file  */
	if (config_file != NULL && (fp = fopen(config_file, "r")) != NULL) {
		DBG(("init_ctx: config file %s", config_file));

		/* Loop through the lines in config file */
		while (fgets(line, sizeof(line), fp) != NULL) {

			/* Skip comments and empty lines */
			if (line[0] == '#' || line[0] == '\n')
				continue;

			/* Trim trailing newline character */
			line[strlen(line) - 1] = '\0';

			if (sscanf(line, "%s %[^#\n]", var, val) != 2)
				elog(E_FATAL,0,"init_ctx: bad line: [%s]",line);

			if ((opt = find_option(0, var)) == NULL)
				elog(E_FATAL, NULL,
				    "set_option: unknown variable [%s]", var);
			set_option(opt, val, tmpvars);
		}
		(void) fclose(fp);
	}

	/* Third pass: process command line args */
	for (i = 1; i < (size_t) argc && argv[i][0] == '-'; i++)
		if ((opt = find_option(argv[i][1], NULL)) != NULL) {
			arg = argv[i][2] ? &argv[i][2] : argv[++i];

			if (arg == NULL)
				usage(argv[0]);

			set_option(opt, arg, tmpvars);
		} else {
			usage(argv[0]);
		}

	/* Call setters functions now */
	for (i = 0; i < NELEMS(options); i++)
		if (tmpvars[i] != NULL) {
			options[i].setter(ctx,
			    ((char *) ctx) + options[i].ofs, tmpvars[i]);
			free(tmpvars[i]);
		}

	/* If document_root is not set, set it to current directory */
	if (ctx->document_root == NULL) {
		(void) my_getcwd(root, sizeof(root));
		ctx->document_root = strdup(root);
	}

#ifdef _WIN32
	{WSADATA data;	WSAStartup(MAKEWORD(2,2), &data);}
#endif /* _WIN32 */

	DBG(("init_ctx: initialized context %p", (void *) ctx));
}

/*
 * Show usage string and exit.
 */
void
usage(const char *prog)
{
	const struct opt	*opt;

	(void) fprintf(stderr,
	    "SHTTPD version %s (c) Sergey Lyubka\n"
	    "usage: %s [OPTIONS] [config_file]\n"
	    "Note: config line keyword for every option is in the "
	    "round brackets\n", VERSION, prog);

#if !defined(NO_AUTH)
	(void) fprintf(stderr, "-A <htpasswd_file> <realm> <user> <passwd>\n");
#endif /* NO_AUTH */

	for (opt = options; opt->name != NULL; opt++)
		(void) fprintf(stderr, "-%c <%s>\t\t%s (%s)\n",
		    opt->sw, opt->arg, opt->desc, opt->name);

	exit(EXIT_FAILURE);
}

struct shttpd_ctx *
init_from_argc_argv(const char *config_file, int argc, char *argv[])
{
	struct shttpd_ctx	*ctx;
	char			*tmpvars[NELEMS(options)];
	size_t			i;

	/* Initialize all temporary holders to NULL */
	for (i = 0; i < NELEMS(tmpvars); i++)
		tmpvars[i] = NULL;

	if ((ctx = malloc(sizeof(*ctx))) != NULL)
		initialize_context(ctx, config_file, argc, argv, tmpvars);

	return (ctx);
}

struct shttpd_ctx *
shttpd_init(const char *config_file, ...)
{
	struct shttpd_ctx	*ctx;
	va_list			ap;
	const char		*opt_name, *opt_value;
	char			*tmpvars[NELEMS(options)];
	const struct opt	*opt;
	size_t			i;

	elog(E_LOG, NULL, "Initializing http server");
	/* Initialize all temporary holders to NULL */
	for (i = 0; i < NELEMS(tmpvars); i++)
		tmpvars[i] = NULL;

	if ((ctx = malloc(sizeof(*ctx))) != NULL) {

		va_start(ap, config_file);
		while ((opt_name = va_arg(ap, const char *)) != NULL) {
			opt_value = va_arg(ap, const char *);

			if ((opt = find_option(0, opt_name)) == NULL)
				elog(E_FATAL, NULL, "shttpd_init: "
				    "unknown variable [%s]", opt_name);
			set_option(opt, opt_value, tmpvars);
		}
		va_end(ap);

		initialize_context(ctx, config_file, 0, NULL, tmpvars);
	}

	return (ctx);
}
