aboutsummaryrefslogtreecommitdiff
path: root/pmult
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org.ua>2008-02-10 14:08:36 +0000
committerSergey Poznyakoff <gray@gnu.org.ua>2008-02-10 14:08:36 +0000
commita1871918823166cac9f0c12164ba27cb3b1f0c2c (patch)
tree7260d118aa5af6a8f006f9b2dac6cabde782db8c /pmult
parent455b645247bdc0f01239b2352a9d8f07f446024f (diff)
downloadmailfromd-a1871918823166cac9f0c12164ba27cb3b1f0c2c.tar.gz
mailfromd-a1871918823166cac9f0c12164ba27cb3b1f0c2c.tar.bz2
Merged HEAD from branches/gmach
git-svn-id: file:///svnroot/mailfromd/trunk@1612 7a8a7f39-df28-0410-adc6-e0d955640f24
Diffstat (limited to 'pmult')
-rw-r--r--pmult/Makefile.am44
-rw-r--r--pmult/debugdef.m454
-rw-r--r--pmult/pdbg.hm43
-rw-r--r--pmult/pmult.c1715
4 files changed, 1816 insertions, 0 deletions
diff --git a/pmult/Makefile.am b/pmult/Makefile.am
new file mode 100644
index 00000000..68c0f522
--- /dev/null
+++ b/pmult/Makefile.am
@@ -0,0 +1,44 @@
+# This file is part of mailfrom filter.
+# Copyright (C) 2008 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 <http://www.gnu.org/licenses/>.
+
+PMULT_PROG = pmult
+sbin_PROGRAMS = @ENABLE_PMULT@
+EXTRA_PROGRAMS = pmult
+EXTRA_DIST = pdbg.hm4 debugdef.m4
+BUILT_SOURCES = pdbg.h
+
+INCLUDES = \
+ $(MAILUTILS_INCLUDES)\
+ $(MU_COMMON_INCLUDES)\
+ -I$(top_srcdir)/lib\
+ -I$(top_srcdir)/gnu\
+ -I../gnu\
+ -I../gacopyz
+
+pmult_SOURCES = pmult.c pdbg.h
+LDADD = \
+ ../lib/libmf.a\
+ $(MAILUTILS_LIBS)\
+ ../gnu/libgnu.a\
+ $(MILTER)\
+ @META1_LIBS@ @PTHREAD_LIBRARIES@
+
+pdbg.h: $(top_srcdir)/pmult/debugdef.m4 pdbg.hm4
+ m4 $(top_srcdir)/pmult/debugdef.m4 pdbg.hm4 > pdbg.h
+
+
+
+
diff --git a/pmult/debugdef.m4 b/pmult/debugdef.m4
new file mode 100644
index 00000000..7e4a0b6f
--- /dev/null
+++ b/pmult/debugdef.m4
@@ -0,0 +1,54 @@
+divert(-1)
+# This file is part of Mailutils.
+# Copyright (C) 2006, 2007, 2008 Free Software Foundation, Inc.
+#
+# Initially written by Sergey Poznyakoff for Mailfromd project.
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+changecom(/*,*/)
+
+define(`__arglist',`dnl
+ifelse($1,$2,x$1,
+`x$1, __arglist(incr($1), $2)')')
+
+define(`MKDEBUG',`
+`#define __PMU_DEBUG'$1`(dbg, lev, fmt, '__arglist(1,$1)) \`
+ do \
+ { \
+ pthread_mutex_lock (&pmult_debug_mutex); \
+ __MU_DEBUG'$1`(dbg, lev, fmt, '__arglist(1,$1)); `\
+ pthread_mutex_unlock (&pmult_debug_mutex); \
+ } \
+ while (0)
+
+#define PMU_DEBUG'$1`(dbg, lev, fmt, '__arglist(1,$1)`) \
+ do \
+ { \
+ if (mu_debug_check_level (dbg, lev)) \
+ __PMU_DEBUG'$1`(dbg, lev, fmt, '__arglist(1,$1)`); \
+ } \
+ while(0)
+'')
+
+define(`forloop',
+ `pushdef(`$1', `$2')_forloop(`$1', `$2', `$3', `$4')popdef(`$1')')
+define(`_forloop',
+ `$4`'ifelse($1, `$3', ,
+ `define(`$1', incr($1))_forloop(`$1', `$2', `$3', `$4')')')
+
+divert(0)dnl
+/* -*- buffer-read-only: t -*- vi: set ro:
+ THIS FILE IS GENERATED AUTOMATICALLY. PLEASE DO NOT EDIT.
+*/
diff --git a/pmult/pdbg.hm4 b/pmult/pdbg.hm4
new file mode 100644
index 00000000..a19506d4
--- /dev/null
+++ b/pmult/pdbg.hm4
@@ -0,0 +1,3 @@
+#include <mailutils/debug.h>
+#define PMU_DEBUG(d,l,s) PMU_DEBUG1(d,l,"%s",s)
+forloop(`i',1,11,`MKDEBUG(i)')
diff --git a/pmult/pmult.c b/pmult/pmult.c
new file mode 100644
index 00000000..92cfc844
--- /dev/null
+++ b/pmult/pmult.c
@@ -0,0 +1,1715 @@
+/* This file is part of Mailfromd.
+ Copyright (C) 2008 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 <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <syslog.h>
+#include <mailutils/mailutils.h>
+#include <mailutils/daemon.h>
+#include <mailutils/syslog.h>
+#include <mailutils/libargp.h>
+#include <sysexits.h>
+#include <pthread.h>
+#include <gacopyz.h>
+#include "xalloc.h"
+#include "libmf.h"
+
+/* FIXME */
+#include <inttypes.h>
+const char *mu_umaxtostr (unsigned slot, uintmax_t val);
+
+#include "sm/error.h"
+#include "sm/smreplycodes.h"
+#include "sm/pmilter.h"
+#include "sm/pmfdef.h"
+#include "sm/pmfapi.h"
+
+#include "pdbg.h"
+
+const char *program_version = "pmult (" PACKAGE_STRING ")";
+const char *package_bugreport = "<" PACKAGE_BUGREPORT ">";
+
+char *portspec; /* Communication socket */
+int log_to_stderr; /* Use stderr for logging */
+char *log_tag; /* override mu_log_tag */
+mu_log_level_t debug_level; /* Debug verbosity level */
+mu_debug_t pmult_debug; /* Debugging object */
+static pthread_mutex_t pmult_debug_mutex = PTHREAD_MUTEX_INITIALIZER;
+char *pidfile; /* pidfile name */
+int no_sig_handler; /* Disable signal handler */
+
+struct pmult_client
+{
+ int type; /* Type, unused so far */
+ char *name;
+ struct timeval timeout[GACOPYZ_TO_COUNT];
+ char *url;
+ int logmask;
+};
+
+/* List of configured clients: */
+mu_list_t /* of struct pmult_client */ client_list;
+
+enum pmult_msg_state
+ {
+ pmult_msg_state_initial,
+ pmult_msg_state_headers,
+ pmult_msg_state_cr1,
+ pmult_msg_state_crlf1,
+ pmult_msg_state_cr2,
+ pmult_msg_state_body
+ };
+
+struct pmult_priv_data
+{
+ mu_debug_t debug;
+ mu_list_t /* of gacopyz_srv_t */ srvlist;
+ pthread_mutex_t mutex;
+ char *seid;
+ char *seid_c;
+ char *taid;
+ unsigned nrcpt;
+ unsigned nbadrcpts;
+ mu_stream_t hdrstream;
+ enum pmult_msg_state state;
+ mu_header_t hdr;
+};
+
+#define PRIV_SEID(p) ((p)->seid ? (p)->seid : "")
+#define PRIV_SEID_C(p) ((p)->seid_c ? (p)->seid_c : pmult_seid_c (p))
+#define PRIV_LOCK(p) pthread_mutex_lock (&(p)->mutex)
+#define PRIV_UNLOCK(p) pthread_mutex_unlock (&(p)->mutex)
+
+static char *
+pmult_seid_c (struct pmult_priv_data *p)
+{
+ if (p->seid)
+ {
+ p->seid_c = malloc (strlen (p->seid) + 2);
+ if (p->seid_c)
+ {
+ strcpy (p->seid_c, p->seid);
+ strcat (p->seid_c, ":");
+ return p->seid_c;
+ }
+ }
+ return "";
+}
+
+
+static pthread_mutex_t pmult_mutex = PTHREAD_MUTEX_INITIALIZER;
+#define protect() pthread_mutex_lock (&pmult_mutex)
+#define unprotect() pthread_mutex_unlock (&pmult_mutex)
+
+
+/* Logging */
+void
+log_setup (int want_stderr)
+{
+ mu_debug_t debug;
+
+ mu_diag_get_debug (&debug);
+
+ if (log_tag)
+ mu_log_tag = log_tag;
+
+ if (!want_stderr)
+ {
+ openlog (MU_LOG_TAG (), LOG_PID, mu_log_facility);
+ gacopyz_set_logger (gacopyz_syslog_log_printer);
+ mu_debug_set_print (debug, mu_diag_syslog_printer, NULL);
+ mu_debug_default_printer = mu_debug_syslog_printer;
+ }
+ else
+ {
+ gacopyz_set_logger (gacopyz_stderr_log_printer);
+ mu_debug_default_printer = mu_debug_stderr_printer;
+ }
+}
+
+static int
+stderr_closed_p()
+{
+ int fd = dup(0);
+ if (fd < 0)
+ return 1;
+ close(fd);
+ return fd <= 2;
+}
+
+
+static int
+_cb_client_type (mu_debug_t debug, void *data, char *arg)
+{
+ if (strcmp (arg, "milter") == 0)
+ /* dobrze */;
+ else if (strcmp (arg, "pmilter") == 0)
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("client type %s is not supported yet"),
+ arg);
+ else
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("unknown client type %s"),
+ arg);
+ return 0;
+}
+
+static int
+cb_timeout (struct timeval *pt, mu_debug_t debug, char *arg)
+{
+ const char *endp;
+ time_t t;
+ if (parse_time_interval (arg, &t, &endp))
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("unrecognized time format (near `%s')"),
+ endp);
+ return 1;
+ }
+ pt->tv_usec = 0;
+ pt->tv_sec = t;
+ return 0;
+}
+
+static int
+_cb_write_timeout (mu_debug_t debug, void *data, char *arg)
+{
+ struct pmult_client *clt = data;
+ return cb_timeout (&clt->timeout[GACOPYZ_TO_WRITE], debug, arg);
+}
+
+static int
+_cb_read_timeout (mu_debug_t debug, void *data, char *arg)
+{
+ struct pmult_client *clt = data;
+ return cb_timeout (&clt->timeout[GACOPYZ_TO_READ], debug, arg);
+}
+
+static int
+_cb_eom_timeout (mu_debug_t debug, void *data, char *arg)
+{
+ struct pmult_client *clt = data;
+ return cb_timeout (&clt->timeout[GACOPYZ_TO_EOM], debug, arg);
+}
+
+static int
+_cb_connect_timeout (mu_debug_t debug, void *data, char *arg)
+{
+ struct pmult_client *clt = data;
+ return cb_timeout (&clt->timeout[GACOPYZ_TO_CONNECT], debug, arg);
+}
+
+static int
+_cb_log_level (mu_debug_t debug, void *data, char *arg)
+{
+ struct pmult_client *clt = data;
+ int argc, i;
+ char **argv;
+ int rc;
+
+ rc = mu_argcv_get_np (arg, strlen (arg), ",", NULL, 0, &argc, &argv, NULL);
+ if (rc)
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ "mu_argcv_get: %s", mu_strerror (rc));
+ return 1;
+ }
+
+ for (i = 0; i < argc; i++)
+ {
+ char *p = argv[i];
+ int lev;
+ int revert = 0;
+ int upto = 0;
+
+ if (*p == '!')
+ {
+ p++;
+ revert = 1;
+ }
+ if (*p == '<')
+ {
+ p++;
+ upto = 1;
+ }
+
+ lev = gacopyz_string_to_log_level (p);
+ if (lev == -1)
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("Invalid log level: %s"), arg);
+ return 1;
+ }
+
+ if (revert)
+ {
+ if (upto)
+ clt->logmask &= ~SMI_LOG_UPTO (lev);
+ else
+ clt->logmask &= ~SMI_LOG_FROM (lev);
+ }
+ else
+ {
+ if (upto)
+ clt->logmask |= SMI_LOG_UPTO (lev);
+ else
+ clt->logmask |= SMI_LOG_FROM (lev);
+ }
+ }
+
+ mu_argcv_free (argc, argv);
+ return 0;
+}
+
+struct mu_cfg_param client_cfg_param[] = {
+ { "type", mu_cfg_callback, NULL, 0, _cb_client_type,
+ N_("Set remote milter type. Only `milter' is understood so far."),
+ N_("{milter [version: number]|pmilter}") },
+ { "url", mu_cfg_string, NULL, mu_offsetof(struct pmult_client, url), NULL,
+ N_("Set remote client URL.") },
+ { "write-timeout", mu_cfg_callback, NULL, 0, _cb_write_timeout,
+ N_("Set write timeout."),
+ N_("time") },
+ { "read-timeout", mu_cfg_callback, NULL, 0, _cb_read_timeout,
+ N_("Set read timeout."),
+ N_("time") },
+ { "eom-timeout", mu_cfg_callback, NULL, 0, _cb_eom_timeout,
+ N_("Set timeout for EOM."),
+ N_("time") },
+ { "connect-timeout", mu_cfg_callback, NULL, 0, _cb_connect_timeout,
+ N_("Set connect timeout."),
+ N_("time") },
+ { "log-level", mu_cfg_callback, NULL, 0, _cb_log_level,
+ N_("Set log verbosity level. Arg is a list of items separated by commas "
+ "or whitespace. Each item is a log level optionally prefixed with `!' "
+ "to indicate `any level except this', or '<', meaning `all levels up "
+ "to and including this'. Log levels in order of increasing priority "
+ "are: debug, info, warn, err, fatal."),
+ N_("arg: list") },
+ { NULL }
+};
+
+static int
+_cb_portspec (mu_debug_t debug, void *data, char *arg)
+{
+ char **pptr = data, *ptr;
+ char *proto = NULL;
+ char *port = NULL;
+ char *path = NULL;
+ size_t len;
+
+ if (gacopyz_parse_connection (arg, &proto, &port, &path))
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("Invalid URL: %s"), arg);
+ return 1;
+ }
+
+ if (!proto)
+ {
+ if (port)
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("Invalid URL: %s"), arg);
+ return 1;
+ }
+ ptr = xstrdup (path);
+ }
+ else
+ {
+ len = strlen (proto) + 1
+ + (port ? strlen (port) + 1 : 0) + strlen (path) + 1;
+ ptr = xmalloc (len);
+ strcpy (ptr, proto);
+ strcat (ptr, ":");
+ if (port)
+ {
+ strcat (ptr, port);
+ strcat (ptr, "@");
+ }
+ strcat (ptr, path);
+ }
+ free (path);
+ free (port);
+ free (proto);
+ *pptr = ptr;
+ return 0;
+}
+
+static int
+_cb_debug (mu_debug_t debug, void *data, char *arg)
+{
+ int rc;
+
+ rc = mu_debug_level_from_string (arg, &debug_level, debug);
+ if (rc)
+ return 0;
+ if (!pmult_debug)
+ mu_debug_create (&pmult_debug, NULL);
+ mu_debug_set_level (pmult_debug, debug_level);
+ return 0;
+}
+
+struct mu_cfg_param pmult_cfg_param[] = {
+ { "listen", mu_cfg_callback, &portspec, 0, _cb_portspec,
+ N_("Listen for milter requests on the given URL."),
+ N_("url") },
+ { "client", mu_cfg_section },
+ { "debug", mu_cfg_callback, NULL, 0, _cb_debug,
+ N_("Set debug verbosity level.") },
+ { "pidfile", mu_cfg_string, &pidfile, 0, NULL,
+ N_("Write PID to this file.") },
+ { NULL }
+};
+
+static void
+pmult_client_free (struct pmult_client *clt)
+{
+ free (clt->name);
+ free (clt);
+}
+
+static int
+client_block_begin (mu_debug_t debug, char *name, void **section_data)
+{
+ extern struct timeval default_gacopyz_timeout[GACOPYZ_TO_COUNT];
+ struct pmult_client *clt = xcalloc (1, sizeof *clt);
+ clt->name = name ? xstrdup (name) : "";
+ memcpy (clt->timeout, default_gacopyz_timeout, sizeof clt->timeout);
+ *section_data = clt;
+ return 0;
+}
+
+static int
+client_block_end (mu_debug_t debug, struct pmult_client *clt)
+{
+ gacopyz_srv_t gsrv;
+ int rc = gacopyz_srv_create (&gsrv, clt->name, clt->url, clt->logmask);
+ if (rc)
+ {
+ pmult_client_free (clt);
+ return 1; /* FIXME: error message */
+ }
+
+ gacopyz_srv_destroy (&gsrv);
+ if (!client_list)
+ MU_ASSERT (mu_list_create (&client_list));
+ mu_list_append (client_list, clt);
+ return 0;
+}
+
+static int
+client_section_parser (enum mu_cfg_section_stage stage,
+ const mu_cfg_node_t *node,
+ const char *section_label, void **section_data,
+ void *call_data,
+ mu_cfg_tree_t *tree)
+{
+ switch (stage)
+ {
+ case mu_cfg_section_start:
+ return client_block_begin (tree->debug, node->tag_label, section_data);
+
+ case mu_cfg_section_end:
+ return client_block_end (tree->debug,
+ (struct pmult_client *)*section_data);
+ }
+ return 0;
+}
+
+static void
+pmult_cfg_init ()
+{
+ struct mu_cfg_section *section;
+
+ if (mu_create_canned_section ("client", &section))
+ exit (EX_SOFTWARE);
+ section->parser = client_section_parser;
+ section->label = N_("ident");
+ mu_cfg_section_add_params (section, client_cfg_param);
+}
+
+
+/* Command line parsing */
+
+static char doc[] = N_("pmult -- pmilter multiplexer");
+static char args_doc[] = "";
+
+enum {
+ OPTION_URL = 256,
+ OPTION_SYSLOG,
+ OPTION_LOG_TAG,
+ OPTION_NO_SIGNAL_HANDLER,
+};
+
+static struct argp_option options[] = {
+ { "url", OPTION_URL, N_("URL"), 0,
+ N_("Listen on the given URL"), 0 },
+ { "syslog", OPTION_SYSLOG, NULL, 0,
+ N_("Log to syslog (default)"), },
+ { "stderr", 's', NULL, 0,
+ N_("Log to stderr"), },
+ { "log-tag", OPTION_LOG_TAG, N_("STRING"), 0,
+ N_("Set the identifier used in syslog messages to STRING"), },
+ { "debug", 'x', N_("LEVEL"), 0,
+ N_("Set debug verbosity level.") },
+ { "no-signal-handler", OPTION_NO_SIGNAL_HANDLER, NULL, 0,
+ N_("Disable signal handling in the main thread (use for debugging).") },
+ { NULL }
+};
+
+static error_t
+parse_opt (int key, char *arg, struct argp_state *state)
+{
+ static struct mu_argp_node_list lst;
+
+ switch (key)
+ {
+ case OPTION_URL:
+ mu_argp_node_list_new (&lst, "listen", arg);
+ break;
+
+ case OPTION_SYSLOG:
+ log_to_stderr = 0;
+ break;
+
+ case OPTION_NO_SIGNAL_HANDLER:
+ no_sig_handler = 1;
+ break;
+
+ case 's':
+ log_to_stderr = 1;
+ break;
+
+ case 'x':
+ mu_argp_node_list_new (&lst, "debug", arg);
+ break;
+
+ case OPTION_LOG_TAG:
+ log_tag = arg;
+ break;
+
+ case ARGP_KEY_INIT:
+ mu_argp_node_list_init (&lst);
+ break;
+
+ case ARGP_KEY_FINI:
+ mu_argp_node_list_finish (&lst, NULL, NULL);
+ break;
+
+ default:
+ return ARGP_ERR_UNKNOWN;
+ }
+ return 0;
+}
+
+static const char *capa[] = {
+ "common",
+ "debug",
+ "logging",
+ NULL
+};
+
+static struct argp argp = {
+ options,
+ parse_opt,
+ args_doc,
+ doc,
+ NULL,
+ NULL,
+ NULL
+};
+
+
+#define SM_ONERROR_ACTION(expr,onerr) do \
+ { \
+ sm_ret_T ret = expr; \
+ if (sm_is_err (ret)) \
+ { \
+ char *cp = smerr2txt (ret); \
+ if (cp) \
+ mu_error ("%s:%d: " #expr " failed: %s", \
+ __FILE__, __LINE__, cp); \
+ else \
+ mu_error ("%s:%d: " #expr " failed: %#x", \
+ __FILE__, __LINE__, ret); \
+ onerr; \
+ } \
+ } \
+while (0)
+
+#define SM_VERBOSE(expr) SM_ONERROR_ACTION(expr,)
+#define SM_CHECK(expr) SM_ONERROR_ACTION(expr,return ret)
+#define SM_ASSERT(expr) SM_ONERROR_ACTION(expr, exit (EX_CONFIG))
+
+
+#define MSG_TEMPFAIL "451 4.3.2 Please try again later\r\n"
+#define MSG_REJECT "550 5.7.1 Command rejected\r\n"
+#define MSG_SHUTDOWN "421 4.7.0 pmult closing connection\r\n"
+
+const char *
+hdrcommname (int rc)
+{
+ switch (rc)
+ {
+ case SMFIR_CHGHEADER:
+ return "chgheader";
+
+ case SMFIR_ADDHEADER:
+ return "addheader";
+
+ case SMFIR_INSHEADER:
+ return "insheader";
+ }
+ return "unknown";
+}
+
+static int
+get_mod_index (struct pmult_priv_data *p, const char *header_name,
+ size_t pos, size_t *pidx)
+{
+ size_t i, count;
+ const char *name;
+
+ if (mu_header_get_field_count (p->hdr, &count))
+ return 1;
+ for (i = 1; i <= count; i++)
+ {
+ if (mu_header_sget_field_name (p->hdr, i, &name))
+ continue;
+ if (strcmp (name, header_name) == 0 && --pos == 0)
+ {
+ *pidx = i - 1;
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int
+cb_reply (gacopyz_srv_t gsrv, int cmd, int rcmd, void *data)
+{
+ char *buf;
+ size_t size;
+ char *cp;
+ char *header;
+ uint type;
+ uint32_t idx = 0;
+ pmse_ctx_P pmse_ctx = data;
+ struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
+
+ switch (rcmd)
+ {
+ case SMFIR_CHGHEADER:
+ case SMFIR_ADDHEADER:
+ case SMFIR_INSHEADER:
+ gacopyz_srv_reply (gsrv, &buf, &size);
+ if (rcmd != SMFIR_ADDHEADER)
+ {
+ idx = ntohl (*(uint32_t*)buf);
+ buf += sizeof (uint32_t);
+ }
+ cp = memchr (buf, 0, size);
+ if (!cp)
+ {
+ mu_diag_output (MU_DIAG_NOTICE, "Malformed CHGHEADER command");
+ break;
+ }
+ cp++;
+ PMU_DEBUG4 (p->debug, MU_DEBUG_TRACE2,
+ "%s=%u '%s','%s'\n",
+ hdrcommname (rcmd), idx, buf, cp);
+
+ header = malloc (size + 5);
+ if (!header)
+ {
+ mu_error ("%s", mu_strerror (ENOMEM));
+ return SMTP_R_ACCEPT;
+ }
+ strcpy (header, buf);
+ strcat (header, ": ");
+ strcat (header, cp);
+ strcat (header, "\r\n");
+
+ switch (rcmd)
+ {
+ case SMFIR_CHGHEADER:
+ {
+ /* Emulate milter semantics */
+ size_t index;
+
+ type = (*cp == 0) ? SM_HDRMOD_T_REMOVE : SM_HDRMOD_T_REPLACE;
+ if (get_mod_index (p, buf, idx, &index))
+ {
+ PMU_DEBUG1 (p->debug, MU_DEBUG_TRACE1,
+ "no such header: %s\n", buf);
+ return 0;
+ }
+ idx = index;
+ }
+ break;
+
+ case SMFIR_ADDHEADER:
+ type = SM_HDRMOD_T_APPEND;
+ break;
+
+ case SMFIR_INSHEADER:
+ type = SM_HDRMOD_T_INSERT;
+ }
+
+ SM_VERBOSE (sm_pmfi_hdr_mod (pmse_ctx, type, idx, header));
+ free (header);
+ }
+ return 0;
+}
+
+#define CRLF "\r\n"
+
+static char *
+trailcrlf (char **pbuf)
+{
+ char *buf = *pbuf;
+ char *retp = NULL;
+ int len;
+
+ if (!buf)
+ *pbuf = CRLF;
+ else if ((len = strlen (buf)) < 2 || memcmp (buf + len - 2, CRLF, 2))
+ {
+ retp = malloc (len + 3);
+ if (!retp)
+ {
+ mu_error ("%s", mu_strerror (ENOMEM));
+ *pbuf = MSG_TEMPFAIL;
+ }
+ strcat (strcpy (retp, buf), CRLF);
+ *pbuf = retp;
+ }
+ return retp;
+}
+
+static int
+pmult_std_reply (struct pmult_priv_data *p, pmse_ctx_P pmse_ctx,
+ gacopyz_srv_t gsrv, int rc,
+ const char *ident, const char *arg)
+{
+ char *buf, *tmp;
+ size_t size;
+ int status;
+
+ if (!arg)
+ arg = "";
+
+ switch (rc)
+ {
+ case SMFIR_CONTINUE:
+ break;
+
+ case SMFIR_REPLYCODE:
+ gacopyz_srv_reply (gsrv, &buf, &size);
+ PMU_DEBUG3 (p->debug, MU_DEBUG_TRACE2,
+ "%s=%s, reply=%s\n",
+ ident, arg, buf);
+ tmp = trailcrlf (&buf);
+ SM_VERBOSE (sm_pmfi_setreply (pmse_ctx, tmp));
+ status = (buf[0] == '4') ? SMTP_R_TEMP : SMTP_R_PERM;
+ free (tmp);
+ return status;
+
+ case SMFIR_REJECT:
+ PMU_DEBUG3 (p->debug, MU_DEBUG_TRACE2,
+ "%s=%s, reply=%s\n",
+ ident, arg, MSG_REJECT);
+ SM_VERBOSE (sm_pmfi_setreply (pmse_ctx, MSG_REJECT));
+ return SMTP_R_PERM;
+
+ case SMFIR_DISCARD:
+ PMU_DEBUG2 (p->debug, MU_DEBUG_TRACE2,
+ "%s=%s, discard\n",
+ ident, arg);
+ return SMTP_R_DISCARD;
+
+ case SMFIR_TEMPFAIL:
+ PMU_DEBUG3 (p->debug, MU_DEBUG_TRACE2,
+ "%s=%s, reply=%s\n",
+ ident, arg, MSG_TEMPFAIL);
+ SM_VERBOSE (sm_pmfi_setreply (pmse_ctx, MSG_TEMPFAIL));
+ return SMTP_R_TEMP;
+
+ case SMFIR_SHUTDOWN:
+ PMU_DEBUG3 (p->debug, MU_DEBUG_TRACE2,
+ "%s=%s, reply=%s\n",
+ ident, arg, MSG_SHUTDOWN);
+ SM_VERBOSE (sm_pmfi_setreply (pmse_ctx, MSG_SHUTDOWN));
+ return SMTP_R_SSD;
+
+ case SMFIR_ADDRCPT:
+ gacopyz_srv_reply (gsrv, &buf, &size);
+ PMU_DEBUG3 (p->debug, MU_DEBUG_TRACE2,
+ "%s=%s, addrcpt=%s\n",
+ ident, arg, buf);
+ SM_VERBOSE (sm_pmfi_rcpt_add (pmse_ctx, buf, NULL));
+ break;
+
+ case SMFIR_DELRCPT:
+ gacopyz_srv_reply (gsrv, &buf, &size);
+ PMU_DEBUG3 (p->debug, MU_DEBUG_TRACE2,
+ "%s=%s, delrcpt=%s\n",
+ ident, arg, buf);
+ /* FIXME: Index is always 0. Should it be 1? */
+ SM_VERBOSE (sm_pmfi_rcpt_del (pmse_ctx, buf, 0));
+ break;
+
+ case SMFIR_CHGHEADER:
+ case SMFIR_ADDHEADER:
+ case SMFIR_INSHEADER:
+ break;
+
+ case SMFIR_REPLBODY:
+ /* FIXME */
+
+ default:
+ if (isascii (rc) && isprint (rc))
+ mu_diag_output (MU_DIAG_WARNING, _("Unsupported reply code: %c (%d)"),
+ rc, rc);
+ else
+ mu_diag_output (MU_DIAG_WARNING, _("Unsupported reply code: (%d)"),
+ rc);
+ }
+ return SMTP_R_CONT;
+}
+
+typedef sfsistat_T (*pmult_runfun_t) (pmse_ctx_P, gacopyz_srv_t, void *);
+
+static sfsistat_T
+pmult_runlist0 (struct pmult_priv_data *p, pmult_runfun_t runfun,
+ pmse_ctx_P pmse_ctx, void *data, char **macros)
+{
+ sfsistat_T rc;
+ mu_iterator_t itr = NULL;
+
+ mu_list_get_iterator (p->srvlist, &itr);
+
+ for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
+ mu_iterator_next (itr))
+ {
+ int i;
+
+ gacopyz_srv_t gsrv;
+
+ mu_iterator_current (itr, (void**)&gsrv);
+
+ if (macros)
+ for (i = 0; macros[i]; i += 2)
+ gacopyz_srv_define_macro (gsrv, macros[i], macros[i+1]);
+
+ rc = runfun (pmse_ctx, gsrv, data);
+ if (rc != SMTP_R_CONT && rc != SMTP_R_OK)
+ break;
+ }
+
+ mu_iterator_destroy (&itr);
+ return rc;
+}
+
+static sfsistat_T
+pmult_runlist (struct pmult_priv_data *p, pmult_runfun_t runfun,
+ pmse_ctx_P pmse_ctx, void *data, char **macros)
+{
+ sfsistat_T rc;
+ PRIV_LOCK (p);
+ rc = pmult_runlist0 (p, runfun, pmse_ctx, data, macros);
+ PRIV_UNLOCK (p);
+ return rc;
+}
+
+void
+pmult_free (struct pmult_priv_data *p)
+{
+ if (!p)
+ return;
+
+ mu_list_destroy (&p->srvlist);
+
+ free (p->taid);
+ free (p->seid);
+ free (p->seid_c);
+
+ mu_stream_destroy (&p->hdrstream, NULL);
+ mu_header_destroy (&p->hdr, mu_header_get_owner (p->hdr));
+ mu_debug_destroy (&p->debug, NULL);
+ pthread_mutex_destroy (&p->mutex);
+ free (p);
+}
+
+void
+pmult_clear (struct pmult_priv_data *p)
+{
+ PRIV_LOCK (p);
+ mu_stream_destroy (&p->hdrstream, NULL);
+ mu_header_destroy (&p->hdr, mu_header_get_owner (p->hdr));
+ p->state = pmult_msg_state_initial;
+ p->nrcpt = 0;
+ p->nbadrcpts = 0;
+ PRIV_UNLOCK (p);
+}
+
+void
+pmult_shutdown (pmse_ctx_P pmse_ctx, struct pmult_priv_data *p)
+{
+ mu_iterator_t itr;
+
+ if (!p)
+ return;
+
+ PRIV_LOCK (p);
+ mu_list_get_iterator (p->srvlist, &itr);
+
+ for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
+ mu_iterator_next (itr))
+ {
+ gacopyz_srv_t gsrv;
+
+ mu_iterator_current (itr, (void**)&gsrv);
+ gacopyz_srv_quit (gsrv);
+ gacopyz_srv_close (gsrv);
+ gacopyz_srv_destroy (&gsrv);
+ }
+ mu_iterator_destroy (&itr);
+ sm_pmfi_set_ctx_se (pmse_ctx, NULL);
+ PRIV_UNLOCK (p);
+ pmult_free (p);
+}
+
+
+static void
+version (FILE *stream, struct argp_state *state)
+{
+ mailfromd_version ("pmult", stream);
+}
+
+
+static sm_ret_T
+pmult_negotiate (pmss_ctx_P pmss_ctx,
+ uint32_t srv_cap, uint32_t srv_fct, uint32_t srv_feat,
+ uint32_t srv_misc, uint32_t *pm_cap, uint32_t *pm_fct,
+ uint32_t *pm_feat, uint32_t *pm_misc)
+{
+ SM_CHECK (sm_pmfi_setmaclist (pmss_ctx, PM_SMST_CONNECT, PMM_SEID, PMM_END));
+ SM_CHECK (sm_pmfi_setmaclist (pmss_ctx, PM_SMST_MAIL, PMM_MAIL_TAID,
+ PMM_END));
+ return SM_SUCCESS;
+
+}
+
+static sfsistat_T
+pmult_connect (pmse_ctx_P pmse_ctx, const char *hostname,
+ sm_sockaddr_T *hostaddr)
+{
+ mu_iterator_t itr;
+ struct pmult_priv_data *p;
+ mu_debug_t dbg = NULL;
+ int status = SMTP_R_CONT;
+ int rc;
+ char *tmp = NULL;
+ char *client_addr = NULL, *client_port = NULL;
+
+ if (debug_level)
+ {
+ mu_debug_create (&dbg, NULL);
+ mu_debug_set_level (dbg, debug_level);
+ }
+
+ if (mu_debug_check_level (dbg, MU_DEBUG_TRACE1))
+ {
+ char *p = mu_sockaddr_to_astr (&hostaddr->sa, sizeof *hostaddr);
+ __PMU_DEBUG2 (dbg, MU_DEBUG_TRACE1,
+ "Connect from: %s, address %s\n", hostname, p);
+ free (p);
+ }
+
+ p = calloc (1, sizeof *p);
+ if (!p)
+ {
+ mu_error ("%s: accept", mu_strerror (ENOMEM));
+ mu_debug_destroy (&dbg, NULL);
+ return SMTP_R_ACCEPT;
+ }
+ p->debug = dbg;
+ pthread_mutex_init (&p->mutex, NULL);
+
+ sm_pmfi_getmac (pmse_ctx, PMM_SEID, &tmp);
+ p->seid = strdup (tmp);
+
+ rc = mu_list_create (&p->srvlist);
+ if (rc)
+ {
+ mu_error ("%smu_list_create: %s", PRIV_SEID_C (p), mu_strerror (rc));
+ mu_debug_destroy (&dbg, NULL);
+ free (p);
+ return SMTP_R_ACCEPT;
+ }
+
+ protect ();
+ mu_list_get_iterator (client_list, &itr);
+
+ client_addr = strdup (inet_ntoa (hostaddr->sin.sin_addr));
+ client_port = strdup (mu_umaxtostr (0, ntohs (hostaddr->sin.sin_port)));
+ unprotect ();
+
+ PRIV_LOCK (p);
+ for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
+ mu_iterator_next (itr))
+ {
+ struct pmult_client *clt;
+ gacopyz_srv_t gsrv;
+
+ mu_iterator_current (itr, (void**)&clt);
+ gacopyz_srv_create (&gsrv, clt->name, clt->url, clt->logmask);
+ gacopyz_srv_set_callback (gsrv, cb_reply);
+ gacopyz_srv_set_callback_data (gsrv, pmse_ctx);
+ gacopyz_srv_set_all_timeouts (gsrv, clt->timeout);
+ if (gacopyz_srv_connect (gsrv) != MI_SUCCESS)
+ {
+ mu_error (_("%sFailed to connect to %s (milter %s)"),
+ PRIV_SEID_C (p), clt->url, clt->name);
+ gacopyz_srv_destroy (&gsrv);
+ continue;
+ }
+ gacopyz_srv_negotiate (gsrv);
+ gacopyz_srv_define_macro (gsrv, "r", "SMTP");
+ gacopyz_srv_define_macro (gsrv, "i", PRIV_SEID (p));
+
+ if (client_addr)
+ gacopyz_srv_define_macro (gsrv, "client_addr", client_addr);
+ gacopyz_srv_define_macro (gsrv, "client_name", hostname);
+ if (client_port)
+ gacopyz_srv_define_macro (gsrv, "client_port", client_port);
+ /* FIXME: client_ptr, client_resolve */
+
+ rc = gacopyz_srv_conn (gsrv, hostname, &hostaddr->sa);
+ status = pmult_std_reply (p, pmse_ctx, gsrv, rc, "connect", hostname);
+ if (status != SMTP_R_CONT)
+ break;
+ mu_list_append (p->srvlist, gsrv);
+ }
+
+ free (client_addr);
+ free (client_port);
+
+ protect ();
+ mu_iterator_destroy (&itr);
+ unprotect ();
+
+ PRIV_UNLOCK (p);
+ if (status == SMTP_R_CONT)
+ sm_pmfi_set_ctx_se (pmse_ctx, p);
+ else
+ pmult_free (p);
+ return status;
+}
+
+static sm_ret_T
+pmult_close (pmse_ctx_P pmse_ctx)
+{
+ struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
+ if (p)
+ {
+ PMU_DEBUG1 (p->debug, MU_DEBUG_TRACE1, "%sClosing connection\n",