/* GNU Mailutils -- a suite of utilities for electronic mail Copyright (C) 2007, 2008, 2009, 2010 Free Software Foundation, Inc. GNU Mailutils 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. GNU Mailutils 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 GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */ #include "maidag.h" enum maidag_mode maidag_mode = mode_mda; int multiple_delivery; /* Don't return errors when delivering to multiple recipients */ int ex_quota_tempfail; /* Return temporary failure if mailbox quota is exceeded. If this variable is not set, maidag will return "service unavailable" */ int exit_code = EX_OK; /* Exit code to be used */ uid_t current_uid; /* Current user id */ char *quotadbname = NULL; /* Name of mailbox quota database */ char *quota_query = NULL; /* SQL query to retrieve mailbox quota */ char *sender_address = NULL; maidag_script_fun script_handler; mu_list_t script_list; char *forward_file = NULL; int forward_file_checks = FWD_ALL; /* Debuggig options */ int debug_level; /* General debugging level */ int sieve_debug_flags; /* Sieve debugging flags */ int sieve_enable_log; /* Enables logging of executed Sieve actions */ char *message_id_header; /* Use the value of this header as message identifier when logging Sieve actions */ /* For LMTP mode */ mu_m_server_t server; char *lmtp_url_string; int reuse_lmtp_address = 1; int maidag_transcript; const char *program_version = "maidag (" PACKAGE_STRING ")"; static char doc[] = N_("GNU maidag -- the mail delivery agent.") "\v" N_("Debug flags are:\n\ g - guimb stack traces\n\ t - sieve trace (MU_SIEVE_DEBUG_TRACE)\n\ i - sieve instructions trace (MU_SIEVE_DEBUG_INSTR)\n\ l - sieve action logs\n\ 0-9 - Set maidag debugging level\n"); static char args_doc[] = N_("[recipient...]"); #define STDERR_OPTION 256 #define MESSAGE_ID_HEADER_OPTION 257 #define LMTP_OPTION 258 #define FOREGROUND_OPTION 260 #define URL_OPTION 261 #define TRANSCRIPT_OPTION 262 #define MDA_OPTION 263 static struct argp_option options[] = { #define GRID 0 { NULL, 0, NULL, 0, N_("General options"), GRID }, { "foreground", FOREGROUND_OPTION, 0, 0, N_("remain in foreground"), GRID + 1 }, { "inetd", 'i', 0, 0, N_("run in inetd mode"), GRID + 1 }, { "daemon", 'd', N_("NUMBER"), OPTION_ARG_OPTIONAL, N_("runs in daemon mode with a maximum of NUMBER children"), GRID + 1 }, { "url", URL_OPTION, 0, 0, N_("deliver to given URLs"), GRID + 1 }, { "mda", MDA_OPTION, 0, 0, N_("force MDA mode even if not started as root"), GRID + 1 }, { "from", 'f', N_("EMAIL"), 0, N_("specify the sender's name"), GRID + 1 }, { NULL, 'r', NULL, OPTION_ALIAS, NULL }, { "lmtp", LMTP_OPTION, N_("URL"), OPTION_ARG_OPTIONAL, N_("operate in LMTP mode"), GRID + 1 }, { "debug", 'x', N_("FLAGS"), 0, N_("enable debugging"), GRID + 1 }, { "stderr", STDERR_OPTION, NULL, 0, N_("log to standard error"), GRID + 1 }, { "transcript", TRANSCRIPT_OPTION, NULL, 0, N_("enable session transcript"), GRID + 1 }, #undef GRID #define GRID 2 { NULL, 0, NULL, 0, N_("Scripting options"), GRID }, { "language", 'l', N_("STRING"), 0, N_("define scripting language for the next --script option"), GRID + 1 }, { "script", 's', N_("PATTERN"), 0, N_("set name pattern for user-defined mail filter"), GRID + 1 }, { "message-id-header", MESSAGE_ID_HEADER_OPTION, N_("STRING"), 0, N_("use this header to identify messages when logging Sieve actions"), GRID + 1 }, #undef GRID { NULL, 0, NULL, 0, NULL, 0 } }; static error_t parse_opt (int key, char *arg, struct argp_state *state); static struct argp argp = { options, parse_opt, args_doc, doc, NULL, NULL, NULL }; static const char *maidag_argp_capa[] = { "auth", "common", "debug", "logging", "mailbox", "locking", "mailer", NULL }; #define D_DEFAULT "9,s" static void set_debug_flags (const char *arg) { while (*arg) { if (mu_isdigit (*arg)) debug_level = strtoul (arg, (char**)&arg, 10); else for (; *arg && *arg != ','; arg++) { switch (*arg) { case 'g': #ifdef WITH_GUILE debug_guile = 1; #endif break; case 't': sieve_debug_flags |= MU_SIEVE_DEBUG_TRACE; break; case 'i': sieve_debug_flags |= MU_SIEVE_DEBUG_INSTR; break; case 'l': sieve_enable_log = 1; break; default: mu_error (_("%c is not a valid debug flag"), *arg); break; } } if (*arg == ',') arg++; else if (*arg) mu_error (_("expected comma, but found %c"), *arg); } } static error_t parse_opt (int key, char *arg, struct argp_state *state) { static mu_list_t lst; switch (key) { case 'd': mu_argp_node_list_new (lst, "mode", "daemon"); if (arg) mu_argp_node_list_new (lst, "max-children", arg); break; case 'i': mu_argp_node_list_new (lst, "mode", "inetd"); break; case FOREGROUND_OPTION: mu_argp_node_list_new (lst, "foreground", "yes"); break; case MESSAGE_ID_HEADER_OPTION: mu_argp_node_list_new (lst, "message-id-header", arg); break; case LMTP_OPTION: mu_argp_node_list_new (lst, "delivery-mode", "lmtp"); if (arg) mu_argp_node_list_new (lst, "listen", arg); break; case MDA_OPTION: mu_argp_node_list_new (lst, "delivery-mode", "mda"); break; case TRANSCRIPT_OPTION: maidag_transcript = 1; break; case 'r': case 'f': if (sender_address != NULL) argp_error (state, _("multiple --from options")); sender_address = arg; break; case 'l': script_handler = script_lang_handler (arg); if (!script_handler) argp_error (state, _("unknown or unsupported language: %s"), arg); break; case 's': switch (script_register (arg)) { case 0: break; case EINVAL: argp_error (state, _("%s has unknown file suffix"), arg); break; default: argp_error (state, _("error registering script")); } break; case 'x': mu_argp_node_list_new (lst, "debug", arg ? arg : D_DEFAULT); break; case STDERR_OPTION: mu_argp_node_list_new (lst, "stderr", "yes"); break; case URL_OPTION: mu_argp_node_list_new (lst, "delivery-mode", "url"); break; case ARGP_KEY_INIT: mu_argp_node_list_init (&lst); break; case ARGP_KEY_FINI: mu_argp_node_list_finish (lst, NULL, NULL); break; case ARGP_KEY_ERROR: exit (EX_USAGE); default: return ARGP_ERR_UNKNOWN; } return 0; } static int cb_debug (void *data, mu_config_value_t *val) { if (mu_cfg_assert_value_type (val, MU_CFG_STRING)) return 1; set_debug_flags (val->v.string); return 0; } static int cb_stderr (void *data, mu_config_value_t *val) { int res; if (mu_cfg_assert_value_type (val, MU_CFG_STRING)) return 1; if (mu_cfg_parse_boolean (val->v.string, &res)) mu_error (_("not a boolean")); mu_log_syslog = !res; return 0; } static int cb2_group (const char *gname, void *data) { mu_list_t list = data; struct group *group; group = getgrnam (gname); if (!group) mu_error (_("unknown group: %s"), gname); else mu_list_append (list, (void*)group->gr_gid); return 0; } static int cb_group (void *data, mu_config_value_t *arg) { mu_list_t *plist = data; if (!*plist) mu_list_create (plist); return mu_cfg_string_value_cb (arg, cb2_group, *plist); } static struct mu_kwd forward_checks[] = { { "all", FWD_ALL }, { "owner", FWD_OWNER }, { "groupwritablefile", FWD_IWGRP }, { "file_iwgrp", FWD_IWGRP }, { "worldwritablefile", FWD_IWOTH }, { "file_iwoth", FWD_IWOTH }, { "linkedfileinwritabledir", FWD_LINK }, { "link", FWD_LINK }, { "fileingroupwritabledir", FWD_DIR_IWGRP }, { "dir_iwgrp", FWD_DIR_IWGRP }, { "fileinworldwritabledir", FWD_DIR_IWOTH }, { "dir_iwoth", FWD_DIR_IWOTH }, { NULL } }; static int cb2_forward_file_checks (const char *name, void *data) { int negate = 0; const char *str; int val; if (strcmp (name, "none") == 0) { forward_file_checks = 0; return 0; } if (strlen (name) > 2 && mu_c_strncasecmp (name, "no", 2) == 0) { negate = 1; str = name + 2; } else str = name; if (mu_kwd_xlat_name_ci (forward_checks, str, &val)) mu_error (_("unknown keyword: %s"), name); else { if (negate) forward_file_checks &= ~val; else forward_file_checks |= val; } return 0; } static int cb_forward_file_checks (void *data, mu_config_value_t *arg) { return mu_cfg_string_value_cb (arg, cb2_forward_file_checks, data); } static int cb_script_language (void *data, mu_config_value_t *val) { if (mu_cfg_assert_value_type (val, MU_CFG_STRING)) return 1; script_handler = script_lang_handler (val->v.string); if (!script_handler) { mu_error (_("unsupported language: %s"), val->v.string); return 1; } return 0; } static int cb_script_pattern (void *data, mu_config_value_t *val) { if (mu_cfg_assert_value_type (val, MU_CFG_STRING)) return 1; switch (script_register (val->v.string)) { case 0: break; case EINVAL: mu_error (_("%s has unknown file suffix"), val->v.string); break; default: mu_error (_("error registering script")); } return 0; } struct mu_cfg_param filter_cfg_param[] = { { "language", mu_cfg_callback, NULL, 0, cb_script_language, N_("Set script language.") }, { "pattern", mu_cfg_callback, NULL, 0, cb_script_pattern, N_("Set script pattern.") }, { NULL } }; static int cb_delivery_mode (void *data, mu_config_value_t *val) { static mu_kwd_t mode_tab[] = { { "mda", mode_mda }, { "url", mode_url }, { "lmtp", mode_lmtp }, { NULL } }; int n; if (mu_cfg_assert_value_type (val, MU_CFG_STRING)) return 1; if (mu_kwd_xlat_name (mode_tab, val->v.string, &n) == 0) { maidag_mode = n; if (maidag_mode == mode_url) mu_log_syslog = 0; } else mu_error (_("%s is unknonw"), val->v.string); return 0; } struct mu_cfg_param maidag_cfg_param[] = { { "delivery-mode", mu_cfg_callback, NULL, 0, cb_delivery_mode, N_("Set delivery mode"), N_("mode: {mda | url | lmtp}") }, { "exit-multiple-delivery-success", mu_cfg_bool, &multiple_delivery, 0, NULL, N_("In case of multiple delivery, exit with code 0 if at least one " "delivery succeeded.") }, { "exit-quota-tempfail", mu_cfg_bool, &ex_quota_tempfail, 0, NULL, N_("Indicate temporary failure if the recipient is over his mail quota.") }, #ifdef USE_DBM { "quota-db", mu_cfg_string, "adbname, 0, NULL, N_("Name of DBM quota database file."), N_("file") }, #endif #ifdef USE_SQL { "quota-query", mu_cfg_string, "a_query, 0, NULL, N_("SQL query to retrieve mailbox quota. This is deprecated, use " "sql { ... } instead."), N_("query") }, #endif { "message-id-header", mu_cfg_string, &message_id_header, 0, NULL, N_("When logging Sieve actions, identify messages by the value of " "this header."), N_("name") }, { "debug", mu_cfg_callback, NULL, 0, cb_debug, N_("Set maidag debug level. Debug level consists of one or more " "of the following letters:\n" " g - guimb stack traces\n" " t - sieve trace (MU_SIEVE_DEBUG_TRACE)\n" " i - sieve instructions trace (MU_SIEVE_DEBUG_INSTR)\n" " l - sieve action logs\n") }, { "stderr", mu_cfg_callback, NULL, 0, cb_stderr, N_("Log to stderr instead of syslog.") }, { "forward-file", mu_cfg_string, &forward_file, 0, NULL, N_("Process forward file.") }, { "forward-file-checks", mu_cfg_callback, NULL, 0, cb_forward_file_checks, N_("Configure safety checks for the forward file."), N_("arg: list") }, /* LMTP support */ { "group", mu_cfg_callback, &lmtp_groups, 0, cb_group, N_("In LMTP mode, retain these supplementary groups."), N_("groups: list of string") }, { "listen", mu_cfg_string, &lmtp_url_string, 0, NULL, N_("In LMTP mode, listen on the given URL. Valid URLs are:\n" " tcp://<address: string>:<port: number> (note that port is " "mandatory)\n" " file://<socket-file-name>\n" "or socket://<socket-file-name>"), N_("url") }, { "reuse-address", mu_cfg_bool, &reuse_lmtp_address, 0, NULL, N_("Reuse existing address (LMTP mode). Default is \"yes\".") }, { "filter", mu_cfg_section, NULL, 0, NULL, N_("Add a message filter") }, { ".server", mu_cfg_section, NULL, 0, NULL, N_("LMTP server configuration.") }, TCP_WRAPPERS_CONFIG { NULL } }; static void maidag_cfg_init () { struct mu_cfg_section *section; if (mu_create_canned_section ("filter", §ion) == 0) { section->docstring = N_("Add new message filter."); mu_cfg_section_add_params (section, filter_cfg_param); } } /* FIXME: These are for compatibility with MU 2.0. Remove in 2.2 */ extern mu_record_t mu_remote_smtp_record; extern mu_record_t mu_remote_sendmail_record; extern mu_record_t mu_remote_prog_record; int main (int argc, char *argv[]) { int arg_index; maidag_delivery_fn delivery_fun = NULL; /* Preparative work: close inherited fds, force a reasonable umask and prepare a logging. */ close_fds (); umask (0077); /* Native Language Support */ MU_APP_INIT_NLS (); /* Default locker settings */ mu_locker_set_default_flags (MU_LOCKER_PID|MU_LOCKER_RETRY, mu_locker_assign); mu_locker_set_default_retry_timeout (1); mu_locker_set_default_retry_count (300); /* Register needed modules */ MU_AUTH_REGISTER_ALL_MODULES (); /* Register all supported mailbox and mailer formats */ mu_register_all_formats (); mu_registrar_record (mu_smtp_record); mu_gocs_register ("sieve", mu_sieve_module_init); mu_tcpwrapper_cfg_init (); mu_acl_cfg_init (); mu_m_server_cfg_init (); maidag_cfg_init (); /* Parse command line */ #ifdef WITH_TLS mu_gocs_register ("tls", mu_tls_module_init); #endif mu_argp_init (NULL, NULL); mu_m_server_create (&server, program_version); mu_m_server_set_conn (server, lmtp_connection); mu_m_server_set_prefork (server, mu_tcp_wrapper_prefork); mu_m_server_set_mode (server, MODE_INTERACTIVE); mu_m_server_set_max_children (server, 20); mu_m_server_set_timeout (server, 600); mu_log_syslog = -1; mu_log_print_severity = 1; if (mu_app_init (&argp, maidag_argp_capa, maidag_cfg_param, argc, argv, 0, &arg_index, server)) exit (EX_CONFIG); current_uid = getuid (); if (mu_log_syslog == -1) { mu_log_syslog = !(maidag_mode == mode_url); mu_stdstream_strerr_setup (mu_log_syslog ? MU_STRERR_SYSLOG : MU_STRERR_STDERR); } argc -= arg_index; argv += arg_index; switch (maidag_mode) { case mode_lmtp: if (argc) { mu_error (_("too many arguments")); return EX_USAGE; } return maidag_lmtp_server (); case mode_url: /* FIXME: Verify if the urls are deliverable? */ delivery_fun = deliver_to_url; break; case mode_mda: if (argc == 0) { mu_error (_("recipients not given")); return EX_USAGE; } delivery_fun = deliver_to_user; break; } return maidag_stdio_delivery (delivery_fun, argc, argv); }