/* This file is part of mailfromd. Copyright (C) 2005, 2006, 2007 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 . */ #define MF_SOURCE_NAME MF_SOURCE_MAIN #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mailfromd.h" /* Configurable options */ int mode = MAILFROMD_DAEMON; /* Default operation mode */ enum smtp_state test_state = smtp_state_envfrom; /* State for --test mode */ int need_config = 1;/* Set if the current mode requires reading the configuration 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 */ #ifdef DEFAULT_PREPROCESSOR # define DEF_EXT_PP DEFAULT_PREPROCESSOR #else # define DEF_EXT_PP NULL #endif char *ext_pp = DEF_EXT_PP; /* External preprocessor to use */ int do_transcript; /* Enable session transript */ int do_trace; /* Enable tracing configuration */ int mtasim_option; /* mtasim compatibility mode */ unsigned optimization_level = 1; /* Optimization level */ int log_to_stderr; /* Use stderr for logging */ char *portspec; /* Communication socket specification. See init_names() */ int force_remove; /* Remove local communication socket if it already exists */ int foreground; /* Stay in foreground */ int single_process_option; /* Run in single process mode. */ unsigned long source_address = INADDR_ANY; /* Source address for TCP connections */ #ifdef USE_SYSLOG_ASYNC int use_syslog_async = DEFAULT_SYSLOG_ASYNC; /* Use asynchronous syslog implementation */ #endif char *syslog_tag; /* Tag to mark syslog entries with. */ char *mailfromd_state_dir; /* see init_names() */ char *pidfile; /* see init_names() */ char *user = DEFAULT_USER; /* Switch to this user privileges after startup */ mu_list_t retain_groups; /* List of group IDs to retain when switching to users privileges. */ /* DBM-related options */ time_t negative_expire_interval = DEFAULT_EXPIRE_INTERVAL/2; /* Expire negative cache entries after this number of seconds */ time_t expire_interval = 0; /* When set, overrides all the three above intervals */ int predict_next_option; double predict_rate; /* Prediction rate for --list --format=rates*/ int ignore_failed_reads_option; /* Ignore failed reads while compacting or expiring */ int all_option; /* Process all databases */ char *time_format_string = "%c"; /* String to format the time stamps */ size_t lock_retry_count_option = 3; time_t lock_retry_timeout_option = 1; /* DBM-related options end */ int source_info_option; /* Debug messages include source locations */ int stack_trace_option; /* Print stack traces on runtime errors */ char *file_option; /* File name for DB management commands */ struct db_format *format_option; /* Connect settings */ time_t connect_timeout = 10; /* I/O settings */ time_t io_timeout = 3; /* Initial response settings */ time_t response_timeout = 30; /* Logging & debugging */ int syslog_printer (int prio, const char *fmt, va_list ap) { #ifdef USE_SYSLOG_ASYNC if (use_syslog_async) { vsyslog_async (prio, fmt, ap); } else #endif { #if HAVE_VSYSLOG vsyslog (prio, fmt, ap); #else char buf[128]; vsnprintf (buf, sizeof buf, fmt, ap); syslog (prio, "%s", buf); #endif } return 0; } #ifdef USE_SYSLOG_ASYNC void mf_gacopyz_syslog_async_log_printer(int level, char *fmt, va_list ap) { switch (level) { case SMI_LOG_DEBUG: level = LOG_DEBUG; break; case SMI_LOG_INFO: level = LOG_INFO; break; case SMI_LOG_WARN: level = LOG_WARNING; break; case SMI_LOG_ERR: level = LOG_ERR; break; case SMI_LOG_FATAL: default: level = LOG_EMERG; } vsyslog_async(level, fmt, ap); } #endif int syslog_error_printer (const char *fmt, va_list ap) { return syslog_printer(LOG_ERR, fmt, ap); } int stderr_error_printer (const char *fmt, va_list ap) { fprintf(stderr, "%s: ", program_invocation_short_name); vfprintf(stderr, fmt, ap); fputc ('\n', stderr); return 0; } void vlogmsg(int prio, const char *fmt, va_list ap) { if (log_to_stderr) { vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); } else syslog_printer(prio, fmt, ap); } void logmsg(int prio, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vlogmsg(prio, fmt, ap); va_end(ap); } void trace(const char *fmt, ...) { if (do_trace) { va_list ap; va_start(ap, fmt); vlogmsg(LOG_INFO, fmt, ap); va_end(ap); } } void debug_log(char *fmt, ...) { va_list ap; va_start(ap, fmt); vlogmsg(LOG_DEBUG, fmt, ap); va_end(ap); } void log_status(sfsistat status, SMFICTX *ctx) { int lev; debug_module_level(NULL, &lev); if (lev >= 1 && status != SMFIS_CONTINUE) { const char *str = sfsistat_str(status); if (str) logmsg(LOG_INFO, "%s%s", mailfromd_msgid(ctx), str); else logmsg(LOG_INFO, "%sstatus %d", mailfromd_msgid(ctx), status); } } /* Sendmail class file support */ static mu_list_t domain_list; int compare_string(const void *item, const void *value) { return strcmp(item, value); } /* Read domains from sendmail-style domain file NAME and store them in DOMAIN_LIST */ void read_domain_file(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; } if (!domain_list) { mu_list_create(&domain_list); mu_list_set_comparator(domain_list, compare_string); } while (p = fgets(buf, sizeof buf, fp)) { char *q; while (*p && isspace(*p)) p++; if (*p == '#') continue; for (q = p + strlen(p) - 1; q > p && isspace(*q); q--) *q = 0; if (*p == 0) continue; mu_list_append(domain_list, strdup(p)); } fclose(fp); } /* 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) { debug2(10,"%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; } /* Option handling. There are two classes of options: those that can be set using #pragma directive in the configuration file, and those that can be used only in command line. The latter are handled as usual. The former are processed in two stages: first, upon processing command line, their command line settings are verified and stored in struct option_cache below. Then, configuration file is parsed. Any valid `#pragma option' directives found there overwrite values in option_cache. Finally, process_options() is called that scans the cache and actually sets any options found there. */ static int option_string(char *opt, void **pval, char *newval); static int option_ehlo(char *opt, void **pval, char *newval) { if (get_locus()->file) parse_warning( "`#pragma option ehlo' is deprecated, consider using " "`set ehlo_domain \"%s\"' instead", (char*) newval); else parse_warning( "option --ehlo is deprecated, consider using " "`-v ehlo_domain=\"%s\"' instead", (char*) newval); return option_string(opt, pval, newval); } static void set_ehlo(void *value) { defer_initialize_variable("ehlo_domain", value); } static int option_mailfrom(char *opt, void **pval, char *newval) { int rc; mu_address_t addr; /* FIXME-MU: compensate for mailutils deficiency */ if (newval[0] == 0) newval = DEFAULT_FROM_ADDRESS; if (get_locus()->file) parse_warning( "`#pragma option mailfrom' is deprecated, consider using " "`set mailfrom_address \"%s\"' instead", (char*) newval); else parse_warning( "option --mailfrom is deprecated, consider using " "`-v mailfrom_address=\"%s\"' instead", (char*) newval); rc = mu_address_create(&addr, newval); if (rc) { mu_error(_("Cannot create address `%s': %s"), newval, mu_strerror(rc)); return 1; } mu_address_destroy(&addr); return option_string(opt, pval, newval); } static void set_mailfrom(void *value) { int rc; mu_address_t addr; /* FIXME-MU: compensate for mailutils deficiency */ if (*(char*)value == 0) value = DEFAULT_FROM_ADDRESS; rc = mu_address_create(&addr, value); if (rc) { mu_error(_("Cannot create address `%s': %s"), value, mu_strerror(rc)); return; } mu_address_destroy(&addr); defer_initialize_variable("mailfrom_address", value); } /* Other options */ static void set_debug(void *value) { debug_parse_spec(value); } static void set_positive_expire(void *value) { cache_format->expire_interval = *(time_t*) value; free(value); } static void set_negative_expire(void *value) { negative_expire_interval = *(time_t*) value; free(value); } static void set_expire(void *value) { expire_interval = *(time_t*) value; free(value); } static void set_rates_expire(void *value) { rate_format->expire_interval = *(time_t*) value; free(value); } static void set_port(void *value) { portspec = value; } static void set_user(void *value) { user = value; } static void set_milter_timeout(void *value) { time_t to = *(time_t*) value; free(value); if (smfi_settimeout(to) == MI_FAILURE) { mu_error(_("Invalid milter timeout: %lu"), (unsigned long) to); exit(EX_USAGE); } } static void set_pidfile(void *value) { pidfile = value; } static void set_io_timeout(void *value) { io_timeout = *(time_t*) value; free(value); } static void set_connect_timeout(void *value) { connect_timeout = *(time_t*) value; free(value); } static void set_response_timeout(void *value) { response_timeout = *(time_t*) value; free(value); } static int load_relay_file(void *item, void *data) { read_domain_file(item); return 0; } static void set_relay(void *value) { mu_list_do(value, load_relay_file, NULL); } static void set_source(void *value) { source_address = *(unsigned long*)value; } static void set_group(void *value) { /* do nothing */ } void set_source_info(void *value) { source_info_option = (int) value; } void set_stack_trace(void *value) { stack_trace_option = (int) value; } static void set_lock_retry_count(void *value) { lock_retry_count_option = strtoul(value, NULL, 0); } static void set_lock_retry_timeout(void *value) { lock_retry_timeout_option = *(time_t*) value; free(value); } static int option_string(char *opt, void **pval, char *newval) { if (*pval) free(*pval); *pval = strdup(newval); return 0; } static int option_number(char *opt, void **pval, char *newval) { char *p; strtoul(newval, &p, 10); if (*p) { errno = EINVAL; return 1; } return option_string(opt, pval, newval); } static int option_boolean(char *opt, void **pval, char *newval) { int val; if (strcmp (newval, "yes") == 0 || strcmp (newval, "true") == 0 || strcmp (newval, "t") == 0) val = 1; else if (strcmp (newval, "no") == 0 || strcmp (newval, "false") == 0 || strcmp (newval, "nil") == 0) val = 0; else { char *p; val = strtoul(newval, &p, 10); if (*p) { errno = EINVAL; return 1; } } *pval = (void*) val; return 0; } static int option_debug(char *opt, void **pval, char *newval) { return option_string(opt, pval, newval); } static int option_time(char *opt, void **pval, char *newval) { time_t interval; const char *endp; if (parse_time_interval(newval, &interval, &endp)) { mu_error(_("%s: unrecognized time format (near `%s')"), opt, endp); return 1; } if (!*pval) *pval = xmalloc(sizeof(time_t)); *(time_t*) *pval = interval; return 0; } static int option_pidfile(char *opt, void **pval, char *newval) { if (newval[0] != '/') { mu_error(_("Invalid pidfile name: must be absolute")); return 1; } return option_string(opt, pval, newval); } static int option_relay(char *opt, void **pval, char *newval) { if (!*pval) mu_list_create((mu_list_t*)pval); mu_list_append(*pval, strdup(newval)); return 0; } static int option_source(char *opt, void **pval, char *newval) { unsigned long address = inet_addr (newval); if (address == INADDR_NONE) { struct hostent *phe = gethostbyname (newval); if (!phe) { mu_error(_("Cannot resolve `%s'"), newval); return 1; } address = *(((unsigned long **) phe->h_addr_list)[0]); } if (*pval == 0) { *pval = malloc (sizeof address); if (!*pval) return 1; } *(unsigned long*)*pval = address; return 0; } static int gid_comp(const void *item, const void *data) { return (gid_t) item != (gid_t) data; } static int option_group(char *opt, void **pval, char *newval) { struct group *group = getgrnam(newval); if (group) { if (!retain_groups) { int rc = mu_list_create(&retain_groups); if (rc) { mu_error(_("Cannot create list: %s"), mu_strerror(rc)); return 1; } mu_list_set_comparator(retain_groups, gid_comp); } mu_list_append(retain_groups, (void*)group->gr_gid); } else { mu_error(_("Unknown group: %s"), newval); return 1; } return 0; } void set_state_directory(void *value) { /* nothing */ } int option_state_directory(char *opt, void **pval, char *newval) { struct stat st; if (stat(newval, &st)) { mu_error(_("Cannot stat file `%s': %s"), newval, mu_strerror(errno)); return 1; } if (!S_ISDIR(st.st_mode)) { mu_error(_("`%s' is not a directory"), newval); return 1; } if (newval[0] != '/') { mu_error(_("State directory `%s' is not an absolute file name"), newval); return 1; } mailfromd_state_dir = xstrdup(newval); return 0; } struct option_cache { char *name; void *value; int (*handler)(char *opt, void **pval, char *newval); void (*set)(void *value); int cumulative; } option_cache[] = { { "ehlo", NULL, option_ehlo, set_ehlo }, { "debug", NULL, option_debug, set_debug }, { "source-info", NULL, option_boolean, set_source_info }, { "stack-trace", NULL, option_boolean, set_stack_trace }, { "expire-interval", NULL, option_time, set_expire }, { "positive-expire-interval", NULL, option_time, set_positive_expire }, { "negative-expire-interval", NULL, option_time, set_negative_expire }, { "rates-expire-interval", NULL, option_time, set_rates_expire }, { "port", NULL, option_string, set_port }, /* FIXME: option_port */ { "user", NULL, option_string, set_user }, { "group", NULL, option_group, set_group }, { "milter-timeout", NULL, option_time, set_milter_timeout }, { "pidfile", NULL, option_pidfile, set_pidfile }, { "mailfrom", NULL, option_mailfrom, set_mailfrom }, { "timeout", NULL, option_time, set_io_timeout }, { "io-timeout", NULL, option_time, set_io_timeout }, { "connect-timeout", NULL, option_time, set_connect_timeout }, { "initial-response-timeout", NULL, option_time, set_response_timeout }, { "relay", NULL, option_relay, set_relay, 1 }, { "source", NULL, option_source, set_source }, { "lock-retry-count", NULL, option_number, set_lock_retry_count }, { "lock-retry-timeout", NULL, option_time, set_lock_retry_timeout }, { "state-directory", NULL, option_state_directory, set_state_directory }, { NULL } }; static struct option_cache * find_option(char *name) { struct option_cache *p; for (p = option_cache; p->name; p++) if (strcmp(p->name, name) == 0) return p; return NULL; } int set_option(char *name, char *value, int override) { struct option_cache *p = find_option(name); if (!p) { errno = ENOENT; return 1; } if (!p->cumulative && !override && p->value) return 0; return p->handler(name, &p->value, value); } void process_options() { struct option_cache *p; for (p = option_cache; p->name; p++) if (p->value && p->set) p->set(p->value); } /* Command line parsing */ const char *program_version = "mailfromd (" PACKAGE_STRING ")"; static char doc[] = N_("mailfromd -- a general purpose milter daemon"); static char args_doc[] = "[var=value...][SCRIPT]"; enum mailfromd_option { OPTION_ALL = 256, OPTION_COMPACT, OPTION_CONFIG_FILE, OPTION_DAEMON, OPTION_DELETE, OPTION_DOMAIN_FILE, OPTION_DUMP_CODE, OPTION_DUMP_GRAMMAR_TRACE, OPTION_DUMP_LEX_TRACE, OPTION_DUMP_MACROS, OPTION_DUMP_TREE, OPTION_DUMP_XREF, OPTION_EXPIRE, OPTION_FOREGROUND, OPTION_GACOPYZ_LOG, OPTION_IGNORE_FAILED_READS, OPTION_LINT, OPTION_LOG_TAG, OPTION_LIST, OPTION_LOCK_RETRY_COUNT, OPTION_LOCK_RETRY_TIMEOUT, OPTION_MILTER_TIMEOUT, OPTION_MTASIM, OPTION_NO_PREPROCESSOR, OPTION_NO_SYSLOG_ASYNC, OPTION_PIDFILE, OPTION_POSTMASTER_EMAIL, OPTION_PREDICT_NEXT, OPTION_PREPROCESSOR, OPTION_SHOW_DEFAULTS, OPTION_SINGLE_PROCESS, OPTION_STACK_TRACE, OPTION_STATE_DIRECTORY, OPTION_SOURCE_INFO, OPTION_SYSLOG, OPTION_SYSLOG_ASYNC, OPTION_TIME_FORMAT, OPTION_TIMEOUT, OPTION_TRACE, OPTION_TRACE_PROGRAM, }; static struct argp_option options[] = { #define GRP 0 { NULL, 0, NULL, 0, N_("Operation modifiers"), GRP }, { "delete", OPTION_DELETE, NULL, 0, N_("Delete given entries from the database"), GRP+1 }, { "list", OPTION_LIST, NULL, 0, N_("List given database (default cache)"), GRP+1 }, { "expire", OPTION_EXPIRE, NULL, 0, N_("Delete expired entries from the database"), GRP+1 }, { "compact", OPTION_COMPACT, NULL, 0, N_("Compact the database"), GRP+1 }, { "test", 't', N_("HANDLER"), OPTION_ARG_OPTIONAL, N_("Run in test mode"), GRP+1 }, { "lint", OPTION_LINT, NULL, 0, N_("Check syntax and exit"), GRP+1 }, { "syntax-check", 0, NULL, OPTION_ALIAS, NULL, GRP+1 }, { "show-defaults", OPTION_SHOW_DEFAULTS, NULL, 0, N_("Show compilation defaults"), GRP+1 }, { "daemon", OPTION_DAEMON, NULL, 0, N_("Run in daemon mode (default)"), GRP+1 }, { NULL, 'E', NULL, 0, N_("Preprocess source files and exit"), GRP+1 }, /* Reserved for future use: */ { "compile", 'c', NULL, OPTION_HIDDEN, N_("Compile files"), GRP+1 }, { "load", 'l', N_("FILE"), OPTION_HIDDEN, N_("Load library"), GRP+1 }, { "load-dir", 'L', N_("DIR"), OPTION_HIDDEN, N_("Add DIR to the load path"), GRP+1 }, #undef GRP #define GRP 15 { NULL, 0, NULL, 0, N_("Database management options"), GRP }, { "file", 'f', N_("FILE"), 0, N_("DB file name to operate upon"), GRP+1 }, { "format", 'H', N_("FORMAT"), 0, N_("Format of the DB file to operate upon"), GRP+1 }, { "ignore-failed-reads", OPTION_IGNORE_FAILED_READS, NULL, 0, N_("Ignore failed reads while compacting the database"), GRP+1 }, { "predict", OPTION_PREDICT_NEXT, N_("RATE"), 0, N_("Predict when the user will be able to send next message"), GRP+1 }, { "lock-retry-count", OPTION_LOCK_RETRY_COUNT, N_("NUMBER"), 0, N_("Set maximum number of attempts to acquire the lock"), GRP+1 }, { "lock-retry-timeout", OPTION_LOCK_RETRY_TIMEOUT, N_("TIME"), 0, N_("Set timeout for acquiring the lockfile"), GRP+1 }, { "expire-interval", 'e', N_("NUMBER"), 0, N_("Set database expiration interval to NUMBER seconds"), GRP+1 }, { "all", OPTION_ALL, NULL, 0, N_("With --compact or --expire: apply the operation to all " "available databases"), GRP+1 }, { "time-format", OPTION_TIME_FORMAT, N_("FMT"), 0, N_("Output timestamps using given format (default \"%c\")"), GRP+1 }, #undef GRP #define GRP 20 { NULL, 0, NULL, 0, N_("General options"), GRP }, { "state-directory", OPTION_STATE_DIRECTORY, N_("DIR"), 0, N_("Set new program state directory"), GRP+1 }, { "config-file", OPTION_CONFIG_FILE, N_("FILE"), OPTION_HIDDEN, N_("Read configuration from FILE"), GRP+1 }, { "include", 'I', N_("DIR"), 0, N_("Add the directory DIR to the list of directories to be " "searched for header files"), GRP+1 }, { "port", 'p', N_("STRING"), 0, N_("Set communication socket"), GRP+1 }, { "remove", 'r', NULL, 0, N_("Force removing local socket file, if it already exists"), GRP+1 }, { "foreground", OPTION_FOREGROUND, NULL, 0, N_("Stay in foreground"), GRP+1 }, { "mtasim", OPTION_MTASIM, NULL, 0, N_("Run in mtasim compatibility mode"), GRP+1 }, { "single-process", OPTION_SINGLE_PROCESS, NULL, 0, N_("Run in single-process mode"), GRP+1 }, { "pidfile", OPTION_PIDFILE, N_("FILE"), 0, N_("Set pidfile name"), GRP+1 }, { "user", 'u', N_("NAME"), 0, N_("Switch to this user privileges after startup"), GRP+1 }, { "group", 'g', N_("NAME"), 0, N_("Retain the supplementary group NAME when switching to user " "privileges"), GRP+1 }, { "source", 'S', N_("ADDRESS"), 0, N_("Set source address for TCP connections"), GRP+1 }, { "optimize", 'O', N_("LEVEL"), OPTION_ARG_OPTIONAL, N_("Set code optimization level"), GRP+1 }, { "variable", 'v', N_("VAR=VALUE"), 0, N_("Assign VALUE to VAR"), GRP+1 }, { "relayed-domain-file", OPTION_DOMAIN_FILE, N_("FILE"), 0, N_("Read relayed domains from FILE"), GRP+1 }, { "preprocessor", OPTION_PREPROCESSOR, N_("COMMAND"), 0, N_("Use command as external preprocessor"), GRP+1 }, { "no-preprocessor", OPTION_NO_PREPROCESSOR, NULL, 0, N_("Disable the use of external preprocessor"), GRP+1 }, #undef GRP #undef GRP #define GRP 30 { NULL, 0, NULL, 0, N_("Timeout control"), GRP }, { "milter-timeout", OPTION_MILTER_TIMEOUT, N_("TIME"), 0, N_("Set MTA connection timeout"), GRP+1 }, { "timeout", OPTION_TIMEOUT, N_("TIME"), 0, N_("Set I/O operation timeout"), GRP+1 }, #undef GRP #define GRP 40 { NULL, 0, NULL, 0, N_("Informational and debugging options"), GRP }, { "transcript", 'X', NULL, 0, N_("Enable transcript of SMTP sessions"), GRP+1 }, { "trace", OPTION_TRACE, NULL, 0, N_("Trace executed actions"), GRP+1 }, { "trace-program", OPTION_TRACE_PROGRAM, N_("MODULES"), OPTION_ARG_OPTIONAL, N_("Enable filter program tracing"), GRP+1 }, { "debug", 'd', N_("LEVEL"), 0, N_("Set debugging level"), GRP+1 }, { "dump-code", OPTION_DUMP_CODE, NULL, 0, N_("Dump disassembled code"), GRP+1 }, { "dump-grammar-trace", OPTION_DUMP_GRAMMAR_TRACE, NULL, 0, N_("Dump grammar traces"), GRP+1 }, { "dump-lex-trace", OPTION_DUMP_LEX_TRACE, NULL, 0, N_("Dump lexical analyzer traces"), GRP+1 }, { "dump-tree", OPTION_DUMP_TREE, NULL, 0, N_("Dump parser tree"), GRP+1 }, { "dump-macros", OPTION_DUMP_MACROS, NULL, 0, N_("Show used Sendmail macros"), GRP+1 }, { "xref", OPTION_DUMP_XREF, NULL, 0, N_("Produce a cross-reference listing"), GRP+1 }, { "dump-xref", 0, NULL, OPTION_ALIAS, NULL, GRP+1 }, { "gacopyz-log", OPTION_GACOPYZ_LOG, N_("LEVEL"), 0, N_("Set Gacopyz log level"), GRP+1 }, { "stderr", 's', NULL, 0, N_("Log to stderr"), GRP+1 }, { "syslog", OPTION_SYSLOG, NULL, 0, N_("Log to syslog (default)"), GRP+1 }, #ifdef USE_SYSLOG_ASYNC { "syslog-async", OPTION_SYSLOG_ASYNC, NULL, 0, N_("Use asynchronous syslog"), GRP+1 }, { "no-syslog-async", OPTION_NO_SYSLOG_ASYNC, NULL, 0, N_("Use system syslog"), GRP+1 }, #endif { "log-tag", OPTION_LOG_TAG, N_("STRING"), 0, N_("Set the identifier used in syslog messages to STRING"), GRP+1 }, { "source-info", OPTION_SOURCE_INFO, NULL, 0, N_("Debug messages include source information"), GRP+1 }, { "stack-trace", OPTION_STACK_TRACE, NULL, 0, N_("Enable stack traces on runtime errors"), GRP+1 }, #undef GRP /*DEPRECATED OPTIONS*/ { "domain", 'D', N_("STRING"), OPTION_HIDDEN, "", 1 }, { "ehlo", 0, NULL, OPTION_ALIAS|OPTION_HIDDEN, NULL, 1 }, { "postmaster-email", OPTION_POSTMASTER_EMAIL, N_("EMAIL"), OPTION_HIDDEN, "", 1 }, { "mailfrom", 0, NULL, OPTION_ALIAS|OPTION_HIDDEN, NULL, 1 }, #if 0 /* This entry is to pacify `make check-docs'. The options below are defined in libmailutils. */ { "log-facility", } { "mailer", } #endif { NULL } }; static int validate_options() { if (all_option && !(mode == MAILFROMD_COMPACT || mode == MAILFROMD_EXPIRE)) { mu_error(_("--all is meaningful only with --expire or --compact option")); return 1; } if (all_option && format_option) mu_error(_("--format is incompatible with --all")); return 0; } static error_t parse_opt (int key, char *arg, struct argp_state *state) { switch (key) { case 'D': set_option("ehlo", arg, 1); break; case 'd': set_option("debug", arg, 1); break; case 'E': preprocess_option = 1; break; case 'e': set_option("expire-interval", arg, 1); break; case 'f': file_option = arg; break; case 'H': format_option = db_format_lookup(arg); if (format_option == NULL) argp_error(state, _("Unknown database format: %s"), arg); break; case 'I': add_include_dir(arg); break; case OPTION_ALL: all_option = 1; break; case OPTION_CONFIG_FILE: /*DEPRECATION*/ mu_error("Warning: --config-file is obsolete; give the filter script name as an argument"); script_file = arg; break; case OPTION_IGNORE_FAILED_READS: ignore_failed_reads_option = 1; break; case OPTION_LOCK_RETRY_COUNT: set_option("lock-retry-count", arg, 1); break; case OPTION_LOCK_RETRY_TIMEOUT: set_option("lock-retry-timeout", arg, 1); break; case OPTION_PREDICT_NEXT: format_option = db_format_lookup("rate"); if (convert_rate(arg, &predict_rate) == 0) predict_next_option = 1; mode = MAILFROMD_LIST; break; case OPTION_PREPROCESSOR: ext_pp = arg; break; case OPTION_NO_PREPROCESSOR: ext_pp = NULL; break; case OPTION_TIME_FORMAT: time_format_string = arg; break; case 'c': case 'l': case 'L': argp_error(state, _("The option `-%c' is not yet implemented"), key); break; case OPTION_LOG_TAG: syslog_tag = arg; break; case OPTION_LINT: log_to_stderr = 1; script_check = 1; need_config = 1; break; case 'p': set_option("port", arg, 1); break; case 'r': force_remove = 1; break; case 'O': if (!arg) optimization_level = 1; else { char *p; optimization_level = strtoul(arg, &p, 0); if (*p) argp_error(state, _("-O level must be a non-negative integer")); } break; case 'S': set_option("source", arg, 1); break; case 's': log_to_stderr = 1; break; case 't': mode = MAILFROMD_TEST; if (arg) { test_state = string_to_state(arg); if (test_state == smtp_state_none) argp_error(state, _("Unknown smtp state tag: %s"), arg); } log_to_stderr = 1; need_config = 1; break; case 'u': set_option("user", arg, 1); break; case 'g': set_option("group", arg, 1); break; case 'v': { char *p; p = strchr(arg, '='); if (!p) argp_error(state, _("Expected assignment, but found `%s'"), arg); *p++ = 0; defer_initialize_variable(arg, p); break; } case 'X': do_transcript = 1; break; case OPTION_COMPACT: need_config = 0; log_to_stderr = 1; mode = MAILFROMD_COMPACT; break; case OPTION_DAEMON: mode = MAILFROMD_DAEMON; need_config = 1; break; case OPTION_DELETE: need_config = 0; log_to_stderr = 1; mode = MAILFROMD_DELETE; break; case OPTION_DOMAIN_FILE: set_option("relay", arg, 1); break; case OPTION_DUMP_CODE: script_dump_code = 1; log_to_stderr = 1; break; case OPTION_DUMP_GRAMMAR_TRACE: script_ydebug = 1; log_to_stderr = 1; break; case OPTION_DUMP_LEX_TRACE: yy_flex_debug = 1; log_to_stderr = 1; break; case OPTION_DUMP_MACROS: script_dump_macros = 1; log_to_stderr = 1; break; case OPTION_DUMP_TREE: script_dump_tree = 1; log_to_stderr = 1; break; case OPTION_DUMP_XREF: script_dump_xref = 1; log_to_stderr = 1; break; case OPTION_EXPIRE: need_config = 0; log_to_stderr = 1; mode = MAILFROMD_EXPIRE; break; case OPTION_FOREGROUND: foreground = 1; break; case OPTION_GACOPYZ_LOG: { int lev = gacopyz_string_to_log_level(arg); if (lev == -1) argp_error(state, _("%s: invalid log level"), arg); smfi_setlogmask(SMI_LOG_FROM(lev)); break; } case OPTION_LIST: need_config = 0; log_to_stderr = 1; mode = MAILFROMD_LIST; break; case OPTION_MILTER_TIMEOUT: set_option("milter-timeout", arg, 1); break; case OPTION_MTASIM: mtasim_option = foreground = 1; break; case OPTION_PIDFILE: set_option("pidfile", arg, 1); break; case OPTION_POSTMASTER_EMAIL: set_option("mailfrom", arg, 1); break; case OPTION_SHOW_DEFAULTS: mode = MAILFROMD_SHOW_DEFAULTS; need_config = 0; break; case OPTION_SINGLE_PROCESS: single_process_option = 1; break; case OPTION_STACK_TRACE: set_option("stack-trace", "yes", 1); break; case OPTION_STATE_DIRECTORY: set_option("state-directory", arg, 1); break; case OPTION_SOURCE_INFO: set_option("source-info", "yes", 1); break; case OPTION_SYSLOG: log_to_stderr = 0; break; #ifdef USE_SYSLOG_ASYNC case OPTION_SYSLOG_ASYNC: use_syslog_async = 1; break; case OPTION_NO_SYSLOG_ASYNC: use_syslog_async = 0; break; #endif case OPTION_TIMEOUT: set_option("timeout", arg, 1); break; case OPTION_TRACE: do_trace = 1; break; case OPTION_TRACE_PROGRAM: enable_prog_trace(arg ? arg: "all"); break; case ARGP_KEY_INIT: smfi_setlogmask(SMI_LOG_FROM(SMI_LOG_WARN)); break; case ARGP_KEY_FINI: if (validate_options()) exit(EX_USAGE); if (!syslog_tag) syslog_tag = program_invocation_short_name; if (!format_option) format_option = db_format_lookup("cache"); break; default: return ARGP_ERR_UNKNOWN; } return 0; } static const char *capa[] = { "auth", "logging", "mailer", NULL }; static struct argp argp = { options, parse_opt, args_doc, doc, NULL, NULL, NULL }; /* Auxiliary functions */ /* Switch to the given UID/GID */ int switch_to_privs(uid_t uid, gid_t gid) { int rc = 0; gid_t *emptygidset; size_t size = 1, j = 1; mu_iterator_t itr; if (uid == 0) { mu_error(_("Refusing to run as root")); return 1; } /* Create a list of supplementary groups */ mu_list_count (retain_groups, &size); size++; emptygidset = xmalloc (size * sizeof emptygidset[0]); emptygidset[0] = gid ? gid : getegid(); if (mu_list_get_iterator(retain_groups, &itr) == 0) { for (mu_iterator_first (itr); !mu_iterator_is_done (itr); mu_iterator_next (itr)) mu_iterator_current (itr, (void **)(emptygidset + j++)); mu_iterator_destroy(&itr); } /* Reset group permissions */ if (geteuid() == 0 && setgroups(j, emptygidset)) { mu_error(_("setgroups(1, %lu) failed: %s"), (unsigned long) emptygidset[0], mu_strerror(errno)); rc = 1; } free(emptygidset); /* Switch to the user's gid. On some OSes the effective gid must be reset first */ #if defined(HAVE_SETEGID) if ((rc = setegid(gid)) < 0) mu_error(_("setegid(%lu) failed: %s"), (unsigned long) gid, mu_strerror(errno)); #elif defined(HAVE_SETREGID) if ((rc = setregid(gid, gid)) < 0) mu_error(_("setregid(%lu,%lu) failed: %s"), (unsigned long) gid, (unsigned long) gid, mu_strerror(errno)); #elif defined(HAVE_SETRESGID) if ((rc = setresgid(gid, gid, gid)) < 0) mu_error(_("setresgid(%lu,%lu,%lu) failed: %s"), (unsigned long) gid, (unsigned long) gid, (unsigned long) gid, mu_strerror(errno)); #endif if (rc == 0 && gid != 0) { if ((rc = setgid(gid)) < 0 && getegid() != gid) mu_error(_("setgid(%lu) failed: %s"), (unsigned long) gid, mu_strerror(errno)); if (rc == 0 && getegid() != gid) { mu_error(_("Cannot set effective gid to %lu"), (unsigned long) gid); rc = 1; } } /* Now reset uid */ if (rc == 0 && uid != 0) { uid_t euid; if (setuid(uid) || geteuid() != uid || (getuid() != uid && (geteuid() == 0 || getuid() == 0))) { #if defined(HAVE_SETREUID) if (geteuid() != uid) { if (setreuid(uid, -1) < 0) { mu_error(_("setreuid(%lu,-1) failed"), (unsigned long) uid, mu_strerror(errno)); rc = 1; } if (setuid(uid) < 0) { mu_error(_("second setuid(%lu) failed"), (unsigned long) uid, mu_strerror(errno)); rc = 1; } } else #endif { mu_error(_("setuid(%lu) failed"), (unsigned long) uid, mu_strerror(errno)); rc = 1; } } euid = geteuid(); if (uid != 0 && setuid(0) == 0) { mu_error(_("seteuid(0) succeeded when it should not")); rc = 1; } else if (uid != euid && setuid(euid) == 0) { mu_error(_("Cannot drop non-root setuid privileges")); rc = 1; } } return rc; } void priv_setup() { if (getuid() == 0) { if (!user) { mu_error(_("When running as root, --user option is mandatory.")); exit(EX_USAGE); } else { struct passwd *pw = getpwnam(user); if (!pw) { mu_error(_("No such user: %s"), user); exit(EX_SOFTWARE); } if (pw && switch_to_privs(pw->pw_uid, pw->pw_gid)) exit(EX_SOFTWARE); } } } void assert_db_format() { if (!format_option) { mu_error(_("Operation is not applicable")); exit(EX_USAGE); } } char * get_db_name() { assert_db_format(); if (file_option) return file_option; if (format_option->dbname) return format_option->dbname; mu_error(_("Database file name is not given")); exit(EX_USAGE); } void init_names() { mailfromd_state_dir = xstrdup(DEFAULT_STATE_DIR); portspec = xstrdup(DEFAULT_SOCKET); pidfile = xstrdup(DEFAULT_PIDFILE); } void db_format_setup() { dns_cache_format = db_format_install(dns_cache_format); cache_format = db_format_install(cache_format); rate_format = db_format_install(rate_format); greylist_format = db_format_install(greylist_format); } static char * state_dir_fixup(char *name) { if (name[0] != '/') { size_t slen = strlen(mailfromd_state_dir); size_t olen = strlen(name); size_t flen = slen + 1 + olen + 1; char *p = xrealloc(name, flen); memmove(p + slen + 1, p, olen + 1); memcpy(p, mailfromd_state_dir, slen); p[slen] = '/'; name = p; } return name; } static int db_fixup_name_enumerator(void *sym, void *data) { struct db_format *fmt = sym; fmt->dbname = state_dir_fixup(fmt->dbname); return 0; } static void portspec_fixup() { char *proto, *port, *path; if (gacopyz_parse_connection(portspec, &proto, &port, &path) != MI_SUCCESS) return; /* No need to complain: it is left to gacopyz connect routines */ if ((!proto || strcmp(proto, "unix") == 0 || strcmp(proto, "local") == 0) && !port && path[0] != '/') { portspec = state_dir_fixup(path); path = NULL; } free(proto); free(port); free(path); } static void fixup_state_dir_names() { size_t slen = strlen(mailfromd_state_dir); if (mailfromd_state_dir[slen-1] == '/') mailfromd_state_dir[slen-1] = 0; db_format_enumerate(db_fixup_name_enumerator, NULL); pidfile = state_dir_fixup(pidfile); portspec_fixup(); } void mailfromd_delete(int argc, char **argv) { int i; char *name = get_db_name(); for (i = 0; i < argc; i++) { debug2(50,"deleting %s info for %s", format_option->name, argv[i]); db_delete(name, argv[i]); } } void mailfromd_list(int argc, char **argv) { int rc; char *name = get_db_name(); if (!format_option->print_item) { /* Should not happen */ mu_error(_("List is not applicable to this DB format")); exit(EX_USAGE); } rc = 0; if (argc) { int i; for (i = 0; i < argc; i++) rc += db_list_item(name, argv[i], format_option->print_item); } else rc = db_list(name, format_option->print_item); exit(rc != 0); } static int db_proc_enumerator(void *sym, void *data) { struct db_format *fmt = sym; int (*func)(char *name, db_expire_t exp) = data; if (fmt->expire) func(fmt->dbname, fmt->expire); return 0; } void mailfromd_expire() { priv_setup(); if (all_option) db_format_enumerate(db_proc_enumerator, db_expire); else { if (!format_option->expire) { mu_error(_("Expire is not applicable to this DB format")); exit(EX_USAGE); } exit(db_expire(get_db_name(), format_option->expire) != 0); } } void mailfromd_compact() { priv_setup(); if (all_option) db_format_enumerate(db_proc_enumerator, db_compact); else { if (!format_option->expire) { mu_error(_("Compact is not applicable to this DB format")); exit(EX_USAGE); } exit(db_compact(get_db_name(), format_option->expire) != 0); } } static int db_format_enumerator(void *sym, void *data) { struct db_format *fmt = sym; printf("%s database: %s\n", fmt->name, fmt->dbname); if (strcmp(fmt->name, "cache") == 0) { printf("%s positive expiration: %lu\n", fmt->name, fmt->expire_interval); printf("%s negative expiration: %lu\n", fmt->name, negative_expire_interval); } else if (strcmp(fmt->name, "dns") == 0) { printf("%s negative expiration: %lu\n", fmt->name, fmt->expire_interval); } else printf("%s expiration: %lu\n", fmt->name, fmt->expire_interval); return 0; } void mailfromd_show_defaults() { printf("version: %s\n", VERSION); printf("script file: %s\n", script_file); printf("preprocessor: %s\n", ext_pp ? ext_pp : "none"); printf("user: %s\n", user); printf("statedir: %s\n", mailfromd_state_dir); printf("socket: %s\n", portspec); printf("pidfile: %s\n", pidfile); #ifdef USE_SYSLOG_ASYNC #if DEFAULT_SYSLOG_ASYNC == 1 printf("default syslog: non-blocking\n"); #else printf("default syslog: blocking\n"); #endif #endif printf("database format: "); #if defined WITH_GDBM printf("GDBM"); #elif defined WITH_BDB printf("Berkeley DB %d.x", WITH_BDB); #endif printf("\n"); db_format_enumerate(db_format_enumerator, NULL); } void log_setup(int want_stderr) { /* Set up logging */ if (!want_stderr) { #ifdef USE_SYSLOG_ASYNC if (use_syslog_async) { openlog_async(syslog_tag, LOG_PID, log_facility); gacopyz_set_logger(mf_gacopyz_syslog_async_log_printer); } else #endif { openlog(syslog_tag, LOG_PID, log_facility); gacopyz_set_logger(gacopyz_syslog_log_printer); } mu_error_set_print(syslog_error_printer); } else { gacopyz_set_logger(gacopyz_stderr_log_printer); mu_error_set_print(stderr_error_printer); } } static int stderr_closed_p() { int fd = dup(0); if (fd < 0) return 1; close(fd); return fd <= 2; } static void version(FILE *stream, struct argp_state *state) { mailfromd_version("mailfromd", stream, state); } int main(int argc, char **argv) { int index; #ifdef ENABLE_NLS mu_init_nls(); setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); #endif MU_AUTH_REGISTER_ALL_MODULES(); mu_register_all_mailer_formats(); if (!program_invocation_short_name) program_invocation_short_name = argv[0]; argp_program_version_hook = version; yy_flex_debug = 0; /* Set default logging */ log_facility = DEFAULT_LOG_FACILITY; log_setup(!stderr_closed_p()); init_names(); init_string_space(); builtin_setup(); db_format_setup(); include_path_setup(); save_cmdline(argc, argv); mu_argp_init(program_version, "<" PACKAGE_BUGREPORT ">"); mu_argp_parse(&argp, &argc, &argv, 0, capa, &index, NULL); log_setup(log_to_stderr); argv += index; argc -= index; if (need_config) { if (argc) { int i, n = -1; 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_file = argv[n]; memmove(argv + n, argv + n + 1, (argc - n + 1) * sizeof argv[0]); argc--; } } if (script_file[0] != '/') save_cmdline(0, NULL); /* Clear saved command line */ if (preprocess_option) exit(preprocess_input(ext_pp)); if (parse_program(script_file, script_ydebug)) exit(EX_CONFIG); } process_options(); fixup_state_dir_names(); /* Process some special options */ if (expire_interval) cache_format->expire_interval = negative_expire_interval = rate_format->expire_interval = expire_interval; 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 || yy_flex_debug || script_ydebug) exit(EX_OK); free_symbols(); free_string_space(); switch (mode) { case MAILFROMD_COMPACT: mailfromd_compact(); break; case MAILFROMD_DAEMON: if (argc > 0) { mu_error(_("Too many arguments")); exit(EX_USAGE); } mailfromd_daemon(); break; case MAILFROMD_TEST: mailfromd_test(argc, argv); break; case MAILFROMD_DELETE: mailfromd_delete(argc, argv); break; case MAILFROMD_LIST: mailfromd_list(argc, argv); break; case MAILFROMD_EXPIRE: mailfromd_expire(); break; case MAILFROMD_SHOW_DEFAULTS: mailfromd_show_defaults(); break; } exit(EX_OK); } void xalloc_die() { parse_error("not enough memory"); abort(); }