diff options
Diffstat (limited to 'src/main.c')
-rw-r--r-- | src/main.c | 401 |
1 files changed, 283 insertions, 118 deletions
@@ -1,18 +1,18 @@ /* This file is part of Mailfromd. - Copyright (C) 2005-2019 Sergey Poznyakoff + Copyright (C) 2005-2024 Sergey Poznyakoff - This program is free software; you can redistribute it and/or modify + Mailfromd 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, + Mailfromd 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/>. */ + along with mailfromd. If not, see <http://www.gnu.org/licenses/>. */ #ifdef HAVE_CONFIG_H # include <config.h> @@ -31,6 +31,8 @@ #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> +#include <termios.h> +#include <sys/ioctl.h> #include <mailutils/mailutils.h> #include <mailutils/server.h> @@ -53,7 +55,7 @@ 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; +char *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 */ @@ -64,7 +66,6 @@ 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; @@ -76,6 +77,7 @@ int stack_trace_option; /* Print stack traces on runtime errors */ char *main_function_name = "main"; char *callout_server_url; +char *echo_output; /* Output file for 'echo' statements. */ mu_stream_t mf_strecho; /* Output stream for 'echo' statements */ #define ARG_UNSET (-1) @@ -334,10 +336,9 @@ opt_daemon(struct mu_parseopt *po, struct mu_option *op, } static void -opt_include_dir(struct mu_parseopt *po, struct mu_option *op, - char const *arg) +opt_path_add(struct mu_parseopt *po, struct mu_option *op, char const *arg) { - add_include_dir(arg); + pathbuf_add((struct pathbuf *)op->opt_ptr, arg); } static void @@ -407,23 +408,45 @@ opt_relayed_domain_file(struct mu_parseopt *po, struct mu_option *op, } static void -opt_clear_ext_pp(struct mu_parseopt *po, struct mu_option *op, char const *arg) +opt_disable_preprocessor(struct mu_parseopt *po, struct mu_option *op, char const *arg) +{ + preprocessor.enabled = 0; +} + +static void +opt_preprocessor(struct mu_parseopt *po, struct mu_option *op, char const *arg) { - ext_pp = NULL; + preprocessor.enabled = 1; + if (arg) + preprocessor.command = (char*) arg; + else if (!preprocessor.command) { + mu_error(_("preprocessor options given, " + "but no default preprocessor defined: " + "use the --preprocessor option")); + exit(EX_USAGE); + } } static void opt_define(struct mu_parseopt *po, struct mu_option *op, char const *arg) { + if (preprocessor.pass_defines) { ext_pp_options_given = 1; add_pp_option("-D", arg); + } else + mu_error("%s", + _("Your configuration file disables passing defines to the preprocessor")); } static void opt_undefine(struct mu_parseopt *po, struct mu_option *op, char const *arg) { + if (preprocessor.pass_defines) { ext_pp_options_given = 1; add_pp_option("-U", arg); + } else + mu_error("%s", + _("Your configuration file disables passing defines to the preprocessor")); } static void @@ -555,10 +578,14 @@ static struct mu_option mailfromd_options[] = { mu_c_void }, MU_OPTION_GROUP(N_("General options")), - { "include", 'I', N_("DIR"), MU_OPTION_DEFAULT, + { "include-path", '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 }, + mu_c_string, &include_path, opt_path_add }, + { "module-path", 'P', N_("DIR"), MU_OPTION_DEFAULT, + N_("add the directory DIR to the list of directories to be " + "searched for MFL module files"), + mu_c_string, &module_path, opt_path_add }, { "port", 'p', N_("STRING"), MU_OPTION_DEFAULT, N_("set communication socket"), mu_c_string, NULL, opt_milter_socket }, @@ -581,14 +608,17 @@ static struct mu_option mailfromd_options[] = { { "resolv-conf-file", 0, N_("FILE"), MU_OPTION_DEFAULT, N_("read resolver configuration from FILE"), mu_c_string, &resolv_conf_file }, + { "echo", 0, N_("FILE"), MU_OPTION_ARG_OPTIONAL, + N_("divert output of the \"echo\" instruction to FILE"), + mu_c_string, &echo_output, NULL, "-" }, MU_OPTION_GROUP(N_("Preprocessor options")), - { "preprocessor", 0, N_("COMMAND"), MU_OPTION_DEFAULT, + { "preprocessor", 0, N_("COMMAND"), MU_OPTION_ARG_OPTIONAL, N_("use command as external preprocessor"), - mu_c_string, &ext_pp }, + mu_c_string, NULL, opt_preprocessor }, { "no-preprocessor", 0, NULL, MU_OPTION_DEFAULT, N_("disable the use of external preprocessor"), - mu_c_string, NULL, opt_clear_ext_pp }, + mu_c_string, NULL, opt_disable_preprocessor }, { "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 }, @@ -735,25 +765,15 @@ cb_set_variable(void *data, mu_config_value_t *arg) } static int -cb_include_path(void *data, mu_config_value_t *val) +cb_pathbuf_add(void *data, mu_config_value_t *val) { int i, rc = 0; - struct mu_wordsplit ws; mu_iterator_t itr; + struct pathbuf *pb = data; 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); + pathbuf_add(pb, val->v.string); break; case MU_CFG_ARRAY: @@ -761,7 +781,7 @@ cb_include_path(void *data, mu_config_value_t *val) 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); + pathbuf_add(pb, val->v.arg.v[i].v.string); } break; @@ -774,7 +794,7 @@ cb_include_path(void *data, mu_config_value_t *val) rc = mu_cfg_assert_value_type(pval, MU_CFG_STRING); if (rc) break; - add_include_dir(pval->v.string); + pathbuf_add(pb, pval->v.string); } mu_iterator_destroy(&itr); } @@ -808,6 +828,15 @@ cb_trace_program(void *data, mu_config_value_t *val) } static int +cfg_load_relay_file(void *item, void *data) +{ + mu_config_value_t *val = item; + if (mu_cfg_assert_value_type(val, MU_CFG_STRING) == 0) + read_domain_file(val->v.string); + return 0; +} + +static int cb_relayed_domain_file(void *data, mu_config_value_t *val) { switch (val->type) { @@ -816,7 +845,7 @@ cb_relayed_domain_file(void *data, mu_config_value_t *val) break; case MU_CFG_LIST: - mu_list_foreach(val->v.list, load_relay_file, NULL); + mu_list_foreach(val->v.list, cfg_load_relay_file, NULL); break; default: @@ -828,6 +857,7 @@ cb_relayed_domain_file(void *data, mu_config_value_t *val) struct mu_cfg_param mf_cfg_param[] = { { ".mfd:server", mu_cfg_section, NULL, 0, NULL, NULL }, + { "resolver", mu_cfg_section, NULL, 0, NULL, NULL }, { "stack-trace", mu_c_bool, &stack_trace_option, 0, NULL, N_("Dump stack traces on runtime errors.") }, @@ -837,11 +867,16 @@ struct mu_cfg_param mf_cfg_param[] = { { "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, + { "include-path", mu_cfg_callback, &include_path, 0, cb_pathbuf_add, 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") }, + { "module-path", mu_cfg_callback, &module_path, 0, cb_pathbuf_add, + N_("Add directories to the list of directories to be searched for " + "MFL modules. 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 <var> to <value>."), N_("var: string> <value: string-or-number") }, @@ -859,16 +894,7 @@ struct mu_cfg_param mf_cfg_param[] = { 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") }, - + { "preprocessor", mu_cfg_section, NULL }, { "runtime", mu_cfg_section, NULL }, { NULL } @@ -926,7 +952,6 @@ mf_runtime_param_finish() } - /* Auxiliary functions */ unsigned keyword_column = 0; unsigned header_column = 2; @@ -970,17 +995,12 @@ db_format_enumerator(struct db_format *fmt, void *data) } static void -list_db_formats(mu_stream_t str, const char *pfx) +print_supported_dbtypes(mu_stream_t str) { 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")); @@ -1001,11 +1021,11 @@ list_db_formats(mu_stream_t str, const char *pfx) mu_stream_write(str, "\n", 1, NULL); mu_iterator_destroy(&itr); } - +//FIXME 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); + mu_stream_write(str, defdb, strlen(defdb), NULL); } struct string_value { @@ -1014,14 +1034,54 @@ struct string_value { union { char *s_const; char **s_var; - char *(*s_func) (void); + char const *(*s_func) (void); + void (*s_print) (mu_stream_t); } data; }; -static char * +char const * string_preprocessor(void) { - return ext_pp ? ext_pp : "none"; + return (preprocessor.enabled && preprocessor.command) + ? preprocessor.command : "none"; +} + +static void +print_optional_values(mu_stream_t str) +{ + static char *features[] = { +#if defined WITH_DKIM + "DKIM", +#endif +#if defined WITH_GEOIP2 + "GeoIP2", +#endif +#if defined WITH_MFMOD + "mfmod", +#endif +#if defined WITH_MAILUTILS_GNUTLS + "STARTTLS", +#endif + NULL + }; + + if (features[0]) { + int i; + + mu_stream_write(str, features[0], strlen(features[0]), NULL); + for (i = 1; features[i]; i++) { + mu_stream_write(str, ", ", 2, NULL); + mu_stream_write(str, features[i], strlen(features[i]), NULL); + } + } +} + +static void +print_script_file(mu_stream_t str) +{ + mu_stream_printf(str, "%s/%s%s", + SYSCONFDIR, DEFAULT_SCRIPT_FILE_STEM, + default_suffixes[0]); } #ifdef USE_SYSLOG_ASYNC @@ -1037,18 +1097,36 @@ string_preprocessor (void) enum { STRING_CONSTANT, STRING_VARIABLE, - STRING_FUNCTION + STRING_FUNCTION, + STRING_PRINTER }; 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 } }, + { "script file", STRING_PRINTER, { .s_print = print_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 } }, + { "include path", STRING_VARIABLE, + { .s_var = &include_path.base } }, + { "module path", STRING_VARIABLE, + { .s_var = &module_path.base } }, +#ifdef WITH_MFMOD + { "mfmod path", STRING_VARIABLE, { .s_var = &mfmod_path } }, +#endif + { "optional features", STRING_PRINTER, + { .s_print = print_optional_values } }, + { "supported database types", STRING_PRINTER, + { .s_print = print_supported_dbtypes } }, { NULL } }; @@ -1061,6 +1139,7 @@ print_string_values(mu_stream_t str) for (p = string_values; p->kw; p++) { set_column(str, keyword_column); mu_stream_printf(str, "%s:", p->kw); + set_column(str, value_column); switch (p->type) { case STRING_CONSTANT: @@ -1073,9 +1152,14 @@ print_string_values(mu_stream_t str) case STRING_FUNCTION: val = p->data.s_func(); + break; + + case STRING_PRINTER: + p->data.s_print(str); + mu_stream_write(str, "\n", 1, NULL); + continue; } - set_column(str, value_column); mu_stream_printf(str, "%s\n", val); } } @@ -1083,30 +1167,19 @@ print_string_values(mu_stream_t str) void mailfromd_show_defaults(void) { - int rc; mu_stream_t str; + struct winsize ws; - rc = mu_wordwrap_stream_create (&str, mu_strout, 0, right_margin); - if (rc) { + ws.ws_col = ws.ws_row = 0; + if ((MAILUTILS_VERSION_MAJOR == 3 && MAILUTILS_VERSION_MINOR < 16) || + ioctl(1, TIOCGWINSZ, (char *) &ws) < 0 || ws.ws_col == 0 || + mu_wordwrap_stream_create (&str, mu_strout, 0, ws.ws_col)) { 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); @@ -1203,15 +1276,23 @@ open_strecho (int daemon_mode) mu_stream_ioctl(mf_strecho, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_SET_SEVERITY, &s); + /* Clear severity suppression, if any */ + s = MU_LOG_DEBUG; + mu_stream_ioctl(mf_strecho, MU_IOCTL_LOGSTREAM, + MU_IOCTL_LOGSTREAM_SUPPRESS_SEVERITY, + &s); } - } else { -#if 0 + } else if (echo_output) { + if (strcmp(echo_output, "-") == 0) { mf_strecho = mu_strout; mu_stream_ref(mf_strecho); rc = 0; -#else + } else { + rc = mu_file_stream_create(&mf_strecho, echo_output, + MU_STREAM_WRITE|MU_STREAM_CREAT); + } + } else { rc = mu_stdio_stream_create(&mf_strecho, MU_STDERR_FD, 0); -#endif } if (rc) { mu_diag_output(MU_LOG_CRIT, @@ -1237,11 +1318,105 @@ struct mu_cli_setup cli = { .prog_args = args_doc }; +/* + * Split command line arguments into macro definitions, script file + * name and proper arguments, depending on the current mode. + * + * On input, pargc points to the number of arguments in the command + * line and pargv to the vector of arguments. On output, both pargc + * and pargv are updated. The script_name is assigned the name of + * the MFL script file (or NULL), mc and mv keep number of macro + * definitions and vector of these. + * + * On error, prints a diagnostic message and exits with EX_USAGE + * status. + */ +static void +argv_split(int *pargc, char ***pargv, char **script_name, + int *mc, char ***mv) +{ + int argc = *pargc; + char **argv = *pargv; + int i, n = -1; + + *script_name = NULL; + *mc = 0; + *mv = NULL; + + switch (mode) { + case MAILFROMD_DAEMON: + if (argc == 1) { + *script_name = argv[0]; + argc--; + argv++; + } else if (argc > 1) { + mu_error(_("extra arguments in daemon mode")); + exit(EX_USAGE); + } + break; + + case MAILFROMD_TEST: + for (i = 0; i < argc; i++) { + if (strchr(argv[i], '=') == 0) { + if (n == -1) { + n = i; + } else { + mu_error(_("script file specified " + "twice (%s and %s)"), + argv[n], argv[i]); + exit(EX_USAGE); + } + } + } + + if (n >= 0) { + *script_name = argv[n]; + memmove(argv + n, argv + n + 1, + (argc - n + 1) * sizeof argv[0]); + argc--; + } + + *mc = argc; + *mv = argv; + argv += argc; + argc = 0; + break; + + case MAILFROMD_RUN: + for (i = 0; i < argc; i++) { + if (strchr(argv[i], '=') == 0) { + n = i; + break; + } + } + + if (n >= 0) { + *script_name = argv[n]; + argv[n] = NULL; + *mc = n; + *mv = argv; + + argv += n + 1; + argc -= n + 1; + } + break; + + default: + mu_error(_("extra arguments in this mode")); + exit(EX_USAGE); + } + + *pargc = argc; + *pargv = argv; +} + int main(int argc, char **argv) { prog_counter_t entry_point; int stderr_is_closed = stderr_closed_p(); + int macc; + char **macv; mf_init_nls(); mf_proctitle_init(argc, argv, environ); @@ -1260,6 +1435,7 @@ main(int argc, char **argv) mu_set_program_name(argv[0]); mu_log_tag = mu_strdup(mu_program_name); mu_log_facility = DEFAULT_LOG_FACILITY; + mu_log_print_severity = 1; mu_stdstream_setup(MU_STDSTREAM_RESET_NONE); mf_srvcfg_log_setup(stderr_is_closed ? "syslog" : "stderr"); @@ -1270,7 +1446,6 @@ main(int argc, char **argv) builtin_setup(); mf_runtime_param_finish(); db_format_setup(); - include_path_setup(); pragma_setup(); mf_server_save_cmdline(argc, argv); @@ -1280,6 +1455,7 @@ main(int argc, char **argv) database_cfg_init(); srvman_init(); mf_srvcfg_init(argv[0], "(milter | callout)"); + preprocessor_cfg_init(); mf_getopt(&cli, &argc, &argv, capa, args_in_order(argc, argv)); @@ -1299,38 +1475,22 @@ main(int argc, char **argv) mf_srvcfg_flush(); - alloc_ext_pp(); + preprocessor_finalize(); 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--; + argv_split(&argc, &argv, &script_file, &macc, &macv); + if (script_file == NULL) { + script_file = path_find_file(SYSCONFDIR, + DEFAULT_SCRIPT_FILE_STEM, + default_suffixes); + if (!script_file) { + mu_error(_("filter script file %s/%s%s does not exist or is unreadable"), + SYSCONFDIR, DEFAULT_SCRIPT_FILE_STEM, + default_suffixes[0]); + exit(EX_NOINPUT); } } - if (new_script) - script_file = new_script; - if (script_file[0] != '/') /* Clear saved command line */ mf_server_save_cmdline(0, NULL); @@ -1398,10 +1558,6 @@ main(int argc, char **argv) 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 " @@ -1409,14 +1565,20 @@ main(int argc, char **argv) server_flags |= MF_SERVER_NORESTART; } open_strecho(1); + /* Startup handlers should be run after assuming user privs. */ + mf_server_startup_hook = run_startup; + mf_server_shutdown_hook = run_shutdown; mf_server_lint_option = "--lint"; mf_server_start("mailfromd", mailfromd_state_dir, pidfile, server_flags); break; case MAILFROMD_TEST: + logger_set_print_severity(0); open_strecho(0); - mailfromd_test(argc, argv); + run_startup(); + mailfromd_test(macc, macv); + run_shutdown(); break; case MAILFROMD_SHOW_DEFAULTS: @@ -1424,8 +1586,11 @@ main(int argc, char **argv) break; case MAILFROMD_RUN: + logger_set_print_severity(0); open_strecho(0); - mailfromd_run(entry_point, argc, argv); + run_startup(); + mailfromd_run(entry_point, argc, argv, macc, macv); + run_shutdown(); } exit(EX_OK); |