/*
 * $Id: 908f6086ca0c41d666ace3a5f95af3b7dc1cc42b $
 *
 * This file is part of nsmtp.
 *
 * Copyright (c) 2007, 2008 Niklas Olmes <niklas@noxa.de>
 *                          http://www.nevm.de/nsmtp/
 *                          NEVM-RIPE
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License Version 2
 * as published by the Free Software Foundation.
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>

#include "ntl.h"
#include "alloc.h"
#include "log.h"
#include "dns.h"
#include "config.h"

static const char rcsid[] = "$Id: 908f6086ca0c41d666ace3a5f95af3b7dc1cc42b $";

static int nsm_config_setdefault(void);

struct nsm_config {
	const char *s;
	void *vi;
	char **vs;
	char ***vsa;
	int t;
	const char *ds;
	const long long dn;
	int nz;
};

static int nsmc_flag = 0;

static const struct nsm_config nsm_config[] = {

	{ "name", NULL, &nsmc_name, NULL, 's', NULL, 0, 1 },
	{ "alias", NULL, &nsmc_alias, NULL, 's', NULL, 0, 1 },
	{ "domain", NULL, &nsmc_domain, NULL, 's', NULL, 0, 1 },

	{ "ports", NULL, NULL, &nsmc_ports, 'S', NULL, 0, 0 },
	{ "ports_ssl", NULL, NULL, &nsmc_ports_ssl, 'S', NULL, 0, 0 },

	{ "mail_root", NULL, &nsmc_mail_root, NULL, 's', "/mail", 0, 1 },
	{ "nologin", NULL, &nsmc_nologin, NULL, 's', "/sbin/nologin", 0, 1 },
	{ "cleanpath", NULL, &nsmc_cleanpath, NULL, 's', "/bin:/usr/bin", 0, 1 },

	{ "pidfile_dir", NULL, &nsmc_pidfile_dir, NULL, 's', "/var/nsmtp/run", 0, 1 },

	{ "smtpd_uid", &nsmc_smtpd_uid, NULL, NULL, 'u', NULL, 350, 1 },
	{ "smtpd_gid", &nsmc_smtpd_gid, NULL, NULL, 'u', NULL, 350, 1 },
	{ "smtpd_chroot", NULL, &nsmc_smtpd_chroot, NULL, 's', "/var/nsmtp", 0, 1 },
	{ "smtpd_maxchilds", &nsmc_smtpd_maxchilds, NULL, NULL, 'i', NULL, 40, 1 },
	{ "smtpd_error_threshold_soft", &nsmc_smtpd_error_threshold_soft, NULL, NULL, 'i', NULL, 6, 1 },
	{ "smtpd_error_threshold_hard", &nsmc_smtpd_error_threshold_hard, NULL, NULL, 'i', NULL, 12, 1 },
	{ "smtpd_noop_threshold", &nsmc_smtpd_noop_threshold, NULL, NULL, 'i', NULL, 4, 1 },

	{ "smtpr_uid", &nsmc_smtpr_uid, NULL, NULL, 'u', NULL, 351, 1 },
	{ "smtpr_gid", &nsmc_smtpr_gid, NULL, NULL, 'u', NULL, 360, 1 },
	{ "smtpr_chroot", NULL, &nsmc_smtpr_chroot, NULL, 's', "/var/nsmtp", 0, 1 },

	{ "smtps_uid", &nsmc_smtps_uid, NULL, NULL, 'u', NULL, 361, 1 },
	{ "smtps_gid", &nsmc_smtps_gid, NULL, NULL, 'u', NULL, 360, 1 },

	{ "ssl_server_pem", NULL, &nsmc_ssl_server_pem, NULL, 's', "/var/nsmtp/ssl/server.pem", 0, 1 },

	{ "ldap_cfg_file", NULL, &nsmc_ldap_cfg_file, NULL, 's', "/var/nsmtp/ntldap.cfg", 0, 1 },

	{ "legacyauth_file", NULL, &nsmc_legacyauth_file, NULL, 's', "/var/nsmtp/legacyauth", 0, 1 },

	{ "smtp_timeout", &nsmc_smtp_timeout, NULL, NULL, 'u', NULL, 60, 1 },

	{ "default_maildir_uid", &nsmc_default_maildir_uid, NULL, NULL, 'u', NULL, 49999, 1 },
	{ "default_maildir_gid", &nsmc_default_maildir_gid, NULL, NULL, 'u', NULL, 49999, 1 },
	{ "maildir_uid_range_low", &nsmc_maildir_uid_range_low, NULL, NULL, 'u', NULL, 30000, 1 },
	{ "maildir_uid_range_high", &nsmc_maildir_uid_range_high, NULL, NULL, 'u', NULL, 50000, 1 },

	{ "mail_basedn", NULL, &nsmc_mail_basedn, NULL, 's', NULL, 0, 1 },

	{ "mail_regex", NULL, &nsmc_mail_regex, NULL, 's', "^[A-Za-z0-9._+=-][A-Za-z0-9._+=-]*@[A-Za-z0-9.-][A-Za-z0-9.-]*\\.[A-Za-z0-9.-][A-Za-z0-9.-]*$", 0, 1 },

	{ "spool_root", NULL, &nsmc_spool_root, NULL, 's', "/var/nsmtp/spool", 0, 1 },
	{ "spool_lock", NULL, &nsmc_spool_lock, NULL, 's', "/var/nsmtp/spool/lock", 0, 1 },
	{ "spool_defer", NULL, &nsmc_spool_defer, NULL, 's', "/var/nsmtp/spool/defer", 0, 1 },

	{ "relay_timeout", &nsmc_relay_timeout, NULL, NULL, 'i', NULL, 2700, 1 },
	{ "relay_root", NULL, &nsmc_relay_root, NULL, 's', "/var/nsmtp/relay", 0, 1 },
	{ "relay_banned", NULL, &nsmc_relay_banned, NULL, 's', "/var/nsmtp/relay/banned", 0, 1 },
	{ "relay_temporary", NULL, &nsmc_relay_temporary, NULL, 's', "/var/nsmtp/relay/temporary", 0, 1 },
	{ "relay_permanent", NULL, &nsmc_relay_permanent, NULL, 's', "/var/nsmtp/relay/permanent", 0, 1 },

	{ "greylist_rcpt_db", NULL, &nsmc_greylist_rcpt_db, NULL, 's', "/var/nsmtp/greylist_rcpt.db", 0, 1 },
	{ "greylist_rcpt_wait", &nsmc_greylist_rcpt_wait, NULL, NULL, 'i', NULL, 240, 1 },
	{ "greylist_rcpt_autowhite_timeout", &nsmc_greylist_rcpt_autowhite_timeout, NULL, NULL, 'i', NULL, 2*24*3600, 1 },
	{ "greylist_rcpt_whitelist_timeout", &nsmc_greylist_rcpt_whitelist_timeout, NULL, NULL, 'i', NULL, 21*24*3600, 1 },

	{ "greylist_spf_db", NULL, &nsmc_greylist_spf_db, NULL, 's', "/var/nsmtp/greylist_spf.db", 0, 1 },
	{ "greylist_spf_wait", &nsmc_greylist_spf_wait, NULL, NULL, 'i', NULL, 240, 1 },
	{ "greylist_rcpt_autowhite_timeout", &nsmc_greylist_spf_autowhite_timeout, NULL, NULL, 'i', NULL, 2*24*3600, 1 },
	{ "greylist_rcpt_whitelist_timeout", &nsmc_greylist_spf_whitelist_timeout, NULL, NULL, 'i', NULL, 21*24*3600, 1 },

	{ "whitelist_host_db", NULL, &nsmc_whitelist_host_db, NULL, 's', "/var/nsmtp/whitelist_host.db", 0, 1 },
	{ "whitelist_host_timeout", &nsmc_whitelist_host_timeout, NULL, NULL, 'i', NULL, 21*24*3600, 1 },

	{ "spam_mailbox", NULL, &nsmc_spam_mailbox, NULL, 's', "/.Spam", 0, 1 },

	{ "spool_max_defers", &nsmc_spool_max_defers, NULL, NULL, 'i', NULL, 30, 1 },
	{ "spool_max_age", &nsmc_spool_max_age, NULL, NULL, 'i', NULL, 24*3600*7, 1 },

	{ "virscan_hosts", NULL, NULL, &nsmc_virscan_hosts, 'S', NULL, 0, 0 },
	{ "spamscan_hosts", NULL, NULL, &nsmc_spamscan_hosts, 'S', NULL, 0, 0 },

	{ NULL, NULL, NULL, NULL, 0, NULL, 0, 0 }
};

static int
nsm_config_setdefault(void)
{
	int i;

	if (nsmc_flag++)
		return 0;

	nsmc_ldap_cfgs = NULL;
	nsmc_ip = NULL;

	for (i = 0; nsm_config[i].s != NULL; i++) {
		switch (nsm_config[i].t) {
		case 'i':
			*((int *)(nsm_config[i].vi)) = (int)nsm_config[i].dn;
			break;
		case 'u':
			*((unsigned int *)(nsm_config[i].vi)) = (unsigned int)nsm_config[i].dn;
			break;
		case 's':
			*(nsm_config[i].vs) = NULL;
			if (nsm_config[i].ds != NULL)
				*(nsm_config[i].vs) = nsm_strdup(nsm_config[i].ds);
			break;
		case 'S':
			*(nsm_config[i].vsa) = NULL;
			break;
		default:
			xwarn("warn: nsm_config_setdefault() => internal error: unhandled type(%c) in table", nsm_config[i].t);
			nsm_config_error = 4;
			nsm_config_error_str = "internal error in nsm_config_setdefault(), see log for details";
			return 1;
		}
		DEBUG(("nsm_config_setdefault() => %d: %s = type(%c) - default (%s)/(%lld) - nz %d\n", i, nsm_config[i].s, nsm_config[i].t, nsm_config[i].ds, nsm_config[i].dn, nsm_config[i].nz));
	}

	return 0;
}

int
nsm_config_check(void)
{
	int i;

	for (i = 0; nsm_config[i].s != NULL; i++)
		switch (nsm_config[i].t) {
		case 'i':
			if (nsm_config[i].nz && *((int *)(nsm_config[i].vi)) == 0) {
				xwarn("warn: nsm_config_check() => signed integer var \"%s\" is zero but it shouldn't.", nsm_config[i].s);
				goto nsm_config_check_error;
			}
			break;
		case 'u':
			if (nsm_config[i].nz && *((unsigned int *)(nsm_config[i].vi)) == 0) {
				xwarn("warn: nsm_config_check() => unsigned integer var \"%s\" is zero but it shouldn't.", nsm_config[i].s);
				goto nsm_config_check_error;
			}
			break;
		case 's':
			if (nsm_config[i].nz && *(nsm_config[i].vs) == NULL) {
				xwarn("warn: nsm_config_check() => string var \"%s\" is not set but it should.", nsm_config[i].s);
				goto nsm_config_check_error;
			}
			break;
		case 'S':
			if (nsm_config[i].nz && *(nsm_config[i].vsa) == NULL) {
				xwarn("warn: nsm_config_check() => string array var \"%s\" is not set but it should.", nsm_config[i].s);
				goto nsm_config_check_error;
			}
			break;
		default:
			xwarn("warn: nsm_config_check() => internal error: unhandled type(%c) in table", nsm_config[i].t);
			nsm_config_error = 5;
			nsm_config_error_str = "internal error in nsm_config_check(), see log for details";
			return 1;
		}

	return 0;

nsm_config_check_error:
	nsm_config_error = 6;
	nsm_config_error_str = "unsupported or missing values for variable, see log for details";
	return 1;
}

void
nsm_config_free(void)
{
	int i, j;

	if (!nsmc_flag)
		return;

	if (nsmc_ip != NULL) {
		nsm_free(nsmc_ip);
		nsmc_ip = NULL;
	}

	for (i = 0; nsm_config[i].s != NULL; i++)
		switch (nsm_config[i].t) {
		case 'i': /* FALLTHRU */
		case 'u':
			break;
		case 's':
			if (*(nsm_config[i].vs) != NULL)
				nsm_free(*(nsm_config[i].vs));
			break;
		case 'S':
			if (*(nsm_config[i].vsa) != NULL) {
				for (j = 0; (*(nsm_config[i].vsa))[j] != NULL; j++)
					nsm_free((*(nsm_config[i].vsa))[j]);
				nsm_free(*(nsm_config[i].vsa));
			}
			break;
		default:
			xwarn("warn: nsm_config_free() => internal error: unhandled type(%c) in table", nsm_config[i].t);
			nsm_config_error = 7;
			nsm_config_error_str = "internal error in nsm_config_free(), see log for details";
			return;
		}
}

int
nsm_config_read(const char *fn)
{
	FILE *fp;
	char b[512];
	char obuf[128];
	char abuf[256];
	char *tmp;
	int line, i, j;

	nsm_config_setdefault();

	nsm_config_error = 0;
	nsm_config_error_line = 0;
	nsm_config_error_file = NULL;
	nsm_config_error_str = NULL;

	if ((fp = fopen(fn, "r")) == NULL) {
		nsm_config_error = 1;
		nsm_config_error_str = "unable to open config file";
		nsm_config_error_file = strdup(fn);
		return 1;
	}

	line = 0;
	while (fgets(b, sizeof(b), fp) != NULL) {
		line++;
		(void)sscanf(b, "%127s %255s", obuf, abuf);
		if (*b == '\n')
			continue;

		for (i = 0; nsm_config[i].s != NULL; i++)
			if (strcmp(obuf, nsm_config[i].s) == 0) {
				switch (nsm_config[i].t) {
				case 'i':
					*((int *)(nsm_config[i].vi)) = atoi(abuf);
					break;
				case 'u':
					*((unsigned int *)(nsm_config[i].vi)) = strtoul(abuf, (char **)NULL, 10);
					break;
				case 's':
					if (*(nsm_config[i].vs) != NULL)
						nsm_free(*(nsm_config[i].vs));
					*(nsm_config[i].vs) = nsm_strdup(abuf);
					break;
				case 'S':
					if (*(nsm_config[i].vsa) == NULL) {
						*(nsm_config[i].vsa) = nsm_calloc(2, sizeof(char *));
						(*(nsm_config[i].vsa))[0] = NULL;
					}

					j = 0;
					while ((*(nsm_config[i].vsa))[j] != NULL)
						j++;

					*(nsm_config[i].vsa) = nsm_realloc(*(nsm_config[i].vsa), (j + 2) * sizeof(char *));
					(*(nsm_config[i].vsa))[j] = nsm_strdup(abuf);
					(*(nsm_config[i].vsa))[j+1] = NULL;

					break;
				default:
					xwarn("warn: nsm_config_setdefault() => internal error: unhandled type(%c) in table", nsm_config[i].t);
					nsm_config_error = 3;
					nsm_config_error_str = "internal error";
					(void)fclose(fp);
					return 1;
				}
				break;
			}

		if (nsm_config[i].s == NULL && *obuf != '#') {
			nsm_config_error = 2;
			nsm_config_error_str = "unknown option in config file";
			goto nsm_config_read_fail;
		}
	}

	(void)fclose(fp);

	if (*(tmp = resolve_to_a(nsmc_name)) == '\0' || strcmp(tmp, "255.255.255.255") == 0) {
		xwarn("warn: unable to resolve ip address for <%s>", nsmc_name);
		nsm_config_error = 8;
		nsm_config_error_str = "unable to resolve ip address for my name";
		return 1;
	}
	nsmc_ip = nsm_strdup(tmp);

	if (nsm_config_check() != 0)
		return 1;

	return 0;

nsm_config_read_fail:
	nsm_config_error_line = line;
	nsm_config_error_file = strdup(fn);
	(void)fclose(fp);
	return 1;
}

void
nsm_config_perror(const char *s)
{

	if (s != NULL)
		(void)fprintf(stderr, "%s: ", s);

	(void)fprintf(stderr, "error reading nsmtp config: (%d) %s (%s, line #%d)\n",
	    nsm_config_error, nsm_config_error_str, (nsm_config_error_file != NULL) ? nsm_config_error_file : "(null)", nsm_config_error_line);

	if (nsm_config_error_file != NULL)
		free(nsm_config_error_file);

	nsm_config_free();

	exit(EXIT_FAILURE);
}
