/* This file is part of Mailfromd. Copyright (C) 2005-2011, 2015-2017 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mailfromd.h" #include "callout.h" #include "srvman.h" #include "srvcfg.h" #include "filenames.h" #include "builtin.h" #include "prog.h" /* Configurable options */ int mode = MAILFROMD_DAEMON; /* Default operation mode */ enum smtp_state test_state = smtp_state_envfrom; /* State for --test mode */ int need_script = 1; /* Set if the current mode requires reading the script file */ char *script_file = DEFAULT_SCRIPT_FILE; int script_check; /* Check config file syntax and exit */ extern int yy_flex_debug; /* Enable tracing the lexical analyzer */ int script_ydebug; /* Enable tracing the parser */ int script_dump_tree; /* Dump created config tree to stdout */ int script_dump_code; /* Dump disassembled code to stdout */ int script_dump_macros; /* Dump used Sendmail macros */ int script_dump_xref; /* Dump cross-reference */ int preprocess_option; /* Only preprocess the sources */ int location_column_option; /* Print column numbers in error locations */ char *ext_pp = DEF_EXT_PP; /* External preprocessor to use */ char *ext_pp_options; int ext_pp_options_given; int do_trace; /* Enable tracing configuration */ unsigned optimization_level = 1; /* Optimization level */ int stack_trace_option; /* Print stack traces on runtime errors */ size_t max_match_mx = MAXMXCOUNT; char *main_function_name = "main"; char *callout_server_url; mu_stream_t mf_strecho; /* Output stream for 'echo' statements */ #define ARG_UNSET (-1) static int trace_option = ARG_UNSET; static mu_list_t trace_modules; /* Preprocessor helper function */ static void add_pp_option(const char *opt, const char *arg) { size_t len; len = 1 + strlen(opt) + (arg ? strlen(arg) : 0); if (ext_pp_options) { len += strlen(ext_pp_options); ext_pp_options = mu_realloc(ext_pp_options, len + 1); } else { ext_pp_options = mu_alloc(len + 1); ext_pp_options[0] = 0; } strcat(ext_pp_options, " "); strcat(ext_pp_options, opt); strcat(ext_pp_options, arg); } void pp_define(const char *symbol) { add_pp_option("-D", symbol); } /* Logging & debugging */ static mu_stream_t mf_trace_stream; static void open_trace_stream() { int rc = mu_filter_create(&mf_trace_stream, mf_strecho, "C-escape", MU_FILTER_ENCODE, MU_STREAM_WRITE); if (rc) { mu_error(_("cannot create trace stream, " "using standard log: %s"), mu_strerror(rc)); mf_trace_stream = mf_strecho; } } void trace(const char *fmt, ...) { if (do_trace) { int bval = 0; va_list ap; if (!mf_trace_stream) open_trace_stream(); va_start(ap, fmt); mu_stream_vprintf(mf_trace_stream, fmt, ap); bval = 1; mu_stream_ioctl(mf_trace_stream, MU_IOCTL_FILTER, MU_IOCTL_FILTER_SET_DISABLED, &bval); mu_stream_write(mf_trace_stream, "\n", 1, NULL); bval = 0; mu_stream_ioctl(mf_trace_stream, MU_IOCTL_FILTER, MU_IOCTL_FILTER_SET_DISABLED, &bval); va_end(ap); } } void log_status(sfsistat status, SMFICTX *ctx) { mu_debug_level_t lev; mu_debug_category_level(NULL, 0, &lev); if ((lev & ~MU_DEBUG_LEVEL_MASK(MU_DEBUG_ERROR)) && status != SMFIS_CONTINUE) { const char *str = sfsistat_str(status); if (str) logmsg(MU_LOG_INFO, "%s%s", mailfromd_msgid(ctx), str); else logmsg(MU_LOG_INFO, "%sstatus %d", mailfromd_msgid(ctx), status); } } /* Sendmail class file support */ static mu_list_t domain_list; /* Read domains from sendmail-style domain file NAME and store them in DOMAIN_LIST */ int read_domain_file(const char *name) { FILE *fp; char buf[256]; char *p; fp = fopen(name, "r"); if (!fp) { mu_error(_("cannot open file `%s': %s"), name, mu_strerror(errno)); return 1; } if (!domain_list) { mu_list_create(&domain_list); mu_list_set_comparator(domain_list, mf_list_compare_string); } while (p = fgets(buf, sizeof buf, fp)) { char *q; while (*p && mu_isspace(*p)) p++; if (*p == '#') continue; for (q = p; *q && !mu_isspace(*q); q++) ; *q = 0; if (*p == 0) continue; mu_list_append(domain_list, strdup(p)); } fclose(fp); return 0; } /* Return true if we relay domain NAME */ int relayed_domain_p(char *name) { char *p = name; while (p) { if (mu_list_locate(domain_list, p, NULL) == 0) { mu_debug(MF_SOURCE_MAIN, MU_DEBUG_TRACE5, ("%s is in relayed domain %s", name, p)); return 1; } p = strchr(p, '.'); if (p) p++; } return 0; } /* Return true if CLIENT represents a host we relay. CLIENT is a dotted-quad IP address. */ int host_in_relayed_domain_p(char *client) { int rc; char *hbuf; if (mu_list_is_empty(domain_list)) return 0; if (resolve_ipstr(client, &hbuf)) return 0; rc = relayed_domain_p(hbuf); free(hbuf); return rc; } static mu_list_t relayed_domain_files; static int load_relay_file(void *item, void *data) { read_domain_file(item); return 0; } static void init_relayed_domains(void) { mu_list_foreach(relayed_domain_files, load_relay_file, NULL); mu_list_destroy(&relayed_domain_files); } /* Command line parsing */ const char *program_version = "mailfromd (" PACKAGE_STRING ")"; static char prog_doc[] = N_("mailfromd -- a general purpose milter daemon"); static char args_doc[] = "[var=value...][SCRIPT]"; static void opt_testmode(struct mu_parseopt *po, struct mu_option *op, char const *arg) { mode = MAILFROMD_TEST; if (arg) { test_state = string_to_state(arg); if (test_state == smtp_state_none) { mu_parseopt_error(po, _("unknown smtp state tag: %s"), arg); exit(po->po_exit_error); } log_stream = "stderr"; need_script = 1; } } static void opt_runmode(struct mu_parseopt *po, struct mu_option *op, char const *arg) { mode = MAILFROMD_RUN; if (arg) main_function_name = mu_strdup (arg); log_stream = "stderr"; need_script = 1; } static void opt_lint(struct mu_parseopt *po, struct mu_option *op, char const *arg) { log_stream = "stderr"; script_check = 1; need_script = 1; } static void opt_show_defaults(struct mu_parseopt *po, struct mu_option *op, char const *arg) { mode = MAILFROMD_SHOW_DEFAULTS; need_script = 0; } static void opt_daemon(struct mu_parseopt *po, struct mu_option *op, char const *arg) { mode = MAILFROMD_DAEMON; need_script = 1; } static void opt_include_dir(struct mu_parseopt *po, struct mu_option *op, char const *arg) { add_include_dir(arg); } static void opt_port(struct mu_parseopt *po, struct mu_option *op, char const *arg) { mf_srvcfg_add("default", arg); } static void opt_mtasim(struct mu_parseopt *po, struct mu_option *op, char const *arg) { mtasim_option = 1; server_flags |= MF_SERVER_FOREGROUND; } static void opt_optimize(struct mu_parseopt *po, struct mu_option *op, char const *arg) { if (!arg) optimization_level = 1; else { char *p; optimization_level = strtoul(arg, &p, 0); if (*p) mu_parseopt_error(po, _("-O level must be a non-negative integer")); } } static void opt_variable(struct mu_parseopt *po, struct mu_option *op, char const *arg) { char *p; struct mu_locus_range locus = {{ "", 1, 0 }}; p = strchr(arg, '='); if (!p) { mu_parseopt_error(po, _("expected assignment, but found `%s'"), arg); exit(po->po_exit_error); } *p++ = 0; defer_initialize_variable(arg, p, &locus); } static void opt_relayed_domain_file(struct mu_parseopt *po, struct mu_option *op, char const *arg) { if (!relayed_domain_files) mu_list_create(&relayed_domain_files); mu_list_append(relayed_domain_files, mu_strdup(arg)); } static void opt_clear_ext_pp(struct mu_parseopt *po, struct mu_option *op, char const *arg) { ext_pp = NULL; } static void opt_define(struct mu_parseopt *po, struct mu_option *op, char const *arg) { ext_pp_options_given = 1; add_pp_option("-D", arg); } static void opt_undefine(struct mu_parseopt *po, struct mu_option *op, char const *arg) { ext_pp_options_given = 1; add_pp_option("-U", arg); } static void opt_set_milter_timeout(struct mu_parseopt *po, struct mu_option *op, char const *arg) { time_t v; if (mu_str_to_c(arg, mu_c_time, &v, NULL)) { mu_parseopt_error(po, _("%s: not a valid interval"), arg); exit(po->po_exit_error); } if (smfi_settimeout(v) == MI_FAILURE) { mu_parseopt_error(po, _("invalid milter timeout: %s"), arg); exit(po->po_exit_error); } } static void destroy_trace_item(void *ptr) { free(ptr); } static void opt_trace_program(struct mu_parseopt *po, struct mu_option *op, char const *arg) { if (!trace_modules) { mu_list_create(&trace_modules); mu_list_set_destroy_item(trace_modules, destroy_trace_item); } mu_list_append(trace_modules, mu_strdup(arg ? arg : "all")); } static void opt_dump_code(struct mu_parseopt *po, struct mu_option *op, char const *arg) { script_dump_code = 1; log_stream = "stderr"; } static void opt_dump_grammar_trace(struct mu_parseopt *po, struct mu_option *op, char const *arg) { script_ydebug = 1; log_stream = "stderr"; } static void opt_dump_lex_trace(struct mu_parseopt *po, struct mu_option *op, char const *arg) { yy_flex_debug = 1; log_stream = "stderr"; } static void opt_dump_macros(struct mu_parseopt *po, struct mu_option *op, char const *arg) { script_dump_macros = 1; log_stream = "stderr"; } static void opt_dump_tree(struct mu_parseopt *po, struct mu_option *op, char const *arg) { script_dump_tree = 1; log_stream = "stderr"; } static void opt_gacopyz_log(struct mu_parseopt *po, struct mu_option *op, char const *arg) { int lev = gacopyz_string_to_log_level(arg); if (lev == -1) { mu_parseopt_error(po, _("%s: invalid log level"), arg); exit(po->po_exit_error); } milter_setlogmask(SMI_LOG_FROM(lev)); } static void opt_dump_xref(struct mu_parseopt *po, struct mu_option *op, char const *arg) { script_dump_xref = 1; log_stream = "stderr"; } static struct mu_option mailfromd_options[] = { MU_OPTION_GROUP(N_("Operation modifiers")), { "test", 't', N_("HANDLER"), MU_OPTION_ARG_OPTIONAL, N_("run in test mode"), mu_c_string, NULL, opt_testmode }, { "run", 0, N_("FUNC"), MU_OPTION_ARG_OPTIONAL, N_("run script and execute function FUNC (\"main\" if not given)"), mu_c_string, NULL, opt_runmode }, { "lint", 0, NULL, MU_OPTION_DEFAULT, N_("check syntax and exit"), mu_c_string, NULL, opt_lint }, { "syntax-check", 0, NULL, MU_OPTION_ALIAS, }, { "show-defaults", 0, NULL, MU_OPTION_DEFAULT, N_("show compilation defaults"), mu_c_string, NULL, opt_show_defaults }, { "daemon", 0, NULL, MU_OPTION_DEFAULT, N_("run in daemon mode (default)"), mu_c_string, NULL, opt_daemon }, { NULL, 'E', NULL, MU_OPTION_DEFAULT, N_("preprocess source files and exit"), mu_c_bool, &preprocess_option }, /* Reserved for future use: */ { "compile", 'c', NULL, MU_OPTION_HIDDEN, N_("compile files"), mu_c_void }, { "load", 'l', N_("FILE"), MU_OPTION_HIDDEN, N_("load library"), mu_c_void }, { "load-dir", 'L', N_("DIR"), MU_OPTION_HIDDEN, N_("add DIR to the load path"), mu_c_void }, MU_OPTION_GROUP(N_("General options")), { "include", 'I', N_("DIR"), MU_OPTION_DEFAULT, N_("add the directory DIR to the list of directories to be " "searched for header files"), mu_c_string, NULL, opt_include_dir }, { "port", 'p', N_("STRING"), MU_OPTION_DEFAULT, N_("set communication socket"), mu_c_string, NULL, opt_port }, { "mtasim", 0, NULL, MU_OPTION_DEFAULT, N_("run in mtasim compatibility mode"), mu_c_string, NULL, opt_mtasim }, { "optimize", 'O', N_("LEVEL"), MU_OPTION_ARG_OPTIONAL, N_("set code optimization level"), mu_c_string, NULL, opt_optimize }, { "variable", 'v', N_("VAR=VALUE"), MU_OPTION_DEFAULT, N_("assign VALUE to VAR"), mu_c_string, NULL, opt_variable }, { "relayed-domain-file", 0, N_("FILE"), MU_OPTION_DEFAULT, N_("read relayed domains from FILE"), mu_c_string, NULL, opt_relayed_domain_file }, MU_OPTION_GROUP(N_("Preprocessor options")), { "preprocessor", 0, N_("COMMAND"), MU_OPTION_DEFAULT, N_("use command as external preprocessor"), mu_c_string, &ext_pp }, { "no-preprocessor", 0, NULL, MU_OPTION_DEFAULT, N_("disable the use of external preprocessor"), mu_c_string, NULL, opt_clear_ext_pp }, { "define", 'D', N_("NAME[=VALUE]"), MU_OPTION_DEFAULT, N_("define a preprocessor symbol NAME as having VALUE, or empty"), mu_c_string, NULL, opt_define }, { "undefine", 'U', N_("NAME"), MU_OPTION_DEFAULT, N_("undefine a preprocessor symbol NAME"), mu_c_string, NULL, opt_undefine }, MU_OPTION_GROUP(N_("Timeout control")), { "milter-timeout", 0, N_("TIME"), MU_OPTION_DEFAULT, N_("set MTA connection timeout"), mu_c_string, NULL, opt_set_milter_timeout }, MU_OPTION_GROUP (N_("Informational and debugging options")), { "location-column", 0, NULL, MU_OPTION_DEFAULT, N_("print location column numbers in compiler diagnostics messages"), mu_c_bool, &location_column_option }, { "trace", 0, NULL, MU_OPTION_DEFAULT, N_("trace executed actions"), mu_c_bool, &trace_option }, { "trace-program", 0, N_("MODULES"), MU_OPTION_ARG_OPTIONAL, N_("enable filter program tracing"), mu_c_string, NULL, opt_trace_program }, { "dump-code", 0, NULL, MU_OPTION_DEFAULT, N_("dump disassembled code"), mu_c_string, NULL, opt_dump_code }, { "dump-grammar-trace", 0, NULL, MU_OPTION_DEFAULT, N_("dump grammar traces"), mu_c_string, NULL, opt_dump_grammar_trace }, { "dump-lex-trace", 0, NULL, MU_OPTION_DEFAULT, N_("dump lexical analyzer traces"), mu_c_string, NULL, opt_dump_lex_trace }, { "dump-tree", 0, NULL, MU_OPTION_DEFAULT, N_("dump parser tree"), mu_c_string, NULL, opt_dump_tree }, { "dump-macros", 0, NULL, MU_OPTION_DEFAULT, N_("show used Sendmail macros"), mu_c_string, NULL, opt_dump_macros }, { "xref", 0, NULL, MU_OPTION_DEFAULT, N_("produce a cross-reference listing"), mu_c_string, NULL, opt_dump_xref }, { "dump-xref", 0, NULL, MU_OPTION_ALIAS }, { "gacopyz-log", 0, N_("LEVEL"), MU_OPTION_DEFAULT, N_("set Gacopyz log level"), mu_c_string, NULL, opt_gacopyz_log }, { "stack-trace", 0, NULL, MU_OPTION_DEFAULT, N_("enable stack traces on runtime errors"), mu_c_bool, &stack_trace_option }, MU_OPTION_END }, *options[] = { mailfromd_options, NULL }; static int validate_options() { /* FIXME */ return 0; } static int flush_trace_module(void *item, void *data) { enable_prog_trace((const char *) item); return 0; } static char *capa[] = { "auth", "debug", "logging", "locking", "mailer", ".mfd:server", NULL }; /* Mailutils configuration */ static int cb_milter_timeout(void *data, mu_config_value_t *arg) { struct timeval tv; int rc = config_cb_timeout (&tv, arg); if (rc) return 1; if (smfi_settimeout(tv.tv_sec) == MI_FAILURE) { mu_error(_("Invalid milter timeout: %lu"), (unsigned long) tv.tv_sec); exit(EX_USAGE); } return 0; } static int cb_set_variable(void *data, mu_config_value_t *arg) { const char *value; char *alloc_str = NULL; struct mu_locus_range locus = MU_LOCUS_RANGE_INITIALIZER; if (mu_cfg_assert_value_type(arg, MU_CFG_ARRAY)) return 1; if (arg->v.arg.c < 2) { mu_error(_("not enough arguments")); return 1; } else if (arg->v.arg.c > 2) { mu_error(_("too many arguments")); return 1; } if (mu_cfg_assert_value_type(&arg->v.arg.v[0], MU_CFG_STRING)) return 1; switch (arg->v.arg.v[1].type) { case MU_CFG_STRING: value = arg->v.arg.v[1].v.string; break; case MU_CFG_ARRAY: alloc_str = config_array_to_string(&arg->v.arg.v[1]); value = alloc_str; break; case MU_CFG_LIST: mu_error(_("unexpected list")); return 1; default: mu_error (_("INTERNAL ERROR at %s:%d: please report"), __FILE__, __LINE__); abort(); } mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_GET_LOCUS_RANGE, &locus); defer_initialize_variable(arg->v.arg.v[0].v.string, value, &locus); mu_locus_range_deinit(&locus); free(alloc_str); return 0; } static int cb_include_path(void *data, mu_config_value_t *val) { int i, rc = 0; struct mu_wordsplit ws; mu_iterator_t itr; switch (val->type) { case MU_CFG_STRING: ws.ws_delim = ":"; if (mu_wordsplit(val->v.string, &ws, MU_WRDSF_NOVAR | MU_WRDSF_NOCMD | MU_WRDSF_DELIM)) { mu_error("mu_wordsplit: %s", mu_wordsplit_strerror(&ws)); return 1; } for (i = 0; i < ws.ws_wordc; i++) add_include_dir(ws.ws_wordv[i]); mu_wordsplit_free(&ws); break; case MU_CFG_ARRAY: for (i = 0; i < val->v.arg.c; i++) { if (mu_cfg_assert_value_type(&val->v.arg.v[i], MU_CFG_STRING)) return 1; add_include_dir(val->v.arg.v[i].v.string); } break; case MU_CFG_LIST: mu_list_get_iterator(val->v.list, &itr); for (mu_iterator_first(itr); !mu_iterator_is_done(itr); mu_iterator_next(itr)) { mu_config_value_t *pval; mu_iterator_current(itr, (void*) &pval); rc = mu_cfg_assert_value_type(pval, MU_CFG_STRING); if (rc) break; add_include_dir(pval->v.string); } mu_iterator_destroy(&itr); } return rc; } static int cb_trace_program(void *data, mu_config_value_t *val) { int i, rc = 0; switch (val->type) { case MU_CFG_STRING: enable_prog_trace(val->v.string); break; case MU_CFG_ARRAY: for (i = 0; i < val->v.arg.c; i++) { if (mu_cfg_assert_value_type(&val->v.arg.v[i], MU_CFG_STRING)) return 1; enable_prog_trace(val->v.arg.v[i].v.string); } break; case MU_CFG_LIST: mu_list_foreach(val->v.list, flush_trace_module, NULL); break; } return rc; } static int cb_relayed_domain_file(void *data, mu_config_value_t *val) { switch (val->type) { case MU_CFG_STRING: read_domain_file(val->v.string); break; case MU_CFG_LIST: mu_list_foreach(val->v.list, load_relay_file, NULL); break; default: mu_error (_("expected string or list of strings")); } return 0; } struct mu_cfg_param mf_cfg_param[] = { { ".mfd:server", mu_cfg_section, NULL, 0, NULL, NULL }, { "stack-trace", mu_c_bool, &stack_trace_option, 0, NULL, N_("Dump stack traces on runtime errors.") }, { "milter-timeout", mu_cfg_callback, NULL, 0, cb_milter_timeout, N_("Set milter timeout."), N_("time: interval") }, { "callout-url", mu_c_string, &callout_server_url, 0, NULL, N_("Sets the URL of the default callout server."), N_("url") }, { "include-path", mu_cfg_callback, NULL, 0, cb_include_path, N_("Add directories to the list of directories to be searched for " "header files. Argument is a list of directory names " "separated by colons."), N_("path: string") }, { "setvar", mu_cfg_callback, NULL, 0, cb_set_variable, N_("Initialize a mailfromd variable to ."), N_("var: string> ."), N_("file") }, { "trace-actions", mu_c_bool, &do_trace, 0, NULL, N_("Trace executed actions.") }, { "trace-program", mu_cfg_callback, NULL, 0, cb_trace_program, N_("Enable filter program tracing."), N_("modules: list") }, { "relayed-domain-file", mu_cfg_callback, NULL, 0, cb_relayed_domain_file, N_("Read relayed domain names from the file"), N_("file: string") }, { "database", mu_cfg_section, NULL }, { "lock-retry-count", mu_cfg_callback, NULL, 0, config_cb_lock_retry_count, N_("Retry acquiring DBM file lock this number of times.") }, { "lock-retry-timeout", mu_cfg_callback, NULL, 0, config_cb_lock_retry_timeout, N_("Set the time span between the two DBM locking attempts."), N_("time: interval") }, { "max-match-mx", mu_c_size, &max_match_mx, 0, NULL, N_("Maximum number of MXs used by MFL \"mx match\" operation.") }, { "runtime", mu_cfg_section, NULL }, { NULL } }; static struct mu_cfg_param *runtime_param; static size_t runtime_param_cnt; static size_t runtime_param_max; static void _add_runtime_param_entry(struct mu_cfg_param *p) { if (runtime_param_cnt == runtime_param_max) { if (runtime_param_max == 0) runtime_param_max = 16; runtime_param = mu_2nrealloc(runtime_param, &runtime_param_max, sizeof(runtime_param[0])); } runtime_param[runtime_param_cnt++] = *p; } void mf_add_runtime_params(struct mu_cfg_param *p) { for (; p->ident; p++) _add_runtime_param_entry(p); } void mf_runtime_param_finish() { if (runtime_param_cnt) { static struct mu_cfg_param term = { NULL }; struct mu_cfg_section *section; _add_runtime_param_entry(&term); if (mu_create_canned_section ("runtime", §ion) == 0) { section->parser = NULL; section->docstring = N_("Configure MFL runtime values."); section->label = NULL; mu_cfg_section_add_params(section, runtime_param); } } else { struct mu_cfg_param *p; for (p = mf_cfg_param; p->ident; p++) { if (strcmp (p->ident, "runtime") == 0) { memset (p, 0, sizeof (*p)); break; } } } } /* Auxiliary functions */ unsigned keyword_column = 0; unsigned header_column = 2; unsigned value_column = 32; unsigned right_margin = 79; static void set_column(mu_stream_t str, unsigned margin) { mu_stream_ioctl(str, MU_IOCTL_WORDWRAPSTREAM, MU_IOCTL_WORDWRAP_SET_MARGIN, &margin); } static int db_format_enumerator(struct db_format *fmt, void *data) { mu_stream_t str = data; set_column(str, keyword_column); mu_stream_printf(str, "%s database:", fmt->name); set_column(str, value_column); mu_stream_printf(str, "%s\n", fmt->dbname); set_column(str, keyword_column); if (strcmp(fmt->name, "cache") == 0) { mu_stream_printf(str, "%s positive expiration:", fmt->name); set_column(str, value_column); mu_stream_printf(str, "%lu\n", fmt->expire_interval); set_column(str, keyword_column); mu_stream_printf(str, "%s negative expiration:", fmt->name); set_column(str, value_column); mu_stream_printf(str, "%lu\n", negative_expire_interval); } else { mu_stream_printf(str, "%s expiration:", fmt->name); set_column(str, value_column); mu_stream_printf(str, "%lu\n", fmt->expire_interval); } return 0; } static void list_db_formats(mu_stream_t str, const char *pfx) { mu_iterator_t itr; int rc; const char *defdb = DEFAULT_DB_TYPE; set_column(str, keyword_column); mu_stream_printf(str, "%s:", pfx); set_column(str, value_column); rc = mu_dbm_impl_iterator(&itr); if (rc) { mu_stream_printf(str, "%s\n", _("unknown")); mu_error("%s", mu_strerror(rc)); } else { int i; for (mu_iterator_first(itr), i = 0; !mu_iterator_is_done(itr); mu_iterator_next(itr), i++) { struct mu_dbm_impl *impl; mu_iterator_current(itr, (void**)&impl); if (i) mu_stream_printf(str, ", "); else if (!defdb) defdb = impl->_dbm_name; mu_stream_printf(str, "%s", impl->_dbm_name); } mu_stream_write(str, "\n", 1, NULL); mu_iterator_destroy(&itr); } set_column(str, keyword_column); mu_stream_printf(str, "%s:", "default database type"); set_column(str, value_column); mu_stream_printf(str, "%s\n", defdb); } struct string_value { char const *kw; int type; union { char *s_const; char **s_var; char *(*s_func) (void); } data; }; static char * string_preprocessor (void) { return ext_pp ? ext_pp : "none"; } #ifdef USE_SYSLOG_ASYNC # if DEFAULT_SYSLOG_ASYNC == 1 # define DEFAULT_SYSLOG "non-blocking" # else # define DEFAULT_SYSLOG "blocking" # endif #else # define DEFAULT_SYSLOG "blocking" #endif enum { STRING_CONSTANT, STRING_VARIABLE, STRING_FUNCTION }; static struct string_value string_values[] = { { "version", STRING_CONSTANT, { .s_const = VERSION } }, { "script file", STRING_VARIABLE, { .s_var = &script_file } }, { "preprocessor", STRING_FUNCTION, { .s_func = string_preprocessor } }, { "user", STRING_VARIABLE, { .s_var = &mf_server_user } }, { "statedir", STRING_VARIABLE, { .s_var = &mailfromd_state_dir } }, { "socket", STRING_CONSTANT, { .s_const = DEFAULT_SOCKET } }, { "pidfile", STRING_VARIABLE, { .s_var = &pidfile } }, { "default syslog", STRING_CONSTANT, { .s_const = DEFAULT_SYSLOG } }, { NULL } }; static void print_string_values(mu_stream_t str) { struct string_value *p; char const *val; for (p = string_values; p->kw; p++) { set_column(str, keyword_column); mu_stream_printf(str, "%s:", p->kw); switch (p->type) { case STRING_CONSTANT: val = p->data.s_const; break; case STRING_VARIABLE: val = *p->data.s_var; break; case STRING_FUNCTION: val = p->data.s_func (); } set_column(str, value_column); mu_stream_printf(str, "%s\n", val); } } void mailfromd_show_defaults(void) { int rc; mu_stream_t str; rc = mu_wordwrap_stream_create (&str, mu_strout, 0, right_margin); if (rc) { str = mu_strout; mu_stream_ref(str); } print_string_values(str); list_db_formats(str, "supported databases"); set_column(str, keyword_column); mu_stream_printf(str, "%s:", "optional features"); set_column(str, value_column); #if defined WITH_GEOIP mu_stream_printf(str, " %s", "GeoIP"); #endif #if defined WITH_DSPAM mu_stream_printf(str, " %s", "DSPAM"); #endif mu_stream_write(str, "\n", 1, NULL); db_format_enumerate(db_format_enumerator, str); mu_stream_destroy (&str); } static int args_in_order(int argc, char **argv) { int i; for (i = 0; i < argc; i++) { size_t len = strcspn(argv[i], "="); if (len > 3 && (memcmp(argv[i], "--ru", 4) == 0 || memcmp(argv[i], "--run", 5) == 0)) { return MF_GETOPT_IN_ORDER; } } return MF_GETOPT_DEFAULT; } static void provide_default_milter_server(void) { if (mfd_srvman_count_servers() == 0) { mu_diag_output(MU_DIAG_WARNING, _("no servers defined; will listen on %s"), DEFAULT_SOCKET); mf_srvcfg_add("milter", DEFAULT_SOCKET); } } static void provide_default_callout_server() { struct variable *var; if (provide_callout && !callout_server_url && (!(var = variable_lookup("callout_server_url")) || (var->sym.flags & SYM_REFERENCED) && !(var->sym.flags & SYM_INITIALIZED))) { mf_srvcfg_add("callout", DEFAULT_CALLOUT_SOCKET); } } static char *modnames[] = { #define __DBGMOD_C_ARRAY # include "mfd-dbgmod.h" #undef __DBGMOD_C_ARRAY NULL }; mu_debug_handle_t mfd_debug_handle; static void debug_init(void) { int i; mfd_debug_handle = mu_debug_register_category (modnames[0]); for (i = 1; modnames[i]; i++) mu_debug_register_category (modnames[i]); } int mf_server_function(const char *key, struct mf_srvcfg *cfg) { if (!key || strcmp(key, "default") == 0 || strcmp(key, "milter") == 0) cfg->server = milter_session_server; else if (strcmp(key, "callout") == 0) { cfg->server = mfd_callout_session_server; if (cfg->defopt || mu_list_locate(cfg->options, "default", NULL) == 0) callout_server_url = mu_strdup(mu_url_to_string(cfg->url)); } else return 1; return 0; } static void open_strecho (int daemon_mode) { int rc; if (daemon_mode) { rc = mu_stream_ioctl(mu_strerr, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_CLONE, &mf_strecho); if (rc == 0) { int s = MU_LOG_INFO; mu_stream_ioctl(mf_strecho, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_SET_SEVERITY, &s); } } else { #if 0 mf_strecho = mu_strout; mu_stream_ref(mf_strecho); rc = 0; #else rc = mu_stdio_stream_create(&mf_strecho, MU_STDERR_FD, 0); #endif } if (rc) { mu_diag_output(MU_LOG_CRIT, _("cannot create echo output stream: %s"), mu_strerror(rc)); exit(EX_UNAVAILABLE); } } void mailfromd_alloc_die() { parse_error("not enough memory"); abort(); } extern char **environ; struct mu_cli_setup cli = { .optv = options, .cfg = mf_cfg_param, .prog_doc = prog_doc, .prog_args = args_doc }; int main(int argc, char **argv) { prog_counter_t entry_point; int stderr_is_closed = stderr_closed_p(); mf_init_nls(); mf_proctitle_init(argc, argv, environ); mu_alloc_die_hook = mailfromd_alloc_die; MU_AUTH_REGISTER_ALL_MODULES(); mu_register_all_formats(); mu_register_all_mailer_formats(); yy_flex_debug = 0; /* Initialize milter */ milter_setlogmask(SMI_LOG_FROM(SMI_LOG_WARN)); milter_settimeout(7200); /* Set default logging */ mu_set_program_name(argv[0]); mu_log_tag = mu_strdup(mu_program_name); mu_log_facility = DEFAULT_LOG_FACILITY; mu_stdstream_setup(MU_STDSTREAM_RESET_NONE); mf_srvcfg_log_setup(stderr_is_closed ? "syslog" : "stderr"); debug_init(); libcallout_init(); init_string_space(); init_symbols(); builtin_setup(); mf_runtime_param_finish(); db_format_setup(); include_path_setup(); pragma_setup(); mf_server_save_cmdline(argc, argv); dnsbase_init(); mu_acl_cfg_init(); database_cfg_init(); srvman_init(); mf_srvcfg_init(argv[0], "(milter | callout)"); mf_getopt(&cli, &argc, &argv, capa, args_in_order(argc, argv)); if (validate_options()) exit(EX_USAGE); init_relayed_domains(); if (trace_option != ARG_UNSET) do_trace = trace_option; if (trace_modules) { mu_list_foreach(trace_modules, flush_trace_module, NULL); mu_list_destroy(&trace_modules); } mf_srvcfg_flush(); alloc_ext_pp(); if (need_script) { char *new_script = NULL; if (argc) { int i, n = -1; for (i = 0; i < argc; i++) { if (strchr(argv[i], '=') == 0) { if (n == -1) { n = i; if (mode == MAILFROMD_RUN) break; } else { mu_error(_("script file " "specified twice " "(%s and %s)"), argv[n], argv[i]); exit(EX_USAGE); } } } if (n >= 0) { new_script = argv[n]; memmove(argv + n, argv + n + 1, (argc - n + 1) * sizeof argv[0]); argc--; } } if (new_script) script_file = new_script; if (script_file[0] != '/') /* Clear saved command line */ mf_server_save_cmdline(0, NULL); if (preprocess_option) exit(preprocess_input()); if (parse_program(script_file, script_ydebug)) exit(EX_CONFIG); } if (script_dump_tree) print_syntax_tree(); if (script_dump_code) print_code(); if (script_dump_xref) print_xref(); if (script_dump_macros) print_used_macros(); fixup_code(); if (script_check || script_dump_macros || script_dump_code || script_dump_tree || script_dump_xref || yy_flex_debug || script_ydebug) exit(EX_OK); switch (mode) { case MAILFROMD_DAEMON: mf_server_log_setup(); provide_default_milter_server(); provide_default_callout_server(); break; case MAILFROMD_RUN: { struct function *fun = function_lookup(main_function_name); if (!fun) { mu_error(_("function %s is not defined"), main_function_name); exit(EX_CONFIG); } if (fun->parmcount || !fun->varargs) { mu_error(_("function %s must take variable number of " "arguments"), main_function_name); exit(EX_CONFIG); } if (fun->rettype != dtype_number) { mu_error(_("function %s must return number"), main_function_name); exit(EX_CONFIG); } entry_point = fun->entry; } } free_exceptions(); free_symbols(); free_string_space(); free_parser_data(); mf_namefixup_run(mailfromd_state_dir); mf_namefixup_free(); switch (mode) { case MAILFROMD_DAEMON: if (argc > 0) { mu_error(_("too many arguments")); exit(EX_USAGE); } if (script_file[0] != '/') { mu_diag_output(MU_DIAG_WARNING, _("script file is given " "without full file name")); server_flags |= MF_SERVER_NORESTART; } open_strecho(1); mf_server_lint_option = "--lint"; mf_server_start("mailfromd", mailfromd_state_dir, pidfile, server_flags); break; case MAILFROMD_TEST: open_strecho(0); mailfromd_test(argc, argv); break; case MAILFROMD_SHOW_DEFAULTS: mailfromd_show_defaults(); break; case MAILFROMD_RUN: open_strecho(0); mailfromd_run(entry_point, argc, argv); } exit(EX_OK); }