/* 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
#if MAILUTILS_VERSION_NUMBER < 1290
# include
#else
# include
#endif
#ifdef HAVE_MAILUTILS_SYSLOG_H
# include
#endif
#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);
}
}
static void
mf_error_on_locus(mu_debug_t err, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
#if MAILUTILS_VERSION_NUMBER >= 1290
if (err) {
mu_debug_vprintf(err, 0, fmt, ap);
mu_debug_printf(err, 0, "\n");
} else
#endif
mu_verror(fmt, ap);
va_end(ap);
}
/* 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 */
int
read_domain_file(mu_debug_t err, char *name)
{
FILE *fp;
char buf[256];
char *p;
fp = fopen(name, "r");
if (!fp) {
mf_error_on_locus(err, _("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, 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 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) {
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;
}
/* ************************************************************************
Configuration directives and options.
This is rather complicated. Mailfromd can take its configurable values
from the following locations (in that order):
1. From the mailutils configuration file suite, $sysconfdir/mailutils.rc
and/or any files included herein;
2. From #pragma statements in the filter script file,
$sysconfdir/mailfromd.rc;
3. From command line options;
Versions prior to 4.2.90 supported only [2] and [3]. Generally speaking,
[2] was needed because Mailutils versions prior to 1.2.90 lacked proper
configuration file support. With the advent of it, [2] became obsolete
and will probably be removed in some point in the future.
For the time being, however, I need to support all flavors of options,
because it is reasonable to assume that many of the installers still have
MU 1.2 or prior, with which [1] is not available.
Technically speaking, [1] is supported by mu_app_init, whenever compiled
with newer Mailutils, [2] is supported by the "option handling" code below,
and [3] is handled by argp machinery (called either from mu_app_init, for
MU >=1.2.90, or from mu_argp_parse, for older Mailutils versions).
There are some functions common to [1] and [2], these are defined in this
section.
************************************************************************ */
static int
gid_comp(const void *item, const void *data)
{
return (gid_t) item != (gid_t) data;
}
static int
mf_option_group(mu_debug_t err, char *arg)
{
struct group *group = getgrnam(arg);
if (group) {
if (!retain_groups) {
int rc = mu_list_create(&retain_groups);
if (rc) {
mf_error_on_locus(err,
_("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 {
mf_error_on_locus(err, _("Unknown group: %s"), arg);
return 1;
}
return 0;
}
static int
mf_option_mailfrom(mu_debug_t err, char *arg)
{
int rc;
mu_address_t addr;
rc = mu_address_create(&addr, arg);
if (rc) {
mf_error_on_locus(err, _("Cannot create address `%s': %s"),
arg, mu_strerror(rc));
return 1;
}
mu_address_destroy(&addr);
defer_initialize_variable("mailfrom_address", arg);
return 0;
}
static int
mf_option_state_directory(mu_debug_t err, char *arg)
{
struct stat st;
if (stat(arg, &st)) {
mf_error_on_locus(err, _("Cannot stat file `%s': %s"),
arg,
mu_strerror(errno));
return 1;
}
if (!S_ISDIR(st.st_mode)) {
mf_error_on_locus(err, _("`%s' is not a directory"), arg);
return 1;
}
if (arg[0] != '/') {
mf_error_on_locus(err,
_("State directory `%s' is not an absolute file name"),
arg);
return 1;
}
mailfromd_state_dir = xstrdup(arg);
return 0;
}
static int
mf_option_source_ip(mu_debug_t err, char *arg, unsigned long *pval)
{
unsigned long address = inet_addr(arg);
if (address == INADDR_NONE) {
struct hostent *phe = gethostbyname(arg);
if (!phe) {
mf_error_on_locus(err, _("Cannot resolve `%s'"),
arg);
return 1;
}
address = *(((unsigned long **) phe->h_addr_list)[0]);
}
*pval = address;
return 0;
}
/* 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"),
(char*)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(NULL, 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;
if (mf_option_source_ip(NULL, newval, &address))
return 1;
if (*pval == 0)
*pval = xmalloc (sizeof address);
*(unsigned long*)*pval = address;
return 0;
}
static int
option_group(char *opt, void **pval, char *newval)
{
return mf_option_group(NULL, newval);
}
void
set_state_directory(void *value)
{
/* nothing */
}
int
option_state_directory(char *opt, void **pval, char *newval)
{
return mf_option_state_directory(NULL, newval);
}
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",
"common",
"logging",
"mailer",
NULL
};
static struct argp argp = {
options,
parse_opt,
args_doc,
doc,
NULL,
NULL,
NULL
};
/* Mailutils-2.0 configuration */
#if MAILUTILS_VERSION_NUMBER >= 1290
int
cb_timeout(time_t *pinterval, mu_debug_t err, void *data, char *arg)
{
const char *endp;
if (parse_time_interval(arg, pinterval, &endp)) {
mf_error_on_locus(err,
_("unrecognized time format (near `%s')"),
endp);
return 1;
}
return 0;
}
int
cb_milter_timeout(mu_debug_t err, void *data, char *arg)
{
time_t interval;
if (cb_timeout(&interval, err, data, arg))
return 1;
if (smfi_settimeout(interval) == MI_FAILURE) {
mf_error_on_locus(err,
_("%s:%d: Invalid milter timeout: %lu"),
(unsigned long) interval);
exit(EX_USAGE);
}
return 0;
}
int
cb_io_timeout(mu_debug_t err, void *data, char *arg)
{
if (cb_timeout(&io_timeout, err, data, arg))
return 1;
return 0;
}
int
cb_connect_timeout(mu_debug_t err, void *data, char *arg)
{
if (cb_timeout(&connect_timeout, err, data, arg))
return 1;
return 0;
}
int
cb_initial_response_timeout(mu_debug_t err, void *data, char *arg)
{
if (cb_timeout(&response_timeout, err, data, arg))
return 1;
return 0;
}
int
cb_lock_retry_timeout(mu_debug_t err, void *data, char *arg)
{
if (cb_timeout(&lock_retry_timeout_option, err, data, arg))
return 1;
return 0;
}
int
cb_set_variable(mu_debug_t err, void *data, char *arg)
{
char *p, *value;
char *tmp = NULL;
for (p = arg; !(*p == ' ' || *p == '\t'); p++)
if (!*p) {
mf_error_on_locus(err, _("missing value"));
return 1;
}
*p++ = 0;
for (; *p == ' ' || *p == '\t'; p++)
if (!*p) {
mf_error_on_locus(err, _("missing value"));
return 1;
}
if (*p == '"' || *p == '\'') {
size_t len = strlen(p) + 1;
tmp = xmalloc(len);
mu_argcv_unquote_copy(tmp, p, len);
value = tmp;
} else
value = p;
defer_initialize_variable(arg, value);
free(tmp);
return 0;
}
int
cb_ehlo_domain(mu_debug_t err, void *data, char *arg)
{
defer_initialize_variable("ehlo_domain", arg);
return 0;
}
int
cb_mail_from_address(mu_debug_t err, void *data, char *arg)
{
return mf_option_mailfrom(err, arg);
}
int
cb_debug(mu_debug_t err, void *data, char *arg)
{
debug_parse_spec(arg);
return 0;
}
/* See also option_group. */
int
cb_group(mu_debug_t err, void *data, char *arg)
{
return mf_option_group(err, arg);
}
int
cb_state_directory(mu_debug_t err, void *data, char *arg)
{
return mf_option_state_directory(err, arg);
}
int
cb_relay_file(mu_debug_t err, void *data, char *arg)
{
return read_domain_file(err, arg);
}
int
cb_source_ip(mu_debug_t err, void *data, char *arg)
{
return mf_option_source_ip(err, arg, &source_address);
}
int
cb_include_path(mu_debug_t err, void *data, char *arg)
{
char *p, *sp;
for (p = strtok_r(arg, ":", &sp); p; p = strtok_r(NULL, ":", &sp))
add_include_dir(p);
return 0;
}
/* Keep alphabetical ordering of statements */
struct mu_cfg_param mf_cfg_param[] = {
{ "debug", mu_cfg_callback, NULL, 0, cb_debug,
N_("Set Mailfromd debug verbosity level. Argument is a comma-"
"separated list of debug specifications, each of which has the "
"following form:\n"
" [=]\n"
"where is the name of a Mailfromd module, and "
"is the desired verbosity level for that module."),
N_("spec: list") },
{ "source-info", mu_cfg_bool, &source_info_option, 0, NULL,
N_("Debug messages include mailfromd source locations.") },
{ "stack-trace", mu_cfg_bool, &stack_trace_option, 0, NULL,
N_("Dump stack traces on runtime errors.") },
{ "connect-timeout", mu_cfg_callback, NULL, 0, cb_connect_timeout,
N_("Set SMTP connect timeout."),
N_("time") },
{ "initial-response-timeout", mu_cfg_callback, NULL, 0,
cb_initial_response_timeout,
N_("Timeout for initial SMTP response."),
N_("time") },
{ "io-timeout", mu_cfg_callback, NULL, 0, cb_io_timeout,
N_("Timeout for SMTP I/O operations."),
N_("time") },
{ "milter-timeout", mu_cfg_callback, NULL, 0, cb_milter_timeout,
N_("Set milter timeout."),
N_("time") },
{ "ehlo-domain", mu_cfg_callback, NULL, 0, cb_ehlo_domain,
N_("Set the domain name for EHLO command.") },
{ "mail-from-address", mu_cfg_callback, NULL, 0, cb_mail_from_address,
N_("Set email address for use in SMTP `MAIL FROM' command. "
"Argument is an email address or a comma-separated list of "
"addresses. Use <> for null address. Other addresses can "
"be given without angle brackets."),
N_("addr") },
{ "group", mu_cfg_callback, NULL, 0, cb_group,
N_("Retain the supplementary group when switching to user "
"privileges") },
{ "user", mu_cfg_string, &user, 0, NULL,
N_("Switch to this user privileges after startup.") },
{ "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 comma-separated list of "
"directory names."),
N_("path") },
{ "lock-retry-count", mu_cfg_size, &lock_retry_count_option, 0, NULL,
N_("Retry acquiring DBM file lock this number of times.") },
{ "lock-retry-timeout", mu_cfg_callback, NULL, 0,
cb_lock_retry_timeout,
N_("Set the time span between the two DBM locking attempts."),
N_("time") },
{ "pidfile", mu_cfg_string, &pidfile, 0, NULL,
N_("Set file to store PID value in."),
N_("file") },
{ "relay-file", mu_cfg_callback, NULL, 0, cb_relay_file,
N_("Read relayed domains from ."),
N_("file") },
{ "setvar", mu_cfg_callback, NULL, 0, cb_set_variable,
N_("Initialize a mailfromd variable. Argument must be:\n"
" \n"
"where is mailfromd variable name and is a value "
"to be assigned to it.") },
{ "script-file", mu_cfg_string, &script_file, 0, NULL,
N_("Read filter stript from ."),
N_("file") },
/* FIXME: Could have used mu_cfg_ipv4 here, but... */
{ "source-ip", mu_cfg_callback, NULL, 0, cb_source_ip,
N_("Set source address for TCP connections."),
N_("ip: ipaddr") },
{ "state-directory", mu_cfg_callback, NULL, 0, cb_state_directory,
N_("Set program state directory."),
N_("dir") },
{ NULL }
};
#endif
/* 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: %s"),
(unsigned long) uid,
mu_strerror(errno));
rc = 1;
}
if (setuid(uid) < 0) {
mu_error(_("second setuid(%lu) failed: %s"),
(unsigned long) uid,
mu_strerror(errno));
rc = 1;
}
} else
#endif
{
mu_error(_("setuid(%lu) failed: %s"),
(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, mu_log_facility);
gacopyz_set_logger(mf_gacopyz_syslog_async_log_printer);
} else
#endif
{
openlog(syslog_tag, LOG_PID, mu_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 rc;
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_formats();
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 */
mu_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 ">");
#if MAILUTILS_VERSION_NUMBER < 1290
rc = mu_argp_parse(&argp, &argc, &argv, 0, capa, &index, NULL);
#else
rc = mu_app_init(&argp, capa, mf_cfg_param, argc, argv, 0, &index,
NULL);
#endif
if (rc)
exit (EX_CONFIG);
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();
}