/* This file is part of Mailfromd. Copyright (C) 2005-2019 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 . */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #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; 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; time_t negative_expire_interval = DEFAULT_EXPIRE_INTERVAL/2; /* Expire negative cache entries after this number of seconds */ 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); } } 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", §ion) == 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", §ion) == 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); } /* 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" " [!][.,...]\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 ."), N_("spec: list") }, { "transcript", mu_c_bool, &smtp_transcript, 0, NULL, N_("Enable transcript of call-out SMTP sessions.") }, { "smtp-timeout", mu_cfg_section, }, { "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 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); mailfromd_state_dir = DEFAULT_STATE_DIR; if (!pidfile) setdefpidfilename(progname); mu_cli_capa_register (&mfd_capa_server); if (mu_create_canned_section (".mfd:server", §ion) == 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); }