aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org.ua>2005-06-04 17:24:49 +0000
committerSergey Poznyakoff <gray@gnu.org.ua>2005-06-04 17:24:49 +0000
commitd75711ebcc2cbf726a7430241319caf9bc3ca3a6 (patch)
tree05f17a08cadeacb20b5350ee92fae772c0625279
parent447f6ddce61e287c6b95a403ddbbe827f8c17ce8 (diff)
downloadmailfromd-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.c1504
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",