/* GNU Mailutils -- a suite of utilities for electronic mail Copyright (C) 2007, 2008 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "maidag.h" 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; char *progfile_pattern = NULL; char *sieve_pattern = NULL; char *forward_file = NULL; int forward_file_checks = FWD_ALL; int log_to_stderr = -1; /* 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; int lmtp_mode; int url_option; char *lmtp_url_string; int reuse_lmtp_address = 1; const char *program_version = "maidag (" PACKAGE_STRING ")"; static char doc[] = N_("GNU maildag -- 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 static struct argp_option options[] = { { "foreground", FOREGROUND_OPTION, 0, 0, N_("Remain in foreground."), 0 }, { "inetd", 'i', 0, 0, N_("Run in inetd mode"), 0 }, { "daemon", 'd', N_("NUMBER"), OPTION_ARG_OPTIONAL, N_("Runs in daemon mode with a maximum of NUMBER children"), 0 }, { "url", URL_OPTION, 0, 0, N_("Deliver to given URLs"), 0 }, { "from", 'f', N_("EMAIL"), 0, N_("Specify the sender's name") }, { NULL, 'r', NULL, OPTION_ALIAS, NULL }, { "sieve", 'S', N_("PATTERN"), 0, N_("Set name pattern for user-defined Sieve mail filters"), 0 }, { "message-id-header", MESSAGE_ID_HEADER_OPTION, N_("STRING"), 0, N_("Identify messages by the value of this header when logging Sieve actions"), 0 }, #ifdef WITH_GUILE { "source", 's', N_("PATTERN"), 0, N_("Set name pattern for user-defined Scheme mail filters"), 0 }, #endif { "lmtp", LMTP_OPTION, N_("URL"), OPTION_ARG_OPTIONAL, N_("Operate in LMTP mode"), 0 }, { "debug", 'x', N_("FLAGS"), 0, N_("Enable debugging"), 0 }, { "stderr", STDERR_OPTION, NULL, 0, N_("Log to standard error"), 0 }, { 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", "license", "logging", "mailbox", "locking", "mailer", NULL }; #define D_DEFAULT "9,s" static void set_debug_flags (mu_debug_t debug, const char *arg) { while (*arg) { if (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_cfg_format_error (debug, MU_DEBUG_ERROR, _("%c is not a valid debug flag"), *arg); break; } } if (*arg == ',') arg++; else if (*arg) mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("expected comma, but found %c"), *arg); } } static error_t parse_opt (int key, char *arg, struct argp_state *state) { static struct mu_argp_node_list 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, "lmtp", "yes"); if (arg) mu_argp_node_list_new (&lst, "listen", arg); break; case 'r': case 'f': if (sender_address != NULL) { argp_error (state, _("Multiple --from options")); return EX_USAGE; } sender_address = arg; break; #ifdef WITH_GUILE case 's': mu_argp_node_list_new (&lst, "guile-filter", arg); break; #endif case 'S': mu_argp_node_list_new (&lst, "sieve-filter", arg); 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: url_option = 1; 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 (mu_debug_t debug, void *data, mu_config_value_t *val) { if (mu_cfg_assert_value_type (val, MU_CFG_STRING, debug)) return 1; set_debug_flags (debug, val->v.string); return 0; } static int cb2_group (mu_debug_t debug, const char *gname, void *data) { mu_list_t *plist = data; struct group *group; if (!*plist) mu_list_create (plist); group = getgrnam (gname); if (!group) mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("Unknown group: %s"), gname); else mu_list_append (*plist, (void*)group->gr_gid); return 0; } static int cb_group (mu_debug_t debug, void *data, mu_config_value_t *arg) { return mu_cfg_string_value_cb (debug, arg, cb2_group, data); } static struct mu_kwd forward_checks[] = { { "all", FWD_ALL }, { "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 (mu_debug_t debug, const char *name, void *data) { int negate = 0; const char *str; int val; if (strlen (name) > 2 && strncasecmp (name, "no", 2) == 0) { negate = 1; str = name + 2; } else str = name; if (mu_kwd_xlat_name_ci (forward_checks, str, &val)) mu_cfg_format_error (debug, MU_DEBUG_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 (mu_debug_t debug, void *data, mu_config_value_t *arg) { return mu_cfg_string_value_cb (debug, arg, cb2_forward_file_checks, data); } struct mu_cfg_param maidag_cfg_param[] = { { "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 { "sieve-filter", mu_cfg_string, &sieve_pattern, 0, NULL, N_("File name or name pattern for Sieve filter file."), N_("file-or-pattern") }, { "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") }, #ifdef WITH_GUILE { "guile-filter", mu_cfg_string, &progfile_pattern, 0, NULL, N_("File name or name pattern for Guile filter file."), N_("file-or-pattern") }, #endif { "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_bool, &log_to_stderr, 0, NULL, 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 */ { "lmtp", mu_cfg_bool, &lmtp_mode, 0, NULL, N_("Run in LMTP mode.") }, { "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\".") }, { ".server", mu_cfg_section, NULL, 0, NULL, N_("LMTP server configuration.") }, TCP_WRAPPERS_CONFIG { NULL } }; /* Logging */ static int _sieve_debug_printer (void *unused, const char *fmt, va_list ap) { mu_diag_vprintf (MU_DIAG_DEBUG, fmt, ap); return 0; } static void _sieve_action_log (void *user_name, const mu_sieve_locus_t *locus, size_t msgno, mu_message_t msg, const char *action, const char *fmt, va_list ap) { int pfx = 0; mu_debug_t debug; mu_diag_get_debug (&debug); mu_debug_set_locus (debug, locus->source_file, locus->source_line); mu_diag_printf (MU_DIAG_NOTICE, _("(user %s) "), (char*) user_name); if (message_id_header) { mu_header_t hdr = NULL; char *val = NULL; mu_message_get_header (msg, &hdr); if (mu_header_aget_value (hdr, message_id_header, &val) == 0 || mu_header_aget_value (hdr, MU_HEADER_MESSAGE_ID, &val) == 0) { pfx = 1; mu_diag_printf (MU_DIAG_NOTICE, _("%s on msg %s"), action, val); free (val); } } if (!pfx) { size_t uid = 0; mu_message_get_uid (msg, &uid); mu_diag_printf (MU_DIAG_NOTICE, _("%s on msg uid %d"), action, uid); } if (fmt && strlen (fmt)) { mu_diag_printf (MU_DIAG_NOTICE, "; "); mu_diag_vprintf (MU_DIAG_NOTICE, fmt, ap); } mu_diag_printf (MU_DIAG_NOTICE, "\n"); mu_debug_set_locus (debug, NULL, 0); } static int _sieve_parse_error (void *user_name, const char *filename, int lineno, const char *fmt, va_list ap) { mu_debug_t debug; mu_diag_get_debug (&debug); if (filename) mu_debug_set_locus (debug, filename, lineno); mu_diag_printf (MU_DIAG_ERROR, _("(user %s) "), (char*) user_name); mu_diag_vprintf (MU_DIAG_ERROR, fmt, ap); mu_diag_printf (MU_DIAG_ERROR, "\n"); mu_debug_set_locus (debug, NULL, 0); return 0; } int sieve_test (struct mu_auth_data *auth, mu_message_t msg) { int rc = 1; char *progfile; if (!sieve_pattern) return 1; progfile = mu_expand_path_pattern (sieve_pattern, auth->name); if (access (progfile, R_OK)) { if (debug_level > 2) mu_diag_output (MU_DIAG_DEBUG, _("Access to %s failed: %m"), progfile); } else { mu_sieve_machine_t mach; rc = mu_sieve_machine_init (&mach, auth->name); if (rc) { mu_error (_("Cannot initialize sieve machine: %s"), mu_strerror (rc)); } else { mu_sieve_set_debug (mach, _sieve_debug_printer); mu_sieve_set_debug_level (mach, sieve_debug_flags); mu_sieve_set_parse_error (mach, _sieve_parse_error); if (sieve_enable_log) mu_sieve_set_logger (mach, _sieve_action_log); rc = mu_sieve_compile (mach, progfile); if (rc == 0) { mu_attribute_t attr; mu_message_get_attribute (msg, &attr); mu_attribute_unset_deleted (attr); if (switch_user_id (auth, 1) == 0) { chdir (auth->dir); rc = mu_sieve_message (mach, msg); if (rc == 0) rc = mu_attribute_is_deleted (attr) == 0; switch_user_id (auth, 0); chdir ("/"); } mu_sieve_machine_destroy (&mach); } } } free (progfile); return rc; } int main (int argc, char *argv[]) { int arg_index; /* 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); /* Register a special mailbox */ mu_registrar_record (mu_remote_mbox_record); mu_gocs_register ("sieve", mu_sieve_module_init); mu_tcpwrapper_cfg_init (); mu_acl_cfg_init (); mu_m_server_cfg_init (); /* Parse command line */ mu_argp_init (program_version, 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); if (mu_app_init (&argp, maidag_argp_capa, maidag_cfg_param, argc, argv, 0, &arg_index, server)) exit (EX_CONFIG); current_uid = getuid (); if (log_to_stderr == -1) log_to_stderr = url_option || (!lmtp_mode && (current_uid != 0)); if (!log_to_stderr) { mu_debug_t debug; openlog (MU_LOG_TAG (), LOG_PID, mu_log_facility); mu_diag_get_debug (&debug); mu_debug_set_print (debug, mu_diag_syslog_printer, NULL); mu_debug_default_printer = mu_debug_syslog_printer; } argc -= arg_index; argv += arg_index; if (lmtp_mode && !url_option) { if (argc) { mu_error (_("Too many arguments")); return EX_USAGE; } return maidag_lmtp_server (); } else { if (current_uid) { if (url_option) { /* FIXME: Verify if the urls are deliverable? */ } else { static char *s_argv[2]; struct mu_auth_data *auth = mu_get_auth_by_uid (current_uid); if (!current_uid) { mu_error (_("Cannot get username")); return EX_UNAVAILABLE; } if (argc > 0 && strcmp (auth->name, argv[0])) { mu_error (_("Recipients given when running as non-root")); return EX_USAGE; } s_argv[0] = auth->name; argv = s_argv; argc = 1; } } return maidag_stdio_delivery (argc, argv); } }