/* This file is part of Mailfromd.
Copyright (C) 2005-2020 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 . */
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "mailfromd.h"
#include "callout.h"
#include "srvman.h"
#include "srvcfg.h"
#include "filenames.h"
#include "builtin.h"
#include "prog.h"
/* Configurable options */
int mode = MAILFROMD_DAEMON; /* Default operation mode */
enum smtp_state test_state = smtp_state_envfrom; /* State for --test mode */
int need_script = 1; /* Set if the current mode requires reading the
script file */
char *script_file = DEFAULT_SCRIPT_FILE;
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 */
int location_column_option; /* Print column numbers in error locations */
char *ext_pp = DEF_EXT_PP; /* External preprocessor to use */
char *ext_pp_options;
int ext_pp_options_given;
int do_trace; /* Enable tracing configuration */
unsigned optimization_level = 1; /* Optimization level */
int stack_trace_option; /* Print stack traces on runtime errors */
char *main_function_name = "main";
char *callout_server_url;
mu_stream_t mf_strecho; /* Output stream for 'echo' statements */
#define ARG_UNSET (-1)
static int trace_option = ARG_UNSET;
static mu_list_t trace_modules;
static char *resolv_conf_file;
/* Preprocessor helper function */
static void
add_pp_option(const char *opt, const char *arg)
{
size_t len;
len = 1 + strlen(opt) + (arg ? strlen(arg) : 0);
if (ext_pp_options) {
len += strlen(ext_pp_options);
ext_pp_options = mu_realloc(ext_pp_options, len + 1);
} else {
ext_pp_options = mu_alloc(len + 1);
ext_pp_options[0] = 0;
}
strcat(ext_pp_options, " ");
strcat(ext_pp_options, opt);
strcat(ext_pp_options, arg);
}
void
pp_define(const char *symbol)
{
add_pp_option("-D", symbol);
}
/* Logging & debugging */
static mu_stream_t mf_trace_stream;
static void
open_trace_stream()
{
int rc = mu_filter_create(&mf_trace_stream,
mf_strecho,
"C-escape",
MU_FILTER_ENCODE,
MU_STREAM_WRITE);
if (rc) {
mu_error(_("cannot create trace stream, "
"using standard log: %s"),
mu_strerror(rc));
mf_trace_stream = mf_strecho;
}
}
void
trace(const char *fmt, ...)
{
if (do_trace) {
int bval = 0;
va_list ap;
if (!mf_trace_stream)
open_trace_stream();
va_start(ap, fmt);
mu_stream_vprintf(mf_trace_stream, fmt, ap);
bval = 1;
mu_stream_ioctl(mf_trace_stream, MU_IOCTL_FILTER,
MU_IOCTL_FILTER_SET_DISABLED,
&bval);
mu_stream_write(mf_trace_stream, "\n", 1, NULL);
bval = 0;
mu_stream_ioctl(mf_trace_stream, MU_IOCTL_FILTER,
MU_IOCTL_FILTER_SET_DISABLED,
&bval);
va_end(ap);
}
}
void
log_status(sfsistat status, SMFICTX *ctx)
{
mu_debug_level_t lev;
mu_debug_category_level(NULL, 0, &lev);
if ((lev & ~MU_DEBUG_LEVEL_MASK(MU_DEBUG_ERROR)) &&
status != SMFIS_CONTINUE) {
const char *str = sfsistat_str(status);
if (str)
logmsg(MU_LOG_INFO,
"%s%s", mailfromd_msgid(ctx), str);
else
logmsg(MU_LOG_INFO,
"%sstatus %d",
mailfromd_msgid(ctx), status);
}
}
/* Sendmail class file support */
static mu_list_t domain_list;
/* Read domains from sendmail-style domain file NAME and store them in
DOMAIN_LIST */
int
read_domain_file(const 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 1;
}
if (!domain_list) {
mu_list_create(&domain_list);
mu_list_set_comparator(domain_list, mf_list_compare_string);
}
while (p = fgets(buf, sizeof buf, fp)) {
char *q;
while (*p && mu_isspace(*p))
p++;
if (*p == '#')
continue;
for (q = p; *q && !mu_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) {
mu_debug(MF_SOURCE_MAIN, MU_DEBUG_TRACE5,
("%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;
}
static mu_list_t relayed_domain_files;
static int
load_relay_file(void *item, void *data)
{
read_domain_file(item);
return 0;
}
static void
init_relayed_domains(void)
{
mu_list_foreach(relayed_domain_files, load_relay_file, NULL);
mu_list_destroy(&relayed_domain_files);
}
/* Command line parsing */
const char *program_version = "mailfromd (" PACKAGE_STRING ")";
static char prog_doc[] = N_("mailfromd -- a general purpose milter daemon");
static char args_doc[] = "[var=value...][SCRIPT]";
static void
opt_testmode(struct mu_parseopt *po, struct mu_option *op, char const *arg)
{
mode = MAILFROMD_TEST;
if (arg) {
test_state = string_to_state(arg);
if (test_state == smtp_state_none) {
mu_parseopt_error(po,
_("unknown smtp state tag: %s"),
arg);
exit(po->po_exit_error);
}
log_stream = "stderr";
need_script = 1;
}
}
static void
opt_runmode(struct mu_parseopt *po, struct mu_option *op, char const *arg)
{
mode = MAILFROMD_RUN;
if (arg)
main_function_name = mu_strdup (arg);
log_stream = "stderr";
need_script = 1;
}
static void
opt_lint(struct mu_parseopt *po, struct mu_option *op, char const *arg)
{
log_stream = "stderr";
script_check = 1;
need_script = 1;
}
static void
opt_show_defaults(struct mu_parseopt *po, struct mu_option *op,
char const *arg)
{
mode = MAILFROMD_SHOW_DEFAULTS;
need_script = 0;
}
static void
opt_daemon(struct mu_parseopt *po, struct mu_option *op,
char const *arg)
{
mode = MAILFROMD_DAEMON;
need_script = 1;
}
static void
opt_include_dir(struct mu_parseopt *po, struct mu_option *op,
char const *arg)
{
add_include_dir(arg);
}
static void
opt_milter_socket(struct mu_parseopt *po, struct mu_option *op,
char const *arg)
{
mf_srvcfg_add("default", arg);
}
static void
opt_callout_socket(struct mu_parseopt *po, struct mu_option *op,
char const *arg)
{
mf_srvcfg_add("callout", arg);
free(callout_server_url);
callout_server_url = mu_strdup(arg);
}
static void
opt_mtasim(struct mu_parseopt *po, struct mu_option *op,
char const *arg)
{
mtasim_option = 1;
server_flags |= MF_SERVER_FOREGROUND;
}
static void
opt_optimize(struct mu_parseopt *po, struct mu_option *op,
char const *arg)
{
if (!arg)
optimization_level = 1;
else {
char *p;
optimization_level = strtoul(arg, &p, 0);
if (*p)
mu_parseopt_error(po,
_("-O level must be a non-negative integer"));
}
}
static void
opt_variable(struct mu_parseopt *po, struct mu_option *op,
char const *arg)
{
char *p;
struct mu_locus_range locus = {{ "", 1, 0 }};
p = strchr(arg, '=');
if (!p) {
mu_parseopt_error(po,
_("expected assignment, but found `%s'"),
arg);
exit(po->po_exit_error);
}
*p++ = 0;
defer_initialize_variable(arg, p, &locus);
}
static void
opt_relayed_domain_file(struct mu_parseopt *po, struct mu_option *op,
char const *arg)
{
if (!relayed_domain_files)
mu_list_create(&relayed_domain_files);
mu_list_append(relayed_domain_files, mu_strdup(arg));
}
static void
opt_clear_ext_pp(struct mu_parseopt *po, struct mu_option *op, char const *arg)
{
ext_pp = NULL;
}
static void
opt_define(struct mu_parseopt *po, struct mu_option *op, char const *arg)
{
ext_pp_options_given = 1;
add_pp_option("-D", arg);
}
static void
opt_undefine(struct mu_parseopt *po, struct mu_option *op, char const *arg)
{
ext_pp_options_given = 1;
add_pp_option("-U", arg);
}
static void
opt_set_milter_timeout(struct mu_parseopt *po, struct mu_option *op,
char const *arg)
{
time_t v;
if (mu_str_to_c(arg, mu_c_time, &v, NULL)) {
mu_parseopt_error(po, _("%s: not a valid interval"), arg);
exit(po->po_exit_error);
}
if (smfi_settimeout(v) == MI_FAILURE) {
mu_parseopt_error(po, _("invalid milter timeout: %s"), arg);
exit(po->po_exit_error);
}
}
static void
destroy_trace_item(void *ptr)
{
free(ptr);
}
static void
opt_trace_program(struct mu_parseopt *po, struct mu_option *op,
char const *arg)
{
if (!trace_modules) {
mu_list_create(&trace_modules);
mu_list_set_destroy_item(trace_modules, destroy_trace_item);
}
mu_list_append(trace_modules, mu_strdup(arg ? arg : "all"));
}
static void
opt_dump_code(struct mu_parseopt *po, struct mu_option *op,
char const *arg)
{
script_dump_code = 1;
log_stream = "stderr";
}
static void
opt_dump_grammar_trace(struct mu_parseopt *po, struct mu_option *op,
char const *arg)
{
script_ydebug = 1;
log_stream = "stderr";
}
static void
opt_dump_lex_trace(struct mu_parseopt *po, struct mu_option *op,
char const *arg)
{
yy_flex_debug = 1;
log_stream = "stderr";
}
static void
opt_dump_macros(struct mu_parseopt *po, struct mu_option *op,
char const *arg)
{
script_dump_macros = 1;
log_stream = "stderr";
}
static void
opt_dump_tree(struct mu_parseopt *po, struct mu_option *op,
char const *arg)
{
script_dump_tree = 1;
log_stream = "stderr";
}
static void
opt_gacopyz_log(struct mu_parseopt *po, struct mu_option *op,
char const *arg)
{
int lev = gacopyz_string_to_log_level(arg);
if (lev == -1) {
mu_parseopt_error(po, _("%s: invalid log level"), arg);
exit(po->po_exit_error);
}
milter_setlogmask(SMI_LOG_FROM(lev));
}
static void
opt_dump_xref(struct mu_parseopt *po, struct mu_option *op,
char const *arg)
{
script_dump_xref = 1;
log_stream = "stderr";
}
static struct mu_option mailfromd_options[] = {
MU_OPTION_GROUP(N_("Operation modifiers")),
{ "test", 't', N_("HANDLER"), MU_OPTION_ARG_OPTIONAL,
N_("run in test mode"),
mu_c_string, NULL, opt_testmode },
{ "run", 0, N_("FUNC"), MU_OPTION_ARG_OPTIONAL,
N_("run script and execute function FUNC (\"main\" if not given)"),
mu_c_string, NULL, opt_runmode },
{ "lint", 0, NULL, MU_OPTION_DEFAULT,
N_("check syntax and exit"),
mu_c_string, NULL, opt_lint },
{ "syntax-check", 0, NULL, MU_OPTION_ALIAS, },
{ "show-defaults", 0, NULL, MU_OPTION_DEFAULT,
N_("show compilation defaults"),
mu_c_string, NULL, opt_show_defaults },
{ "daemon", 0, NULL, MU_OPTION_DEFAULT,
N_("run in daemon mode (default)"),
mu_c_string, NULL, opt_daemon },
{ NULL, 'E', NULL, MU_OPTION_DEFAULT,
N_("preprocess source files and exit"),
mu_c_bool, &preprocess_option },
/* Reserved for future use: */
{ "compile", 'c', NULL, MU_OPTION_HIDDEN,
N_("compile files"),
mu_c_void },
{ "load", 'l', N_("FILE"), MU_OPTION_HIDDEN,
N_("load library"),
mu_c_void },
{ "load-dir", 'L', N_("DIR"), MU_OPTION_HIDDEN,
N_("add DIR to the load path"),
mu_c_void },
MU_OPTION_GROUP(N_("General options")),
{ "include", 'I', N_("DIR"), MU_OPTION_DEFAULT,
N_("add the directory DIR to the list of directories to be "
"searched for header files"),
mu_c_string, NULL, opt_include_dir },
{ "port", 'p', N_("STRING"), MU_OPTION_DEFAULT,
N_("set communication socket"),
mu_c_string, NULL, opt_milter_socket },
{ "milter-socket", 0, NULL, MU_OPTION_ALIAS },
{ "callout-socket", 0, N_("STRING"), MU_OPTION_DEFAULT,
N_("set callout socket"),
mu_c_string, NULL, opt_callout_socket },
{ "mtasim", 0, NULL, MU_OPTION_IMMEDIATE,
N_("run in mtasim compatibility mode"),
mu_c_string, NULL, opt_mtasim },
{ "optimize", 'O', N_("LEVEL"), MU_OPTION_ARG_OPTIONAL,
N_("set code optimization level"),
mu_c_string, NULL, opt_optimize },
{ "variable", 'v', N_("VAR=VALUE"), MU_OPTION_DEFAULT,
N_("assign VALUE to VAR"),
mu_c_string, NULL, opt_variable },
{ "relayed-domain-file", 0, N_("FILE"), MU_OPTION_DEFAULT,
N_("read relayed domains from FILE"),
mu_c_string, NULL, opt_relayed_domain_file },
{ "resolv-conf-file", 0, N_("FILE"), MU_OPTION_DEFAULT,
N_("read resolver configuration from FILE"),
mu_c_string, &resolv_conf_file },
MU_OPTION_GROUP(N_("Preprocessor options")),
{ "preprocessor", 0, N_("COMMAND"), MU_OPTION_DEFAULT,
N_("use command as external preprocessor"),
mu_c_string, &ext_pp },
{ "no-preprocessor", 0, NULL, MU_OPTION_DEFAULT,
N_("disable the use of external preprocessor"),
mu_c_string, NULL, opt_clear_ext_pp },
{ "define", 'D', N_("NAME[=VALUE]"), MU_OPTION_DEFAULT,
N_("define a preprocessor symbol NAME as having VALUE, or empty"),
mu_c_string, NULL, opt_define },
{ "undefine", 'U', N_("NAME"), MU_OPTION_DEFAULT,
N_("undefine a preprocessor symbol NAME"),
mu_c_string, NULL, opt_undefine },
MU_OPTION_GROUP(N_("Timeout control")),
{ "milter-timeout", 0, N_("TIME"), MU_OPTION_DEFAULT,
N_("set MTA connection timeout"),
mu_c_string, NULL, opt_set_milter_timeout },
MU_OPTION_GROUP (N_("Informational and debugging options")),
{ "location-column", 0, NULL, MU_OPTION_DEFAULT,
N_("print location column numbers in compiler diagnostics messages"),
mu_c_bool, &location_column_option },
{ "trace", 0, NULL, MU_OPTION_DEFAULT,
N_("trace executed actions"),
mu_c_bool, &trace_option },
{ "trace-program", 0, N_("MODULES"), MU_OPTION_ARG_OPTIONAL,
N_("enable filter program tracing"),
mu_c_string, NULL, opt_trace_program },
{ "dump-code", 0, NULL, MU_OPTION_DEFAULT,
N_("dump disassembled code"),
mu_c_string, NULL, opt_dump_code },
{ "dump-grammar-trace", 0, NULL, MU_OPTION_DEFAULT,
N_("dump grammar traces"),
mu_c_string, NULL, opt_dump_grammar_trace },
{ "dump-lex-trace", 0, NULL, MU_OPTION_DEFAULT,
N_("dump lexical analyzer traces"),
mu_c_string, NULL, opt_dump_lex_trace },
{ "dump-tree", 0, NULL, MU_OPTION_DEFAULT,
N_("dump parser tree"),
mu_c_string, NULL, opt_dump_tree },
{ "dump-macros", 0, NULL, MU_OPTION_DEFAULT,
N_("show used Sendmail macros"),
mu_c_string, NULL, opt_dump_macros },
{ "xref", 0, NULL, MU_OPTION_DEFAULT,
N_("produce a cross-reference listing"),
mu_c_string, NULL, opt_dump_xref },
{ "dump-xref", 0, NULL, MU_OPTION_ALIAS },
{ "gacopyz-log", 0, N_("LEVEL"), MU_OPTION_DEFAULT,
N_("set Gacopyz log level"),
mu_c_string, NULL, opt_gacopyz_log },
{ "stack-trace", 0, NULL, MU_OPTION_DEFAULT,
N_("enable stack traces on runtime errors"),
mu_c_bool, &stack_trace_option },
MU_OPTION_END
}, *options[] = { mailfromd_options, NULL };
static int
validate_options()
{
/* FIXME */
return 0;
}
static int
flush_trace_module(void *item, void *data)
{
enable_prog_trace((const char *) item);
return 0;
}
static char *capa[] = {
"auth",
"debug",
"logging",
"locking",
"mailer",
".mfd:server",
NULL
};
/* Mailutils configuration */
static int
cb_milter_timeout(void *data, mu_config_value_t *arg)
{
struct timeval tv;
int rc = config_cb_timeout (&tv, arg);
if (rc)
return 1;
if (smfi_settimeout(tv.tv_sec) == MI_FAILURE) {
mu_error(_("Invalid milter timeout: %lu"),
(unsigned long) tv.tv_sec);
exit(EX_USAGE);
}
return 0;
}
static int
cb_set_variable(void *data, mu_config_value_t *arg)
{
const char *value;
char *alloc_str = NULL;
struct mu_locus_range locus = MU_LOCUS_RANGE_INITIALIZER;
if (mu_cfg_assert_value_type(arg, MU_CFG_ARRAY))
return 1;
if (arg->v.arg.c < 2) {
mu_error(_("not enough arguments"));
return 1;
} else if (arg->v.arg.c > 2) {
mu_error(_("too many arguments"));
return 1;
}
if (mu_cfg_assert_value_type(&arg->v.arg.v[0], MU_CFG_STRING))
return 1;
switch (arg->v.arg.v[1].type) {
case MU_CFG_STRING:
value = arg->v.arg.v[1].v.string;
break;
case MU_CFG_ARRAY:
alloc_str = config_array_to_string(&arg->v.arg.v[1]);
value = alloc_str;
break;
case MU_CFG_LIST:
mu_error(_("unexpected list"));
return 1;
default:
mu_error (_("INTERNAL ERROR at %s:%d: please report"),
__FILE__, __LINE__);
abort();
}
mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM,
MU_IOCTL_LOGSTREAM_GET_LOCUS_RANGE, &locus);
defer_initialize_variable(arg->v.arg.v[0].v.string, value, &locus);
mu_locus_range_deinit(&locus);
free(alloc_str);
return 0;
}
static int
cb_include_path(void *data, mu_config_value_t *val)
{
int i, rc = 0;
struct mu_wordsplit ws;
mu_iterator_t itr;
switch (val->type) {
case MU_CFG_STRING:
ws.ws_delim = ":";
if (mu_wordsplit(val->v.string, &ws,
MU_WRDSF_NOVAR |
MU_WRDSF_NOCMD | MU_WRDSF_DELIM)) {
mu_error("mu_wordsplit: %s",
mu_wordsplit_strerror(&ws));
return 1;
}
for (i = 0; i < ws.ws_wordc; i++)
add_include_dir(ws.ws_wordv[i]);
mu_wordsplit_free(&ws);
break;
case MU_CFG_ARRAY:
for (i = 0; i < val->v.arg.c; i++) {
if (mu_cfg_assert_value_type(&val->v.arg.v[i],
MU_CFG_STRING))
return 1;
add_include_dir(val->v.arg.v[i].v.string);
}
break;
case MU_CFG_LIST:
mu_list_get_iterator(val->v.list, &itr);
for (mu_iterator_first(itr);
!mu_iterator_is_done(itr); mu_iterator_next(itr)) {
mu_config_value_t *pval;
mu_iterator_current(itr, (void*) &pval);
rc = mu_cfg_assert_value_type(pval, MU_CFG_STRING);
if (rc)
break;
add_include_dir(pval->v.string);
}
mu_iterator_destroy(&itr);
}
return rc;
}
static int
cb_trace_program(void *data, mu_config_value_t *val)
{
int i, rc = 0;
switch (val->type) {
case MU_CFG_STRING:
enable_prog_trace(val->v.string);
break;
case MU_CFG_ARRAY:
for (i = 0; i < val->v.arg.c; i++) {
if (mu_cfg_assert_value_type(&val->v.arg.v[i],
MU_CFG_STRING))
return 1;
enable_prog_trace(val->v.arg.v[i].v.string);
}
break;
case MU_CFG_LIST:
mu_list_foreach(val->v.list, flush_trace_module, NULL);
break;
}
return rc;
}
static int
cb_relayed_domain_file(void *data, mu_config_value_t *val)
{
switch (val->type) {
case MU_CFG_STRING:
read_domain_file(val->v.string);
break;
case MU_CFG_LIST:
mu_list_foreach(val->v.list, load_relay_file, NULL);
break;
default:
mu_error (_("expected string or list of strings"));
}
return 0;
}
struct mu_cfg_param mf_cfg_param[] = {
{ ".mfd:server", mu_cfg_section, NULL, 0, NULL, NULL },
{ "stack-trace", mu_c_bool, &stack_trace_option, 0, NULL,
N_("Dump stack traces on runtime errors.") },
{ "milter-timeout", mu_cfg_callback, NULL, 0, cb_milter_timeout,
N_("Set milter timeout."),
N_("time: interval") },
{ "callout-url", mu_c_string, &callout_server_url, 0, NULL,
N_("Sets the URL of the default callout server."),
N_("url") },
{ "include-path", mu_cfg_callback, NULL, 0, cb_include_path,
N_("Add directories to the list of directories to be searched for "
"header files. Argument is a list of directory names "
"separated by colons."),
N_("path: string") },
{ "setvar", mu_cfg_callback, NULL, 0, cb_set_variable,
N_("Initialize a mailfromd variable to ."),
N_("var: string> ."),
N_("file") },
{ "trace-actions", mu_c_bool, &do_trace, 0, NULL,
N_("Trace executed actions.") },
{ "trace-program", mu_cfg_callback, NULL, 0, cb_trace_program,
N_("Enable filter program tracing."),
N_("modules: list") },
{ "relayed-domain-file", mu_cfg_callback, NULL, 0,
cb_relayed_domain_file,
N_("Read relayed domain names from the file"),
N_("file: string") },
{ "lock-retry-count", mu_cfg_callback, NULL, 0,
config_cb_lock_retry_count,
N_("Retry acquiring DBM file lock this number of times.") },
{ "lock-retry-timeout", mu_cfg_callback, NULL, 0,
config_cb_lock_retry_timeout,
N_("Set the time span between the two DBM locking attempts."),
N_("time: interval") },
{ "runtime", mu_cfg_section, NULL },
{ NULL }
};
static struct mu_cfg_param *runtime_param;
static size_t runtime_param_cnt;
static size_t runtime_param_max;
static void
_add_runtime_param_entry(struct mu_cfg_param *p)
{
if (runtime_param_cnt == runtime_param_max) {
if (runtime_param_max == 0)
runtime_param_max = 16;
runtime_param = mu_2nrealloc(runtime_param,
&runtime_param_max,
sizeof(runtime_param[0]));
}
runtime_param[runtime_param_cnt++] = *p;
}
void
mf_add_runtime_params(struct mu_cfg_param *p)
{
for (; p->ident; p++)
_add_runtime_param_entry(p);
}
void
mf_runtime_param_finish()
{
if (runtime_param_cnt) {
static struct mu_cfg_param term = { NULL };
struct mu_cfg_section *section;
_add_runtime_param_entry(&term);
if (mu_create_canned_section ("runtime", §ion) == 0) {
section->parser = NULL;
section->docstring = N_("Configure MFL runtime values.");
section->label = NULL;
mu_cfg_section_add_params(section, runtime_param);
}
} else {
struct mu_cfg_param *p;
for (p = mf_cfg_param; p->ident; p++) {
if (strcmp (p->ident, "runtime") == 0) {
memset (p, 0, sizeof (*p));
break;
}
}
}
}
/* Auxiliary functions */
unsigned keyword_column = 0;
unsigned header_column = 2;
unsigned value_column = 32;
unsigned right_margin = 79;
static void
set_column(mu_stream_t str, unsigned margin)
{
mu_stream_ioctl(str, MU_IOCTL_WORDWRAPSTREAM,
MU_IOCTL_WORDWRAP_SET_MARGIN,
&margin);
}
static int
db_format_enumerator(struct db_format *fmt, void *data)
{
mu_stream_t str = data;
set_column(str, keyword_column);
mu_stream_printf(str, "%s database:", fmt->name);
set_column(str, value_column);
mu_stream_printf(str, "%s\n", fmt->dbname);
set_column(str, keyword_column);
if (strcmp(fmt->name, "cache") == 0) {
mu_stream_printf(str, "%s positive expiration:", fmt->name);
set_column(str, value_column);
mu_stream_printf(str, "%lu\n", fmt->expire_interval);
set_column(str, keyword_column);
mu_stream_printf(str, "%s negative expiration:", fmt->name);
set_column(str, value_column);
mu_stream_printf(str, "%lu\n", negative_expire_interval);
} else {
mu_stream_printf(str, "%s expiration:", fmt->name);
set_column(str, value_column);
mu_stream_printf(str, "%lu\n", fmt->expire_interval);
}
return 0;
}
static void
list_db_formats(mu_stream_t str, const char *pfx)
{
mu_iterator_t itr;
int rc;
const char *defdb = DEFAULT_DB_TYPE;
set_column(str, keyword_column);
mu_stream_printf(str, "%s:", pfx);
set_column(str, value_column);
rc = mu_dbm_impl_iterator(&itr);
if (rc) {
mu_stream_printf(str, "%s\n", _("unknown"));
mu_error("%s", mu_strerror(rc));
} else {
int i;
for (mu_iterator_first(itr), i = 0; !mu_iterator_is_done(itr);
mu_iterator_next(itr), i++) {
struct mu_dbm_impl *impl;
mu_iterator_current(itr, (void**)&impl);
if (i)
mu_stream_printf(str, ", ");
else if (!defdb)
defdb = impl->_dbm_name;
mu_stream_printf(str, "%s", impl->_dbm_name);
}
mu_stream_write(str, "\n", 1, NULL);
mu_iterator_destroy(&itr);
}
set_column(str, keyword_column);
mu_stream_printf(str, "%s:", "default database type");
set_column(str, value_column);
mu_stream_printf(str, "%s\n", defdb);
}
struct string_value {
char const *kw;
int type;
union {
char *s_const;
char **s_var;
char *(*s_func) (void);
} data;
};
static char *
string_preprocessor (void)
{
return ext_pp ? ext_pp : "none";
}
#ifdef USE_SYSLOG_ASYNC
# if DEFAULT_SYSLOG_ASYNC == 1
# define DEFAULT_SYSLOG "non-blocking"
# else
# define DEFAULT_SYSLOG "blocking"
# endif
#else
# define DEFAULT_SYSLOG "blocking"
#endif
enum {
STRING_CONSTANT,
STRING_VARIABLE,
STRING_FUNCTION
};
static struct string_value string_values[] = {
{ "version", STRING_CONSTANT, { .s_const = VERSION } },
{ "script file", STRING_VARIABLE, { .s_var = &script_file } },
{ "preprocessor", STRING_FUNCTION, { .s_func = string_preprocessor } },
{ "user", STRING_VARIABLE, { .s_var = &mf_server_user } },
{ "statedir", STRING_VARIABLE, { .s_var = &mailfromd_state_dir } },
{ "socket", STRING_CONSTANT, { .s_const = DEFAULT_SOCKET } },
{ "pidfile", STRING_VARIABLE, { .s_var = &pidfile } },
{ "default syslog", STRING_CONSTANT, { .s_const = DEFAULT_SYSLOG } },
{ NULL }
};
static void
print_string_values(mu_stream_t str)
{
struct string_value *p;
char const *val;
for (p = string_values; p->kw; p++) {
set_column(str, keyword_column);
mu_stream_printf(str, "%s:", p->kw);
switch (p->type) {
case STRING_CONSTANT:
val = p->data.s_const;
break;
case STRING_VARIABLE:
val = *p->data.s_var;
break;
case STRING_FUNCTION:
val = p->data.s_func ();
}
set_column(str, value_column);
mu_stream_printf(str, "%s\n", val);
}
}
void
mailfromd_show_defaults(void)
{
int rc;
mu_stream_t str;
rc = mu_wordwrap_stream_create (&str, mu_strout, 0, right_margin);
if (rc) {
str = mu_strout;
mu_stream_ref(str);
}
print_string_values(str);
list_db_formats(str, "supported databases");
set_column(str, keyword_column);
mu_stream_printf(str, "%s:", "optional features");
set_column(str, value_column);
#if defined WITH_DKIM
mu_stream_printf(str, " %s", "DKIM");
#endif
#if defined WITH_GEOIP
mu_stream_printf(str, " %s", "GeoIP");
#endif
#if defined WITH_DSPAM
mu_stream_printf(str, " %s", "DSPAM");
#endif
mu_stream_write(str, "\n", 1, NULL);
db_format_enumerate(db_format_enumerator, str);
mu_stream_destroy (&str);
}
static int
args_in_order(int argc, char **argv)
{
int i;
for (i = 0; i < argc; i++) {
size_t len = strcspn(argv[i], "=");
if (len > 3
&& (memcmp(argv[i], "--ru", 4) == 0
|| memcmp(argv[i], "--run", 5) == 0)) {
return MF_GETOPT_IN_ORDER;
}
}
return MF_GETOPT_DEFAULT;
}
static void
provide_default_milter_server(void)
{
if (mfd_srvman_count_servers() == 0) {
mu_diag_output(MU_DIAG_WARNING,
_("no servers defined; will listen on %s"),
DEFAULT_SOCKET);
mf_srvcfg_add("milter", DEFAULT_SOCKET);
}
}
static void
provide_default_callout_server(void)
{
struct variable *var;
if (provide_callout &&
!callout_server_url &&
(!(var = variable_lookup("callout_server_url")) ||
(var->sym.flags & SYM_REFERENCED) &&
!(var->sym.flags & SYM_INITIALIZED))) {
mf_srvcfg_add("callout", DEFAULT_CALLOUT_SOCKET);
}
}
static char *modnames[] = {
#define __DBGMOD_C_ARRAY
# include "mfd-dbgmod.h"
#undef __DBGMOD_C_ARRAY
NULL
};
mu_debug_handle_t mfd_debug_handle;
static void
debug_init(void)
{
int i;
mfd_debug_handle = mu_debug_register_category (modnames[0]);
for (i = 1; modnames[i]; i++)
mu_debug_register_category (modnames[i]);
}
int
mf_server_function(const char *key, struct mf_srvcfg *cfg)
{
if (!key || strcmp(key, "default") == 0 || strcmp(key, "milter") == 0)
cfg->server = milter_session_server;
else if (strcmp(key, "callout") == 0) {
cfg->server = mfd_callout_session_server;
if (cfg->defopt ||
mu_list_locate(cfg->options, "default", NULL) == 0)
callout_server_url =
mu_strdup(mu_url_to_string(cfg->url));
} else
return 1;
return 0;
}
static void
open_strecho (int daemon_mode)
{
int rc;
if (daemon_mode) {
rc = mu_stream_ioctl(mu_strerr, MU_IOCTL_LOGSTREAM,
MU_IOCTL_LOGSTREAM_CLONE,
&mf_strecho);
if (rc == 0) {
int s = MU_LOG_INFO;
mu_stream_ioctl(mf_strecho, MU_IOCTL_LOGSTREAM,
MU_IOCTL_LOGSTREAM_SET_SEVERITY,
&s);
}
} else {
#if 0
mf_strecho = mu_strout;
mu_stream_ref(mf_strecho);
rc = 0;
#else
rc = mu_stdio_stream_create(&mf_strecho, MU_STDERR_FD, 0);
#endif
}
if (rc) {
mu_diag_output(MU_LOG_CRIT,
_("cannot create echo output stream: %s"),
mu_strerror(rc));
exit(EX_UNAVAILABLE);
}
}
void
mailfromd_alloc_die()
{
parse_error("not enough memory");
abort();
}
extern char **environ;
struct mu_cli_setup cli = {
.optv = options,
.cfg = mf_cfg_param,
.prog_doc = prog_doc,
.prog_args = args_doc
};
int
main(int argc, char **argv)
{
prog_counter_t entry_point;
int stderr_is_closed = stderr_closed_p();
mf_init_nls();
mf_proctitle_init(argc, argv, environ);
mu_alloc_die_hook = mailfromd_alloc_die;
MU_AUTH_REGISTER_ALL_MODULES();
mu_register_all_formats();
mu_register_all_mailer_formats();
yy_flex_debug = 0;
/* Initialize milter */
milter_setlogmask(SMI_LOG_FROM(SMI_LOG_WARN));
milter_settimeout(7200);
/* Set default logging */
mu_set_program_name(argv[0]);
mu_log_tag = mu_strdup(mu_program_name);
mu_log_facility = DEFAULT_LOG_FACILITY;
mu_stdstream_setup(MU_STDSTREAM_RESET_NONE);
mf_srvcfg_log_setup(stderr_is_closed ? "syslog" : "stderr");
debug_init();
libcallout_init();
init_string_space();
init_symbols();
builtin_setup();
mf_runtime_param_finish();
db_format_setup();
include_path_setup();
pragma_setup();
mf_server_save_cmdline(argc, argv);
dnsbase_init();
mu_acl_cfg_init();
database_cfg_init();
srvman_init();
mf_srvcfg_init(argv[0], "(milter | callout)");
mf_getopt(&cli, &argc, &argv, capa, args_in_order(argc, argv));
if (validate_options())
exit(EX_USAGE);
init_relayed_domains();
if (resolv_conf_file)
dnsbase_file_init(resolv_conf_file);
if (trace_option != ARG_UNSET)
do_trace = trace_option;
if (trace_modules) {
mu_list_foreach(trace_modules, flush_trace_module, NULL);
mu_list_destroy(&trace_modules);
}
mf_srvcfg_flush();
alloc_ext_pp();
if (need_script) {
char *new_script = NULL;
if (argc) {
int i, n = -1;
for (i = 0; i < argc; i++) {
if (strchr(argv[i], '=') == 0) {
if (n == -1) {
n = i;
if (mode == MAILFROMD_RUN)
break;
} else {
mu_error(_("script file "
"specified twice "
"(%s and %s)"),
argv[n], argv[i]);
exit(EX_USAGE);
}
}
}
if (n >= 0) {
new_script = argv[n];
memmove(argv + n, argv + n + 1,
(argc - n + 1) * sizeof argv[0]);
argc--;
}
}
if (new_script)
script_file = new_script;
if (script_file[0] != '/')
/* Clear saved command line */
mf_server_save_cmdline(0, NULL);
if (preprocess_option)
exit(preprocess_input());
if (parse_program(script_file, script_ydebug))
exit(EX_CONFIG);
}
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 || script_dump_xref
|| yy_flex_debug || script_ydebug)
exit(EX_OK);
switch (mode) {
case MAILFROMD_DAEMON:
mf_server_log_setup();
provide_default_milter_server();
provide_default_callout_server();
break;
case MAILFROMD_RUN: {
struct function *fun = function_lookup(main_function_name);
if (!fun) {
mu_error(_("function %s is not defined"),
main_function_name);
exit(EX_CONFIG);
}
if (fun->parmcount || !fun->varargs) {
mu_error(_("function %s must take variable number of "
"arguments"),
main_function_name);
exit(EX_CONFIG);
}
if (fun->rettype != dtype_number) {
mu_error(_("function %s must return number"),
main_function_name);
exit(EX_CONFIG);
}
entry_point = fun->entry;
}
}
free_exceptions();
free_symbols();
free_string_space();
free_parser_data();
mf_namefixup_run(mailfromd_state_dir);
mf_namefixup_free();
switch (mode) {
case MAILFROMD_DAEMON:
if (argc > 0) {
mu_error(_("too many arguments"));
exit(EX_USAGE);
}
if (script_file[0] != '/') {
mu_diag_output(MU_DIAG_WARNING,
_("script file is given "
"without full file name"));
server_flags |= MF_SERVER_NORESTART;
}
open_strecho(1);
mf_server_lint_option = "--lint";
mf_server_start("mailfromd", mailfromd_state_dir, pidfile,
server_flags);
break;
case MAILFROMD_TEST:
open_strecho(0);
mailfromd_test(argc, argv);
break;
case MAILFROMD_SHOW_DEFAULTS:
mailfromd_show_defaults();
break;
case MAILFROMD_RUN:
open_strecho(0);
mailfromd_run(entry_point, argc, argv);
}
exit(EX_OK);
}