diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2005-06-04 17:24:49 +0000 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2005-06-04 17:24:49 +0000 |
commit | d75711ebcc2cbf726a7430241319caf9bc3ca3a6 (patch) | |
tree | 05f17a08cadeacb20b5350ee92fae772c0625279 | |
parent | 447f6ddce61e287c6b95a403ddbbe827f8c17ce8 (diff) | |
download | mailfromd-d75711ebcc2cbf726a7430241319caf9bc3ca3a6.tar.gz mailfromd-d75711ebcc2cbf726a7430241319caf9bc3ca3a6.tar.bz2 |
Moved from ../. Added result caching and new operating modes.
git-svn-id: file:///svnroot/mailfromd/trunk@20 7a8a7f39-df28-0410-adc6-e0d955640f24
-rw-r--r-- | src/main.c | 1504 |
1 files changed, 1504 insertions, 0 deletions
diff --git a/src/main.c b/src/main.c new file mode 100644 index 00000000..800256a8 --- /dev/null +++ b/src/main.c @@ -0,0 +1,1504 @@ +/* This file is part of mailfrom filter. + Copyright (C) 2005, 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 2, 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301 USA */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#define obstack_chunk_alloc malloc +#define obstack_chunk_free free +#include <obstack.h> +#include <syslog.h> +#include <pwd.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <pthread.h> + +#ifdef HAVE_SYSEXITS_H +# include <sysexits.h> +#else +# define EX_OK 0 /* successful termination */ +# define EX__BASE 64 /* base value for error messages */ +# define EX_USAGE 64 /* command line usage error */ +# define EX_DATAERR 65 /* data format error */ +# define EX_NOINPUT 66 /* cannot open input */ +# define EX_NOUSER 67 /* addressee unknown */ +# define EX_NOHOST 68 /* host name unknown */ +# define EX_UNAVAILABLE 69 /* service unavailable */ +# define EX_SOFTWARE 70 /* internal software error */ +# define EX_OSERR 71 /* system error (e.g., can't fork) */ +# define EX_OSFILE 72 /* critical OS file missing */ +# define EX_CANTCREAT 73 /* can't create (user) output file */ +# define EX_IOERR 74 /* input/output error */ +# define EX_TEMPFAIL 75 /* temp failure; user is invited to retry */ +# define EX_PROTOCOL 76 /* remote error in protocol */ +# define EX_NOPERM 77 /* permission denied */ +# define EX_CONFIG 78 /* configuration error */ +# define EX__MAX 78 /* maximum listed value */ +#endif + +#include <mailutils/mailutils.h> +#include <mailutils/argp.h> +#include <libmilter/mfapi.h> + +#include "mailfrom.h" + + +/* Verification methods */ +typedef sfsistat (*method_fp)(SMFICTX *ctx, char *email, char *client_addr); + +static sfsistat method_standard(SMFICTX *ctx, char *email, char *client_addr); +static sfsistat method_strict(SMFICTX *ctx, char *email, char *client_addr); + +struct method_tab { + char *name; + method_fp handler; +}; + +struct method_tab method_tab[] = { + { "standard", method_standard }, + { "strict", method_strict }, + NULL +}; + +static method_fp method = method_standard; + +static method_fp +find_method(char *name) +{ + struct method_tab *p; + + for (p = method_tab; p->name; p++) + if (strcmp(p->name, name) == 0) + return p->handler; + return NULL; +} + + +/* An actions is described by "action function", that actually performs + what should be done, and "action data", that supply necessary data to + the function. + + Action data may be of the following two types: */ + +/* 1. Return action. Describes what value to return to Milter and, optionally, + how to set the SMTP reply state. */ +struct return_action { + sfsistat status; /* Return status */ + char *code; /* State code. Three digits, e.g. "550" */ + char *xcode; /* Extended state code, e.g. "5.5.3" */ + char *message; /* Textual message */ +}; + +/* 2. Header action. Instructs mainfromd to add this header to the message */ +struct header_action { + char *name; /* Header name */ + char *value; /* Header value */ +}; + +/* Combine both structures */ +union action_data { + struct return_action ret; + struct header_action hdr; +}; + +/* Action function has the following type */ +typedef sfsistat (*action_fp)(SMFICTX *ctx, union action_data *data); + +/* Handy macro used to create bitmaps of allowed return values */ +#define MAP(c) (1<<(c)) + +/* Actions and corresponding closures. Apart from function/data pare, we + define a textual default action, and a map of allowed Milter return + codes. + All variables are in BSS. They are initialized to their values (either + explicit or default) by parse_opt() function below. */ +action_fp action_success; +union action_data action_success_data; +#define action_success_default "return:accept" +#define action_success_map MAP(SMFIS_ACCEPT)|MAP(SMFIS_CONTINUE) + +action_fp action_failure; +union action_data action_failure_data; +#define action_failure_default "return:accept" +#define action_failure_map MAP(SMFIS_ACCEPT)|MAP(SMFIS_CONTINUE)|\ + MAP(SMFIS_DISCARD)|MAP(SMFIS_REJECT) + +action_fp action_bad_sender; +union action_data action_bad_sender_data; +#define action_bad_sender_default "return:reject" +#define action_bad_sender_map MAP(SMFIS_DISCARD)|MAP(SMFIS_REJECT) + +action_fp action_temp_failure; +union action_data action_temp_failure_data; +#define action_temp_failure_default "return:tempfail" +#define action_temp_failure_map MAP(SMFIS_ACCEPT)|MAP(SMFIS_CONTINUE)|\ + MAP(SMFIS_DISCARD)|MAP(SMFIS_REJECT)|\ + MAP(SMFIS_TEMPFAIL) + +/* Add_header keeps the header to be added for mlfi_eom() function */ +struct header_action *add_header; + + +/* Configurable options */ + +#define MAILFROMD_DAEMON 0 +#define MAILFROMD_TEST 1 +#define MAILFROMD_EXPIRE 2 +#define MAILFROMD_LISTDB 3 +int mode = MAILFROMD_DAEMON; /* Default operation mode */ + +int do_transcript; /* Enable session transript */ +int debug_level; /* Debugging level */ +int log_to_stderr; /* Use stderr for logging */ +char *smtp_domain; /* Default SMTP domain for EHLO command */ +char *portspec = DEFAULT_SOCKET; /* Communication socket specification */ +int force_remove; /* Remove local communication socket if it already + exists */ +int foreground; /* Stay in foreground */ + +char *pidfile = DEFAULT_PIDFILE; +char *user = DEFAULT_USER; /* Switch to this user privileges after + startup */ +#ifdef USE_DBM +time_t expire_interval = DEFAULT_EXPIRE_INTERVAL; + /* Expire cache entries after this number + of seconds */ +#endif + +/* I/O settings */ +unsigned io_timeout = 3; +unsigned io_attempts = 3; + + +/* Logging & debugging */ +static const char *sfsistat_str(sfsistat stat); + +int +syslog_printer (int prio, const char *fmt, va_list ap) +{ +#ifdef HAVE_VSYSLOG + vsyslog (prio, fmt, ap); +#else + char buf[128]; + vsnprintf (buf, sizeof buf, fmt, ap); + syslog (prio, "%s", buf); +#endif + return 0; +} + +int +syslog_error_printer (const char *fmt, va_list ap) +{ + return syslog_printer(LOG_ERR, fmt, ap); +} + +void +vlogmsg(int prio, 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, char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vlogmsg(prio, fmt, ap); + va_end(ap); +} + +void +transcript(char *prefix, char *msg) +{ + if (do_transcript) { + int len = strlen(msg); + if (msg[len-1] == '\n') { + --len; + if (len > 0 && msg[len-1] == '\r') + --len; + } + if (len) + logmsg(LOG_INFO, "%s %*.*s", prefix, len, len, msg); + } +} + +void +debug_log(char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vlogmsg(LOG_DEBUG, fmt, ap); + va_end(ap); +} + + +/* Sendmail class file support */ + +static list_t domain_list; + +static int +compare_string(const void *item, const void *value) +{ + return strcmp(item, value); +} + +/* Read domains from sendmail-style domain file NAME and store them in + DOMAIN_LIST */ +void +read_domain_file(char *name) +{ + FILE *fp; + char buf[256]; + char *p; + + fp = fopen(name, "r"); + if (!fp) { + mu_error("cannot open file `%s': %s", + name, mu_strerror(errno)); + return; + } + + if (!domain_list) { + list_create(&domain_list); + 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; + + list_append(domain_list, strdup(p)); + } + + fclose(fp); +} + +/* Return true if we relay domain NAME */ +int +relayed_domain_p(char *name) +{ + char *p; + + for (p = strchr(name, '.'); p; p = strchr(p+1, '.')) + if (list_locate(domain_list, p+1, NULL) == 0) { + debug(10,("%s is in relayed domain %s", + name, p+1)); + return 1; + } + 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; + struct sockaddr_in sa; + char hbuf[NI_MAXHOST]; + + if (list_is_empty(domain_list)) + return 0; + + memset(&sa, 0, sizeof sa); + sa.sin_family = AF_INET; + if (inet_aton(client, &sa.sin_addr) == 0) + return 0; + rc = getnameinfo((struct sockaddr *)&sa, sizeof sa, + hbuf, sizeof(hbuf), + NULL, 0, + 0); + if (rc) + return 0; + return relayed_domain_p(hbuf); +} + + +/* Action functions */ + +/* Return */ +static sfsistat +action_return(SMFICTX *ctx, union action_data *data) +{ + if (data->ret.code) + smfi_setreply(ctx, data->ret.code, data->ret.xcode, + data->ret.message); + return data->ret.status; +} + +/* Add header */ +static sfsistat +action_add(SMFICTX *ctx, union action_data *data) +{ + add_header = &data->hdr; + return SMFIS_CONTINUE; +} + + +/* SMTP I/O functions */ + +#define MLFIPRIV(ctx) ((struct smtp_io_data*) smfi_getpriv(ctx)) + +struct smtp_io_data { + SMFICTX *ctx; /* Milter context */ + stream_t stream; /* I/O stream */ + size_t send_off; /* Send offset */ + size_t recv_off; /* Receive offset */ + struct obstack stk; /* Obstack for keeping commands/replies */ + char *command; /* Last issued command */ + char *reply; /* Last received reply */ + char *start; /* First line of the reply, if it was multiline */ + char buf[128]; /* Input buffer */ + size_t level; /* Number of bytes in buf */ + int code; /* Reply code */ +}; + +void +smtp_io_data_init(struct smtp_io_data *dat, SMFICTX *ctx, stream_t stream) +{ + obstack_init(&dat->stk); + dat->send_off = dat->recv_off = 0; + dat->stream = stream; + dat->start = dat->command = dat->reply = NULL; + dat->level = 0; + dat->ctx = ctx; + if (ctx) /* save the private data */ + smfi_setpriv(ctx, dat); +} + +void +smtp_io_data_destroy(struct smtp_io_data *dat) +{ + if (dat) { + stream_close (dat->stream); + stream_destroy(&dat->stream, stream_get_owner(dat->stream)); + obstack_free(&dat->stk, NULL); + if (dat->ctx) + smfi_setpriv(dat->ctx, NULL); + } +} + +void +smtp_stream_wait(stream_t stream, int flags) +{ + struct timeval tv; + tv.tv_sec = io_timeout; + tv.tv_usec = 0; + stream_wait(stream, &flags, &tv); +} + +void +smtp_wait(struct smtp_io_data *dat, int flags) +{ + return smtp_stream_wait(dat->stream, flags); +} + +int +smtp_send(struct smtp_io_data *dat, char *command) +{ + int attempt = 0; + size_t len = strlen(command); + + transcript("SEND:", command); + do { + size_t nb; + int rc = stream_write(dat->stream, command, len, + dat->send_off, &nb); + if (rc == 0) { + dat->send_off += nb; + len -= nb; + command += nb; + } else if (rc == EAGAIN) { + if (attempt > io_attempts) { + mu_error ("stream_write timed out"); + return -1; + } else { + smtp_wait(dat, MU_STREAM_READY_WR); + attempt++; + continue; + } + } else { + mu_error ("stream_write: %s", mu_strerror (rc)); + return -1; + } + } while (len > 0); + return 0; +} + +int +smtp_send2(struct smtp_io_data *dat, char *command, char *arg) +{ + int rc; + + obstack_grow(&dat->stk, command, strlen(command)); + if (arg) + obstack_grow(&dat->stk, arg, strlen(arg)); + obstack_1grow(&dat->stk, 0); + dat->command = obstack_finish(&dat->stk); + + rc = smtp_send(dat, dat->command); + if (rc == 0) + rc = smtp_send(dat, "\r\n"); + return rc; +} + +int +smtp_send3(struct smtp_io_data *dat, char *command, char *arg1, char *arg2) +{ + int rc; + + obstack_grow(&dat->stk, command, strlen(command)); + obstack_grow(&dat->stk, arg1, strlen(arg1)); + obstack_grow(&dat->stk, arg2, strlen(arg2)); + obstack_1grow(&dat->stk, 0); + dat->command = obstack_finish(&dat->stk); + + rc = smtp_send(dat, dat->command); + if (rc == 0) + rc = smtp_send(dat, "\r\n"); + + return rc; +} + +int +smtp_recvline(struct smtp_io_data *dat) +{ + size_t attempt = 0; + for (;;) { + char *p; + + if (dat->level == 0) { + int rc = stream_read(dat->stream, + dat->buf, sizeof dat->buf, + dat->recv_off, &dat->level); + if (rc == 0) { + if (dat->level == 0) { + mu_error("stream_read: read 0 bytes"); + return -1; + } + dat->recv_off += dat->level; + } else if (rc == EAGAIN) { + if (attempt > io_attempts) { + mu_error("stream_read timed out"); + return -1; + } + smtp_wait(dat, MU_STREAM_READY_RD); + continue; + } else { + mu_error ("stream_read: %s", mu_strerror (rc)); + return -1; + } + } + attempt = 0; + + p = memchr(dat->buf, '\n', dat->level); + if (!p) { + obstack_grow(&dat->stk, dat->buf, dat->level); + dat->level = 0; + continue; + } else { + size_t len = p - dat->buf + 1; + obstack_grow(&dat->stk, dat->buf, len); + obstack_1grow(&dat->stk, 0); + dat->reply = obstack_finish(&dat->stk); + dat->level -= len; + memmove(dat->buf, dat->buf + len, dat->level); + break; + } + } + return 0; +} + +int +smtp_recv(struct smtp_io_data *dat) +{ + char *p; + dat->start = NULL; + do { + int code; + int rc = smtp_recvline(dat); + if (rc) + return -1; + transcript("RECV:", dat->reply); + code = strtoul (dat->reply, &p, 0); + if (p - dat->reply != 3 || (*p != '-' && *p != ' ')) { + mu_error("Unexpected reply from server: %s", + dat->reply); + return -1; + } else if (!dat->start) { + dat->start = dat->reply; + dat->code = code; + } else if (dat->code != code) { + mu_error("Unexpected reply code from server: %d", + code); + return -1; + } + } while (*p == '-'); + obstack_1grow (&dat->stk, 0); + obstack_finish (&dat->stk); + return 0; +} + + +/* Milter-specific functions */ + +void +log_status(sfsistat status, SMFICTX *ctx, struct smtp_io_data *io) +{ + if (debug_level >= 1) { + char *id = smfi_getsymval(ctx, "i"); + const char *str = sfsistat_str(status); + if (status != SMFIS_ACCEPT && io) { + if (str) + logmsg(LOG_INFO, + "%s: %s, command=%s, reply=%s", + id ? id : "(nil)", str, + io->command, io->reply); + else + logmsg(LOG_INFO, + "%s: status %d, command=%s, reply=%s", + id ? id : "(nil)", status, + io->command, io->reply); + } else { + if (str) + logmsg(LOG_INFO, + "%s: %s", id ? id : "(nil)", str); + else + logmsg(LOG_INFO, + "%s: status %d", + id ? id : "(nil)", status); + } + } +} + +sfsistat +run_action(SMFICTX *ctx, getmx_status rc) +{ + sfsistat status; + + switch (rc) { + case getmx_temp_failure: + status = action_temp_failure(ctx, &action_temp_failure_data); + break; + + case getmx_failure: + status = action_failure(ctx, &action_failure_data); + break; + + case getmx_not_found: + status = action_bad_sender(ctx, &action_bad_sender_data); + break; + + case getmx_success: + status = action_success(ctx, &action_success_data); + } + log_status(status, ctx, NULL); + return status; +} + +/* Verify whether EMAIL address is served by host CLIENT_ADDR. */ +getmx_status +check_on_host(SMFICTX *ctx, char *email, char *client_addr) +{ + int rc; + stream_t stream; + struct smtp_io_data io; + getmx_status status = getmx_success; + int count = 0; + + debug(10, ("email = %s, client_addr = %s", email, client_addr)); + rc = tcp_stream_create (&stream, client_addr, 25, MU_STREAM_NONBLOCK); + if (rc) { + mu_error ("tcp_stream_create: %s", mu_strerror (rc)); + return getmx_temp_failure; + } + + while (rc = stream_open(stream)) { + if ((rc == EAGAIN || rc == EINPROGRESS) + && count <= io_attempts) { + count++; + smtp_stream_wait(stream, MU_STREAM_READY_WR); + continue; + } + mu_error("stream_open: %s, %d", mu_strerror(rc), count); + stream_destroy(&stream, stream_get_owner(stream)); + if (rc == EAGAIN || rc == EINPROGRESS) + return getmx_temp_failure; + else + return getmx_failure; + } + debug(100,("stream opened")); + + smtp_io_data_init(&io, ctx, stream); + + do { + char *buf; + char *descr; + if (smtp_recv(&io) || io.code != 220) { + status = getmx_not_found; + break; + } + smtp_send2(&io, "HELO ", smtp_domain); + if (smtp_recv(&io) || io.code != 250) { + status = getmx_not_found; + break; + } + smtp_send2(&io, "MAIL FROM: <>", NULL); + if (smtp_recv(&io) || io.code != 250) { + status = getmx_not_found; + break; + } + smtp_send3(&io, "RCPT TO: <", email, ">"); + if (smtp_recv(&io) || (io.code != 250 && io.code / 100 != 4)) { + status = getmx_not_found; + break; + } + smtp_send2(&io, "QUIT", NULL); + } while (0); + + smtp_io_data_destroy(&io); + + return status; +} + +getmx_status +check_mx_records(SMFICTX *ctx, char *email, char *client_addr) +{ + int i; + mxbuf_t mxbuf; + getmx_status rc, mxstat = getmx(client_addr, mxbuf); + + switch (mxstat) { + case getmx_temp_failure: + rc = getmx_temp_failure; + break; + + case getmx_failure: + case getmx_not_found: + rc = getmx_not_found; + break; + + case getmx_success: + debug(2,("Checking MX servers for %s", email)); + for (i = 0; i < MAXMXCOUNT && mxbuf[i]; i++) { + rc = check_on_host(ctx, email, mxbuf[i]); + if (getmx_resolved(rc)) + break; + free(mxbuf[i]); + } + for (; i < MAXMXCOUNT && mxbuf[i]; i++) + free(mxbuf[i]); + } + return rc; +} + +/* Method "strict". Verifies whether EMAIL is understood either by + host CLIENT_ADDR or one of MX servers of its domain */ +sfsistat +method_strict(SMFICTX *ctx, char *email, char *client_addr) +{ + getmx_status rc = cache_get2(email, client_addr); + + if (!getmx_resolved(rc)) { + if ((rc = check_on_host(ctx, email, client_addr)) + != getmx_success) + rc = check_mx_records(ctx, email, client_addr); + if (getmx_resolved(rc)) + cache_insert2(email, client_addr, rc); + } + return run_action(ctx, rc); +} + +/* Method "standard". Verifies whether EMAIL is understood by + any of its MXs */ +sfsistat +method_standard(SMFICTX *ctx, char *email, char *client_addr) +{ + getmx_status rc = cache_get(email); + + if (!getmx_resolved(rc)) { + char *p = strchr(email, '@'); + if (p == NULL) { + mu_error("Invalid address: %s", email); + rc = getmx_not_found; + } else + rc = check_mx_records(ctx, email, p+1); + if (getmx_resolved(rc)) + cache_insert(email, rc); + } + return run_action(ctx, rc); +} + +/* Cleanup functions */ +void +filter_cleanup(struct smtp_io_data *dat) +{ + debug(100,("cleaning up")); + if (!foreground) + daemon_remove_pidfile(); + smtp_io_data_destroy(dat); +} + + +/* Milter interface functions */ + +sfsistat +mlfi_envfrom(SMFICTX *ctx, char **argv) +{ + char *mail_addr = smfi_getsymval(ctx, "f"); + char *client = smfi_getsymval(ctx, "{client_addr}"); + sfsistat status; + + if (!mail_addr) { + mu_error("$f is not available. Fix your confMILTER_MACROS_ENVFROM settings"); + return action_failure(ctx, &action_failure_data); + } + if (mail_addr[0] == 0) + return SMFIS_ACCEPT; + if (!client) { + mu_error("${client_addr} is not available. Fix your confMILTER_MACROS_ENVFROM settings"); + return action_failure(ctx, &action_failure_data); + } + + if (host_in_relayed_domain_p(client)) + return SMFIS_ACCEPT; + + return (*method)(ctx, mail_addr, client); +} + +sfsistat +mlfi_eom(SMFICTX *ctx) +{ + if (add_header) + smfi_addheader(ctx, add_header->name, add_header->value); + + return SMFIS_CONTINUE; +} + +sfsistat +mlfi_abort(SMFICTX *ctx) +{ + filter_cleanup(MLFIPRIV(ctx)); + return SMFIS_ACCEPT; /* FIXME? */ +} + + +struct smfiDesc smfilter = +{ + "MailfromFilter", + SMFI_VERSION, + SMFIF_ADDHDRS, + NULL, /* connection info filter */ + NULL, /* SMTP HELO command filter */ + mlfi_envfrom, /* envelope sender filter */ + NULL, /* envelope recipient filter */ + NULL, /* header filter */ + NULL, /* end of header */ + NULL, /* body block filter */ + mlfi_eom, /* end of message */ + mlfi_abort, /* message aborted */ + NULL, /* connection cleanup */ +}; + + +/* Command line parsing */ + +const char *program_version = "mailfromd (" PACKAGE_STRING ")"; +static char doc[] = N_("mailfromd -- MAIL FROM milter checker"); +static char args_doc[] = ""; + +#define OPTION_MILTER_TIMEOUT 256 +#define OPTION_SHOW_DEFAULTS 257 +#define OPTION_FOREGROUND 258 +#define OPTION_PIDFILE 259 +#define OPTION_DOMAIN_FILE 260 +#define OPTION_TIMEOUT 261 +#define OPTION_RETRY 262 +#define OPTION_EXPIRE 263 +#define OPTION_DAEMON 264 +#define OPTION_LISTDB 265 + +static struct argp_option options[] = { +#define GRP 0 + { NULL, 0, NULL, 0, + "Operation modifiers", GRP }, +#ifdef USE_DBM + { "expire", OPTION_EXPIRE, NULL, 0, + "Expire given entries from the cache", GRP+1 }, + { "list", OPTION_LISTDB, NULL, 0, + "List cache database", GRP+1 }, +#endif + { "test", 't', NULL, 0, + "Run in test mode", GRP+1 }, + { "show-defaults", OPTION_SHOW_DEFAULTS, NULL, 0, + "Show compilation defaults", GRP+1 }, + { "daemon", OPTION_DAEMON, NULL, 0, + "Run in daemon mode (default)", GRP+1 }, +#undef GRP +#define GRP 10 + { NULL, 0, NULL, 0, + "General options", GRP }, + {"domain", 'D', "STRING", 0, + "Set default SMTP domain", GRP+1}, + {"port", 'p', "STRING", 0, + "Set communication socket", GRP+1}, + {"remove", 'r', NULL, 0, + "Forse removing of the local socket file, if it already exists", + GRP+1}, + {"foreground", OPTION_FOREGROUND, NULL, 0, + "Stay in foreground", GRP+1}, + {"pidfile", OPTION_PIDFILE, "FILE", 0, + "Set pidfile name", GRP+1}, + {"user", 'u', "NAME", 0, + "Switch to this user privileges after startup", GRP+1}, + {"method", 'm', "METHOD-NAME", 0, + "Set verification method", GRP+1}, +#ifdef USE_DBM + {"expire-interval", 'e', "NUMBER", 0, + "Set cache expiration interval to NUMBER seconds", GRP+1}, +#endif +#undef GRP +#define GRP 20 + { NULL, 0, NULL, 0, + "Action control", GRP }, + {"action-success", 'S', "ACTION", 0, + "Action to take on success. Default is return:accept.", GRP+1}, + {"action-failure", 'F', "ACTION", 0, + "Action to take on failure. Default is return:continue.", GRP+1}, + {"action-bad-sender", 'B', "ACTION", 0, + "Action to take if sender validity is not confirmed. Default is return:discard.", GRP+1}, + {"action-temp-failure", 'T', "ACTION", 0, + "Action to take on temporary failures. Default is return:tempfail", + GRP+1}, + +#undef GRP +#define GRP 30 + { NULL, 0, NULL, 0, + "Timeout control", GRP }, + {"milter-timeout", OPTION_MILTER_TIMEOUT, 0, 0, + "Set MTA connection timeout", GRP+1 }, + {"timeout", OPTION_TIMEOUT, "NUMBER", 0, + "Set I/O operation timeout (seconds)", GRP+1}, + {"retry", OPTION_RETRY, "NUMBER", 0, + "Retry failed I/O operations NUMBER times", GRP+1}, + + {"relayed-domain-file", OPTION_DOMAIN_FILE, "FILE", 0, + "Read relayed domains from FILE", GRP+1}, + +#undef GRP +#define GRP 40 + { NULL, 0, NULL, 0, + "Informational and debugging options", GRP }, + {"transcript", 'X', NULL, 0, + "Enable transcript", GRP+1}, + {"debug", 'd', "LEVEL", 0, + "Set debugging level", GRP+1}, + {"stderr", 's', NULL, 0, + "Log to stderr", GRP+1}, +#undef GRP + NULL +}; + +struct sfsistat_tab { + char *name; + sfsistat stat; +} sfsistat_tab[] = { + { "accept", SMFIS_ACCEPT }, + { "continue", SMFIS_CONTINUE }, + { "discard", SMFIS_DISCARD }, + { "reject", SMFIS_REJECT }, + { "tempfail", SMFIS_TEMPFAIL }, + NULL +}; + +static const char * +sfsistat_str(sfsistat stat) +{ + struct sfsistat_tab *p; + for (p = sfsistat_tab; p->name; p++) + if (p->stat == stat) + return p->name; + return NULL; +} + +void +print_stat(sfsistat stat) +{ + struct sfsistat_tab *p; + for (p = sfsistat_tab; p->name; p++) + if (p->stat == stat) { + printf("%s", p->name); + return; + } + printf("%d", stat); +} + +#define STAT_PREFIX "SMFIS_" + +void +parse_action(char *option, + char *arg, action_fp *action, union action_data *data, + unsigned long map, struct argp_state *state) +{ + char *code = strdup(arg); + char *p; + char *sp; + + p = strtok_r(code, ":", &sp); + if (strcasecmp(p, "return") == 0) { + struct sfsistat_tab *statp; + + *action = action_return; + p = strtok_r(NULL, ":", &sp); + if (!p) + argp_error(state, + "%s: Requested action parameter missing", option); + + if (strncasecmp(p, STAT_PREFIX, sizeof STAT_PREFIX - 1) == 0) + p += sizeof STAT_PREFIX - 1; + for (statp = sfsistat_tab; statp->name; statp++) + if (strcasecmp(statp->name, p) == 0) { + if ((MAP(statp->stat) & map) == 0) + argp_error(state, + "%s: Return value `%s' is not allowed", option, p); + data->ret.status = statp->stat; + data->ret.code = strtok_r(NULL, ":", &sp); + if (*sp == ':') { + sp++; + data->ret.xcode = NULL; + } else + data->ret.xcode = strtok_r(NULL, ":", &sp); + data->ret.message = strtok_r(NULL, ":", &sp); + return; + } + argp_error(state, "%s: Unknown action parameter value: %s", + option, p); + } else if (strcasecmp(p, "add") == 0) { + *action = action_add; + data->hdr.name = strtok_r(NULL, ":", &sp); + data->hdr.value = strtok_r(NULL, ":", &sp); + } else + argp_error(state, "%s: Unknown action: %s", option, p); +} + +static error_t +parse_opt (int key, char *arg, struct argp_state *state) +{ + switch (key) { + case 'B': + parse_action("action-bad-sender", + arg, + &action_bad_sender, &action_bad_sender_data, + action_bad_sender_map, state); + break; + + case 'D': + smtp_domain = arg; + break; + + case 'F': + parse_action("action-failure", + arg, + &action_failure, &action_failure_data, + action_failure_map, state); + break; + + case 'S': + parse_action("action-success", + arg, + &action_success, &action_success_data, + action_success_map, state); + break; + + case 'T': + parse_action("action-temp-failure", + arg, + &action_temp_failure, + &action_temp_failure_data, + action_temp_failure_map, + state); + break; + + case 'X': + do_transcript = 1; + break; + + case 'd': + debug_level = atoi(arg); + break; + + case 'e': + expire_interval = strtoul(arg, NULL, 10); + break; + + case 'm': + method = find_method(arg); + if (!method) + argp_error(state, "Unknown verification method: %s", + arg); + break; + + case 'p': + portspec = arg; + break; + + case 'r': + force_remove = 1; + break; + + case 's': + log_to_stderr = 1; + break; + + case 't': + mode = MAILFROMD_TEST; + log_to_stderr = 1; + break; + + case 'u': + user = arg; + break; + + case OPTION_DAEMON: + mode = MAILFROMD_DAEMON; + break; + + case OPTION_DOMAIN_FILE: + read_domain_file(arg); + break; + + case OPTION_EXPIRE: + mode = MAILFROMD_EXPIRE; + break; + + case OPTION_FOREGROUND: + foreground = 1; + break; + + case OPTION_LISTDB: + mode = MAILFROMD_LISTDB; + break; + + case OPTION_MILTER_TIMEOUT: + if (smfi_settimeout(atoi(arg)) == MI_FAILURE) + argp_error(state, "Invalid timeout value"); + break; + + case OPTION_PIDFILE: + if (arg[0] != '/') + argp_error(state, + "Invalid pidfile name: must be absolute"); + else + pidfile = arg; + break; + + case OPTION_RETRY: + io_attempts = atoi(arg); + break; + + case OPTION_SHOW_DEFAULTS: + printf("method: standard\n"); + printf("action-success: %s\n", action_success_default); + printf("action-bad-sender: %s\n", action_bad_sender_default); + printf("action-failure: %s\n", action_failure_default); + printf("action-temp-failure: %s\n", + action_temp_failure_default); + printf("user: %s\n", DEFAULT_USER); + printf("localstatedir: %s\n", LOCALSTATEDIR); + printf("socket: %s\n", DEFAULT_SOCKET); + printf("pidfile: %s\n", DEFAULT_PIDFILE); +#ifdef USE_DBM + printf("cache database: %s\n", DEFAULT_DATABASE); + printf("cache expiration: %lu\n", DEFAULT_EXPIRE_INTERVAL); +#else + printf("cache database: %s\n", "DISABLED"); +#endif + break; + + case OPTION_TIMEOUT: + io_timeout = atoi(arg); + break; + + case ARGP_KEY_FINI: + if (!smtp_domain) + mu_get_host_name(&smtp_domain); + if (!log_to_stderr) { + openlog(program_invocation_short_name, + LOG_PID, log_facility); + mu_error_set_print(syslog_error_printer); + } + if (!action_success) + parse_action("action-success", + action_success_default, + &action_success, &action_success_data, + action_success_map, state); + if (!action_bad_sender) + parse_action("action-bad-sender", + action_bad_sender_default, + &action_bad_sender, + &action_bad_sender_data, + action_bad_sender_map, + state); + if (!action_failure) + parse_action("action-failure", + action_failure_default, + &action_failure, + &action_failure_data, + action_failure_map, + state); + if (!action_temp_failure) + parse_action("action-temp-failure", + action_temp_failure_default, + &action_temp_failure, + &action_temp_failure_data, + action_temp_failure_map, + state); + break; + + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +static const char *capa[] = { + "logging", |