diff options
-rw-r--r-- | ChangeLog | 7 | ||||
-rw-r--r-- | doc/mailfromd.texi | 96 | ||||
-rw-r--r-- | src/main.c | 392 | ||||
-rw-r--r-- | tests/atlocal.in | 2 |
4 files changed, 435 insertions, 62 deletions
@@ -2,2 +2,9 @@ + * src/main.c: Implement MU configuration statements. + * tests/atlocal.in (MFOPTS): Ignore site-wide and per-user + configuration files. + * doc/mailfromd.texi: Document sieve interface. + +2007-11-22 Sergey Poznyakoff <gray@gnu.org.ua> + * doc/mailfromd.texi: Update diff --git a/doc/mailfromd.texi b/doc/mailfromd.texi index 731c4ae1..95c6741f 100644 --- a/doc/mailfromd.texi +++ b/doc/mailfromd.texi @@ -223,2 +223,3 @@ Built-in and Library Functions * System functions:: +* Sieve Interface:: * Interfaces to Third-Party Programs:: @@ -987,3 +988,2 @@ the corresponding section below. @cindex Upgrading from 4.1 to 4.2 -@UNREVISED{} Upgrading to this version does not require any special efforts. You @@ -5260,2 +5260,3 @@ in version @value{VERSION}. * System functions:: +* Sieve Interface:: * Interfaces to Third-Party Programs:: @@ -6480,2 +6481,95 @@ the return status of the command otherwise. +@node Sieve Interface +@subsubsection Sieve Interface +@cindex Sieve +@UNREVISED{} + @samp{Sieve} is a powerful mail filtering language, defined in +@acronym{RFC} 3028. @command{Mailfromd} supports an extended form +of this language. For description of the language and available +extensions, @xref{Sieve Language, Sieve Language, Sieve Language, +mailutils, GNU Mailutils Manual}. + +@deftypefn {Built-in Function} boolean sieve (string @var{script} @ + [, number @var{flags}]) +Compile the Sieve source file @var{script} and execute it over the +collected message. This function can be used only in @code{eom} +handler. + +@findex sieve.mfh +Optional @var{flags} define additional debugging and verbosity +settings. It is a bit-mask field, consisting of a bitwise @code{or} +of one or more of the following flags, defined in @file{sieve.mfh}: + +@table @code +@item MF_SIEVE_LOG +Log every executed @samp{Sieve} action. + +@item MF_SIEVE_DEBUG_TRACE +Trace execution of @samp{Sieve} tests. + +@item MF_SIEVE_DEBUG_INSTR +Log every instruction, executed in the compiled @samp{Sieve} code. +This produces huge amounts of output and is rarely useful, unless you +suspect some bug in @samp{Sieve} implementation and wish to trace it. + +@item MF_SIEVE_DEBUG_MAILUTILS +Log debugging information about the underlying Mailutils calls. + +@item MF_SIEVE_DEBUG_PROT +Trace networking protocols. +@end table + +For example, @code{MF_SIEVE_LOG|MF_SIEVE_DEBUG_TRACE} enables logging +@samp{Sieve} actions and tests. + +The @code{sieve} function returns @code{true} if the message was +accepted by the @var{script} program, and @code{false} otherwise. +Here, the word @dfn{accepted} means that some form of @samp{KEEP} +action (@pxref{Actions, keep, Actions, mailutils, GNU Mailutils +Manual}) was executed over the message. +@end deftypefn + +The following example discards each message not accepted by the +@samp{Sieve} program @file{/etc/mail/filter.siv}: + +@smallexample +#include_once <sieve.mfh> +group eom +do + if not sieve("/etc/mail/filter.siv", MF_SIEVE_LOG) + discard + fi +done +@end smallexample + +The example below illustrates how one can adjust logging flags +depending on the current debugging level: + +@smallexample +#include_once <sieve.mfh> +prog eom +do + number flags 0 + number level debug_level("bi_sieve") + if %level >= 1 + set flags %flags | MF_SIEVE_LOG + fi + if %level >= 2 + set flags %flags | MF_SIEVE_DEBUG_TRACE + fi + if %level > 9 + set flags %flags | MF_SIEVE_DEBUG_INSTR + fi + if %level > 19 + set flags %flags | MF_SIEVE_DEBUG_MAILUTILS + fi + if %level > 20 + set flags %flags | MF_SIEVE_DEBUG_PROT + fi + + if not sieve("/etc/mail/filter.siv", %flags) + discard + fi +done +@end smallexample @@ -39,2 +39,6 @@ # include <mailutils/argp.h> +typedef struct { + char *file; + int line; +} mu_cfg_locus_t; #else @@ -239,2 +243,17 @@ log_status(sfsistat status, SMFICTX *ctx) +static void +mf_error_on_locus(mu_cfg_locus_t *locus, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + if (locus) { + char *newfmt = NULL; + asprintf(&newfmt, "%s:%d: %s", locus->file, locus->line, fmt); + mu_verror(newfmt, ap); + } else + mu_verror(fmt, ap); + va_end(ap); +} + @@ -252,4 +271,4 @@ compare_string(const void *item, const void *value) DOMAIN_LIST */ -void -read_domain_file(char *name) +int +read_domain_file(mu_cfg_locus_t *locus, char *name) { @@ -261,5 +280,5 @@ read_domain_file(char *name) if (!fp) { - mu_error(_("Cannot open file `%s': %s"), + mf_error_on_locus(locus, _("Cannot open file `%s': %s"), name, mu_strerror(errno)); - return; + return 1; } @@ -289,2 +308,3 @@ read_domain_file(char *name) fclose(fp); + return 0; } @@ -329,2 +349,120 @@ host_in_relayed_domain_p(char *client) +/* ************************************************************************ + 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_cfg_locus_t *locus, 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(locus, + _("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(locus, _("Unknown group: %s"), arg); + return 1; + } + return 0; +} + +static int +mf_option_mailfrom(mu_cfg_locus_t *locus, char *arg) +{ + int rc; + mu_address_t addr; + + rc = mu_address_create(&addr, arg); + if (rc) { + mf_error_on_locus(locus, _("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_cfg_locus_t *locus, char *arg) +{ + struct stat st; + if (stat(arg, &st)) { + mf_error_on_locus(locus, _("Cannot stat file `%s': %s"), + arg, + mu_strerror(errno)); + return 1; + } + if (!S_ISDIR(st.st_mode)) { + mf_error_on_locus(locus, _("`%s' is not a directory"), arg); + return 1; + } + if (arg[0] != '/') { + mf_error_on_locus(locus, + _("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_cfg_locus_t *locus, 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(locus, _("Cannot resolve `%s'"), + arg); + return 1; + } + address = *(((unsigned long **) phe->h_addr_list)[0]); + } + *pval = address; + return 0; +} + + /* Option handling. @@ -506,3 +644,3 @@ load_relay_file(void *item, void *data) { - read_domain_file(item); + read_domain_file(NULL, item); return 0; @@ -646,17 +784,9 @@ 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]); - } + unsigned long address; - if (*pval == 0) { - *pval = malloc (sizeof address); - if (!*pval) + if (mf_option_source_ip(NULL, newval, &address)) return 1; - } + + if (*pval == 0) + *pval = xmalloc (sizeof address); *(unsigned long*)*pval = address; @@ -666,27 +796,5 @@ option_source(char *opt, void **pval, char *newval) 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; + return mf_option_group(NULL, newval); } @@ -702,20 +810,3 @@ 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; + return mf_option_state_directory(NULL, newval); } @@ -1366,2 +1457,182 @@ static struct argp argp = { +/* Mailutils-2.0 configuration */ +#if MAILUTILS_VERSION_NUMBER >= 1290 +int +cb_timeout(time_t *pinterval, mu_cfg_locus_t *locus, void *data, char *arg) +{ + const char *endp; + if (parse_time_interval(arg, pinterval, &endp)) { + mf_error_on_locus(locus, + _("unrecognized time format (near `%s')"), + endp); + return 1; + } + return 0; +} + +int +cb_milter_timeout(mu_cfg_locus_t *locus, void *data, char *arg) +{ + time_t interval; + + if (cb_timeout(&interval, locus, data, arg)) + return 1; + if (smfi_settimeout(interval) == MI_FAILURE) { + mf_error_on_locus(locus, + _("%s:%d: Invalid milter timeout: %lu"), + (unsigned long) interval); + exit(EX_USAGE); + } + + return 0; +} + +int +cb_io_timeout(mu_cfg_locus_t *locus, void *data, char *arg) +{ + if (cb_timeout(&io_timeout, locus, data, arg)) + return 1; + return 0; +} + +int +cb_connect_timeout(mu_cfg_locus_t *locus, void *data, char *arg) +{ + if (cb_timeout(&connect_timeout, locus, data, arg)) + return 1; + return 0; +} + +int +cb_initial_response_timeout(mu_cfg_locus_t *locus, void *data, char *arg) +{ + if (cb_timeout(&response_timeout, locus, data, arg)) + return 1; + return 0; +} + +int +cb_lock_retry_timeout(mu_cfg_locus_t *locus, void *data, char *arg) +{ + if (cb_timeout(&lock_retry_timeout_option, locus, data, arg)) + return 1; + return 0; +} + +int +cb_set_variable(mu_cfg_locus_t *locus, void *data, char *arg) +{ + char *p, *value; + char *tmp = NULL; + + for (p = arg; !(*p == ' ' || *p == '\t'); p++) + if (!*p) { + mf_error_on_locus(locus, _("missing value")); + return 1; + } + + for (; *p == ' ' || *p == '\t'; p++) + if (!*p) { + mf_error_on_locus(locus, _("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_cfg_locus_t *locus, void *data, char *arg) +{ + defer_initialize_variable("ehlo_domain", arg); + return 0; +} + +int +cb_mail_from_address(mu_cfg_locus_t *locus, void *data, char *arg) +{ + return mf_option_mailfrom(locus, arg); +} + +int +cb_debug(mu_cfg_locus_t *locus, void *data, char *arg) +{ + debug_parse_spec(arg); + return 0; +} + +/* See also option_group. */ +int +cb_group(mu_cfg_locus_t *locus, void *data, char *arg) +{ + return mf_option_group(locus, arg); +} + +int +cb_state_directory(mu_cfg_locus_t *locus, void *data, char *arg) +{ + return mf_option_state_directory(locus, arg); +} + +int +cb_relay_file(mu_cfg_locus_t *locus, void *data, char *arg) +{ + return read_domain_file(locus, arg); +} + +int +cb_source_ip(mu_cfg_locus_t *locus, void *data, char *arg) +{ + return mf_option_source_ip(locus, arg, &source_address); +} + +int +cb_include_path(mu_cfg_locus_t *locus, 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[] = { + { "connect-timeout", mu_cfg_callback, NULL, cb_connect_timeout }, + { "debug", mu_cfg_callback, NULL, cb_debug }, + { "ehlo-domain", mu_cfg_callback, NULL, cb_ehlo_domain }, + { "group", mu_cfg_callback, NULL, cb_group }, + { "include-path", mu_cfg_callback, NULL, cb_include_path }, + { "initial-response-timeout", mu_cfg_callback, NULL, + cb_initial_response_timeout }, + { "io-timeout", mu_cfg_callback, NULL, cb_io_timeout }, + { "lock-retry-count", mu_cfg_size, &lock_retry_count_option }, + { "lock-retry-timeout", mu_cfg_callback, NULL, + cb_lock_retry_timeout }, + { "mail-from-address", mu_cfg_callback, NULL, cb_mail_from_address }, + { "milter-timeout", mu_cfg_callback, NULL, cb_milter_timeout }, + { "pidfile", mu_cfg_string, &pidfile }, + { "relay-file", mu_cfg_callback, NULL, cb_relay_file }, + { "setvar", mu_cfg_callback, NULL, cb_set_variable }, + { "script-file", mu_cfg_string, &script_file }, + { "source-info", mu_cfg_bool, &source_info_option }, + /* FIXME: Could have used mu_cfg_ipv4 here, but... */ + { "source-ip", mu_cfg_callback, NULL, cb_source_ip }, + { "stack-trace", mu_cfg_bool, &stack_trace_option }, + { "state-directory", mu_cfg_callback, NULL, cb_state_directory }, + { "user", mu_cfg_string, &user }, + { NULL } +}; +#endif + + /* Auxiliary functions */ @@ -1795,3 +2066,4 @@ main(int argc, char **argv) #else - rc = mu_app_init(&argp, capa, NULL, argc, argv, 0, &index, NULL); + rc = mu_app_init(&argp, capa, mf_cfg_param, argc, argv, 0, &index, + NULL); #endif diff --git a/tests/atlocal.in b/tests/atlocal.in index b4679606..6e19f90a 100644 --- a/tests/atlocal.in +++ b/tests/atlocal.in @@ -24,3 +24,3 @@ trap "cleanup; test -r $XFAILFILE && cat $XFAILFILE; exit $?" 1 2 13 15 -MFOPTS="-I@abs_builddir@/etc -I@abs_top_srcdir@/tests/etc -I@abs_top_srcdir@/src -I@abs_top_srcdir@/mflib --no-preprocess" +MFOPTS="-I@abs_builddir@/etc -I@abs_top_srcdir@/tests/etc -I@abs_top_srcdir@/src -I@abs_top_srcdir@/mflib --no-preprocess --no-site-rcfile --no-user-rcfile" ETCDIR=@abs_top_srcdir@/tests/etc |