/* This file is part of Mailfromd.
   Copyright (C) 2005-2025 Sergey Poznyakoff

   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 3, 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, see <http://www.gnu.org/licenses/>. */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <mailutils/mailutils.h>
#include <mailutils/cli.h>
#include <mailutils/dbm.h>
#include <mailutils/alloc.h>
#include <sysexits.h>

#include "libmf.h"
#include "filenames.h"
#include "callout.h"
#include "srvman.h"
#include "gacopyz.h"
#include "srvcfg.h"
#include "mfdb.h"

char *mailfromd_state_dir;
int server_flags = 0;
char *log_stream = DEFAULT_LOG_STREAM;
char *pidfile;                
int smtp_transcript;
smtp_starttls_t smtp_use_tls = tls_ondemand;
struct mu_tls_config tls_conf = {
	.priorities = "NORMAL:%COMPAT",
	.handshake_timeout = 60
};

static int transcript_option;
struct mu_sockaddr *source_address;  /* Source address for TCP connections */
int mtasim_option;     /* mtasim compatibility mode */
char *db_type_str = DEFAULT_DB_TYPE;

/* Timeouts */
time_t smtp_timeout_soft[SMTP_NUM_TIMEOUT] = {
	10,
	30,
	0,
	0,
	0,
	0,
	0,
};

/* Hard timeouts comply to RFC 2821 */
time_t smtp_timeout_hard[SMTP_NUM_TIMEOUT] = {
	5*60,       /* smtp_timeout_connect */
	5*60,	    /* smtp_timeout_initial */
	5*60,	    /* smtp_timeout_helo */   
	10*60,	    /* smtp_timeout_mail */   
	5*60,	    /* smtp_timeout_rcpt */   
	5*60,	    /* smtp_timeout_rset */   
	2*60,	    /* smtp_timeout_quit */    
};

/* I/O timeout. Overrides unset smtp_timeouts */
time_t io_timeout = 3;

static const char *
next_server_id()
{
	static unsigned long count;
	static char nbuf[INT_BUFSIZE_BOUND(unsigned long)];
	count++;
	snprintf(nbuf, sizeof(nbuf), "%lu", count);
	return nbuf;
}

static mu_url_t
_parse_url(const char *str)
{
	mu_url_t url;
	int rc;
	const char *s;
	
	rc = mu_url_create(&url, str);
	if (rc) {
		mu_error(_("cannot create URL from `%s': %s"),
			 str, mu_strerror(rc));	
		return NULL;
	}
	if (mu_url_sget_scheme(url, &s) == 0 && strcmp(s, "file") == 0)
		mu_url_set_scheme(url, "unix");
	
	return url;
}

mu_url_t
parse_milter_url(const char *str)
{
	mu_url_t url;
	
	char *tmp;
	char *proto, *port, *path;

	/* FIXME: This is awkward. */
	if (gacopyz_parse_connection(str,
				     &proto,
				     &port, &path) != MI_SUCCESS) {
		mu_error(_("%s: error parsing URL"), str);
	}
	if (port) {
		if (!proto)
			mu_asprintf(&tmp, "unix://%s:%s", path, port);
		else if (strcmp(proto, "inet6") == 0)
			mu_asprintf(&tmp, "inet6://[%s]:%s", path, port);
		else
			mu_asprintf(&tmp, "%s://%s:%s", proto, path, port);
	} else
		mu_asprintf(&tmp, "%s://%s",
			    proto ? proto : "unix", path);
	free(proto);
	free(path);
	free(port);
	url = _parse_url(tmp);
	free(tmp);
	return url;
}

static int
mf_option_group(const char *arg)
{
	struct group *group = getgrnam(arg);
	if (group) {
		if (!mf_server_retain_groups)
			mf_server_retain_groups = mf_gid_list_alloc();
		mf_gid_list_add(mf_server_retain_groups, group->gr_gid);
	} else {
		mu_error(_("unknown group: %s"), arg);
		return 1;
	}
	return 0;
}

static int
mf_option_state_directory(const char *arg)
{
	struct stat st;
	if (stat(arg, &st)) {
		mu_error(_("cannot stat file `%s': %s"),
			 arg,
			 mu_strerror(errno));
		return 1;
	}
	if (!S_ISDIR(st.st_mode)) {
		mu_error(_("`%s' is not a directory"), arg);
		return 1;
	}
	if (arg[0] != '/') {
		mu_error(_("state directory `%s' is not an absolute "
			   "file name"), arg);
		return 1;
	}
	mailfromd_state_dir = mu_strdup(arg);
	return 0;
}

static int
set_source_ip(char const *arg)
{
	int rc;
	struct mu_sockaddr_hints hints;

	memset(&hints, 0, sizeof hints);
	hints.family = AF_INET;
	hints.socktype = SOCK_STREAM;
	hints.protocol = IPPROTO_TCP;
	rc = mu_sockaddr_from_node(&source_address, arg, NULL, &hints);
	if (rc) {
		mu_error(_("cannot convert %s to sockaddr: %s"),
			 arg, mu_strerror(rc));
		return 1;
	}
	return 0;
}

void
mf_srvcfg_add(const char *type, const char *urlstr)
{
	struct mf_srvcfg cfg;
	
	memset(&cfg, 0, sizeof(cfg));
	cfg.id = next_server_id();
	cfg.url = parse_milter_url(urlstr);
	if (cfg.url) {
		mfd_server_t srv;
		
		if (mf_server_function(type, &cfg)) {
			mu_error(_("INTERNAL ERROR: no such server type: %s"),
				 type);
			exit(EX_SOFTWARE);
		} else if (!cfg.server) {
			mu_error(_("INTERNAL ERROR at %s:%d: "
				   "server function not defined"),
				 __FILE__, __LINE__);
			exit(EX_SOFTWARE);
		}
		srv = mfd_server_new(cfg.id, cfg.url, cfg.server, 0);
		if (srv)
			mfd_srvman_attach_server(srv);
	}
}

/* FIXME: This duplicates the debug.level statement */
static int
cb_debug(void *data, mu_config_value_t *arg)
{
	if (mu_cfg_assert_value_type(arg, MU_CFG_STRING))
		return 1;
	mu_debug_parse_spec(arg->v.string);
	return 0;
}

/* See also option_group. */
static int
cb_group(void *data, mu_config_value_t *arg)
{
	if (mu_cfg_assert_value_type(arg, MU_CFG_STRING))
		return 1;
	return mf_option_group(arg->v.string);
}

static int
cb_source_ip(void *data, mu_config_value_t *arg)
{
	if (mu_cfg_assert_value_type(arg, MU_CFG_STRING))
		return 1;
	set_source_ip(arg->v.string);
	return 0;
}

static struct mf_srvcfg server_config_stmt;

static int
cb_server_stmt_listen(void *data, mu_config_value_t *arg)
{
	if (mu_cfg_assert_value_type(arg, MU_CFG_STRING))
		return 1;
	*(mu_url_t*)data = parse_milter_url(arg->v.string);
	return 0;
}

struct mu_cfg_param server_section_param[] = {
	{ "id", mu_c_string,
	  &server_config_stmt.id, 0,
	  NULL,
	  N_("Server ID.") },
	{ "listen", mu_cfg_callback,
	  &server_config_stmt.url, 0,
	  cb_server_stmt_listen,
	  N_("Listen on this URL."),
	  N_("url: string") },
	{ "max-instances", mu_c_size,
	  &server_config_stmt.max_children, 0,
	  NULL,
	  N_("Maximum number of instances allowed for this server.") },
	{ "single-process", mu_c_bool,
	  &server_config_stmt.single_process, 0, NULL,
	  N_("Single-process mode.") },
	{ "reuseaddr", mu_c_bool,
	  &server_config_stmt.reuseaddr, 0, NULL,
	  N_("Reuse existing socket (default).") },
	{ "acl", mu_cfg_section,
	  &server_config_stmt.acl },
	{ "option", MU_CFG_LIST_OF(mu_c_string),
	  &server_config_stmt.options, 0, NULL,
	  N_("Server-dependent options") },
        { "default", mu_c_bool,
	  &server_config_stmt.defopt, 0, NULL,
	  N_("Deprecated. It is equivalent to `option \"default\"', "
	     "i.e. it marks this callout server as the default one.") },
	{ "backlog", mu_c_int,
	  &server_config_stmt.backlog, 0, NULL,
	  N_("Size of queue of pending connections.") },
	{ NULL }
};

static int
server_section_parser(enum mu_cfg_section_stage stage,
		      const mu_cfg_node_t *node,
		      const char *section_label, void **section_data,
		      void *call_data,
		      mu_cfg_tree_t *tree)
{
	switch (stage) {
	case mu_cfg_section_start:
		memset(&server_config_stmt, 0, sizeof(server_config_stmt));
		server_config_stmt.reuseaddr = 1;
		if (node->label &&
		    mu_cfg_assert_value_type(node->label, MU_CFG_STRING))
			return 1;
		break;

	case mu_cfg_section_end:
		if (mtasim_option)
			mu_url_destroy(&server_config_stmt.url);
		else {
			if (server_config_stmt.options)
				mu_list_set_comparator(
					server_config_stmt.options,
					mf_list_compare_string);
			if (mf_server_function(node->label ?
					       node->label->v.string : NULL,
					       &server_config_stmt)) {
				mu_error(_("unknown server type"));
				return 1;
			} else if (!server_config_stmt.server) {
				mu_error(_("INTERNAL ERROR at %s:%d: "
					   "server function not defined"),
					 __FILE__, __LINE__);
				return 1;
			}
			if (!server_config_stmt.id)
				server_config_stmt.id = next_server_id();
			if (server_config_stmt.url
			    && server_config_stmt.server) {
				int flags = 0;
				mfd_server_t srv;
				
				if (server_config_stmt.single_process)
					flags |= SRV_SINGLE_PROCESS;
				if (!server_config_stmt.reuseaddr)
					flags |= SRV_KEEP_EXISTING;
				srv = mfd_server_new(server_config_stmt.id,
						     server_config_stmt.url,
						     server_config_stmt.server,
						     flags);
				if (srv) {
					mfd_server_set_max_children(srv,
					      server_config_stmt.max_children);
					mfd_server_set_acl(srv,
					      server_config_stmt.acl);
					if (server_config_stmt.backlog)
						mfd_server_set_backlog(srv, server_config_stmt.backlog);
					mfd_srvman_attach_server(srv);
				}
			}
		}
		mu_list_destroy(&server_config_stmt.options);
		break;
	}
	return 0;
}

static void
server_stmt_init(const char *label)
{
	struct mu_cfg_section *section;
	
	if (mu_create_canned_section ("server", &section) == 0) {
		section->parser = server_section_parser;
		section->docstring = N_("Configure server.");
		section->label = (char*) (label ? label : _("label"));
		mu_cfg_section_add_params(section, server_section_param);
	}
}


static int
smtp_timeout_section_parser (enum mu_cfg_section_stage stage,
			     const mu_cfg_node_t *node,
			     const char *section_label, void **section_data,
			     void *call_data,
			     mu_cfg_tree_t *tree)
{
	switch (stage) {
	case mu_cfg_section_start:
		if (!node->label)
			*section_data = smtp_timeout_soft;
		else {
			if (mu_cfg_assert_value_type(node->label,
						     MU_CFG_STRING))
				return 0;
			if (strcmp(node->label->v.string, "soft") == 0)
				*section_data = smtp_timeout_soft;
			else if (strcmp(node->label->v.string, "hard") == 0)
				*section_data = smtp_timeout_hard;
			else
				mu_error (_("unknown timeout class: %s"),
					  node->label->v.string);
		}
		break;
		
	case mu_cfg_section_end:
		break;
	}
	return 0;
}

static int
cb_timeout(void *data, mu_config_value_t *arg)
{
	struct timeval tv;
	int rc = config_cb_timeout (&tv, arg);
	if (rc == 0)
		*(time_t*) data = tv.tv_sec;
	return rc;
}

struct mu_cfg_param smtp_timeout_section_param[] = {
	{ "connection", mu_cfg_callback,
	  NULL, smtp_timeout_connect * sizeof(time_t), cb_timeout,
	  N_("Initial SMTP connection timeout."),
	  N_("time: interval") },
	{ "initial-response",
	  mu_cfg_callback,
	  NULL, smtp_timeout_initial * sizeof(time_t), cb_timeout,
	  N_("Timeout for initial SMTP response."),
	  N_("time: interval") },
	{ "helo",
	  mu_cfg_callback,
	  NULL, smtp_timeout_helo * sizeof(time_t), cb_timeout,
	  N_("Timeout for HELO response."),
	  N_("time: interval") },
	{ "mail",
	  mu_cfg_callback,
	  NULL, smtp_timeout_mail * sizeof(time_t), cb_timeout,
	  N_("Timeout for MAIL response."),
	  N_("time: interval") },
	{ "rcpt",
	  mu_cfg_callback,
	  NULL, smtp_timeout_rcpt * sizeof(time_t), 
	  cb_timeout,
	  N_("Timeout for RCPT response."),
	  N_("time: interval") },
	{ "rset",
	  mu_cfg_callback,
	  NULL, smtp_timeout_rset * sizeof(time_t), 
	  cb_timeout,
	  N_("Timeout for RSET response."),
	  N_("time: interval") },
	{ "quit",
	  mu_cfg_callback,
	  NULL, smtp_timeout_quit * sizeof(time_t), 
	  cb_timeout,
	  N_("Timeout for QUIT response."),
	  N_("time: interval") },
	{ NULL }
};

static void
smtp_timeout_cfg_init()
{
	struct mu_cfg_section *section;
	
	if (mu_create_canned_section ("smtp-timeout", &section) == 0) {
		section->parser = smtp_timeout_section_parser;
		section->docstring = N_("Set SMTP timeouts.");
		/* TRANSLATORS: soft and hard are keywords, do not translate
		   them */
		section->label = N_("[soft | hard]");
		mu_cfg_section_add_params (section, smtp_timeout_section_param);
	}
}

static int
cb_state_directory(void *data, mu_config_value_t *arg)
{
	if (mu_cfg_assert_value_type(arg, MU_CFG_STRING))
		return 1;
	return mf_option_state_directory(arg->v.string);
}

static int
cb_starttls(void *data, mu_config_value_t *arg)
{
	if (mu_cfg_assert_value_type(arg, MU_CFG_STRING))
		return 1;
	return smtp_str_to_starttls(arg->v.string, (smtp_starttls_t*)data);
}

/* FIXME: Umask not configurable */

static struct mu_cfg_param srv_cfg_param[] = {
	{ "debug", mu_cfg_callback, NULL, 0, cb_debug,
	  N_("Set mailfromd debugging level. Argument is a semicolon-separated list "
	     "of debugging specifications. A simplified specification syntax is:\n"
	     "   [!]<category: string>[.<level: string>,<level: string>...]\n"
	     "For details, please see the Mailfromd manual, section 3.18 \"Logging\n"
	     "and Debugging\", GNU Mailutils manual, section 3.3 \"Debugging\",\n"
	     "or visit <http://mailutils.org/wiki/Debug_level>."),
	  N_("spec: list") },
        { "transcript", mu_c_bool, &smtp_transcript, 0, NULL,
	  N_("Enable transcript of call-out SMTP sessions.") },

	{ "smtp-timeout", mu_cfg_section, },
	{ "smtp-starttls", mu_cfg_callback, &smtp_use_tls, 0, cb_starttls,
	  /* TRANSLATORS: Don't translate "ondemand" - it is a keyword. */
	  N_("Use of STARTTLS in callout sessions (default: ondemand)."),
	  /* TRANSLATORS: Don't translate keywords to the right of arg: */
	  N_("arg: never|always|ondemand") },

	{ "tls", mu_cfg_section, &tls_conf },
	
        { "io-timeout", mu_cfg_callback, &io_timeout, 0, cb_timeout,
	  N_("Timeout for all SMTP I/O operations."),
          N_("time: seconds") },
        /* FIXME: Could have used mu_cfg_ipv4 here, but... */
	{ "source-ip", mu_cfg_callback, NULL, 0, cb_source_ip,
	  N_("Set source address for TCP connections."),
	  N_("ip: ipaddr") },
	{ "group", mu_cfg_callback, NULL, 0, cb_group,
	   N_("Retain the supplementary group <name> when switching to user "
	      "privileges"),
	  N_("group: string") },
	{ "user", mu_c_string, &mf_server_user, 0, NULL,
	  N_("Switch to this user privileges after startup.") },
	{ "pidfile", mu_c_string, &pidfile, 0, NULL,
	  N_("Set file to store PID value in."),
	  N_("file") },
	{ "server", mu_cfg_section },
	{ "acl", mu_cfg_section, &srvman_param.acl },
	{ "logger", mu_c_string, &log_stream, 0, NULL,
	  N_("Set logger stream.") },
	{ "state-directory", mu_cfg_callback, NULL, 0, cb_state_directory,
	  N_("Set program state directory."),
	  N_("dir: string") },
	{ "database-type", mu_c_string, &db_type_str, 0, NULL,
	  N_("Default database type"),
	  N_("type") },
	{ "database", mu_cfg_section, NULL, 0, NULL, NULL },
	{ "database-mode", mu_cfg_callback,
	  &mf_database_mode, 0, cb_database_mode,
	  N_("Configure file mode for database files"),
	  N_("mode: octal") },
        { "ehlo-domain", mu_c_string, &ehlo_domain, 0, NULL,
	  N_("Set the domain name for EHLO command.") },
	{ "mail-from-address", mu_c_string, &mailfrom_address, 0, NULL,
	  N_("Set email address for use in SMTP `MAIL FROM' command.  "
             "Argument is an email address or a comma-separated list of "
             "addresses.  Use <> for null address.  Other addresses can "
             "be given without angle brackets."),
	  N_("addr") },
	{ "enable-vrfy", mu_c_bool, &enable_vrfy, 0, NULL,
	  N_("Use the SMTP VRFY command, when available.") },
	{ NULL }
};

static void
opt_foreground (struct mu_parseopt *po, struct mu_option *op, char const *arg)
{
	server_flags |= MF_SERVER_FOREGROUND;
}

static void
opt_single_process (struct mu_parseopt *po, struct mu_option *op,
		    char const *arg)
{
	srvman_param.flags |= SRV_SINGLE_PROCESS;
}

static void
opt_group (struct mu_parseopt *po, struct mu_option *op, char const *arg)
{
	mf_option_group(arg);
}

static void
opt_source_ip (struct mu_parseopt *po, struct mu_option *op, char const *arg)
{
	if (set_source_ip(arg))
		exit (po->po_exit_error);
}

static void
opt_state_directory (struct mu_parseopt *po, struct mu_option *op,
		     char const *arg)
{
	struct stat st;
	if (stat(arg, &st)) {
		mu_parseopt_error(po, _("cannot stat file `%s': %s"),
				  arg,
				  mu_strerror(errno));
		exit(po->po_exit_error);
	}
	if (!S_ISDIR(st.st_mode)) {
		mu_parseopt_error(po, _("`%s' is not a directory"), arg);
		exit(po->po_exit_error);
	}
	if (arg[0] != '/') {
		mu_parseopt_error(po,
				  _("state directory `%s' is not an absolute "
				    "file name"), arg);
		exit(po->po_exit_error);
	}
	mailfromd_state_dir = mu_strdup(arg);
}

static void
opt_debug(struct mu_parseopt *po, struct mu_option *op, char const *arg)
{
	mu_debug_parse_spec(arg);
}

static void
opt_logger_stream(struct mu_parseopt *po, struct mu_option *op,
		  char const *arg)
{
	//FIXME: Check arg: either syslog or stderr */
	/* This option is handled twice. First, it must take
	   effect immediately, so that any eventual startup
	   errors end up being reported to syslog: */
	mf_srvcfg_log_setup(arg);
	/* Second, it overrides any logging settings read from the
	   configuration file. */
	log_stream = mu_strdup(arg);
}

static void
opt_pidfile(struct mu_parseopt *po, struct mu_option *op, char const *arg)
{
	if (arg[0] != '/') {
		mu_parseopt_error(po,
				  _("invalid pidfile name: must be absolute"));
		exit(po->po_exit_error);
	}
	pidfile = mu_strdup(arg);
}

static struct mu_option srv_options[] = {
	MU_OPTION_GROUP(N_("Server configuration modifiers")),
	{ "foreground", 0, NULL, MU_OPTION_DEFAULT,
	  N_("stay in foreground"),
	  mu_c_string, NULL, opt_foreground },
	{ "single-process", 0, NULL, MU_OPTION_DEFAULT,
	  N_("run in single-process mode"),
	  mu_c_string, NULL, opt_single_process },
	{ "pidfile", 0, N_("FILE"), MU_OPTION_DEFAULT,
	  N_("set pidfile name"),
	  mu_c_string, &pidfile, opt_pidfile },
	{ "user", 'u', N_("NAME"), MU_OPTION_DEFAULT,
	  N_("switch to this user privileges after startup"), 
	  mu_c_string, &mf_server_user },
	{ "group", 'g', N_("NAME"), MU_OPTION_DEFAULT,
	  N_("retain the supplementary group NAME when switching to user "
	     "privileges"),
	  mu_c_string, NULL, opt_group },
        { "source-ip", 'S', N_("ADDRESS"), MU_OPTION_DEFAULT,
	  N_("set source address for TCP connections"),
	  mu_c_string, NULL, opt_source_ip },
	{ "state-directory", 0, N_("DIR"), MU_OPTION_DEFAULT,
	  N_("set new program state directory"),
	  mu_c_string, NULL, opt_state_directory },

	MU_OPTION_GROUP(N_("Logging and debugging options")),
	{ "transcript", 'X', NULL, MU_OPTION_DEFAULT,
	  N_("enable transcript of SMTP sessions"),
	  mu_c_bool, &transcript_option },
	{ "debug", 'd', N_("LEVEL"), MU_OPTION_DEFAULT,
	  N_("set debugging level"),
	  mu_c_string, NULL, opt_debug },

	/* In the contrast to --syslog, this option takes effect
	   only after the configuration file has been parsed.
	   This is so because --stderr is the default behavior,
	   so that any configuration file errors are reported to
	   stderr anyway (unless it is closed, in which case they
	   are redirected to syslog). */
	{ "stderr", 0, NULL, MU_OPTION_DEFAULT,
	  N_("log to stderr"),
	  mu_c_string, NULL, opt_logger_stream, "stderr" },
	{ "syslog", 0, NULL, MU_OPTION_DEFAULT,
	  N_("log to syslog (default)"),
	  mu_c_string, NULL, opt_logger_stream, "syslog" },
	{ "logger", 0, N_("STREAM"), MU_OPTION_DEFAULT,
	  N_("select logger stream"),
	  mu_c_string, NULL, opt_logger_stream },
	{ "log-tag", 0, N_("STRING"), MU_OPTION_DEFAULT,
	  N_("set the identifier used in syslog messages to STRING"),
	  mu_c_string, &mu_log_tag },
	{ "source-info", 0, NULL, MU_OPTION_DEFAULT,
	  N_("debug messages include source information"),
	  mu_c_bool, &mu_debug_line_info },

	MU_OPTION_END
};

#define PIDSUF ".pid"

static void
setdefpidfilename(const char *progname)
{
	size_t len;
	const char *base = strrchr(progname, '/');
	if (base)
		base++;
	else
		base = progname;
	len = strlen(base);
	pidfile = mu_alloc(len + sizeof(PIDSUF));
	memcpy(pidfile, base, len);
	strcpy(pidfile + len, PIDSUF);
}

static struct mu_cli_capa mfd_capa_server = {
	".mfd:server",
	srv_options,
};

void
mf_srvcfg_init(const char *progname, const char *label)
{
	struct mu_cfg_section *section;

	smtp_timeout_cfg_init();
	server_stmt_init(label);
	mu_tls_cfg_init ();

	mailfromd_state_dir = DEFAULT_STATE_DIR;
	if (!pidfile)
		setdefpidfilename(progname);

	mu_cli_capa_register (&mfd_capa_server);
	
	if (mu_create_canned_section (".mfd:server", &section) == 0) {
		mu_cfg_section_add_params (section, srv_cfg_param);
	}
}

static void
init_ehlo_domain(void)
{
        char *smtp_hostname;
        char *smtp_domain;
        mu_get_host_name(&smtp_hostname);

        smtp_domain = strchr(smtp_hostname, '.');
        if (smtp_domain)
		smtp_domain++;
        else
		smtp_domain = smtp_hostname;
	ehlo_domain = mu_strdup(smtp_domain);
}

void
mf_srvcfg_flush()
{
	int i;
		
	if (transcript_option)
		smtp_transcript = transcript_option;
	
        /* Fix unspecified timeouts */
        for (i = 0; i < SMTP_NUM_TIMEOUT; i++) {
                if (smtp_timeout_soft[i] == 0)
                        smtp_timeout_soft[i] = io_timeout;
                if (smtp_timeout_hard[i] == 0)
                        smtp_timeout_hard[i] = io_timeout;
        }

	if (!ehlo_domain)
		init_ehlo_domain();

	if (db_type_str) {
		mu_url_t dbhint;
		int rc;
		static char const *defparam[] = { "linkwrdir", "awrdir" };
		
		if ((rc = mu_url_create_null(&dbhint)) ||
		    (rc = mu_url_set_scheme(dbhint, db_type_str)) ||
		    (rc = mu_url_add_param(dbhint,
					   MU_ARRAY_SIZE(defparam),
					   defparam))) {
			mu_error(_("cannot initialize DBM hint: %s"),
				 mu_strerror(rc));
			exit(EX_SOFTWARE);
		}

		mu_url_destroy(&mu_dbm_hint);
		mu_dbm_hint = dbhint;
	}
}

void
mf_srvcfg_log_setup(char const *stream)
{
	if (logger_select(stream)) {
		mu_error(_("unsupported logger stream: %s"), stream);
		exit(EX_USAGE);
	}
	logger_open();

	FD_ZERO(&srvman_param.keepfds);
	logger_fdset(&srvman_param.keepfds);
	if (logger_flags(LOGF_STDERR))
		/* Keep also stdout.
		   FIXME: Is it still needed? */
		FD_SET(1, &srvman_param.keepfds);
}

void
mf_server_log_setup(void)
{
	mf_srvcfg_log_setup(log_stream);
}

