/*
 * ffproxy (c) 2002-2004 Niklas Olmes <niklas@noxa.de>
 * http://faith.eu.org
 * 
 * $Id: db.c,v 2.1 2004/12/31 08:59:15 niklas Exp $
 * 
 * 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., 675
 * Mass Ave, Cambridge, MA 02139, USA.
 */

#include "configure.h"
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif

#include <stdio.h>
#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#endif
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <string.h>
#include <regex.h>
#include <pwd.h>
#include <grp.h>

#include "cfg.h"
#include "print.h"
#include "msg.h"
#include "alloc.h"
#include "file.h"
#include "db.h"

static void     clear_databases(void);
static void     clear_db(char *[]);
static void     clear_rdb(regex_t *[]);
static void     read_db(const char *, char *[]);
static void     read_rdb(const char *, regex_t *[]);
static void     read_file(const char *, struct msg *);
static void	read_config_file(void);
static void	verify_config(void);

#define MAX_E 256
regex_t        *a_ip[MAX_E];
regex_t        *a_host[MAX_E];
char           *a_dyndns[MAX_E];
regex_t        *f_host[MAX_E];
regex_t        *f_url[MAX_E];
regex_t        *f_hdr_drop[MAX_E];
regex_t        *f_hdr_match[MAX_E];
char           *f_hdr_entry[MAX_E];
char           *f_hdr_add[MAX_E];
regex_t        *f_rhdr_drop[MAX_E];
regex_t        *f_rhdr_match[MAX_E];
char           *f_rhdr_entry[MAX_E];
struct msg      e_inv;
struct msg      e_res;
struct msg      e_con;
struct msg      e_post;
struct msg      e_fil;


void
reload_databases(void)
{
	clear_databases();
	load_databases();
}

void
load_databases(void)
{
	extern struct cfg config;

	read_config_file();
	verify_config();

	if (*config.dbdir != '\0' && chdir(config.dbdir) != 0)
		fatal("could not chdir() to dbdir (%s)", config.dbdir);

	read_rdb("db/access.ip", a_ip);
	read_rdb("db/access.host", a_host);
	read_db("db/access.dyndns", a_dyndns);
	read_rdb("db/filter.host.match", f_host);
	read_rdb("db/filter.url.match", f_url);
	read_rdb("db/filter.header.drop", f_hdr_drop);
	read_rdb("db/filter.header.match", f_hdr_match);
	read_db("db/filter.header.entry", f_hdr_entry);
	read_db("db/filter.header.add", f_hdr_add);
	read_rdb("db/filter.rheader.drop", f_rhdr_drop);
	read_rdb("db/filter.rheader.match", f_rhdr_match);
	read_db("db/filter.rheader.entry", f_rhdr_entry);
	read_file("html/invalid", &e_inv);
	read_file("html/resolve", &e_res);
	read_file("html/connect", &e_con);
	read_file("html/post", &e_post);
	read_file("html/filtered", &e_fil);
}

static void
clear_databases(void)
{
	clear_rdb(a_ip);
	clear_rdb(a_host);
	clear_db(a_dyndns);
	clear_rdb(f_host);
	clear_rdb(f_url);
	clear_rdb(f_hdr_drop);
	clear_rdb(f_hdr_match);
	clear_db(f_hdr_entry);
	clear_db(f_hdr_add);
	clear_rdb(f_rhdr_drop);
	clear_rdb(f_rhdr_match);
	clear_db(f_rhdr_entry);
	free(e_inv.c);
	free(e_res.c);
	free(e_con.c);
	free(e_post.c);
	free(e_fil.c);
	e_inv.len = e_res.len = e_con.len = e_post.len = e_fil.len = 0;
}

static void
clear_db(char *db[])
{
	int             i;

	i = 0;
	while (db[i] != NULL) {
		free(db[i]);
		db[i++] = NULL;
	}
}

static void
clear_rdb(regex_t * r[])
{
	int             i;

	i = 0;
	while (r[i] != NULL) {
		regfree(r[i]);
		free(r[i]);
		r[i++] = NULL;
	}
}

static void
read_db(const char *f, char *db[])
{
	FILE           *fp;
	char            buf[512], *p;
	size_t          i;

	fp = my_fopen(f);

	i = 0;
	while (fgets(buf, sizeof(buf), fp) != NULL && i < MAX_E - 1) {
		if (buf[0] == '#' || buf[0] == '\r' || buf[0] == '\n')
			continue;
		if ((p = strchr(buf, '\n')) == NULL) {
			(void) fclose(fp);
			fatal_n("line too long in file %s", f);
		}
		*p = '\0';
		p = (char *) my_alloc(strlen(buf) + 1);
		strcpy(p, buf);
		db[i++] = p;
	}
	(void) fclose(fp);

	db[i] = NULL;
}

static void
read_rdb(const char *f, regex_t * r[])
{
	FILE           *fp;
	regex_t        *regex;
	char            buf[512], *p;
	char            errbuf[512];
	size_t          i;
	int             err;

	fp = my_fopen(f);

	i = 0;
	while (fgets(buf, sizeof(buf), fp) != NULL && i < MAX_E - 1) {
		if (buf[0] == '#' || buf[0] == '\n' || buf[0] == '\r')
			continue;
		if ((p = strchr(buf, '\n')) == NULL) {
			(void) fclose(fp);
			fatal_n("line too long in file %s", f);
		}
		*p = '\0';
		regex = (regex_t *) my_alloc(sizeof(regex_t));
		if ((err = regcomp(regex, buf, REG_EXTENDED)) != 0) {
			(void) regerror(err, regex, errbuf, sizeof(errbuf));
			warn("invalid regular expression (%s) in file (%s): %s", buf, f, errbuf);
			free(regex);
			continue;
		}
		r[i++] = regex;
	}
	(void) fclose(fp);

	r[i] = NULL;
}

static void
read_file(const char *fn, struct msg * m)
{
	int             f;
	char            buf[8192];
	ssize_t         len;

	f = my_open(fn);
	len = read(f, &buf, sizeof(buf));

	m->c = (char *) my_alloc(len + 1);
	(void) memcpy(m->c, buf, len);
	m->c[len] = '\0';
	m->len = len;

	(void) close(f);
}

#include "dns.h"

static void
read_config_file(void)
{
	extern struct cfg config;
	FILE           *fp;
	char            obuf[100];
	char            abuf[100];
	char            b[300];

	if (*config.file == '\0') {
		;
	} else if ((fp = fopen(config.file, "r")) != NULL) {
		while (fgets(b, sizeof(b), fp) != NULL) {
			(void) sscanf(b, "%99s %99s", obuf, abuf);
			if (config.first && strcmp("daemonize", obuf) == 0) {
				if (strcmp(abuf, "yes") == 0)
					config.daemon = 1;
				else
					config.daemon = 0;
				continue;
			} else if (strcmp("child_processes", obuf) == 0) {
				config.childs = atoi(abuf);
				continue;
			} else if (config.first && strcmp("bind_ipv4", obuf) == 0) {
				if (strcmp(abuf, "yes") == 0)
					config.bind_ipv4 = 1;
				else
					config.bind_ipv4 = 0;
				continue;
			} else if (config.first && strcmp("bind_ipv6", obuf) == 0) {
				if (strcmp(abuf, "yes") == 0)
					config.bind_ipv6 = 1;
				else
					config.bind_ipv6 = 0;
				continue;
			} else if (config.first && strcmp("bind_ipv4_host", obuf) == 0) {
				(void) strncpy(config.ipv4, abuf, sizeof(config.ipv4) - 1);
				config.ipv4[sizeof(config.ipv4) - 1] = '\0';
				continue;
			} else if (config.first && strcmp("bind_ipv6_host", obuf) == 0) {
				(void) strncpy(config.ipv6, abuf, sizeof(config.ipv6) - 1);
				config.ipv6[sizeof(config.ipv6) - 1] = '\0';
				continue;
			} else if (config.first && strcmp("port", obuf) == 0) {
				config.port = atoi(abuf);
				continue;
			} else if (strcmp("use_ipv6", obuf) == 0) {
				if (strcmp(abuf, "yes") == 0)
					config.use_ipv6 = 1;
				else
					config.use_ipv6 = 0;
				continue;
			} else if (config.first && strcmp("uid", obuf) == 0) {
				if (!(config.uid = atoi(abuf))) {
					struct passwd *pwd;
					if ((pwd = getpwnam(abuf)))
						config.uid = (unsigned long) pwd->pw_uid;
					else
						fatal_n("UID %s not found", abuf);
				}
				continue;
			} else if (config.first && strcmp("gid", obuf) == 0) {
				if (!(config.gid = atoi(abuf))) {
					struct group *grp;
					if ((grp = getgrnam(abuf)))
						config.gid = (unsigned long) grp->gr_gid;
					else
						fatal_n("GID %s not found", abuf);
				}
				continue;
			} else if (config.first && strcmp("chroot_dir", obuf) == 0) {
				(void) strncpy(config.chroot, abuf, sizeof(config.chroot) - 1);
				config.chroot[sizeof(config.chroot) - 1] = 0;
				continue;
			} else if (strcmp("forward_proxy", obuf) == 0) {
				(void) strncpy(config.proxyhost, abuf, sizeof(config.proxyhost) - 1);
				config.proxyhost[sizeof(config.proxyhost) - 1] = 0;
				continue;
			} else if (strcmp("forward_proxy_port", obuf) == 0) {
				config.proxyport = atoi(abuf);
				continue;
			} else if (strcmp("forward_proxy_ipv6", obuf) == 0) {
				if (strcmp(abuf, "yes") == 0)
					config.aux_proxy_ipv6 = 1;
				else
					config.aux_proxy_ipv6 = 0;
				continue;
			} else if (config.first && strcmp("db_files_path", obuf) == 0) {
				(void) strncpy(config.dbdir, abuf, sizeof(config.dbdir) - 1);
				config.dbdir[sizeof(config.dbdir) - 1] = 0;
				continue;
			} else if (strcmp("backlog_size", obuf) == 0) {
				config.backlog = atoi(abuf);
				continue;
			} else if (strcmp("use_syslog", obuf) == 0) {
				if (strcmp(abuf, "yes") == 0)
					config.syslog = 1;
				else
					config.syslog = 0;
				continue;
			} else if (strcmp("log_all_requests", obuf) == 0) {
				if (strcmp(abuf, "yes") == 0)
					config.logrequests = 1;
				else
					config.logrequests = 0;
				continue;
			} else if (strcmp("accel_host", obuf) == 0) {
				(void) strncpy(config.accelhost, abuf, sizeof(config.accelhost) - 1);
				config.accelhost[sizeof(config.accelhost) - 1] = '\0';
				continue;
			} else if (strcmp("accel_port", obuf) == 0) {
				config.accelport = atoi(abuf);
				continue;
			} else if (strcmp("accel_user_host", obuf) == 0) {
				if (strcmp(abuf, "yes") == 0)
					config.accelusrhost = 1;
				else
					config.accelusrhost = 0;
				continue;
			} else if (strcmp("use_keep_alive", obuf) == 0) {
				if (strcmp(abuf, "yes") == 0)
					config.kalive = 1;
				else
					config.kalive = 0;
				continue;
			} else if (strcmp("unrestricted_connect", obuf) == 0) {
				if (strcmp(abuf, "yes") == 0)
					config.unr_con = 1;
				else
					config.unr_con = 0;
				continue;
			} else if (strcmp("timeout_connect", obuf) == 0) {
				config.to_con = atoi(abuf);
				continue;
			} else if (!config.first) {
				continue;
			} else if (*obuf != '#') {
				warn("unknown option in config file %s:  %s", config.file, obuf);
				continue;
			}
		}
		(void) fclose(fp);
	} else {
		if (strcmp(config.file, CFGFILE) == 0)
			info("default config file (%s) not available, not using config file", CFGFILE);
		else
			fatal("unable to open config file %s", config.file);
	}
	config.first = 0;
}

#define ZHWRONG(a, o, v)	if(a < 1 || a > v) fatal_n("%s is set < 1 or value is too high (maximum:  %d, current:  %d)", o, v, a);
#define HWRONG(a, o, v)		if(a < 0 || a > v) fatal_n("Value of %s is set too high or negative (maximum:  %d, current:  %d)", o, v, a);
#define HUWRONG(a, o, v)		if(a > v) fatal_n("Value of %s is set too high (maximum:  %d, current:  %d)", o, v, a);

static void
verify_config(void)
{
	extern struct cfg config;

	HUWRONG(config.port, "port", MAX_PORTS);
	ZHWRONG(config.childs, "child_processes", MAX_CHILDS);
	HWRONG(config.backlog, "backlog_size", MAX_BACKLOG);
	HUWRONG(config.proxyport, "forward_proxy_port", MAX_PORTS);
	HUWRONG(config.uid, "uid", MAX_UID);
	HUWRONG(config.gid, "gid", MAX_GID);
	HUWRONG(config.accelport, "accel_port", MAX_PORTS);

	if ((config.uid && !config.gid) || (!config.uid && config.gid))
		fatal_n("Only one of uid and gid is set to non-zero.\nYou have to use both or none of them");
	if (*config.accelhost && config.accelport)
		config.accel = 1;
	else
		config.accel = 0;
	if (!config.bind_ipv4 && !config.bind_ipv6)
		fatal_n("Both IPv4 and IPv6 binding disabled.  This makes no sense");
}
