summaryrefslogtreecommitdiffstats
path: root/src/milter
diff options
context:
space:
mode:
Diffstat (limited to 'src/milter')
l---------src/milter/.indent.pro1
-rw-r--r--src/milter/Makefile.in145
-rw-r--r--src/milter/milter.c1121
-rw-r--r--src/milter/milter.h223
-rw-r--r--src/milter/milter8.c2911
-rw-r--r--src/milter/milter_macros.c303
-rw-r--r--src/milter/test-list49
-rw-r--r--src/milter/test-milter.c840
8 files changed, 5593 insertions, 0 deletions
diff --git a/src/milter/.indent.pro b/src/milter/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/milter/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/milter/Makefile.in b/src/milter/Makefile.in
new file mode 100644
index 0000000..98e3ba0
--- /dev/null
+++ b/src/milter/Makefile.in
@@ -0,0 +1,145 @@
+SHELL = /bin/sh
+SRCS = milter.c milter8.c milter_macros.c
+OBJS = milter.o milter8.o milter_macros.o
+HDRS = milter.h
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+INCL =
+LIB = libmilter.a
+TESTPROG= milter test-milter
+
+LIBS = ../../$(LIB_PREFIX)global$(LIB_SUFFIX) \
+ ../../$(LIB_PREFIX)util$(LIB_SUFFIX)
+LIB_DIR = ../../lib
+INC_DIR = ../../include
+MAKES =
+
+.c.o:; $(CC) $(CFLAGS) -c $*.c
+
+all: $(LIB)
+
+$(OBJS): ../../conf/makedefs.out
+
+Makefile: Makefile.in
+ cat ../../conf/makedefs.out $? >$@
+
+test: $(TESTPROG)
+
+tests:
+
+root_tests:
+
+$(LIB): $(OBJS)
+ $(_AR) $(ARFL) $(LIB) $?
+ $(_RANLIB) $(LIB)
+
+$(LIB_DIR)/$(LIB): $(LIB)
+ cp $(LIB) $(LIB_DIR)
+ $(_RANLIB) $(LIB_DIR)/$(LIB)
+
+update: $(LIB_DIR)/$(LIB) $(HDRS)
+ -for i in $(HDRS); \
+ do \
+ cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \
+ done
+ cd $(INC_DIR); chmod 644 $(HDRS)
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir printfck
+ cp *.h printfck
+ sed '1,/^# do not edit/!d' Makefile >printfck/Makefile
+ set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done
+ cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o`
+
+lint:
+ lint $(DEFS) $(SRCS) $(LINTFIX)
+
+clean:
+ rm -f *.o $(LIB) *core $(TESTPROG) junk
+ rm -rf printfck
+
+tidy: clean
+
+milter: milter.c $(LIB) $(LIBS)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+test-milter: test-milter.c
+ cc -g -I/usr/local/include -o $@ $? -L/usr/local/lib -lmilter -lpthread
+
+depend: $(MAKES)
+ (sed '1,/^# do not edit/!d' Makefile.in; \
+ set -e; for i in [a-z][a-z0-9]*.c; do \
+ $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \
+ -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \
+ -e 's/o: \.\//o: /' -e p -e '}' ; \
+ done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in
+ @$(EXPORT) make -f Makefile.in Makefile 1>&2
+
+# do not edit below this line - it is generated by 'make depend'
+milter.o: ../../include/argv.h
+milter.o: ../../include/attr.h
+milter.o: ../../include/attr_override.h
+milter.o: ../../include/check_arg.h
+milter.o: ../../include/htable.h
+milter.o: ../../include/iostuff.h
+milter.o: ../../include/mail_params.h
+milter.o: ../../include/mail_proto.h
+milter.o: ../../include/msg.h
+milter.o: ../../include/mymalloc.h
+milter.o: ../../include/nvtable.h
+milter.o: ../../include/rec_type.h
+milter.o: ../../include/record.h
+milter.o: ../../include/stringops.h
+milter.o: ../../include/sys_defs.h
+milter.o: ../../include/vbuf.h
+milter.o: ../../include/vstream.h
+milter.o: ../../include/vstring.h
+milter.o: milter.c
+milter.o: milter.h
+milter8.o: ../../include/argv.h
+milter8.o: ../../include/attr.h
+milter8.o: ../../include/check_arg.h
+milter8.o: ../../include/compat_va_copy.h
+milter8.o: ../../include/connect.h
+milter8.o: ../../include/header_opts.h
+milter8.o: ../../include/htable.h
+milter8.o: ../../include/iostuff.h
+milter8.o: ../../include/is_header.h
+milter8.o: ../../include/mail_params.h
+milter8.o: ../../include/mail_proto.h
+milter8.o: ../../include/mime_state.h
+milter8.o: ../../include/msg.h
+milter8.o: ../../include/mymalloc.h
+milter8.o: ../../include/name_code.h
+milter8.o: ../../include/name_mask.h
+milter8.o: ../../include/nvtable.h
+milter8.o: ../../include/rec_type.h
+milter8.o: ../../include/record.h
+milter8.o: ../../include/split_at.h
+milter8.o: ../../include/stringops.h
+milter8.o: ../../include/sys_defs.h
+milter8.o: ../../include/vbuf.h
+milter8.o: ../../include/vstream.h
+milter8.o: ../../include/vstring.h
+milter8.o: milter.h
+milter8.o: milter8.c
+milter_macros.o: ../../include/argv.h
+milter_macros.o: ../../include/attr.h
+milter_macros.o: ../../include/check_arg.h
+milter_macros.o: ../../include/htable.h
+milter_macros.o: ../../include/iostuff.h
+milter_macros.o: ../../include/mail_proto.h
+milter_macros.o: ../../include/msg.h
+milter_macros.o: ../../include/mymalloc.h
+milter_macros.o: ../../include/nvtable.h
+milter_macros.o: ../../include/sys_defs.h
+milter_macros.o: ../../include/vbuf.h
+milter_macros.o: ../../include/vstream.h
+milter_macros.o: ../../include/vstring.h
+milter_macros.o: milter.h
+milter_macros.o: milter_macros.c
+test-milter.o: test-milter.c
diff --git a/src/milter/milter.c b/src/milter/milter.c
new file mode 100644
index 0000000..dfd5e1c
--- /dev/null
+++ b/src/milter/milter.c
@@ -0,0 +1,1121 @@
+/*++
+/* NAME
+/* milter 3
+/* SUMMARY
+/* generic MTA-side mail filter interface
+/* SYNOPSIS
+/* #include <milter.h>
+/*
+/* MILTERS *milter_create(milter_names, conn_timeout, cmd_timeout,
+/* msg_timeout, protocol, def_action,
+/* conn_macros, helo_macros,
+/* mail_macros, rcpt_macros,
+/* data_macros, eoh_macros,
+/* eod_macros, unk_macros,
+/* macro_deflts)
+/* const char *milter_names;
+/* int conn_timeout;
+/* int cmd_timeout;
+/* int msg_timeout;
+/* const char *protocol;
+/* const char *def_action;
+/* const char *conn_macros;
+/* const char *helo_macros;
+/* const char *mail_macros;
+/* const char *rcpt_macrps;
+/* const char *data_macros;
+/* const char *eoh_macros;
+/* const char *eod_macros;
+/* const char *unk_macros;
+/* const char *macro_deflts;
+/*
+/* void milter_free(milters)
+/* MILTERS *milters;
+/*
+/* void milter_macro_callback(milters, mac_lookup, mac_context)
+/* const char *(*mac_lookup)(const char *name, void *context);
+/* void *mac_context;
+/*
+/* void milter_edit_callback(milters, add_header, upd_header,
+/* ins_header, del_header, chg_from,
+/* add_rcpt, add_rcpt_par, del_rcpt,
+/* repl_body, context)
+/* MILTERS *milters;
+/* MILTER_ADD_HEADER_FN add_header;
+/* MILTER_EDIT_HEADER_FN upd_header;
+/* MILTER_EDIT_HEADER_FN ins_header;
+/* MILTER_DEL_HEADER_FN del_header;
+/* MILTER_EDIT_FROM_FN chg_from;
+/* MILTER_EDIT_RCPT_FN add_rcpt;
+/* MILTER_EDIT_RCPT_PAR_FN add_rcpt_par;
+/* MILTER_EDIT_RCPT_FN del_rcpt;
+/* MILTER_EDIT_BODY_FN repl_body;
+/* void *context;
+/*
+/* const char *milter_conn_event(milters, client_name, client_addr,
+/* client_port, addr_family)
+/* MILTERS *milters;
+/* const char *client_name;
+/* const char *client_addr;
+/* const char *client_port;
+/* int addr_family;
+/*
+/* const char *milter_disc_event(milters)
+/* MILTERS *milters;
+/*
+/* const char *milter_helo_event(milters, helo_name, esmtp_flag)
+/* MILTERS *milters;
+/* const char *helo_name;
+/* int esmtp_flag;
+/*
+/* const char *milter_mail_event(milters, argv)
+/* MILTERS *milters;
+/* const char **argv;
+/*
+/* const char *milter_rcpt_event(milters, flags, argv)
+/* MILTERS *milters;
+/* int flags;
+/* const char **argv;
+/*
+/* const char *milter_data_event(milters)
+/* MILTERS *milters;
+/*
+/* const char *milter_unknown_event(milters, command)
+/* MILTERS *milters;
+/* const char *command;
+/*
+/* const char *milter_other_event(milters)
+/* MILTERS *milters;
+/*
+/* const char *milter_message(milters, qfile, data_offset, auto_hdrs)
+/* MILTERS *milters;
+/* VSTREAM *qfile;
+/* off_t data_offset;
+/* ARGV *auto_hdrs;
+/*
+/* const char *milter_abort(milters)
+/* MILTERS *milters;
+/*
+/* int milter_send(milters, fp)
+/* MILTERS *milters;
+/* VSTREAM *fp;
+/*
+/* MILTERS *milter_receive(fp, count)
+/* VSTREAM *fp;
+/* int count;
+/*
+/* int milter_dummy(milters, fp)
+/* MILTERS *milters;
+/* VSTREAM *fp;
+/* DESCRIPTION
+/* The functions in this module manage one or more milter (mail
+/* filter) clients. Currently, only the Sendmail 8 filter
+/* protocol is supported.
+/*
+/* The functions that inspect content or envelope commands
+/* return either an SMTP reply ([45]XX followed by enhanced
+/* status code and text), "D" (discard), "H" (quarantine),
+/* "S" (shutdown connection), or a null pointer, which means
+/* "no news is good news".
+/*
+/* milter_create() instantiates the milter clients specified
+/* with the milter_names argument. The conn_macros etc.
+/* arguments specify the names of macros that are sent to the
+/* mail filter applications upon a connect etc. event, and the
+/* macro_deflts argument specifies macro defaults that will be used
+/* only if the application's lookup call-back returns null. This
+/* function should be called during process initialization,
+/* before entering a chroot jail. The timeout parameters specify
+/* time limits for the completion of the specified request
+/* classes. The protocol parameter specifies a protocol version
+/* and optional extensions. When the milter application is
+/* unavailable, the milter client will go into a suitable error
+/* state as specified with the def_action parameter (i.e.
+/* reject, tempfail or accept all subsequent events).
+/*
+/* milter_free() disconnects from the milter instances that
+/* are still opened, and destroys the data structures created
+/* by milter_create(). This function is safe to call at any
+/* point after milter_create().
+/*
+/* milter_macro_callback() specifies a call-back function and
+/* context for macro lookup. This function must be called
+/* before milter_conn_event().
+/*
+/* milter_edit_callback() specifies call-back functions and
+/* context for editing the queue file after the end-of-data
+/* is received. This function must be called before milter_message();
+/*
+/* milter_conn_event() reports an SMTP client connection event
+/* to the specified milter instances, after sending the macros
+/* specified with the milter_create() conn_macros argument.
+/* This function must be called before reporting any other
+/* events.
+/*
+/* milter_disc_event() reports an SMTP client disconnection
+/* event to the specified milter instances. No events can
+/* reported after this call. To simplify usage, redundant calls
+/* of this function are NO-OPs and don't raise a run-time
+/* error.
+/*
+/* milter_helo_event() reports a HELO or EHLO event to the
+/* specified milter instances, after sending the macros that
+/* were specified with the milter_create() helo_macros argument.
+/*
+/* milter_mail_event() reports a MAIL FROM event to the specified
+/* milter instances, after sending the macros that were specified
+/* with the milter_create() mail_macros argument.
+/*
+/* milter_rcpt_event() reports an RCPT TO event to the specified
+/* milter instances, after sending the macros that were specified
+/* with the milter_create() rcpt_macros argument. The flags
+/* argument supports the following:
+/* .IP MILTER_FLAG_WANT_RCPT_REJ
+/* When this flag is cleared, invoke all milters. When this
+/* flag is set, invoke only milters that want to receive
+/* rejected recipients; with Sendmail V8 Milters, {rcpt_mailer}
+/* is set to "error", {rcpt_host} is set to an enhanced status
+/* code, and {rcpt_addr} is set to descriptive text.
+/* .PP
+/* milter_data_event() reports a DATA event to the specified
+/* milter instances, after sending the macros that were specified
+/* with the milter_create() data_macros argument.
+/*
+/* milter_unknown_event() reports an unknown command event to
+/* the specified milter instances, after sending the macros
+/* that were specified with the milter_create() unk_macros
+/* argument.
+/*
+/* milter_other_event() returns the current default mail filter
+/* reply for the current SMTP connection state; it does not
+/* change milter states. A null pointer result means that all
+/* is well. This function can be used for SMTP commands such
+/* as AUTH, STARTTLS that don't have their own milter event
+/* routine.
+/*
+/* milter_message() sends the message header and body to the
+/* to the specified milter instances, and sends the macros
+/* specified with the milter_create() eoh_macros after the
+/* message header, and with the eod_macros argument at
+/* the end. Each milter sees the result of any changes made
+/* by a preceding milter. This function must be called with
+/* as argument an open Postfix queue file.
+/*
+/* milter_abort() cancels a mail transaction in progress. To
+/* simplify usage, redundant calls of this function are NO-OPs
+/* and don't raise a run-time error.
+/*
+/* milter_send() sends a list of mail filters over the specified
+/* stream. When given a null list pointer, a "no filter"
+/* indication is sent. The result is non-zero in case of
+/* error.
+/*
+/* milter_receive() receives the specified number of mail
+/* filters over the specified stream. The result is a null
+/* pointer when no milters were sent, or when an error happened.
+/*
+/* milter_dummy() is like milter_send(), except that it sends
+/* a dummy, but entirely valid, mail filter list.
+/* SEE ALSO
+/* milter8(3) Sendmail 8 Milter protocol
+/* DIAGNOSTICS
+/* Panic: interface violation.
+/* Fatal errors: memory allocation problem.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <argv.h>
+#include <attr.h>
+#include <htable.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <record.h>
+#include <rec_type.h>
+#include <mail_params.h>
+#include <attr_override.h>
+
+/* Postfix Milter library. */
+
+#include <milter.h>
+
+/* Application-specific. */
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+
+/* milter_macro_defaults_create - parse default macro entries */
+
+HTABLE *milter_macro_defaults_create(const char *macro_defaults)
+{
+ const char myname[] = "milter_macro_defaults_create";
+ char *saved_defaults = mystrdup(macro_defaults);
+ char *cp = saved_defaults;
+ HTABLE *table = 0;
+ VSTRING *canon_buf = 0;
+ char *nameval;
+
+ while ((nameval = mystrtokq(&cp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) {
+ const char *err;
+ char *name;
+ char *value;
+
+ /*
+ * Split the input into (name, value) pairs. Allow the forms
+ * name=value and { name = value }, where the last form ignores
+ * whitespace after the opening "{", around the "=", and before the
+ * closing "}". A name may also be specified as {name}.
+ *
+ * Use the form {name} for table lookups, because that is the form of
+ * the S8_MAC_* macro names.
+ */
+ if (*nameval == CHARS_BRACE[0]
+ && nameval[balpar(nameval, CHARS_BRACE)] != '='
+ && (err = extpar(&nameval, CHARS_BRACE, EXTPAR_FLAG_NONE)) != 0)
+ msg_fatal("malformed default macro entry: %s in \"%s\"",
+ err, macro_defaults);
+ if ((err = split_nameval(nameval, &name, &value)) != 0)
+ msg_fatal("malformed default macro entry: %s in \"%s\"",
+ err, macro_defaults);
+ if (*name != '{') /* } */
+ name = STR(vstring_sprintf(canon_buf ? canon_buf :
+ (canon_buf = vstring_alloc(20)), "{%s}", name));
+ if (table == 0)
+ table = htable_create(1);
+ if (htable_find(table, name) != 0) {
+ msg_warn("ignoring multiple default macro entries for %s in \"%s\"",
+ name, macro_defaults);
+ } else {
+ (void) htable_enter(table, name, mystrdup(value));
+ if (msg_verbose)
+ msg_info("%s: add name=%s default=%s", myname, name, value);
+ }
+ }
+ myfree(saved_defaults);
+ if (canon_buf)
+ vstring_free(canon_buf);
+ return (table);
+}
+
+/* milter_macro_lookup - look up macros */
+
+static ARGV *milter_macro_lookup(MILTERS *milters, const char *macro_names)
+{
+ const char *myname = "milter_macro_lookup";
+ char *saved_names = mystrdup(macro_names);
+ char *cp = saved_names;
+ ARGV *argv = argv_alloc(10);
+ VSTRING *canon_buf = vstring_alloc(20);
+ const char *value;
+ const char *name;
+ const char *cname;
+
+ while ((name = mystrtok(&cp, CHARS_COMMA_SP)) != 0) {
+ if (msg_verbose)
+ msg_info("%s: \"%s\"", myname, name);
+ if (*name != '{') /* } */
+ cname = STR(vstring_sprintf(canon_buf, "{%s}", name));
+ else
+ cname = name;
+ if ((value = milters->mac_lookup(cname, milters->mac_context)) != 0) {
+ if (msg_verbose)
+ msg_info("%s: result \"%s\"", myname, value);
+ argv_add(argv, name, value, (char *) 0);
+ } else if (milters->macro_defaults != 0
+ && (value = htable_find(milters->macro_defaults, cname)) != 0) {
+ if (msg_verbose)
+ msg_info("%s: using default \"%s\"", myname, value);
+ argv_add(argv, name, value, (char *) 0);
+ }
+ }
+ myfree(saved_names);
+ vstring_free(canon_buf);
+ return (argv);
+}
+
+/* milter_macro_callback - specify macro lookup */
+
+void milter_macro_callback(MILTERS *milters,
+ const char *(*mac_lookup) (const char *, void *),
+ void *mac_context)
+{
+ milters->mac_lookup = mac_lookup;
+ milters->mac_context = mac_context;
+}
+
+/* milter_edit_callback - specify queue file edit call-back information */
+
+void milter_edit_callback(MILTERS *milters,
+ MILTER_ADD_HEADER_FN add_header,
+ MILTER_EDIT_HEADER_FN upd_header,
+ MILTER_EDIT_HEADER_FN ins_header,
+ MILTER_DEL_HEADER_FN del_header,
+ MILTER_EDIT_FROM_FN chg_from,
+ MILTER_EDIT_RCPT_FN add_rcpt,
+ MILTER_EDIT_RCPT_PAR_FN add_rcpt_par,
+ MILTER_EDIT_RCPT_FN del_rcpt,
+ MILTER_EDIT_BODY_FN repl_body,
+ void *chg_context)
+{
+ milters->add_header = add_header;
+ milters->upd_header = upd_header;
+ milters->ins_header = ins_header;
+ milters->del_header = del_header;
+ milters->chg_from = chg_from;
+ milters->add_rcpt = add_rcpt;
+ milters->add_rcpt_par = add_rcpt_par;
+ milters->del_rcpt = del_rcpt;
+ milters->repl_body = repl_body;
+ milters->chg_context = chg_context;
+}
+
+/* milter_conn_event - report connect event */
+
+const char *milter_conn_event(MILTERS *milters,
+ const char *client_name,
+ const char *client_addr,
+ const char *client_port,
+ unsigned addr_family)
+{
+ const char *resp;
+ MILTER *m;
+ ARGV *global_macros = 0;
+ ARGV *any_macros;
+
+#define MILTER_MACRO_EVAL(global_macros, m, milters, member) \
+ ((m->macros && m->macros->member[0]) ? \
+ milter_macro_lookup(milters, m->macros->member) : \
+ global_macros ? global_macros : \
+ (global_macros = \
+ milter_macro_lookup(milters, milters->macros->member)))
+
+ if (msg_verbose)
+ msg_info("report connect to all milters");
+ for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
+ if (m->connect_on_demand != 0)
+ m->connect_on_demand(m);
+ any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, conn_macros);
+ resp = m->conn_event(m, client_name, client_addr, client_port,
+ addr_family, any_macros);
+ if (any_macros != global_macros)
+ argv_free(any_macros);
+ }
+ if (global_macros)
+ argv_free(global_macros);
+ return (resp);
+}
+
+/* milter_helo_event - report helo event */
+
+const char *milter_helo_event(MILTERS *milters, const char *helo_name,
+ int esmtp_flag)
+{
+ const char *resp;
+ MILTER *m;
+ ARGV *global_macros = 0;
+ ARGV *any_macros;
+
+ if (msg_verbose)
+ msg_info("report helo to all milters");
+ for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
+ any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, helo_macros);
+ resp = m->helo_event(m, helo_name, esmtp_flag, any_macros);
+ if (any_macros != global_macros)
+ argv_free(any_macros);
+ }
+ if (global_macros)
+ argv_free(global_macros);
+ return (resp);
+}
+
+/* milter_mail_event - report mail from event */
+
+const char *milter_mail_event(MILTERS *milters, const char **argv)
+{
+ const char *resp;
+ MILTER *m;
+ ARGV *global_macros = 0;
+ ARGV *any_macros;
+
+ if (msg_verbose)
+ msg_info("report sender to all milters");
+ for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
+ any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, mail_macros);
+ resp = m->mail_event(m, argv, any_macros);
+ if (any_macros != global_macros)
+ argv_free(any_macros);
+ }
+ if (global_macros)
+ argv_free(global_macros);
+ return (resp);
+}
+
+/* milter_rcpt_event - report rcpt to event */
+
+const char *milter_rcpt_event(MILTERS *milters, int flags, const char **argv)
+{
+ const char *resp;
+ MILTER *m;
+ ARGV *global_macros = 0;
+ ARGV *any_macros;
+
+ if (msg_verbose)
+ msg_info("report recipient to all milters (flags=0x%x)", flags);
+ for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
+ if ((flags & MILTER_FLAG_WANT_RCPT_REJ) == 0
+ || (m->flags & MILTER_FLAG_WANT_RCPT_REJ) != 0) {
+ any_macros =
+ MILTER_MACRO_EVAL(global_macros, m, milters, rcpt_macros);
+ resp = m->rcpt_event(m, argv, any_macros);
+ if (any_macros != global_macros)
+ argv_free(any_macros);
+ }
+ }
+ if (global_macros)
+ argv_free(global_macros);
+ return (resp);
+}
+
+/* milter_data_event - report data event */
+
+const char *milter_data_event(MILTERS *milters)
+{
+ const char *resp;
+ MILTER *m;
+ ARGV *global_macros = 0;
+ ARGV *any_macros;
+
+ if (msg_verbose)
+ msg_info("report data to all milters");
+ for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
+ any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, data_macros);
+ resp = m->data_event(m, any_macros);
+ if (any_macros != global_macros)
+ argv_free(any_macros);
+ }
+ if (global_macros)
+ argv_free(global_macros);
+ return (resp);
+}
+
+/* milter_unknown_event - report unknown command */
+
+const char *milter_unknown_event(MILTERS *milters, const char *command)
+{
+ const char *resp;
+ MILTER *m;
+ ARGV *global_macros = 0;
+ ARGV *any_macros;
+
+ if (msg_verbose)
+ msg_info("report unknown command to all milters");
+ for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
+ any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, unk_macros);
+ resp = m->unknown_event(m, command, any_macros);
+ if (any_macros != global_macros)
+ argv_free(any_macros);
+ }
+ if (global_macros)
+ argv_free(global_macros);
+ return (resp);
+}
+
+/* milter_other_event - other SMTP event */
+
+const char *milter_other_event(MILTERS *milters)
+{
+ const char *resp;
+ MILTER *m;
+
+ if (msg_verbose)
+ msg_info("query milter states for other event");
+ for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next)
+ resp = m->other_event(m);
+ return (resp);
+}
+
+/* milter_message - inspect message content */
+
+const char *milter_message(MILTERS *milters, VSTREAM *fp, off_t data_offset,
+ ARGV *auto_hdrs)
+{
+ const char *resp;
+ MILTER *m;
+ ARGV *global_eoh_macros = 0;
+ ARGV *global_eod_macros = 0;
+ ARGV *any_eoh_macros;
+ ARGV *any_eod_macros;
+
+ if (msg_verbose)
+ msg_info("inspect content by all milters");
+ for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
+ any_eoh_macros = MILTER_MACRO_EVAL(global_eoh_macros, m, milters, eoh_macros);
+ any_eod_macros = MILTER_MACRO_EVAL(global_eod_macros, m, milters, eod_macros);
+ resp = m->message(m, fp, data_offset, any_eoh_macros, any_eod_macros,
+ auto_hdrs);
+ if (any_eoh_macros != global_eoh_macros)
+ argv_free(any_eoh_macros);
+ if (any_eod_macros != global_eod_macros)
+ argv_free(any_eod_macros);
+ }
+ if (global_eoh_macros)
+ argv_free(global_eoh_macros);
+ if (global_eod_macros)
+ argv_free(global_eod_macros);
+ return (resp);
+}
+
+/* milter_abort - cancel message receiving state, all milters */
+
+void milter_abort(MILTERS *milters)
+{
+ MILTER *m;
+
+ if (msg_verbose)
+ msg_info("abort all milters");
+ for (m = milters->milter_list; m != 0; m = m->next)
+ m->abort(m);
+}
+
+/* milter_disc_event - report client disconnect event to all milters */
+
+void milter_disc_event(MILTERS *milters)
+{
+ MILTER *m;
+
+ if (msg_verbose)
+ msg_info("disconnect event to all milters");
+ for (m = milters->milter_list; m != 0; m = m->next)
+ m->disc_event(m);
+}
+
+ /*
+ * Table-driven parsing of main.cf parameter overrides for specific Milters.
+ * We derive the override names from the corresponding main.cf parameter
+ * names by skipping the redundant "milter_" prefix.
+ */
+static ATTR_OVER_TIME time_table[] = {
+ 7 + (const char *) VAR_MILT_CONN_TIME, DEF_MILT_CONN_TIME, 0, 1, 0,
+ 7 + (const char *) VAR_MILT_CMD_TIME, DEF_MILT_CMD_TIME, 0, 1, 0,
+ 7 + (const char *) VAR_MILT_MSG_TIME, DEF_MILT_MSG_TIME, 0, 1, 0,
+ 0,
+};
+static ATTR_OVER_STR str_table[] = {
+ 7 + (const char *) VAR_MILT_PROTOCOL, 0, 1, 0,
+ 7 + (const char *) VAR_MILT_DEF_ACTION, 0, 1, 0,
+ 0,
+};
+
+#define link_override_table_to_variable(table, var) \
+ do { table[var##_offset].target = &var; } while (0)
+
+#define my_conn_timeout_offset 0
+#define my_cmd_timeout_offset 1
+#define my_msg_timeout_offset 2
+
+#define my_protocol_offset 0
+#define my_def_action_offset 1
+
+/* milter_new - create milter list */
+
+MILTERS *milter_new(const char *names,
+ int conn_timeout,
+ int cmd_timeout,
+ int msg_timeout,
+ const char *protocol,
+ const char *def_action,
+ MILTER_MACROS *macros,
+ HTABLE *macro_defaults)
+{
+ MILTERS *milters;
+ MILTER *head = 0;
+ MILTER *tail = 0;
+ char *name;
+ MILTER *milter;
+ const char *sep = CHARS_COMMA_SP;
+ const char *parens = CHARS_BRACE;
+ int my_conn_timeout;
+ int my_cmd_timeout;
+ int my_msg_timeout;
+ const char *my_protocol;
+ const char *my_def_action;
+
+ /*
+ * Initialize.
+ */
+ link_override_table_to_variable(time_table, my_conn_timeout);
+ link_override_table_to_variable(time_table, my_cmd_timeout);
+ link_override_table_to_variable(time_table, my_msg_timeout);
+ link_override_table_to_variable(str_table, my_protocol);
+ link_override_table_to_variable(str_table, my_def_action);
+
+ /*
+ * Parse the milter list.
+ */
+ milters = (MILTERS *) mymalloc(sizeof(*milters));
+ if (names != 0 && *names != 0) {
+ char *saved_names = mystrdup(names);
+ char *cp = saved_names;
+ char *op;
+ char *err;
+
+ /*
+ * Instantiate Milters, allowing for per-Milter overrides.
+ */
+ while ((name = mystrtokq(&cp, sep, parens)) != 0) {
+ my_conn_timeout = conn_timeout;
+ my_cmd_timeout = cmd_timeout;
+ my_msg_timeout = msg_timeout;
+ my_protocol = protocol;
+ my_def_action = def_action;
+ if (name[0] == parens[0]) {
+ op = name;
+ if ((err = extpar(&op, parens, EXTPAR_FLAG_NONE)) != 0)
+ msg_fatal("milter service syntax error: %s", err);
+ if ((name = mystrtok(&op, sep)) == 0)
+ msg_fatal("empty milter definition: \"%s\"", names);
+ attr_override(op, sep, parens,
+ CA_ATTR_OVER_STR_TABLE(str_table),
+ CA_ATTR_OVER_TIME_TABLE(time_table),
+ CA_ATTR_OVER_END);
+ }
+ milter = milter8_create(name, my_conn_timeout, my_cmd_timeout,
+ my_msg_timeout, my_protocol,
+ my_def_action, milters);
+ if (head == 0) {
+ head = milter;
+ } else {
+ tail->next = milter;
+ }
+ tail = milter;
+ }
+ myfree(saved_names);
+ }
+ milters->milter_list = head;
+ milters->mac_lookup = 0;
+ milters->mac_context = 0;
+ milters->macros = macros;
+ milters->macro_defaults = macro_defaults;
+ milters->add_header = 0;
+ milters->upd_header = milters->ins_header = 0;
+ milters->del_header = 0;
+ milters->add_rcpt = milters->del_rcpt = 0;
+ milters->repl_body = 0;
+ milters->chg_context = 0;
+ return (milters);
+}
+
+/* milter_free - destroy all milters */
+
+void milter_free(MILTERS *milters)
+{
+ MILTER *m;
+ MILTER *next;
+
+ if (msg_verbose)
+ msg_info("free all milters");
+ for (m = milters->milter_list; m != 0; m = next)
+ next = m->next, m->free(m);
+ if (milters->macros)
+ milter_macros_free(milters->macros);
+ if (milters->macro_defaults)
+ htable_free(milters->macro_defaults, myfree);
+ myfree((void *) milters);
+}
+
+/* milter_dummy - send empty milter list */
+
+int milter_dummy(MILTERS *milters, VSTREAM *stream)
+{
+ MILTERS dummy = *milters;
+
+ dummy.milter_list = 0;
+ return (milter_send(&dummy, stream));
+}
+
+/* milter_send - send Milter instances over stream */
+
+int milter_send(MILTERS *milters, VSTREAM *stream)
+{
+ MILTER *m;
+ int status = 0;
+ int count = 0;
+
+ /*
+ * XXX Optimization: send only the filters that are actually used in the
+ * remote process. No point sending a filter that looks at HELO commands
+ * to a cleanup server. For now we skip only the filters that are known
+ * to be disabled (either in global error state or in global accept
+ * state).
+ *
+ * XXX We must send *some* information, even when there are no active
+ * filters, otherwise the cleanup server would try to apply its own
+ * non_smtpd_milters settings.
+ */
+ if (milters != 0)
+ for (m = milters->milter_list; m != 0; m = m->next)
+ if (m->active(m))
+ count++;
+ (void) rec_fprintf(stream, REC_TYPE_MILT_COUNT, "%d", count);
+
+ if (msg_verbose)
+ msg_info("send %d milters", count);
+
+ /*
+ * XXX Optimization: don't send or receive further information when there
+ * aren't any active filters.
+ */
+ if (count <= 0)
+ return (0);
+
+ /*
+ * Send the filter macro name lists.
+ */
+ (void) attr_print(stream, ATTR_FLAG_MORE,
+ SEND_ATTR_FUNC(milter_macros_print,
+ (const void *) milters->macros),
+ ATTR_TYPE_END);
+
+ /*
+ * Send the filter macro defaults.
+ */
+ count = milters->macro_defaults ? milters->macro_defaults->used : 0;
+ (void) attr_print(stream, ATTR_FLAG_MORE,
+ SEND_ATTR_INT(MAIL_ATTR_SIZE, count),
+ ATTR_TYPE_END);
+ if (count > 0)
+ (void) attr_print(stream, ATTR_FLAG_MORE,
+ SEND_ATTR_HASH(milters->macro_defaults),
+ ATTR_TYPE_END);
+
+ /*
+ * Send the filter instances.
+ */
+ for (m = milters->milter_list; m != 0; m = m->next)
+ if (m->active(m) && (status = m->send(m, stream)) != 0)
+ break;
+
+ /*
+ * Over to you.
+ */
+ if (status != 0
+ || attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+ ATTR_TYPE_END) != 1
+ || status != 0) {
+ msg_warn("cannot send milters to service %s", VSTREAM_PATH(stream));
+ return (-1);
+ }
+ return (0);
+}
+
+/* milter_receive - receive milters from stream */
+
+MILTERS *milter_receive(VSTREAM *stream, int count)
+{
+ MILTERS *milters;
+ MILTER *head = 0;
+ MILTER *tail = 0;
+ MILTER *milter = 0;
+ int macro_default_count;
+
+ if (msg_verbose)
+ msg_info("receive %d milters", count);
+
+ /*
+ * XXX We must instantiate a MILTERS structure even when the sender has
+ * no active filters, otherwise the cleanup server would try to use its
+ * own non_smtpd_milters settings.
+ */
+#define NO_MILTERS ((char *) 0)
+#define NO_TIMEOUTS 0, 0, 0
+#define NO_PROTOCOL ((char *) 0)
+#define NO_ACTION ((char *) 0)
+#define NO_MACROS ((MILTER_MACROS *) 0)
+#define NO_MACRO_DEFLTS ((HTABLE *) 0)
+
+ milters = milter_new(NO_MILTERS, NO_TIMEOUTS, NO_PROTOCOL, NO_ACTION,
+ NO_MACROS, NO_MACRO_DEFLTS);
+
+ /*
+ * XXX Optimization: don't send or receive further information when there
+ * aren't any active filters.
+ */
+ if (count <= 0)
+ return (milters);
+
+ /*
+ * Receive the global macro name lists.
+ */
+ milters->macros = milter_macros_alloc(MILTER_MACROS_ALLOC_ZERO);
+ if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE,
+ RECV_ATTR_FUNC(milter_macros_scan,
+ (void *) milters->macros),
+ ATTR_TYPE_END) != 1) {
+ milter_free(milters);
+ return (0);
+ }
+
+ /*
+ * Receive the filter macro defaults.
+ */
+ if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE,
+ RECV_ATTR_INT(MAIL_ATTR_SIZE, &macro_default_count),
+ ATTR_TYPE_END) != 1
+ || (macro_default_count > 0
+ && attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE,
+ RECV_ATTR_HASH(milters->macro_defaults
+ = htable_create(1)),
+ ATTR_TYPE_END) != macro_default_count)) {
+ milter_free(milters);
+ return (0);
+ }
+
+ /*
+ * Receive the filters.
+ */
+ for (; count > 0; count--) {
+ if ((milter = milter8_receive(stream, milters)) == 0) {
+ msg_warn("cannot receive milters via service %s socket",
+ VSTREAM_PATH(stream));
+ milter_free(milters);
+ return (0);
+ }
+ if (head == 0) {
+ /* Coverity: milter_free() depends on milters->milter_list. */
+ milters->milter_list = head = milter;
+ } else {
+ tail->next = milter;
+ }
+ tail = milter;
+ }
+
+ /*
+ * Over to you.
+ */
+ (void) attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, 0),
+ ATTR_TYPE_END);
+ return (milters);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program. This can be used interactively, but is
+ * typically used for automated regression tests from a script.
+ */
+
+/* System library. */
+
+#include <sys/socket.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include "msg_vstream.h"
+#include "vstring_vstream.h"
+
+/* Global library. */
+
+#include <mail_params.h>
+
+int var_milt_conn_time = 10;
+int var_milt_cmd_time = 10;
+int var_milt_msg_time = 100;
+char *var_milt_protocol = DEF_MILT_PROTOCOL;
+char *var_milt_def_action = DEF_MILT_DEF_ACTION;
+
+static void usage(void)
+{
+ vstream_fprintf(VSTREAM_ERR, "usage: \n"
+ " create names... create and connect\n"
+#if 0
+ " conn_macros names... define connect macros\n"
+ " helo_macros names... define helo command macros\n"
+ " mail_macros names... define mail command macros\n"
+ " rcpt_macros names... define rcpt command macros\n"
+ " data_macros names... define data command macros\n"
+ " unk_macros names... unknown command macros\n"
+ " message_macros names... define message macros\n"
+#endif
+ " free disconnect and destroy\n"
+ " connect name addr port family\n"
+ " helo hostname\n"
+ " ehlo hostname\n"
+ " mail from sender...\n"
+ " rcpt to recipient...\n"
+ " data\n"
+ " disconnect\n"
+ " unknown command\n");
+ vstream_fflush(VSTREAM_ERR);
+}
+
+int main(int argc, char **argv)
+{
+ MILTERS *milters = 0;
+ char *conn_macros, *helo_macros, *mail_macros, *rcpt_macros;
+ char *data_macros, *eoh_macros, *eod_macros, *unk_macros;
+ char *macro_deflts;
+ VSTRING *inbuf = vstring_alloc(100);
+ char *bufp;
+ char *cmd;
+ int ch;
+ int istty = isatty(vstream_fileno(VSTREAM_IN));
+
+ conn_macros = helo_macros = mail_macros = rcpt_macros = data_macros
+ = eoh_macros = eod_macros = unk_macros = macro_deflts = "";
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ while ((ch = GETOPT(argc, argv, "V:v")) > 0) {
+ switch (ch) {
+ default:
+ msg_fatal("usage: %s [-a action] [-p protocol] [-v]", argv[0]);
+ case 'a':
+ var_milt_def_action = optarg;
+ break;
+ case 'p':
+ var_milt_protocol = optarg;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ }
+ }
+ optind = OPTIND;
+
+ for (;;) {
+ const char *resp = 0;
+ ARGV *argv;
+ char **args;
+
+ if (istty) {
+ vstream_printf("- ");
+ vstream_fflush(VSTREAM_OUT);
+ }
+ if (vstring_fgets_nonl(inbuf, VSTREAM_IN) <= 0)
+ break;
+ bufp = vstring_str(inbuf);
+ if (!istty) {
+ vstream_printf("> %s\n", bufp);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ if (*bufp == '#')
+ continue;
+ cmd = mystrtok(&bufp, " ");
+ if (cmd == 0) {
+ usage();
+ continue;
+ }
+ argv = argv_split(bufp, " ");
+ args = argv->argv;
+ if (strcmp(cmd, "create") == 0 && argv->argc == 1) {
+ if (milters != 0) {
+ msg_warn("deleting existing milters");
+ milter_free(milters);
+ }
+ milters = milter_create(args[0], var_milt_conn_time,
+ var_milt_cmd_time, var_milt_msg_time,
+ var_milt_protocol, var_milt_def_action,
+ conn_macros, helo_macros, mail_macros,
+ rcpt_macros, data_macros, eoh_macros,
+ eod_macros, unk_macros, macro_deflts);
+ } else if (strcmp(cmd, "free") == 0 && argv->argc == 0) {
+ if (milters == 0) {
+ msg_warn("no milters");
+ continue;
+ }
+ milter_free(milters);
+ milters = 0;
+ } else if (strcmp(cmd, "connect") == 0 && argv->argc == 4) {
+ if (milters == 0) {
+ msg_warn("no milters");
+ continue;
+ }
+ resp = milter_conn_event(milters, args[0], args[1], args[2],
+ strcmp(args[3], "AF_INET") == 0 ? AF_INET :
+ strcmp(args[3], "AF_INET6") == 0 ? AF_INET6 :
+ strcmp(args[3], "AF_UNIX") == 0 ? AF_UNIX :
+ AF_UNSPEC);
+ } else if (strcmp(cmd, "helo") == 0 && argv->argc == 1) {
+ if (milters == 0) {
+ msg_warn("no milters");
+ continue;
+ }
+ resp = milter_helo_event(milters, args[0], 0);
+ } else if (strcmp(cmd, "ehlo") == 0 && argv->argc == 1) {
+ if (milters == 0) {
+ msg_warn("no milters");
+ continue;
+ }
+ resp = milter_helo_event(milters, args[0], 1);
+ } else if (strcmp(cmd, "mail") == 0 && argv->argc > 0) {
+ if (milters == 0) {
+ msg_warn("no milters");
+ continue;
+ }
+ resp = milter_mail_event(milters, (const char **) args);
+ } else if (strcmp(cmd, "rcpt") == 0 && argv->argc > 0) {
+ if (milters == 0) {
+ msg_warn("no milters");
+ continue;
+ }
+ resp = milter_rcpt_event(milters, 0, (const char **) args);
+ } else if (strcmp(cmd, "unknown") == 0 && argv->argc > 0) {
+ if (milters == 0) {
+ msg_warn("no milters");
+ continue;
+ }
+ resp = milter_unknown_event(milters, args[0]);
+ } else if (strcmp(cmd, "data") == 0 && argv->argc == 0) {
+ if (milters == 0) {
+ msg_warn("no milters");
+ continue;
+ }
+ resp = milter_data_event(milters);
+ } else if (strcmp(cmd, "disconnect") == 0 && argv->argc == 0) {
+ if (milters == 0) {
+ msg_warn("no milters");
+ continue;
+ }
+ milter_disc_event(milters);
+ } else {
+ usage();
+ }
+ if (resp != 0)
+ msg_info("%s", resp);
+ argv_free(argv);
+ }
+ if (milters != 0)
+ milter_free(milters);
+ vstring_free(inbuf);
+ return (0);
+}
+
+#endif
diff --git a/src/milter/milter.h b/src/milter/milter.h
new file mode 100644
index 0000000..3a1e3f9
--- /dev/null
+++ b/src/milter/milter.h
@@ -0,0 +1,223 @@
+#ifndef _MILTER_H_INCLUDED_
+#define _MILTER_H_INCLUDED_
+
+/*++
+/* NAME
+/* milter 3h
+/* SUMMARY
+/* smtp server
+/* SYNOPSIS
+/* Postfix MTA-side Milter implementation
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+#include <vstream.h>
+#include <argv.h>
+
+ /*
+ * Global library.
+ */
+#include <attr.h>
+
+ /*
+ * Each Milter handle is an element of a null-terminated linked list. The
+ * functions are virtual so that we can support multiple MTA-side Milter
+ * implementations. The Sendmail 8 and Sendmail X Milter-side APIs are too
+ * different to implement the MTA side as a single hybrid.
+ */
+typedef struct MILTER {
+ char *name; /* full name including transport */
+ int flags; /* see below */
+ struct MILTER *next; /* linkage */
+ struct MILTERS *parent; /* parent information */
+ struct MILTER_MACROS *macros; /* private macros */
+ void (*connect_on_demand) (struct MILTER *);
+ const char *(*conn_event) (struct MILTER *, const char *, const char *, const char *, unsigned, ARGV *);
+ const char *(*helo_event) (struct MILTER *, const char *, int, ARGV *);
+ const char *(*mail_event) (struct MILTER *, const char **, ARGV *);
+ const char *(*rcpt_event) (struct MILTER *, const char **, ARGV *);
+ const char *(*data_event) (struct MILTER *, ARGV *);
+ const char *(*message) (struct MILTER *, VSTREAM *, off_t, ARGV *, ARGV *, ARGV *);
+ const char *(*unknown_event) (struct MILTER *, const char *, ARGV *);
+ const char *(*other_event) (struct MILTER *);
+ void (*abort) (struct MILTER *);
+ void (*disc_event) (struct MILTER *);
+ int (*active) (struct MILTER *);
+ int (*send) (struct MILTER *, VSTREAM *);
+ void (*free) (struct MILTER *);
+} MILTER;
+
+#define MILTER_FLAG_NONE (0)
+#define MILTER_FLAG_WANT_RCPT_REJ (1<<0) /* see S8_RCPT_MAILER_ERROR */
+
+extern MILTER *milter8_create(const char *, int, int, int, const char *, const char *, struct MILTERS *);
+extern MILTER *milter8_receive(VSTREAM *, struct MILTERS *);
+
+ /*
+ * As of Sendmail 8.14 each milter can override the default macro list. If a
+ * Milter has its own macro list, a null member means use the global
+ * definition.
+ */
+typedef struct MILTER_MACROS {
+ char *conn_macros; /* macros for connect event */
+ char *helo_macros; /* macros for HELO/EHLO command */
+ char *mail_macros; /* macros for MAIL FROM command */
+ char *rcpt_macros; /* macros for RCPT TO command */
+ char *data_macros; /* macros for DATA command */
+ char *eoh_macros; /* macros for end-of-headers */
+ char *eod_macros; /* macros for END-OF-DATA command */
+ char *unk_macros; /* macros for unknown command */
+} MILTER_MACROS;
+
+extern MILTER_MACROS *milter_macros_create(const char *, const char *,
+ const char *, const char *,
+ const char *, const char *,
+ const char *, const char *);
+extern MILTER_MACROS *milter_macros_alloc(int);
+extern void milter_macros_free(MILTER_MACROS *);
+extern int milter_macros_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *);
+extern int milter_macros_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *);
+
+#define MILTER_MACROS_ALLOC_ZERO 1 /* null pointer */
+#define MILTER_MACROS_ALLOC_EMPTY 2 /* mystrdup(""); */
+
+ /*
+ * Helper to parse list of name=value default macro settings.
+ */
+extern struct HTABLE *milter_macro_defaults_create(const char *);
+
+ /*
+ * A bunch of Milters.
+ */
+typedef const char *(*MILTER_MAC_LOOKUP_FN) (const char *, void *);
+typedef const char *(*MILTER_ADD_HEADER_FN) (void *, const char *, const char *, const char *);
+typedef const char *(*MILTER_EDIT_HEADER_FN) (void *, ssize_t, const char *, const char *, const char *);
+typedef const char *(*MILTER_DEL_HEADER_FN) (void *, ssize_t, const char *);
+typedef const char *(*MILTER_EDIT_FROM_FN) (void *, const char *, const char *);
+typedef const char *(*MILTER_EDIT_RCPT_FN) (void *, const char *);
+typedef const char *(*MILTER_EDIT_RCPT_PAR_FN) (void *, const char *, const char *);
+typedef const char *(*MILTER_EDIT_BODY_FN) (void *, int, int, VSTRING *);
+
+typedef struct MILTERS {
+ MILTER *milter_list; /* linked list of Milters */
+ MILTER_MAC_LOOKUP_FN mac_lookup;
+ void *mac_context; /* macro lookup context */
+ struct MILTER_MACROS *macros;
+ struct HTABLE *macro_defaults;
+ void *chg_context; /* context for queue file changes */
+ MILTER_ADD_HEADER_FN add_header;
+ MILTER_EDIT_HEADER_FN upd_header;
+ MILTER_DEL_HEADER_FN del_header;
+ MILTER_EDIT_HEADER_FN ins_header;
+ MILTER_EDIT_FROM_FN chg_from;
+ MILTER_EDIT_RCPT_FN add_rcpt;
+ MILTER_EDIT_RCPT_PAR_FN add_rcpt_par;
+ MILTER_EDIT_RCPT_FN del_rcpt;
+ MILTER_EDIT_BODY_FN repl_body;
+} MILTERS;
+
+#define milter_create(milter_names, conn_timeout, cmd_timeout, msg_timeout, \
+ protocol, def_action, conn_macros, helo_macros, \
+ mail_macros, rcpt_macros, data_macros, eoh_macros, \
+ eod_macros, unk_macros, macro_deflts) \
+ milter_new(milter_names, conn_timeout, cmd_timeout, msg_timeout, \
+ protocol, def_action, milter_macros_create(conn_macros, \
+ helo_macros, mail_macros, rcpt_macros, data_macros, \
+ eoh_macros, eod_macros, unk_macros), \
+ milter_macro_defaults_create(macro_deflts))
+
+extern MILTERS *milter_new(const char *, int, int, int, const char *,
+ const char *, MILTER_MACROS *,
+ struct HTABLE *);
+extern void milter_macro_callback(MILTERS *, MILTER_MAC_LOOKUP_FN, void *);
+extern void milter_edit_callback(MILTERS *milters, MILTER_ADD_HEADER_FN,
+ MILTER_EDIT_HEADER_FN, MILTER_EDIT_HEADER_FN,
+ MILTER_DEL_HEADER_FN, MILTER_EDIT_FROM_FN,
+ MILTER_EDIT_RCPT_FN, MILTER_EDIT_RCPT_PAR_FN,
+ MILTER_EDIT_RCPT_FN, MILTER_EDIT_BODY_FN,
+ void *);
+extern const char *milter_conn_event(MILTERS *, const char *, const char *, const char *, unsigned);
+extern const char *milter_helo_event(MILTERS *, const char *, int);
+extern const char *milter_mail_event(MILTERS *, const char **);
+extern const char *milter_rcpt_event(MILTERS *, int, const char **);
+extern const char *milter_data_event(MILTERS *);
+extern const char *milter_message(MILTERS *, VSTREAM *, off_t, ARGV *);
+extern const char *milter_unknown_event(MILTERS *, const char *);
+extern const char *milter_other_event(MILTERS *);
+extern void milter_abort(MILTERS *);
+extern void milter_disc_event(MILTERS *);
+extern int milter_dummy(MILTERS *, VSTREAM *);
+extern int milter_send(MILTERS *, VSTREAM *);
+extern MILTERS *milter_receive(VSTREAM *, int);
+extern void milter_free(MILTERS *);
+
+ /*
+ * Milter body edit commands.
+ */
+#define MILTER_BODY_START 1 /* start message body */
+#define MILTER_BODY_LINE 2 /* message body line */
+#define MILTER_BODY_END 3 /* end message body */
+
+ /*
+ * Sendmail 8 macro names. We support forms with and without the {}.
+ */
+#define S8_MAC__ "{_}" /* sender host, see client_resolve */
+#define S8_MAC_J "{j}" /* myhostname */
+#define S8_MAC_V "{v}" /* mail_name + mail_version */
+
+#define S8_MAC_DAEMON_NAME "{daemon_name}"
+#define S8_MAC_IF_NAME "{if_name}"
+#define S8_MAC_IF_ADDR "{if_addr}"
+
+#define S8_MAC_CLIENT_ADDR "{client_addr}"
+#define S8_MAC_CLIENT_CONN "{client_connections}"
+#define S8_MAC_CLIENT_NAME "{client_name}"
+#define S8_MAC_CLIENT_PORT "{client_port}"
+#define S8_MAC_CLIENT_PTR "{client_ptr}"
+#define S8_MAC_CLIENT_RES "{client_resolve}"
+
+#define S8_MAC_DAEMON_ADDR "{daemon_addr}"
+#define S8_MAC_DAEMON_PORT "{daemon_port}"
+
+#define S8_MAC_TLS_VERSION "{tls_version}"
+#define S8_MAC_CIPHER "{cipher}"
+#define S8_MAC_CIPHER_BITS "{cipher_bits}"
+#define S8_MAC_CERT_SUBJECT "{cert_subject}"
+#define S8_MAC_CERT_ISSUER "{cert_issuer}"
+
+#define S8_MAC_I "{i}" /* queue ID */
+#define S8_MAC_AUTH_TYPE "{auth_type}" /* SASL method */
+#define S8_MAC_AUTH_AUTHEN "{auth_authen}" /* SASL username */
+#define S8_MAC_AUTH_AUTHOR "{auth_author}" /* SASL sender */
+
+#define S8_MAC_MAIL_MAILER "{mail_mailer}" /* sender transport */
+#define S8_MAC_MAIL_HOST "{mail_host}" /* sender nexthop */
+#define S8_MAC_MAIL_ADDR "{mail_addr}" /* sender address */
+
+#define S8_MAC_RCPT_MAILER "{rcpt_mailer}" /* recip transport */
+#define S8_MAC_RCPT_HOST "{rcpt_host}" /* recip nexthop */
+#define S8_MAC_RCPT_ADDR "{rcpt_addr}" /* recip address */
+
+#define S8_RCPT_MAILER_ERROR "error" /* see MILTER_FLAG_WANT_RCPT_REJ */
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/milter/milter8.c b/src/milter/milter8.c
new file mode 100644
index 0000000..ab2bc86
--- /dev/null
+++ b/src/milter/milter8.c
@@ -0,0 +1,2911 @@
+/*++
+/* NAME
+/* milter8 3
+/* SUMMARY
+/* MTA-side Sendmail 8 Milter protocol
+/* SYNOPSIS
+/* #include <milter8.h>
+/*
+/* MILTER *milter8_create(name, conn_timeout, cmd_timeout, msg_timeout,
+/* protocol, def_action, parent)
+/* const char *name;
+/* int conn_timeout;
+/* int cmd_timeout;
+/* int msg_timeout;
+/* const char *protocol;
+/* const char *def_action;
+/* MILTERS *parent;
+/*
+/* MILTER *milter8_receive(stream)
+/* VSTREAM *stream;
+/* DESCRIPTION
+/* This module implements the MTA side of the Sendmail 8 mail
+/* filter protocol.
+/*
+/* milter8_create() creates a MILTER data structure with virtual
+/* functions that implement a client for the Sendmail 8 Milter
+/* protocol. These virtual functions are then invoked via the
+/* milter(3) interface. The *timeout, protocol and def_action
+/* arguments come directly from milter_create(). The parent
+/* argument specifies a context for content editing.
+/*
+/* milter8_receive() receives a mail filter definition from the
+/* specified stream. The result is zero in case of success.
+/*
+/* Arguments:
+/* .IP name
+/* The Milter application endpoint, either inet:host:port or
+/* unix:/pathname.
+/* DIAGNOSTICS
+/* Panic: interface violation. Fatal errors: out of memory.
+/* CONFIGURATION PARAMETERS
+/* milter8_protocol, protocol version and extensions
+/* SEE ALSO
+/* milter(3) generic Milter interface
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <stddef.h> /* offsetof() */
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <limits.h> /* INT_MAX */
+
+#ifndef SHUT_RDWR
+#define SHUT_RDWR 2
+#endif
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <split_at.h>
+#include <connect.h>
+#include <argv.h>
+#include <name_mask.h>
+#include <name_code.h>
+#include <stringops.h>
+#include <compat_va_copy.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+#include <rec_type.h>
+#include <record.h>
+#include <mime_state.h>
+#include <is_header.h>
+
+/* Postfix Milter library. */
+
+#include <milter.h>
+
+/* Application-specific. */
+
+ /*
+ * Use our own protocol definitions, so that Postfix can be built even when
+ * libmilter is not installed. This means that we must specify the libmilter
+ * protocol version in main.cf, and that we must send only the commands that
+ * are supported for that protocol version.
+ */
+
+ /*
+ * Commands from MTA to filter.
+ */
+#define SMFIC_ABORT 'A' /* Abort */
+#define SMFIC_BODY 'B' /* Body chunk */
+#define SMFIC_CONNECT 'C' /* Connection information */
+#define SMFIC_MACRO 'D' /* Define macro */
+#define SMFIC_BODYEOB 'E' /* final body chunk (End) */
+#define SMFIC_HELO 'H' /* HELO/EHLO */
+#define SMFIC_HEADER 'L' /* Header */
+#define SMFIC_MAIL 'M' /* MAIL from */
+#define SMFIC_EOH 'N' /* EOH */
+#define SMFIC_OPTNEG 'O' /* Option negotiation */
+#define SMFIC_QUIT 'Q' /* QUIT */
+#define SMFIC_RCPT 'R' /* RCPT to */
+#define SMFIC_DATA 'T' /* DATA */
+#define SMFIC_UNKNOWN 'U' /* Any unknown command */
+ /* Introduced with Sendmail 8.14. */
+#define SMFIC_QUIT_NC 'K' /* Quit + new connection */
+
+static const NAME_CODE smfic_table[] = {
+ "SMFIC_ABORT", SMFIC_ABORT,
+ "SMFIC_BODY", SMFIC_BODY,
+ "SMFIC_CONNECT", SMFIC_CONNECT,
+ "SMFIC_MACRO", SMFIC_MACRO,
+ "SMFIC_BODYEOB", SMFIC_BODYEOB,
+ "SMFIC_HELO", SMFIC_HELO,
+ "SMFIC_HEADER", SMFIC_HEADER,
+ "SMFIC_MAIL", SMFIC_MAIL,
+ "SMFIC_EOH", SMFIC_EOH,
+ "SMFIC_OPTNEG", SMFIC_OPTNEG,
+ "SMFIC_QUIT", SMFIC_QUIT,
+ "SMFIC_RCPT", SMFIC_RCPT,
+ "SMFIC_DATA", SMFIC_DATA,
+ "SMFIC_UNKNOWN", SMFIC_UNKNOWN,
+ /* Introduced with Sendmail 8.14. */
+ "SMFIC_QUIT_NC", SMFIC_QUIT_NC,
+ 0, 0,
+};
+
+ /*
+ * Responses from filter to MTA.
+ */
+#define SMFIR_ADDRCPT '+' /* add recipient */
+#define SMFIR_DELRCPT '-' /* remove recipient */
+#define SMFIR_ACCEPT 'a' /* accept */
+#define SMFIR_REPLBODY 'b' /* replace body (chunk) */
+#define SMFIR_CONTINUE 'c' /* continue */
+#define SMFIR_DISCARD 'd' /* discard */
+#define SMFIR_CONN_FAIL 'f' /* cause a connection failure */
+#define SMFIR_CHGHEADER 'm' /* change header */
+#define SMFIR_PROGRESS 'p' /* progress */
+#define SMFIR_REJECT 'r' /* reject */
+#define SMFIR_TEMPFAIL 't' /* tempfail */
+#define SMFIR_SHUTDOWN '4' /* 421: shutdown (internal to MTA) */
+#define SMFIR_ADDHEADER 'h' /* add header */
+#define SMFIR_INSHEADER 'i' /* insert header */
+#define SMFIR_REPLYCODE 'y' /* reply code etc */
+#define SMFIR_QUARANTINE 'q' /* quarantine */
+ /* Introduced with Sendmail 8.14. */
+#define SMFIR_SKIP 's' /* skip further events of this type */
+#define SMFIR_CHGFROM 'e' /* change sender (incl. ESMTP args) */
+#define SMFIR_ADDRCPT_PAR '2' /* add recipient (incl. ESMTP args) */
+#define SMFIR_SETSYMLIST 'l' /* set list of symbols (macros) */
+
+static const NAME_CODE smfir_table[] = {
+ "SMFIR_ADDRCPT", SMFIR_ADDRCPT,
+ "SMFIR_DELRCPT", SMFIR_DELRCPT,
+ "SMFIR_ACCEPT", SMFIR_ACCEPT,
+ "SMFIR_REPLBODY", SMFIR_REPLBODY,
+ "SMFIR_CONTINUE", SMFIR_CONTINUE,
+ "SMFIR_DISCARD", SMFIR_DISCARD,
+ "SMFIR_CONN_FAIL", SMFIR_CONN_FAIL,
+ "SMFIR_CHGHEADER", SMFIR_CHGHEADER,
+ "SMFIR_PROGRESS", SMFIR_PROGRESS,
+ "SMFIR_REJECT", SMFIR_REJECT,
+ "SMFIR_TEMPFAIL", SMFIR_TEMPFAIL,
+ "SMFIR_SHUTDOWN", SMFIR_SHUTDOWN,
+ "SMFIR_ADDHEADER", SMFIR_ADDHEADER,
+ "SMFIR_INSHEADER", SMFIR_INSHEADER,
+ "SMFIR_REPLYCODE", SMFIR_REPLYCODE,
+ "SMFIR_QUARANTINE", SMFIR_QUARANTINE,
+ /* Introduced with Sendmail 8.14. */
+ "SMFIR_SKIP", SMFIR_SKIP,
+ "SMFIR_CHGFROM", SMFIR_CHGFROM,
+ "SMFIR_ADDRCPT_PAR", SMFIR_ADDRCPT_PAR,
+ "SMFIR_SETSYMLIST", SMFIR_SETSYMLIST,
+ 0, 0,
+};
+
+ /*
+ * Commands that the filter does not want to receive, and replies that the
+ * filter will not send. Plus some other random stuff.
+ */
+#define SMFIP_NOCONNECT (1L<<0) /* filter does not want connect info */
+#define SMFIP_NOHELO (1L<<1) /* filter does not want HELO info */
+#define SMFIP_NOMAIL (1L<<2) /* filter does not want MAIL info */
+#define SMFIP_NORCPT (1L<<3) /* filter does not want RCPT info */
+#define SMFIP_NOBODY (1L<<4) /* filter does not want body */
+#define SMFIP_NOHDRS (1L<<5) /* filter does not want headers */
+#define SMFIP_NOEOH (1L<<6) /* filter does not want EOH */
+#define SMFIP_NR_HDR (1L<<7) /* filter won't reply for header */
+#define SMFIP_NOHREPL SMFIP_NR_HDR
+#define SMFIP_NOUNKNOWN (1L<<8) /* filter does not want unknown cmd */
+#define SMFIP_NODATA (1L<<9) /* filter does not want DATA */
+ /* Introduced with Sendmail 8.14. */
+#define SMFIP_SKIP (1L<<10)/* MTA supports SMFIR_SKIP */
+#define SMFIP_RCPT_REJ (1L<<11)/* filter wants rejected RCPTs */
+#define SMFIP_NR_CONN (1L<<12)/* filter won't reply for connect */
+#define SMFIP_NR_HELO (1L<<13)/* filter won't reply for HELO */
+#define SMFIP_NR_MAIL (1L<<14)/* filter won't reply for MAIL */
+#define SMFIP_NR_RCPT (1L<<15)/* filter won't reply for RCPT */
+#define SMFIP_NR_DATA (1L<<16)/* filter won't reply for DATA */
+#define SMFIP_NR_UNKN (1L<<17)/* filter won't reply for UNKNOWN */
+#define SMFIP_NR_EOH (1L<<18)/* filter won't reply for eoh */
+#define SMFIP_NR_BODY (1L<<19)/* filter won't reply for body chunk */
+#define SMFIP_HDR_LEADSPC (1L<<20)/* header value has leading space */
+
+#define SMFIP_NOSEND_MASK \
+ (SMFIP_NOCONNECT | SMFIP_NOHELO | SMFIP_NOMAIL | SMFIP_NORCPT \
+ | SMFIP_NOBODY | SMFIP_NOHDRS | SMFIP_NOEOH | SMFIP_NOUNKNOWN \
+ | SMFIP_NODATA)
+
+#define SMFIP_NOREPLY_MASK \
+ (SMFIP_NR_CONN | SMFIP_NR_HELO | SMFIP_NR_MAIL | SMFIP_NR_RCPT \
+ | SMFIP_NR_DATA | SMFIP_NR_UNKN | SMFIP_NR_HDR | SMFIP_NR_EOH | \
+ SMFIP_NR_BODY)
+
+static const NAME_MASK smfip_table[] = {
+ "SMFIP_NOCONNECT", SMFIP_NOCONNECT,
+ "SMFIP_NOHELO", SMFIP_NOHELO,
+ "SMFIP_NOMAIL", SMFIP_NOMAIL,
+ "SMFIP_NORCPT", SMFIP_NORCPT,
+ "SMFIP_NOBODY", SMFIP_NOBODY,
+ "SMFIP_NOHDRS", SMFIP_NOHDRS,
+ "SMFIP_NOEOH", SMFIP_NOEOH,
+ "SMFIP_NR_HDR", SMFIP_NR_HDR,
+ "SMFIP_NOUNKNOWN", SMFIP_NOUNKNOWN,
+ "SMFIP_NODATA", SMFIP_NODATA,
+ /* Introduced with Sendmail 8.14. */
+ "SMFIP_SKIP", SMFIP_SKIP,
+ "SMFIP_RCPT_REJ", SMFIP_RCPT_REJ,
+ "SMFIP_NR_CONN", SMFIP_NR_CONN,
+ "SMFIP_NR_HELO", SMFIP_NR_HELO,
+ "SMFIP_NR_MAIL", SMFIP_NR_MAIL,
+ "SMFIP_NR_RCPT", SMFIP_NR_RCPT,
+ "SMFIP_NR_DATA", SMFIP_NR_DATA,
+ "SMFIP_NR_UNKN", SMFIP_NR_UNKN,
+ "SMFIP_NR_EOH", SMFIP_NR_EOH,
+ "SMFIP_NR_BODY", SMFIP_NR_BODY,
+ "SMFIP_HDR_LEADSPC", SMFIP_HDR_LEADSPC,
+ 0, 0,
+};
+
+ /*
+ * Options that the filter may send at initial handshake time, and message
+ * modifications that the filter may request at the end of the message body.
+ */
+#define SMFIF_ADDHDRS (1L<<0) /* filter may add headers */
+#define SMFIF_CHGBODY (1L<<1) /* filter may replace body */
+#define SMFIF_ADDRCPT (1L<<2) /* filter may add recipients */
+#define SMFIF_DELRCPT (1L<<3) /* filter may delete recipients */
+#define SMFIF_CHGHDRS (1L<<4) /* filter may change/delete headers */
+#define SMFIF_QUARANTINE (1L<<5) /* filter may quarantine envelope */
+ /* Introduced with Sendmail 8.14. */
+#define SMFIF_CHGFROM (1L<<6) /* filter may replace sender */
+#define SMFIF_ADDRCPT_PAR (1L<<7) /* filter may add recipients + args */
+#define SMFIF_SETSYMLIST (1L<<8) /* filter may send macro names */
+
+static const NAME_MASK smfif_table[] = {
+ "SMFIF_ADDHDRS", SMFIF_ADDHDRS,
+ "SMFIF_CHGBODY", SMFIF_CHGBODY,
+ "SMFIF_ADDRCPT", SMFIF_ADDRCPT,
+ "SMFIF_DELRCPT", SMFIF_DELRCPT,
+ "SMFIF_CHGHDRS", SMFIF_CHGHDRS,
+ "SMFIF_QUARANTINE", SMFIF_QUARANTINE,
+ /* Introduced with Sendmail 8.14. */
+ "SMFIF_CHGFROM", SMFIF_CHGFROM,
+ "SMFIF_ADDRCPT_PAR", SMFIF_ADDRCPT_PAR,
+ "SMFIF_SETSYMLIST", SMFIF_SETSYMLIST,
+ 0, 0,
+};
+
+ /*
+ * Network protocol families, used when sending CONNECT information.
+ */
+#define SMFIA_UNKNOWN 'U' /* unknown */
+#define SMFIA_UNIX 'L' /* unix/local */
+#define SMFIA_INET '4' /* inet */
+#define SMFIA_INET6 '6' /* inet6 */
+
+ /*
+ * External macro class numbers, to identify the optional macro name lists
+ * that may be sent after the initial negotiation header.
+ */
+#define SMFIM_CONNECT 0 /* macros for connect */
+#define SMFIM_HELO 1 /* macros for HELO */
+#define SMFIM_ENVFROM 2 /* macros for MAIL */
+#define SMFIM_ENVRCPT 3 /* macros for RCPT */
+#define SMFIM_DATA 4 /* macros for DATA */
+#define SMFIM_EOM 5 /* macros for end-of-message */
+#define SMFIM_EOH 6 /* macros for end-of-header */
+
+static const NAME_CODE smfim_table[] = {
+ "SMFIM_CONNECT", SMFIM_CONNECT,
+ "SMFIM_HELO", SMFIM_HELO,
+ "SMFIM_ENVFROM", SMFIM_ENVFROM,
+ "SMFIM_ENVRCPT", SMFIM_ENVRCPT,
+ "SMFIM_DATA", SMFIM_DATA,
+ "SMFIM_EOM", SMFIM_EOM,
+ "SMFIM_EOH", SMFIM_EOH,
+ 0, 0,
+};
+
+ /*
+ * Mapping from external macro class numbers to our internal MILTER_MACROS
+ * structure members, without using a switch statement.
+ */
+static const size_t milter8_macro_offsets[] = {
+ offsetof(MILTER_MACROS, conn_macros), /* SMFIM_CONNECT */
+ offsetof(MILTER_MACROS, helo_macros), /* SMFIM_HELO */
+ offsetof(MILTER_MACROS, mail_macros), /* SMFIM_ENVFROM */
+ offsetof(MILTER_MACROS, rcpt_macros), /* SMFIM_ENVRCPT */
+ offsetof(MILTER_MACROS, data_macros), /* SMFIM_DATA */
+ offsetof(MILTER_MACROS, eod_macros),/* Note: SMFIM_EOM < SMFIM_EOH */
+ offsetof(MILTER_MACROS, eoh_macros),/* Note: SMFIM_EOH > SMFIM_EOM */
+};
+
+#define MILTER8_MACRO_PTR(__macros, __class) \
+ ((char **) (((char *) (__macros)) + milter8_macro_offsets[(__class)]))
+
+ /*
+ * How much buffer space is available for sending body content.
+ */
+#define MILTER_CHUNK_SIZE 65535 /* body chunk size */
+
+/*#define msg_verbose 2*/
+
+ /*
+ * Sendmail 8 mail filter client.
+ */
+typedef struct {
+ MILTER m; /* parent class */
+ int conn_timeout; /* connect timeout */
+ int cmd_timeout; /* per-command timeout */
+ int msg_timeout; /* content inspection timeout */
+ char *protocol; /* protocol version/extension */
+ char *def_action; /* action if unavailable */
+ int version; /* application protocol version */
+ int rq_mask; /* application requests (SMFIF_*) */
+ int ev_mask; /* application events (SMFIP_*) */
+ int np_mask; /* events outside my protocol version */
+ VSTRING *buf; /* I/O buffer */
+ VSTRING *body; /* I/O buffer */
+ VSTREAM *fp; /* stream or null (closed) */
+
+ /*
+ * Following fields must be reset after successful CONNECT, to avoid
+ * leakage from one use to another.
+ */
+ int state; /* MILTER8_STAT_mumble */
+ char *def_reply; /* error response or null */
+ int skip_event_type; /* skip operations of this type */
+} MILTER8;
+
+ /*
+ * XXX Sendmail 8 libmilter automatically closes the MTA-to-filter socket
+ * when it finds out that the SMTP client has disconnected. Because of this
+ * behavior, Postfix has to open a new MTA-to-filter socket each time an
+ * SMTP client connects.
+ */
+#define LIBMILTER_AUTO_DISCONNECT
+
+ /*
+ * Milter internal state. For the external representation we use SMTP
+ * replies (4XX X.Y.Z text, 5XX X.Y.Z text) and one-letter strings
+ * (H=quarantine, D=discard, S=shutdown).
+ */
+#define MILTER8_STAT_ERROR 1 /* error, must be non-zero */
+#define MILTER8_STAT_CLOSED 2 /* no connection */
+#define MILTER8_STAT_READY 3 /* wait for connect event */
+#define MILTER8_STAT_ENVELOPE 4 /* in envelope */
+#define MILTER8_STAT_MESSAGE 5 /* in message */
+#define MILTER8_STAT_ACCEPT_CON 6 /* accept all commands */
+#define MILTER8_STAT_ACCEPT_MSG 7 /* accept one message */
+#define MILTER8_STAT_REJECT_CON 8 /* reject all commands */
+
+ /*
+ * Protocol formatting requests. Note: the terms "long" and "short" refer to
+ * the data types manipulated by htonl(), htons() and friends. These types
+ * are network specific, not host platform specific.
+ */
+#define MILTER8_DATA_END 0 /* no more arguments */
+#define MILTER8_DATA_HLONG 1 /* host long */
+#define MILTER8_DATA_BUFFER 2 /* network-formatted buffer */
+#define MILTER8_DATA_STRING 3 /* null-terminated string */
+#define MILTER8_DATA_NSHORT 4 /* network short */
+#define MILTER8_DATA_ARGV 5 /* array of null-terminated strings */
+#define MILTER8_DATA_OCTET 6 /* byte */
+#define MILTER8_DATA_MORE 7 /* more arguments in next call */
+
+ /*
+ * We don't accept insane amounts of data.
+ */
+#define XXX_MAX_DATA (INT_MAX / 2)
+#define XXX_TIMEOUT 10
+
+ /*
+ * We implement the protocol up to and including version 6, and configure in
+ * main.cf what protocol version we will use. The version is the first data
+ * item in the SMFIC_OPTNEG packet.
+ *
+ * We must send only events that are defined for the specified protocol
+ * version. Libmilter may disconnect when we send unexpected events.
+ *
+ * The following events are supported in all our milter protocol versions.
+ */
+#define MILTER8_V2_PROTO_MASK \
+ (SMFIP_NOCONNECT | SMFIP_NOHELO | SMFIP_NOMAIL | SMFIP_NORCPT | \
+ SMFIP_NOBODY | SMFIP_NOHDRS | SMFIP_NOEOH)
+
+ /*
+ * Events supported by later versions.
+ */
+#define MILTER8_V3_PROTO_MASK (MILTER8_V2_PROTO_MASK | SMFIP_NOUNKNOWN)
+#define MILTER8_V4_PROTO_MASK (MILTER8_V3_PROTO_MASK | SMFIP_NODATA)
+#define MILTER8_V6_PROTO_MASK \
+ (MILTER8_V4_PROTO_MASK | SMFIP_SKIP | SMFIP_RCPT_REJ \
+ | SMFIP_NOREPLY_MASK | SMFIP_HDR_LEADSPC)
+
+ /*
+ * What events we can send to the milter application. The milter8_protocol
+ * parameter can specify a protocol version as well as protocol extensions
+ * such as "no_header_reply", a feature that speeds up the protocol by not
+ * sending a filter reply for every individual message header.
+ *
+ * This looks unclean because the user can specify multiple protocol versions,
+ * but that is taken care of by the table that follows this one.
+ *
+ * XXX Is this still needed? Sendmail 8.14 provides a proper way to negotiate
+ * what replies the mail filter will send.
+ *
+ * XXX Keep this table in reverse numerical order. This is needed by the code
+ * that implements compatibility with older Milter protocol versions.
+ */
+static const NAME_CODE milter8_event_masks[] = {
+ "6", MILTER8_V6_PROTO_MASK,
+ "4", MILTER8_V4_PROTO_MASK,
+ "3", MILTER8_V3_PROTO_MASK,
+ "2", MILTER8_V2_PROTO_MASK,
+ "no_header_reply", SMFIP_NOHREPL,
+ 0, -1,
+};
+
+ /*
+ * The following table lets us use the same milter8_protocol parameter
+ * setting to derive the protocol version number. In this case we ignore
+ * protocol extensions such as "no_header_reply", and require that exactly
+ * one version number is specified.
+ */
+static const NAME_CODE milter8_versions[] = {
+ "2", 2,
+ "3", 3,
+ "4", 4,
+ "6", 6,
+ "no_header_reply", 0,
+ 0, -1,
+};
+
+/* SLMs. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* milter8_def_reply - set persistent response */
+
+static const char *milter8_def_reply(MILTER8 *milter, const char *reply)
+{
+ if (milter->def_reply)
+ myfree(milter->def_reply);
+ milter->def_reply = reply ? mystrdup(reply) : 0;
+ return (milter->def_reply);
+}
+
+/* milter8_conf_error - local/remote configuration error */
+
+static int milter8_conf_error(MILTER8 *milter)
+{
+ const char *reply;
+
+ /*
+ * XXX When the cleanup server closes its end of the Milter socket while
+ * editing a queue file, the SMTP server is left out of sync with the
+ * Milter. Sending an ABORT to the Milters will not restore
+ * synchronization, because there may be any number of Milter replies
+ * already in flight. Workaround: poison the socket and force the SMTP
+ * server to abandon it.
+ */
+ if (milter->fp != 0) {
+ (void) shutdown(vstream_fileno(milter->fp), SHUT_RDWR);
+ (void) vstream_fclose(milter->fp);
+ milter->fp = 0;
+ }
+ if (strcasecmp(milter->def_action, "accept") == 0) {
+ reply = 0;
+ } else if (strcasecmp(milter->def_action, "quarantine") == 0) {
+ reply = "H";
+ } else {
+ reply = "451 4.3.5 Server configuration problem - try again later";
+ }
+ milter8_def_reply(milter, reply);
+ return (milter->state = MILTER8_STAT_ERROR);
+}
+
+/* milter8_comm_error - read/write/format communication error */
+
+static int milter8_comm_error(MILTER8 *milter)
+{
+ const char *reply;
+
+ /*
+ * XXX When the cleanup server closes its end of the Milter socket while
+ * editing a queue file, the SMTP server is left out of sync with the
+ * Milter. Sending an ABORT to the Milters will not restore
+ * synchronization, because there may be any number of Milter replies
+ * already in flight. Workaround: poison the socket and force the SMTP
+ * server to abandon it.
+ */
+ if (milter->fp != 0) {
+ (void) shutdown(vstream_fileno(milter->fp), SHUT_RDWR);
+ (void) vstream_fclose(milter->fp);
+ milter->fp = 0;
+ }
+ if (strcasecmp(milter->def_action, "accept") == 0) {
+ reply = 0;
+ } else if (strcasecmp(milter->def_action, "reject") == 0) {
+ reply = "550 5.5.0 Service unavailable";
+ } else if (strcasecmp(milter->def_action, "tempfail") == 0) {
+ reply = "451 4.7.1 Service unavailable - try again later";
+ } else if (strcasecmp(milter->def_action, "quarantine") == 0) {
+ reply = "H";
+ } else {
+ msg_warn("milter %s: unrecognized default action: %s",
+ milter->m.name, milter->def_action);
+ reply = "451 4.3.5 Server configuration problem - try again later";
+ }
+ milter8_def_reply(milter, reply);
+ return (milter->state = MILTER8_STAT_ERROR);
+}
+
+/* milter8_close_stream - close stream to milter application */
+
+static void milter8_close_stream(MILTER8 *milter)
+{
+ if (milter->fp != 0) {
+ (void) vstream_fclose(milter->fp);
+ milter->fp = 0;
+ }
+ milter->state = MILTER8_STAT_CLOSED;
+}
+
+/* milter8_read_resp - receive command code now, receive data later */
+
+static int milter8_read_resp(MILTER8 *milter, int event, unsigned char *command,
+ ssize_t *data_len)
+{
+ UINT32_TYPE len;
+ ssize_t pkt_len;
+ const char *smfic_name;
+ int cmd;
+
+ /*
+ * Receive the packet length.
+ */
+ if ((vstream_fread(milter->fp, (void *) &len, UINT32_SIZE))
+ != UINT32_SIZE) {
+ smfic_name = str_name_code(smfic_table, event);
+ msg_warn("milter %s: can't read %s reply packet header: %m",
+ milter->m.name, smfic_name != 0 ?
+ smfic_name : "(unknown MTA event)");
+ return (milter8_comm_error(milter));
+ } else if ((pkt_len = ntohl(len)) < 1) {
+ msg_warn("milter %s: bad packet length: %ld",
+ milter->m.name, (long) pkt_len);
+ return (milter8_comm_error(milter));
+ } else if (pkt_len > XXX_MAX_DATA) {
+ msg_warn("milter %s: unreasonable packet length: %ld > %ld",
+ milter->m.name, (long) pkt_len, (long) XXX_MAX_DATA);
+ return (milter8_comm_error(milter));
+ }
+
+ /*
+ * Receive the command code.
+ */
+ else if ((cmd = VSTREAM_GETC(milter->fp)) == VSTREAM_EOF) {
+ msg_warn("milter %s: EOF while reading command code: %m",
+ milter->m.name);
+ return (milter8_comm_error(milter));
+ }
+
+ /*
+ * All is well.
+ */
+ else {
+ *command = cmd;
+ *data_len = pkt_len - 1;
+ return (0);
+ }
+}
+
+static int milter8_read_data(MILTER8 *milter, ssize_t *data_len,...);
+
+/* vmilter8_read_data - read command data */
+
+static int vmilter8_read_data(MILTER8 *milter, ssize_t *data_len, va_list ap)
+{
+ const char *myname = "milter8_read_data";
+ int arg_type;
+ UINT32_TYPE net_long;
+ UINT32_TYPE *host_long_ptr;
+ VSTRING *buf;
+ int ch;
+
+ while ((arg_type = va_arg(ap, int)) > 0 && arg_type != MILTER8_DATA_MORE) {
+ switch (arg_type) {
+
+ /*
+ * Host order long.
+ */
+ case MILTER8_DATA_HLONG:
+ if (*data_len < UINT32_SIZE) {
+ msg_warn("milter %s: input packet too short for network long",
+ milter->m.name);
+ return (milter8_comm_error(milter));
+ }
+ host_long_ptr = va_arg(ap, UINT32_TYPE *);
+ if (vstream_fread(milter->fp, (void *) &net_long, UINT32_SIZE)
+ != UINT32_SIZE) {
+ msg_warn("milter %s: EOF while reading network long: %m",
+ milter->m.name);
+ return (milter8_comm_error(milter));
+ }
+ *data_len -= UINT32_SIZE;
+ *host_long_ptr = ntohl(net_long);
+ break;
+
+ /*
+ * Raw on-the-wire format, without explicit null terminator.
+ */
+ case MILTER8_DATA_BUFFER:
+ if (*data_len < 0) {
+ msg_warn("milter %s: no data in input packet", milter->m.name);
+ return (milter8_comm_error(milter));
+ }
+ buf = va_arg(ap, VSTRING *);
+ if (vstream_fread_buf(milter->fp, buf, *data_len)
+ != *data_len) {
+ msg_warn("milter %s: EOF while reading data: %m", milter->m.name);
+ return (milter8_comm_error(milter));
+ }
+ *data_len = 0;
+ break;
+
+ /*
+ * Pointer to null-terminated string.
+ */
+ case MILTER8_DATA_STRING:
+ if (*data_len < 1) {
+ msg_warn("milter %s: packet too short for string",
+ milter->m.name);
+ return (milter8_comm_error(milter));
+ }
+ buf = va_arg(ap, VSTRING *);
+ VSTRING_RESET(buf);
+ for (;;) {
+ if ((ch = VSTREAM_GETC(milter->fp)) == VSTREAM_EOF) {
+ msg_warn("%s: milter %s: EOF while reading string: %m",
+ myname, milter->m.name);
+ return (milter8_comm_error(milter));
+ }
+ *data_len -= 1;
+ if (ch == 0)
+ break;
+ VSTRING_ADDCH(buf, ch);
+ if (*data_len <= 0) {
+ msg_warn("%s: milter %s: missing string null termimator",
+ myname, milter->m.name);
+ return (milter8_comm_error(milter));
+ }
+ }
+ VSTRING_TERMINATE(buf);
+ break;
+
+ /*
+ * Error.
+ */
+ default:
+ msg_panic("%s: unknown argument type: %d", myname, arg_type);
+ }
+ }
+
+ /*
+ * Sanity checks. We may have excess data when the sender is confused. We
+ * may have a negative count when we're confused ourselves.
+ */
+ if (arg_type != MILTER8_DATA_MORE && *data_len > 0) {
+ msg_warn("%s: left-over data %ld bytes", myname, (long) *data_len);
+ return (milter8_comm_error(milter));
+ }
+ if (*data_len < 0)
+ msg_panic("%s: bad left-over data count %ld",
+ myname, (long) *data_len);
+ return (0);
+}
+
+/* milter8_read_data - read command data */
+
+static int milter8_read_data(MILTER8 *milter, ssize_t *data_len,...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, data_len);
+ ret = vmilter8_read_data(milter, data_len, ap);
+ va_end(ap);
+ return (ret);
+}
+
+/* vmilter8_size_data - compute command data length */
+
+static ssize_t vmilter8_size_data(va_list ap)
+{
+ const char *myname = "vmilter8_size_data";
+ ssize_t data_len;
+ int arg_type;
+ VSTRING *buf;
+ const char *str;
+ const char **cpp;
+
+ /*
+ * Compute data size.
+ */
+ for (data_len = 0; (arg_type = va_arg(ap, int)) > 0; /* void */ ) {
+ switch (arg_type) {
+
+ /*
+ * Host order long.
+ */
+ case MILTER8_DATA_HLONG:
+ (void) va_arg(ap, UINT32_TYPE);
+ data_len += UINT32_SIZE;
+ break;
+
+ /*
+ * Raw on-the-wire format.
+ */
+ case MILTER8_DATA_BUFFER:
+ buf = va_arg(ap, VSTRING *);
+ data_len += LEN(buf);
+ break;
+
+ /*
+ * Pointer to null-terminated string.
+ */
+ case MILTER8_DATA_STRING:
+ str = va_arg(ap, char *);
+ data_len += strlen(str) + 1;
+ break;
+
+ /*
+ * Array of pointers to null-terminated strings.
+ */
+ case MILTER8_DATA_ARGV:
+ for (cpp = va_arg(ap, const char **); *cpp; cpp++)
+ data_len += strlen(*cpp) + 1;
+ break;
+
+ /*
+ * Network order short, promoted to int.
+ */
+ case MILTER8_DATA_NSHORT:
+ (void) va_arg(ap, unsigned);
+ data_len += UINT16_SIZE;
+ break;
+
+ /*
+ * Octet, promoted to int.
+ */
+ case MILTER8_DATA_OCTET:
+ (void) va_arg(ap, unsigned);
+ data_len += 1;
+ break;
+
+ /*
+ * Error.
+ */
+ default:
+ msg_panic("%s: bad argument type: %d", myname, arg_type);
+ }
+ }
+ va_end(ap);
+ return (data_len);
+}
+
+/* vmilter8_write_cmd - write command to Sendmail 8 Milter */
+
+static int vmilter8_write_cmd(MILTER8 *milter, int command, ssize_t data_len,
+ va_list ap)
+{
+ const char *myname = "vmilter8_write_cmd";
+ int arg_type;
+ UINT32_TYPE pkt_len;
+ UINT32_TYPE host_long;
+ UINT32_TYPE net_long;
+ UINT16_TYPE net_short;
+ VSTRING *buf;
+ const char *str;
+ const char **cpp;
+ char ch;
+
+ /*
+ * Deliver the packet.
+ */
+ if ((pkt_len = 1 + data_len) < 1)
+ msg_panic("%s: bad packet length %d", myname, pkt_len);
+ pkt_len = htonl(pkt_len);
+ (void) vstream_fwrite(milter->fp, (void *) &pkt_len, UINT32_SIZE);
+ (void) VSTREAM_PUTC(command, milter->fp);
+ while ((arg_type = va_arg(ap, int)) > 0) {
+ switch (arg_type) {
+
+ /*
+ * Network long.
+ */
+ case MILTER8_DATA_HLONG:
+ host_long = va_arg(ap, UINT32_TYPE);
+ net_long = htonl(host_long);
+ (void) vstream_fwrite(milter->fp, (void *) &net_long, UINT32_SIZE);
+ break;
+
+ /*
+ * Raw on-the-wire format.
+ */
+ case MILTER8_DATA_BUFFER:
+ buf = va_arg(ap, VSTRING *);
+ (void) vstream_fwrite(milter->fp, STR(buf), LEN(buf));
+ break;
+
+ /*
+ * Pointer to null-terminated string.
+ */
+ case MILTER8_DATA_STRING:
+ str = va_arg(ap, char *);
+ (void) vstream_fwrite(milter->fp, str, strlen(str) + 1);
+ break;
+
+ /*
+ * Octet, promoted to int.
+ */
+ case MILTER8_DATA_OCTET:
+ ch = va_arg(ap, unsigned);
+ (void) vstream_fwrite(milter->fp, &ch, 1);
+ break;
+
+ /*
+ * Array of pointers to null-terminated strings.
+ */
+ case MILTER8_DATA_ARGV:
+ for (cpp = va_arg(ap, const char **); *cpp; cpp++)
+ (void) vstream_fwrite(milter->fp, *cpp, strlen(*cpp) + 1);
+ break;
+
+ /*
+ * Network order short, promoted to int.
+ */
+ case MILTER8_DATA_NSHORT:
+ net_short = va_arg(ap, unsigned);
+ (void) vstream_fwrite(milter->fp, (void *) &net_short, UINT16_SIZE);
+ break;
+
+ /*
+ * Error.
+ */
+ default:
+ msg_panic("%s: bad argument type: %d", myname, arg_type);
+ }
+
+ /*
+ * Report errors immediately.
+ */
+ if (vstream_ferror(milter->fp)) {
+ msg_warn("milter %s: error writing command: %m", milter->m.name);
+ milter8_comm_error(milter);
+ break;
+ }
+ }
+ va_end(ap);
+ return (milter->state == MILTER8_STAT_ERROR);
+}
+
+/* milter8_write_cmd - write command to Sendmail 8 Milter */
+
+static int milter8_write_cmd(MILTER8 *milter, int command,...)
+{
+ va_list ap;
+ va_list ap2;
+ ssize_t data_len;
+ int err;
+
+ /*
+ * Initialize argument lists.
+ */
+ va_start(ap, command);
+ VA_COPY(ap2, ap);
+
+ /*
+ * Size the command data.
+ */
+ data_len = vmilter8_size_data(ap);
+ va_end(ap);
+
+ /*
+ * Send the command and data.
+ */
+ err = vmilter8_write_cmd(milter, command, data_len, ap2);
+ va_end(ap2);
+
+ return (err);
+}
+
+/* milter8_event - report event and receive reply */
+
+static const char *milter8_event(MILTER8 *milter, int event,
+ int skip_event_flag,
+ int skip_reply,
+ ARGV *macros,...)
+{
+ const char *myname = "milter8_event";
+ va_list ap;
+ va_list ap2;
+ ssize_t data_len;
+ int err;
+ unsigned char cmd;
+ ssize_t data_size;
+ const char *smfic_name;
+ const char *smfir_name;
+ MILTERS *parent = milter->m.parent;
+ UINT32_TYPE index;
+ const char *edit_resp = 0;
+ const char *retval = 0;
+ VSTRING *body_line_buf = 0;
+ int done = 0;
+ int body_edit_lockout = 0;
+
+#define DONT_SKIP_REPLY 0
+
+ /*
+ * Sanity check.
+ */
+ if (milter->fp == 0 || milter->def_reply != 0) {
+ msg_warn("%s: attempt to send event %s to milter %s after error",
+ myname,
+ (smfic_name = str_name_code(smfic_table, event)) != 0 ?
+ smfic_name : "(unknown MTA event)", milter->m.name);
+ return (milter->def_reply);
+ }
+
+ /*
+ * Skip this event if it doesn't exist in the protocol that I announced.
+ */
+ if ((skip_event_flag & milter->np_mask) != 0) {
+ if (msg_verbose)
+ msg_info("skipping non-protocol event %s for milter %s",
+ (smfic_name = str_name_code(smfic_table, event)) != 0 ?
+ smfic_name : "(unknown MTA event)", milter->m.name);
+ return (milter->def_reply);
+ }
+
+ /*
+ * Skip further events of this type if the filter told us so.
+ */
+ if (milter->skip_event_type != 0) {
+ if (event == milter->skip_event_type) {
+ if (msg_verbose)
+ msg_info("skipping event %s after SMFIR_SKIP from milter %s",
+ (smfic_name = str_name_code(smfic_table, event)) != 0 ?
+ smfic_name : "(unknown MTA event)", milter->m.name);
+ return (milter->def_reply);
+ } else {
+ milter->skip_event_type = 0;
+ }
+ }
+
+ /*
+ * Send the macros for this event, even when we're not reporting the
+ * event itself. This does not introduce a performance problem because
+ * we're sending macros and event parameters in one VSTREAM transaction.
+ *
+ * XXX Is this still necessary?
+ */
+ if (msg_verbose) {
+ VSTRING *buf = vstring_alloc(100);
+
+ if (macros) {
+ if (macros->argc > 0) {
+ char **cpp;
+
+ for (cpp = macros->argv; *cpp && cpp[1]; cpp += 2)
+ vstring_sprintf_append(buf, " %s=%s", *cpp, cpp[1]);
+ }
+ }
+ msg_info("event: %s; macros:%s",
+ (smfic_name = str_name_code(smfic_table, event)) != 0 ?
+ smfic_name : "(unknown MTA event)", *STR(buf) ?
+ STR(buf) : " (none)");
+ vstring_free(buf);
+ }
+ if (macros) {
+ if (milter8_write_cmd(milter, SMFIC_MACRO,
+ MILTER8_DATA_OCTET, event,
+ MILTER8_DATA_ARGV, macros->argv,
+ MILTER8_DATA_END) != 0)
+ return (milter->def_reply);
+ }
+
+ /*
+ * Skip this event if the Milter told us not to send it.
+ */
+ if ((skip_event_flag & milter->ev_mask) != 0) {
+ if (msg_verbose)
+ msg_info("skipping event %s for milter %s",
+ (smfic_name = str_name_code(smfic_table, event)) != 0 ?
+ smfic_name : "(unknown MTA event)", milter->m.name);
+ return (milter->def_reply);
+ }
+
+ /*
+ * Initialize argument lists.
+ */
+ va_start(ap, macros);
+ VA_COPY(ap2, ap);
+
+ /*
+ * Compute the command data size. This is necessary because the protocol
+ * sends length before content.
+ */
+ data_len = vmilter8_size_data(ap);
+ va_end(ap);
+
+ /*
+ * Send the command and data.
+ */
+ err = vmilter8_write_cmd(milter, event, data_len, ap2);
+ va_end(ap2);
+
+ /*
+ * C99 requires that we finalize argument lists before returning.
+ */
+ if (err != 0)
+ return (milter->def_reply);
+
+ /*
+ * Special feature: don't wait for one reply per header. This allows us
+ * to send multiple headers in one VSTREAM transaction, and improves
+ * over-all performance.
+ */
+ if (skip_reply) {
+ if (msg_verbose)
+ msg_info("skipping reply for event %s from milter %s",
+ (smfic_name = str_name_code(smfic_table, event)) != 0 ?
+ smfic_name : "(unknown MTA event)", milter->m.name);
+ return (milter->def_reply);
+ }
+
+ /*
+ * Receive the reply or replies.
+ *
+ * Intercept all loop exits so that we can do post header/body edit
+ * processing.
+ *
+ * XXX Bound the loop iteration count.
+ *
+ * In the end-of-body stage, the Milter may reply with one or more queue
+ * file edit requests before it replies with its final decision: accept,
+ * reject, etc. After a local queue file edit error (file too big, media
+ * write error), do not close the Milter socket in the cleanup server.
+ * Instead skip all further Milter replies until the final decision. This
+ * way the Postfix SMTP server stays in sync with the Milter, and Postfix
+ * doesn't have to lose the ability to handle multiple deliveries within
+ * the same SMTP session. This requires that the Postfix SMTP server uses
+ * something other than CLEANUP_STAT_WRITE when it loses contact with the
+ * cleanup server.
+ */
+#define IN_CONNECT_EVENT(e) ((e) == SMFIC_CONNECT || (e) == SMFIC_HELO)
+
+ /*
+ * XXX Don't evaluate this macro's argument multiple times. Since we use
+ * "continue" the macro can't be enclosed in do .. while (0).
+ */
+#define MILTER8_EVENT_BREAK(s) { \
+ retval = (s); \
+ done = 1; \
+ continue; \
+ }
+
+ while (done == 0) {
+ char *cp;
+ char *rp;
+ char ch;
+ char *next;
+
+ if (milter8_read_resp(milter, event, &cmd, &data_size) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ if (msg_verbose)
+ msg_info("reply: %s data %ld bytes",
+ (smfir_name = str_name_code(smfir_table, cmd)) != 0 ?
+ smfir_name : "unknown", (long) data_size);
+
+ /*
+ * Handle unfinished message body replacement first.
+ *
+ * XXX When SMFIR_REPLBODY is followed by some different request, we
+ * assume that the body replacement operation is complete. The queue
+ * file editing implementation currently does not support sending
+ * part 1 of the body replacement text, doing some other queue file
+ * updates, and then sending part 2 of the body replacement text. To
+ * avoid loss of data, we log an error when SMFIR_REPLBODY requests
+ * are alternated with other requests.
+ */
+ if (body_line_buf != 0 && cmd != SMFIR_REPLBODY) {
+ /* In case the last body replacement line didn't end in CRLF. */
+ if (edit_resp == 0 && LEN(body_line_buf) > 0)
+ edit_resp = parent->repl_body(parent->chg_context,
+ MILTER_BODY_LINE,
+ REC_TYPE_NORM,
+ body_line_buf);
+ if (edit_resp == 0)
+ edit_resp = parent->repl_body(parent->chg_context,
+ MILTER_BODY_END,
+ /* unused*/ 0,
+ (VSTRING *) 0);
+ body_edit_lockout = 1;
+ vstring_free(body_line_buf);
+ body_line_buf = 0;
+ }
+ switch (cmd) {
+
+ /*
+ * Still working on it.
+ */
+ case SMFIR_PROGRESS:
+ if (data_size != 0)
+ break;
+ continue;
+
+ /*
+ * Decision: continue processing.
+ */
+ case SMFIR_CONTINUE:
+ if (data_size != 0)
+ break;
+ MILTER8_EVENT_BREAK(milter->def_reply);
+
+ /*
+ * Decision: accept this message, or accept all further commands
+ * in this SMTP connection. This decision is final (i.e. Sendmail
+ * 8 changes receiver state).
+ */
+ case SMFIR_ACCEPT:
+ if (data_size != 0)
+ break;
+ if (IN_CONNECT_EVENT(event)) {
+#ifdef LIBMILTER_AUTO_DISCONNECT
+ milter8_close_stream(milter);
+#endif
+ /* No more events for this SMTP connection. */
+ milter->state = MILTER8_STAT_ACCEPT_CON;
+ } else {
+ /* No more events for this message. */
+ milter->state = MILTER8_STAT_ACCEPT_MSG;
+ }
+ MILTER8_EVENT_BREAK(milter->def_reply);
+
+ /*
+ * Decision: accept and silently discard this message. According
+ * to the milter API documentation there will be no action when
+ * this is requested by a connection-level function. This
+ * decision is final (i.e. Sendmail 8 changes receiver state).
+ */
+ case SMFIR_DISCARD:
+ if (data_size != 0)
+ break;
+ if (IN_CONNECT_EVENT(event)) {
+ msg_warn("milter %s: DISCARD action is not allowed "
+ "for connect or helo", milter->m.name);
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ } else {
+ /* No more events for this message. */
+ milter->state = MILTER8_STAT_ACCEPT_MSG;
+ MILTER8_EVENT_BREAK("D");
+ }
+
+ /*
+ * Decision: reject connection, message or recipient. This
+ * decision is final (i.e. Sendmail 8 changes receiver state).
+ */
+ case SMFIR_REJECT:
+ if (data_size != 0)
+ break;
+ if (IN_CONNECT_EVENT(event)) {
+#ifdef LIBMILTER_AUTO_DISCONNECT
+ milter8_close_stream(milter);
+#endif
+ milter->state = MILTER8_STAT_REJECT_CON;
+ MILTER8_EVENT_BREAK(milter8_def_reply(milter, "550 5.7.1 Command rejected"));
+ } else {
+ MILTER8_EVENT_BREAK("550 5.7.1 Command rejected");
+ }
+
+ /*
+ * Decision: tempfail. This decision is final (i.e. Sendmail 8
+ * changes receiver state).
+ */
+ case SMFIR_TEMPFAIL:
+ if (data_size != 0)
+ break;
+ if (IN_CONNECT_EVENT(event)) {
+#ifdef LIBMILTER_AUTO_DISCONNECT
+ milter8_close_stream(milter);
+#endif
+ milter->state = MILTER8_STAT_REJECT_CON;
+ MILTER8_EVENT_BREAK(milter8_def_reply(milter,
+ "451 4.7.1 Service unavailable - try again later"));
+ } else {
+ MILTER8_EVENT_BREAK("451 4.7.1 Service unavailable - try again later");
+ }
+
+ /*
+ * Decision: disconnect. This decision is final (i.e. Sendmail 8
+ * changes receiver state).
+ */
+ case SMFIR_SHUTDOWN:
+ if (data_size != 0)
+ break;
+#ifdef LIBMILTER_AUTO_DISCONNECT
+ milter8_close_stream(milter);
+#endif
+ milter->state = MILTER8_STAT_REJECT_CON;
+ MILTER8_EVENT_BREAK(milter8_def_reply(milter, "S"));
+
+ /*
+ * Decision: "ddd d.d+.d+ text". This decision is final (i.e.
+ * Sendmail 8 changes receiver state). Note: the reply may be in
+ * multi-line SMTP format.
+ *
+ * XXX Sendmail compatibility: sendmail 8 uses the reply as a format
+ * string; therefore any '%' characters in the reply are doubled.
+ * Postfix doesn't use replies as format strings; we replace '%%'
+ * by '%', and remove single (i.e. invalid) '%' characters.
+ */
+ case SMFIR_REPLYCODE:
+ if (milter8_read_data(milter, &data_size,
+ MILTER8_DATA_BUFFER, milter->buf,
+ MILTER8_DATA_END) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ /* XXX Enforce this for each line of a multi-line reply. */
+ if ((STR(milter->buf)[0] != '4' && STR(milter->buf)[0] != '5')
+ || !ISDIGIT(STR(milter->buf)[1])
+ || !ISDIGIT(STR(milter->buf)[2])
+ || (STR(milter->buf)[3] != ' ' && STR(milter->buf)[3] != '-')
+ || (ISDIGIT(STR(milter->buf)[4])
+ && (STR(milter->buf)[4] != STR(milter->buf)[0]))) {
+ msg_warn("milter %s: malformed reply: %s",
+ milter->m.name, STR(milter->buf));
+ milter8_conf_error(milter);
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ }
+ if ((rp = cp = strchr(STR(milter->buf), '%')) != 0) {
+ for (;;) {
+ if ((ch = *cp++) == '%')
+ ch = *cp++;
+ *rp++ = ch;
+ if (ch == 0)
+ break;
+ }
+ }
+ if (var_soft_bounce) {
+ for (cp = STR(milter->buf); /* void */ ; cp = next) {
+ if (cp[0] == '5') {
+ cp[0] = '4';
+ if (cp[4] == '5')
+ cp[4] = '4';
+ }
+ if ((next = strstr(cp, "\r\n")) == 0)
+ break;
+ next += 2;
+ }
+ }
+ if (IN_CONNECT_EVENT(event)) {
+#ifdef LIBMILTER_AUTO_DISCONNECT
+ milter8_close_stream(milter);
+#endif
+ milter->state = MILTER8_STAT_REJECT_CON;
+ MILTER8_EVENT_BREAK(milter8_def_reply(milter, STR(milter->buf)));
+ } else {
+ MILTER8_EVENT_BREAK(STR(milter->buf));
+ }
+
+ /*
+ * Decision: quarantine. In Sendmail 8.13 this does not imply a
+ * transition in the receiver state (reply, reject, tempfail,
+ * accept, discard). We should not transition, either, otherwise
+ * we get out of sync.
+ */
+ case SMFIR_QUARANTINE:
+ /* XXX What to do with the "reason" text? */
+ if (milter8_read_data(milter, &data_size,
+ MILTER8_DATA_BUFFER, milter->buf,
+ MILTER8_DATA_END) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ milter8_def_reply(milter, "H");
+ continue;
+
+ /*
+ * Decision: skip further events of this type.
+ */
+ case SMFIR_SKIP:
+ if (data_size != 0)
+ break;
+ milter->skip_event_type = event;
+ MILTER8_EVENT_BREAK(milter->def_reply);
+
+ /*
+ * Modification request or error.
+ */
+ default:
+ if (event == SMFIC_BODYEOB) {
+ switch (cmd) {
+
+#define MILTER8_HDR_SPACE(m) (((m)->ev_mask & SMFIP_HDR_LEADSPC) ? "" : " ")
+
+ /*
+ * Modification request: replace, insert or delete
+ * header. Index 1 means the first instance.
+ */
+ case SMFIR_CHGHEADER:
+ if (milter8_read_data(milter, &data_size,
+ MILTER8_DATA_HLONG, &index,
+ MILTER8_DATA_STRING, milter->buf,
+ MILTER8_DATA_STRING, milter->body,
+ MILTER8_DATA_END) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
+ /* XXX Sendmail 8 compatibility. */
+ if (index == 0)
+ index = 1;
+ if ((ssize_t) index < 1) {
+ msg_warn("milter %s: bad change header index: %ld",
+ milter->m.name, (long) index);
+ milter8_conf_error(milter);
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ }
+ if (LEN(milter->buf) == 0) {
+ msg_warn("milter %s: null change header name",
+ milter->m.name);
+ milter8_conf_error(milter);
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ }
+ if (STR(milter->body)[0])
+ edit_resp = parent->upd_header(parent->chg_context,
+ (ssize_t) index,
+ STR(milter->buf),
+ MILTER8_HDR_SPACE(milter),
+ STR(milter->body));
+ else
+ edit_resp = parent->del_header(parent->chg_context,
+ (ssize_t) index,
+ STR(milter->buf));
+ continue;
+
+ /*
+ * Modification request: append header.
+ */
+ case SMFIR_ADDHEADER:
+ if (milter8_read_data(milter, &data_size,
+ MILTER8_DATA_STRING, milter->buf,
+ MILTER8_DATA_STRING, milter->body,
+ MILTER8_DATA_END) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
+ edit_resp = parent->add_header(parent->chg_context,
+ STR(milter->buf),
+ MILTER8_HDR_SPACE(milter),
+ STR(milter->body));
+ continue;
+
+ /*
+ * Modification request: insert header. With Sendmail 8,
+ * index 0 means the top-most header. We use 1-based
+ * indexing for consistency with header change
+ * operations.
+ */
+ case SMFIR_INSHEADER:
+ if (milter8_read_data(milter, &data_size,
+ MILTER8_DATA_HLONG, &index,
+ MILTER8_DATA_STRING, milter->buf,
+ MILTER8_DATA_STRING, milter->body,
+ MILTER8_DATA_END) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
+ if ((ssize_t) index + 1 < 1) {
+ msg_warn("milter %s: bad insert header index: %ld",
+ milter->m.name, (long) index);
+ milter8_conf_error(milter);
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ }
+ edit_resp = parent->ins_header(parent->chg_context,
+ (ssize_t) index + 1,
+ STR(milter->buf),
+ MILTER8_HDR_SPACE(milter),
+ STR(milter->body));
+ continue;
+
+ /*
+ * Modification request: replace sender, with optional
+ * ESMTP args.
+ */
+ case SMFIR_CHGFROM:
+ if (milter8_read_data(milter, &data_size,
+ MILTER8_DATA_STRING, milter->buf,
+ MILTER8_DATA_MORE) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ if (data_size > 0) {
+ if (milter8_read_data(milter, &data_size,
+ MILTER8_DATA_STRING, milter->body,
+ MILTER8_DATA_END) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ } else {
+ VSTRING_RESET(milter->body);
+ VSTRING_TERMINATE(milter->body);
+ }
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
+ edit_resp = parent->chg_from(parent->chg_context,
+ STR(milter->buf),
+ STR(milter->body));
+ continue;
+
+ /*
+ * Modification request: append recipient.
+ */
+ case SMFIR_ADDRCPT:
+ if (milter8_read_data(milter, &data_size,
+ MILTER8_DATA_STRING, milter->buf,
+ MILTER8_DATA_END) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
+ edit_resp = parent->add_rcpt(parent->chg_context,
+ STR(milter->buf));
+ continue;
+
+ /*
+ * Modification request: append recipient, with optional
+ * ESMTP args.
+ */
+ case SMFIR_ADDRCPT_PAR:
+ if (milter8_read_data(milter, &data_size,
+ MILTER8_DATA_STRING, milter->buf,
+ MILTER8_DATA_MORE) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ if (data_size > 0) {
+ if (milter8_read_data(milter, &data_size,
+ MILTER8_DATA_STRING, milter->body,
+ MILTER8_DATA_END) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ } else {
+ VSTRING_RESET(milter->body);
+ VSTRING_TERMINATE(milter->body);
+ }
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
+ edit_resp = parent->add_rcpt_par(parent->chg_context,
+ STR(milter->buf),
+ STR(milter->body));
+ continue;
+
+ /*
+ * Modification request: delete (expansion of) recipient.
+ */
+ case SMFIR_DELRCPT:
+ if (milter8_read_data(milter, &data_size,
+ MILTER8_DATA_STRING, milter->buf,
+ MILTER8_DATA_END) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
+ edit_resp = parent->del_rcpt(parent->chg_context,
+ STR(milter->buf));
+ continue;
+
+ /*
+ * Modification request: replace the message body, and
+ * update the message size.
+ */
+ case SMFIR_REPLBODY:
+ if (body_edit_lockout) {
+ msg_warn("milter %s: body replacement requests can't "
+ "currently be mixed with other requests",
+ milter->m.name);
+ milter8_conf_error(milter);
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ }
+ if (milter8_read_data(milter, &data_size,
+ MILTER8_DATA_BUFFER, milter->body,
+ MILTER8_DATA_END) != 0)
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
+ /* Start body replacement. */
+ if (body_line_buf == 0) {
+ body_line_buf = vstring_alloc(var_line_limit);
+ edit_resp = parent->repl_body(parent->chg_context,
+ MILTER_BODY_START,
+ /* unused */ 0,
+ (VSTRING *) 0);
+ }
+ /* Extract lines from the on-the-wire CRLF format. */
+ for (cp = STR(milter->body); edit_resp == 0
+ && cp < vstring_end(milter->body); cp++) {
+ ch = *(unsigned char *) cp;
+ if (ch == '\n') {
+ if (LEN(body_line_buf) > 0
+ && vstring_end(body_line_buf)[-1] == '\r')
+ vstring_truncate(body_line_buf,
+ LEN(body_line_buf) - 1);
+ edit_resp = parent->repl_body(parent->chg_context,
+ MILTER_BODY_LINE,
+ REC_TYPE_NORM,
+ body_line_buf);
+ VSTRING_RESET(body_line_buf);
+ } else {
+ /* Preserves \r if not followed by \n. */
+ if (LEN(body_line_buf) == var_line_limit) {
+ edit_resp = parent->repl_body(parent->chg_context,
+ MILTER_BODY_LINE,
+ REC_TYPE_CONT,
+ body_line_buf);
+ VSTRING_RESET(body_line_buf);
+ }
+ VSTRING_ADDCH(body_line_buf, ch);
+ }
+ }
+ continue;
+ }
+ }
+ msg_warn("milter %s: unexpected filter response %s after event %s",
+ milter->m.name,
+ (smfir_name = str_name_code(smfir_table, cmd)) != 0 ?
+ smfir_name : "(unknown filter reply)",
+ (smfic_name = str_name_code(smfic_table, event)) != 0 ?
+ smfic_name : "(unknown MTA event)");
+ milter8_comm_error(milter);
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ }
+
+ /*
+ * Get here when the reply was followed by data bytes that weren't
+ * supposed to be there.
+ */
+ msg_warn("milter %s: reply %s was followed by %ld data bytes",
+ milter->m.name, (smfir_name = str_name_code(smfir_table, cmd)) != 0 ?
+ smfir_name : "unknown", (long) data_len);
+ milter8_comm_error(milter);
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ }
+
+ /*
+ * Clean up after aborted message body replacement.
+ */
+ if (body_line_buf)
+ vstring_free(body_line_buf);
+
+ /*
+ * XXX Some cleanup clients ask the cleanup server to bounce mail for
+ * them. In that case we must override a hard reject retval result after
+ * queue file update failure. This is not a big problem; the odds are
+ * small that a Milter application sends a hard reject after replacing
+ * the message body.
+ */
+ if (edit_resp && (retval == 0 || strchr("DS4", retval[0]) == 0))
+ retval = edit_resp;
+ return (retval);
+}
+
+/* milter8_connect - connect to filter */
+
+static void milter8_connect(MILTER8 *milter)
+{
+ const char *myname = "milter8_connect";
+ ssize_t data_len;
+ unsigned char cmd;
+ char *transport;
+ char *endpoint;
+ int (*connect_fn) (const char *, int, int);
+ int fd;
+ const UINT32_TYPE my_actions = (SMFIF_ADDHDRS | SMFIF_ADDRCPT
+ | SMFIF_DELRCPT | SMFIF_CHGHDRS
+ | SMFIF_CHGBODY
+ | SMFIF_QUARANTINE
+ | SMFIF_CHGFROM
+ | SMFIF_ADDRCPT_PAR
+ | SMFIF_SETSYMLIST
+ );
+ UINT32_TYPE my_version = 0;
+ UINT32_TYPE my_events = 0;
+ char *saved_version;
+ char *cp;
+ char *name;
+
+ /*
+ * Sanity check.
+ */
+ if (milter->fp != 0)
+ msg_panic("%s: milter %s: socket is not closed",
+ myname, milter->m.name);
+
+ /*
+ * For user friendliness reasons the milter_protocol configuration
+ * parameter can specify both the protocol version and protocol
+ * extensions (e.g., don't reply for each individual message header).
+ *
+ * The protocol version is sent as is to the milter application.
+ *
+ * The version and extensions determine what events we can send to the
+ * milter application.
+ *
+ * We don't announce support for events that aren't defined for my protocol
+ * version. Today's libmilter implementations don't seem to care, but we
+ * don't want to take the risk that a future version will be more picky.
+ */
+ cp = saved_version = mystrdup(milter->protocol);
+ while ((name = mystrtok(&cp, CHARS_COMMA_SP)) != 0) {
+ int mask;
+ int vers;
+
+ if ((mask = name_code(milter8_event_masks,
+ NAME_CODE_FLAG_NONE, name)) == -1
+ || (vers = name_code(milter8_versions,
+ NAME_CODE_FLAG_NONE, name)) == -1
+ || (vers != 0 && my_version != 0)) {
+ msg_warn("milter %s: bad protocol information: %s",
+ milter->m.name, name);
+ milter8_conf_error(milter);
+ return;
+ }
+ if (vers != 0)
+ my_version = vers;
+ my_events |= mask;
+ }
+ myfree(saved_version);
+ if (my_events == 0 || my_version == 0) {
+ msg_warn("milter %s: no protocol version information", milter->m.name);
+ milter8_conf_error(milter);
+ return;
+ }
+
+ /*
+ * Don't send events that aren't defined for my protocol version.
+ */
+ milter->np_mask = (SMFIP_NOSEND_MASK & ~my_events);
+ if (msg_verbose)
+ msg_info("%s: non-protocol events for protocol version %d: %s",
+ myname, my_version,
+ str_name_mask_opt(milter->buf, "non-protocol event mask",
+ smfip_table, milter->np_mask, NAME_MASK_NUMBER));
+
+ /*
+ * Parse the Milter application endpoint.
+ */
+#define FREE_TRANSPORT_AND_BAIL_OUT(milter, milter_error) do { \
+ myfree(transport); \
+ milter_error(milter); \
+ return; \
+ } while (0);
+
+ transport = mystrdup(milter->m.name);
+ if ((endpoint = split_at(transport, ':')) == 0
+ || *endpoint == 0 || *transport == 0) {
+ msg_warn("Milter service needs transport:endpoint instead of \"%s\"",
+ milter->m.name);
+ FREE_TRANSPORT_AND_BAIL_OUT(milter, milter8_conf_error);
+ }
+ if (msg_verbose)
+ msg_info("%s: transport=%s endpoint=%s", myname, transport, endpoint);
+ if (strcmp(transport, "inet") == 0) {
+ connect_fn = inet_connect;
+ } else if (strcmp(transport, "unix") == 0) {
+ connect_fn = unix_connect;
+ } else if (strcmp(transport, "local") == 0) {
+ connect_fn = LOCAL_CONNECT;
+ } else {
+ msg_warn("invalid transport name: %s in Milter service: %s",
+ transport, milter->m.name);
+ FREE_TRANSPORT_AND_BAIL_OUT(milter, milter8_conf_error);
+ }
+
+ /*
+ * Connect to the Milter application.
+ */
+ if ((fd = connect_fn(endpoint, BLOCKING, milter->conn_timeout)) < 0) {
+ msg_warn("connect to Milter service %s: %m", milter->m.name);
+ FREE_TRANSPORT_AND_BAIL_OUT(milter, milter8_comm_error);
+ }
+ myfree(transport);
+ milter->fp = vstream_fdopen(fd, O_RDWR);
+ vstream_control(milter->fp,
+ CA_VSTREAM_CTL_DOUBLE,
+ CA_VSTREAM_CTL_TIMEOUT(milter->cmd_timeout),
+ CA_VSTREAM_CTL_END);
+ /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */
+ if (connect_fn == inet_connect)
+ vstream_tweak_tcp(milter->fp);
+
+ /*
+ * Open the negotiations by sending what actions the Milter may request
+ * and what events the Milter can receive.
+ */
+ if (msg_verbose) {
+ msg_info("%s: my_version=0x%lx", myname, (long) my_version);
+ msg_info("%s: my_actions=0x%lx %s", myname, (long) my_actions,
+ str_name_mask_opt(milter->buf, "request mask",
+ smfif_table, my_actions, NAME_MASK_NUMBER));
+ msg_info("%s: my_events=0x%lx %s", myname, (long) my_events,
+ str_name_mask_opt(milter->buf, "event mask",
+ smfip_table, my_events, NAME_MASK_NUMBER));
+ }
+ errno = 0;
+ if (milter8_write_cmd(milter, SMFIC_OPTNEG,
+ MILTER8_DATA_HLONG, my_version,
+ MILTER8_DATA_HLONG, my_actions,
+ MILTER8_DATA_HLONG, my_events,
+ MILTER8_DATA_END) != 0) {
+ msg_warn("milter %s: write error in initial handshake",
+ milter->m.name);
+ /* milter8_write_cmd() called milter8_comm_error() */
+ return;
+ }
+
+ /*
+ * Receive the filter's response and verify that we are compatible.
+ */
+ if (milter8_read_resp(milter, SMFIC_OPTNEG, &cmd, &data_len) != 0) {
+ msg_warn("milter %s: read error in initial handshake", milter->m.name);
+ /* milter8_read_resp() called milter8_comm_error() */
+ return;
+ }
+ if (cmd != SMFIC_OPTNEG) {
+ msg_warn("milter %s: unexpected reply \"%c\" in initial handshake",
+ milter->m.name, cmd);
+ (void) milter8_comm_error(milter);
+ return;
+ }
+ if (milter8_read_data(milter, &data_len,
+ MILTER8_DATA_HLONG, &milter->version,
+ MILTER8_DATA_HLONG, &milter->rq_mask,
+ MILTER8_DATA_HLONG, &milter->ev_mask,
+ MILTER8_DATA_MORE) != 0) {
+ msg_warn("milter %s: read error in initial handshake", milter->m.name);
+ /* milter8_read_data() called milter8_comm_error() */
+ return;
+ }
+ if (milter->version > my_version) {
+ msg_warn("milter %s: protocol version %d conflict"
+ " with MTA protocol version %d",
+ milter->m.name, milter->version, my_version);
+ (void) milter8_comm_error(milter);
+ return;
+ }
+ if ((milter->rq_mask & my_actions) != milter->rq_mask) {
+ msg_warn("milter %s: request mask 0x%x conflict"
+ " with MTA request mask 0x%lx",
+ milter->m.name, milter->rq_mask, (long) my_actions);
+ (void) milter8_comm_error(milter);
+ return;
+ }
+ if (milter->ev_mask & SMFIP_RCPT_REJ)
+ milter->m.flags |= MILTER_FLAG_WANT_RCPT_REJ;
+
+ /*
+ * Allow the remote application to run an older protocol version, but
+ * don't them send events that their protocol version doesn't support.
+ * Based on a suggestion by Kouhei Sutou.
+ *
+ * XXX When the Milter sends a protocol version that we don't have
+ * information for, use the information for the next-lower protocol
+ * version instead. This code assumes that the milter8_event_masks table
+ * is organized in reverse numerical order.
+ */
+ if (milter->version < my_version) {
+ const NAME_CODE *np;
+ int version;
+
+ for (np = milter8_event_masks; /* see below */ ; np++) {
+ if (np->name == 0) {
+ msg_warn("milter %s: unexpected protocol version %d",
+ milter->m.name, milter->version);
+ break;
+ }
+ if ((version = atoi(np->name)) > 0 && version <= milter->version) {
+ milter->np_mask |= (SMFIP_NOSEND_MASK & ~np->code);
+ if (msg_verbose)
+ msg_info("%s: non-protocol events for milter %s"
+ " protocol version %d: %s",
+ myname, milter->m.name, milter->version,
+ str_name_mask_opt(milter->buf,
+ "non-protocol event mask",
+ smfip_table, milter->np_mask,
+ NAME_MASK_NUMBER));
+ break;
+ }
+ }
+ }
+
+ /*
+ * Initial negotiations completed.
+ */
+ if (msg_verbose) {
+ if ((milter->ev_mask & my_events) != milter->ev_mask)
+ msg_info("milter %s: event mask 0x%x includes features not"
+ " offered in MTA event mask 0x%lx",
+ milter->m.name, milter->ev_mask, (long) my_events);
+ msg_info("%s: milter %s version %d",
+ myname, milter->m.name, milter->version);
+ msg_info("%s: events %s", myname,
+ str_name_mask_opt(milter->buf, "event mask",
+ smfip_table, milter->ev_mask, NAME_MASK_NUMBER));
+ msg_info("%s: requests %s", myname,
+ str_name_mask_opt(milter->buf, "request mask",
+ smfif_table, milter->rq_mask, NAME_MASK_NUMBER));
+ }
+ milter->state = MILTER8_STAT_READY;
+ milter8_def_reply(milter, 0);
+ milter->skip_event_type = 0;
+
+ /*
+ * Secondary negotiations: override lists of macro names.
+ */
+ if (data_len > 0) {
+ VSTRING *buf = vstring_alloc(100);
+ UINT32_TYPE mac_type;
+ const char *smfim_name;
+ char **mac_value_ptr;
+
+ milter->m.macros = milter_macros_alloc(MILTER_MACROS_ALLOC_EMPTY);
+
+ while (data_len > 0
+ && milter8_read_data(milter, &data_len,
+ MILTER8_DATA_HLONG, &mac_type,
+ MILTER8_DATA_STRING, buf,
+ MILTER8_DATA_MORE) == 0) {
+ smfim_name = str_name_code(smfim_table, mac_type);
+ if (smfim_name == 0) {
+ msg_warn("milter %s: ignoring unknown macro type %u",
+ milter->m.name, (unsigned) mac_type);
+ } else {
+ if (msg_verbose)
+ msg_info("override %s macro list with \"%s\"",
+ smfim_name, STR(buf));
+ mac_value_ptr = MILTER8_MACRO_PTR(milter->m.macros, mac_type);
+ myfree(*mac_value_ptr);
+ *mac_value_ptr = mystrdup(STR(buf));
+ }
+ }
+ /* milter8_read_data() calls milter8_comm_error() after error. */
+ vstring_free(buf);
+ /* At this point the filter state is either READY or ERROR. */
+ }
+}
+
+/* milter8_conn_event - report connect event to Sendmail 8 milter */
+
+static const char *milter8_conn_event(MILTER *m,
+ const char *client_name,
+ const char *client_addr,
+ const char *client_port,
+ unsigned addr_family,
+ ARGV *macros)
+{
+ const char *myname = "milter8_conn_event";
+ MILTER8 *milter = (MILTER8 *) m;
+ int port;
+ int skip_reply;
+ const char *sm_name;
+ char *ptr = 0;
+ const char *resp;
+
+ /*
+ * Need a global definition for "unknown" host name or address that is
+ * shared by smtpd, cleanup and libmilter.
+ */
+#define XXX_UNKNOWN "unknown"
+#define STR_EQ(x,y) (strcmp((x), (y)) == 0)
+#define STR_NE(x,y) (strcmp((x), (y)) != 0)
+
+ /*
+ * Report the event.
+ */
+ switch (milter->state) {
+ case MILTER8_STAT_ERROR:
+ if (msg_verbose)
+ msg_info("%s: skip milter %s", myname, milter->m.name);
+ return (milter->def_reply);
+ case MILTER8_STAT_READY:
+ if (msg_verbose)
+ msg_info("%s: milter %s: connect %s/%s",
+ myname, milter->m.name, client_name, client_addr);
+ if (client_port == 0) {
+ port = 0;
+ } else if (!alldig(client_port) || (port = atoi(client_port)) < 0
+ || port > 65535) {
+ msg_warn("milter %s: bad client port number %s",
+ milter->m.name, client_port);
+ port = 0;
+ }
+ milter->state = MILTER8_STAT_ENVELOPE;
+ skip_reply = ((milter->ev_mask & SMFIP_NR_CONN) != 0);
+ /* Transform unknown hostname from Postfix to Sendmail form. */
+ sm_name = (STR_NE(client_name, XXX_UNKNOWN) ? client_name :
+ STR_EQ(client_addr, XXX_UNKNOWN) ? client_name :
+ (ptr = concatenate("[", client_addr, "]", (char *) 0)));
+ switch (addr_family) {
+ case AF_INET:
+ resp = milter8_event(milter, SMFIC_CONNECT, SMFIP_NOCONNECT,
+ skip_reply, macros,
+ MILTER8_DATA_STRING, sm_name,
+ MILTER8_DATA_OCTET, SMFIA_INET,
+ MILTER8_DATA_NSHORT, htons(port),
+ MILTER8_DATA_STRING, client_addr,
+ MILTER8_DATA_END);
+ break;
+#ifdef HAS_IPV6
+ case AF_INET6:
+ resp = milter8_event(milter, SMFIC_CONNECT, SMFIP_NOCONNECT,
+ skip_reply, macros,
+ MILTER8_DATA_STRING, sm_name,
+ MILTER8_DATA_OCTET, SMFIA_INET6,
+ MILTER8_DATA_NSHORT, htons(port),
+ MILTER8_DATA_STRING, client_addr,
+ MILTER8_DATA_END);
+ break;
+#endif
+ case AF_UNIX:
+ resp = milter8_event(milter, SMFIC_CONNECT, SMFIP_NOCONNECT,
+ skip_reply, macros,
+ MILTER8_DATA_STRING, sm_name,
+ MILTER8_DATA_OCTET, SMFIA_UNIX,
+ MILTER8_DATA_NSHORT, htons(0),
+ MILTER8_DATA_STRING, client_addr,
+ MILTER8_DATA_END);
+ break;
+ default:
+ resp = milter8_event(milter, SMFIC_CONNECT, SMFIP_NOCONNECT,
+ skip_reply, macros,
+ MILTER8_DATA_STRING, sm_name,
+ MILTER8_DATA_OCTET, SMFIA_UNKNOWN,
+ MILTER8_DATA_END);
+ break;
+ }
+ if (ptr != 0)
+ myfree(ptr);
+ return (resp);
+ default:
+ msg_panic("%s: milter %s: bad state %d",
+ myname, milter->m.name, milter->state);
+ }
+}
+
+/* milter8_helo_event - report HELO/EHLO command to Sendmail 8 milter */
+
+static const char *milter8_helo_event(MILTER *m, const char *helo_name,
+ int unused_esmtp,
+ ARGV *macros)
+{
+ const char *myname = "milter8_helo_event";
+ MILTER8 *milter = (MILTER8 *) m;
+ int skip_reply;
+
+ /*
+ * Report the event.
+ */
+ switch (milter->state) {
+ case MILTER8_STAT_ERROR:
+ case MILTER8_STAT_ACCEPT_CON:
+ case MILTER8_STAT_REJECT_CON:
+ if (msg_verbose)
+ msg_info("%s: skip milter %s", myname, milter->m.name);
+ return (milter->def_reply);
+ case MILTER8_STAT_ENVELOPE:
+ case MILTER8_STAT_ACCEPT_MSG:
+ /* With HELO after MAIL, smtpd(8) calls milter8_abort() next. */
+ if (msg_verbose)
+ msg_info("%s: milter %s: helo %s",
+ myname, milter->m.name, helo_name);
+ skip_reply = ((milter->ev_mask & SMFIP_NR_HELO) != 0);
+ return (milter8_event(milter, SMFIC_HELO, SMFIP_NOHELO,
+ skip_reply, macros,
+ MILTER8_DATA_STRING, helo_name,
+ MILTER8_DATA_END));
+ default:
+ msg_panic("%s: milter %s: bad state %d",
+ myname, milter->m.name, milter->state);
+ }
+}
+
+/* milter8_mail_event - report MAIL command to Sendmail 8 milter */
+
+static const char *milter8_mail_event(MILTER *m, const char **argv,
+ ARGV *macros)
+{
+ const char *myname = "milter8_mail_event";
+ MILTER8 *milter = (MILTER8 *) m;
+ const char **cpp;
+ int skip_reply;
+
+ /*
+ * Report the event.
+ */
+ switch (milter->state) {
+ case MILTER8_STAT_ERROR:
+ case MILTER8_STAT_ACCEPT_CON:
+ case MILTER8_STAT_REJECT_CON:
+ if (msg_verbose)
+ msg_info("%s: skip milter %s", myname, milter->m.name);
+ return (milter->def_reply);
+ case MILTER8_STAT_ENVELOPE:
+ if (msg_verbose) {
+ VSTRING *buf = vstring_alloc(100);
+
+ for (cpp = argv; *cpp; cpp++)
+ vstring_sprintf_append(buf, " %s", *cpp);
+ msg_info("%s: milter %s: mail%s",
+ myname, milter->m.name, STR(buf));
+ vstring_free(buf);
+ }
+ skip_reply = ((milter->ev_mask & SMFIP_NR_MAIL) != 0);
+ return (milter8_event(milter, SMFIC_MAIL, SMFIP_NOMAIL,
+ skip_reply, macros,
+ MILTER8_DATA_ARGV, argv,
+ MILTER8_DATA_END));
+ default:
+ msg_panic("%s: milter %s: bad state %d",
+ myname, milter->m.name, milter->state);
+ }
+}
+
+/* milter8_rcpt_event - report RCPT command to Sendmail 8 milter */
+
+static const char *milter8_rcpt_event(MILTER *m, const char **argv,
+ ARGV *macros)
+{
+ const char *myname = "milter8_rcpt_event";
+ MILTER8 *milter = (MILTER8 *) m;
+ const char **cpp;
+ int skip_reply;
+
+ /*
+ * Report the event.
+ */
+ switch (milter->state) {
+ case MILTER8_STAT_ERROR:
+ case MILTER8_STAT_ACCEPT_CON:
+ case MILTER8_STAT_REJECT_CON:
+ case MILTER8_STAT_ACCEPT_MSG:
+ if (msg_verbose)
+ msg_info("%s: skip milter %s", myname, milter->m.name);
+ return (milter->def_reply);
+ case MILTER8_STAT_ENVELOPE:
+ if (msg_verbose) {
+ VSTRING *buf = vstring_alloc(100);
+
+ for (cpp = argv; *cpp; cpp++)
+ vstring_sprintf_append(buf, " %s", *cpp);
+ msg_info("%s: milter %s: rcpt%s",
+ myname, milter->m.name, STR(buf));
+ vstring_free(buf);
+ }
+ skip_reply = ((milter->ev_mask & SMFIP_NR_RCPT) != 0);
+ return (milter8_event(milter, SMFIC_RCPT, SMFIP_NORCPT,
+ skip_reply, macros,
+ MILTER8_DATA_ARGV, argv,
+ MILTER8_DATA_END));
+ default:
+ msg_panic("%s: milter %s: bad state %d",
+ myname, milter->m.name, milter->state);
+ }
+}
+
+/* milter8_data_event - report DATA command to Sendmail 8 milter */
+
+static const char *milter8_data_event(MILTER *m, ARGV *macros)
+{
+ const char *myname = "milter8_data_event";
+ MILTER8 *milter = (MILTER8 *) m;
+ int skip_reply;
+
+ /*
+ * Report the event.
+ */
+ switch (milter->state) {
+ case MILTER8_STAT_ERROR:
+ case MILTER8_STAT_ACCEPT_CON:
+ case MILTER8_STAT_REJECT_CON:
+ case MILTER8_STAT_ACCEPT_MSG:
+ if (msg_verbose)
+ msg_info("%s: skip milter %s", myname, milter->m.name);
+ return (milter->def_reply);
+ case MILTER8_STAT_ENVELOPE:
+ if (msg_verbose)
+ msg_info("%s: milter %s: data command", myname, milter->m.name);
+ skip_reply = ((milter->ev_mask & SMFIP_NR_DATA) != 0);
+ return (milter8_event(milter, SMFIC_DATA, SMFIP_NODATA,
+ skip_reply, macros,
+ MILTER8_DATA_END));
+ default:
+ msg_panic("%s: milter %s: bad state %d",
+ myname, milter->m.name, milter->state);
+ }
+}
+
+/* milter8_unknown_event - report unknown SMTP command to Sendmail 8 milter */
+
+static const char *milter8_unknown_event(MILTER *m, const char *command,
+ ARGV *macros)
+{
+ const char *myname = "milter8_unknown_event";
+ MILTER8 *milter = (MILTER8 *) m;
+ int skip_reply;
+
+ /*
+ * Report the event.
+ */
+ switch (milter->state) {
+ case MILTER8_STAT_ERROR:
+ case MILTER8_STAT_ACCEPT_CON:
+ case MILTER8_STAT_REJECT_CON:
+ case MILTER8_STAT_ACCEPT_MSG:
+ if (msg_verbose)
+ msg_info("%s: skip milter %s", myname, milter->m.name);
+ return (milter->def_reply);
+ case MILTER8_STAT_ENVELOPE:
+ if (msg_verbose)
+ msg_info("%s: milter %s: unknown command: %s",
+ myname, milter->m.name, command);
+ /* XXX Sendmail doesn't send macros (checked with 8.6.13). */
+ skip_reply = ((milter->ev_mask & SMFIP_NR_UNKN) != 0);
+ return (milter8_event(milter, SMFIC_UNKNOWN, SMFIP_NOUNKNOWN,
+ skip_reply, macros,
+ MILTER8_DATA_STRING, command,
+ MILTER8_DATA_END));
+ default:
+ msg_panic("%s: milter %s: bad state %d",
+ myname, milter->m.name, milter->state);
+ }
+}
+
+/* milter8_other_event - reply for other event */
+
+static const char *milter8_other_event(MILTER *m)
+{
+ const char *myname = "milter8_other_event";
+ MILTER8 *milter = (MILTER8 *) m;
+
+ /*
+ * Return the default reply.
+ */
+ if (msg_verbose)
+ msg_info("%s: milter %s", myname, milter->m.name);
+ return (milter->def_reply);
+}
+
+/* milter8_abort - cancel one milter's message receiving state */
+
+static void milter8_abort(MILTER *m)
+{
+ const char *myname = "milter8_abort";
+ MILTER8 *milter = (MILTER8 *) m;
+
+ /*
+ * XXX Sendmail 8 libmilter closes the MTA-to-filter socket when it finds
+ * out that the SMTP client has disconnected. Because of this, Postfix
+ * has to open a new MTA-to-filter socket for each SMTP client.
+ */
+ switch (milter->state) {
+ case MILTER8_STAT_CLOSED:
+ case MILTER8_STAT_READY:
+ return;
+ case MILTER8_STAT_ERROR:
+ case MILTER8_STAT_ACCEPT_CON:
+ case MILTER8_STAT_REJECT_CON:
+ if (msg_verbose)
+ msg_info("%s: skip milter %s", myname, milter->m.name);
+ break;
+ case MILTER8_STAT_ENVELOPE:
+ case MILTER8_STAT_MESSAGE:
+ case MILTER8_STAT_ACCEPT_MSG:
+ if (msg_verbose)
+ msg_info("%s: abort milter %s", myname, milter->m.name);
+ (void) milter8_write_cmd(milter, SMFIC_ABORT, MILTER8_DATA_END);
+ if (milter->state != MILTER8_STAT_ERROR)
+ milter->state = MILTER8_STAT_ENVELOPE;
+ break;
+ default:
+ msg_panic("%s: milter %s: bad state %d",
+ myname, milter->m.name, milter->state);
+ }
+}
+
+/* milter8_disc_event - report client disconnect event */
+
+static void milter8_disc_event(MILTER *m)
+{
+ const char *myname = "milter8_disc_event";
+ MILTER8 *milter = (MILTER8 *) m;
+
+ /*
+ * XXX Sendmail 8 libmilter closes the MTA-to-filter socket when it finds
+ * out that the SMTP client has disconnected. Because of this, Postfix
+ * has to open a new MTA-to-filter socket for each SMTP client.
+ */
+ switch (milter->state) {
+ case MILTER8_STAT_CLOSED:
+ case MILTER8_STAT_READY:
+ return;
+ case MILTER8_STAT_ERROR:
+#ifdef LIBMILTER_AUTO_DISCONNECT
+ case MILTER8_STAT_ACCEPT_CON:
+ case MILTER8_STAT_REJECT_CON:
+#endif
+ if (msg_verbose)
+ msg_info("%s: skip quit milter %s", myname, milter->m.name);
+ break;
+ case MILTER8_STAT_ENVELOPE:
+ case MILTER8_STAT_MESSAGE:
+#ifndef LIBMILTER_AUTO_DISCONNECT
+ case MILTER8_STAT_ACCEPT_CON:
+ case MILTER8_STAT_REJECT_CON:
+#endif
+ case MILTER8_STAT_ACCEPT_MSG:
+ if (msg_verbose)
+ msg_info("%s: quit milter %s", myname, milter->m.name);
+ (void) milter8_write_cmd(milter, SMFIC_QUIT, MILTER8_DATA_END);
+ break;
+ }
+#ifdef LIBMILTER_AUTO_DISCONNECT
+ milter8_close_stream(milter);
+#else
+ if (milter->state != MILTER8_STAT_ERROR)
+ milter->state = MILTER8_STAT_READY;
+#endif
+ milter8_def_reply(milter, 0);
+}
+
+ /*
+ * Structure to ship context across the MIME_STATE engine.
+ */
+typedef struct {
+ MILTER8 *milter; /* milter client */
+ ARGV *eoh_macros; /* end-of-header macros */
+ ARGV *eod_macros; /* end-of-body macros */
+ ARGV *auto_hdrs; /* auto-generated headers */
+ int auto_done; /* good enough for now */
+ int first_header; /* first header */
+ int first_body; /* first body line */
+ const char *resp; /* milter application response */
+} MILTER_MSG_CONTEXT;
+
+/* milter8_header - milter8_message call-back for message header */
+
+static void milter8_header(void *ptr, int unused_header_class,
+ const HEADER_OPTS *header_info,
+ VSTRING *buf, off_t unused_offset)
+{
+ const char *myname = "milter8_header";
+ MILTER_MSG_CONTEXT *msg_ctx = (MILTER_MSG_CONTEXT *) ptr;
+ MILTER8 *milter = msg_ctx->milter;
+ char *cp;
+ int skip_reply;
+ char **cpp;
+ unsigned done;
+
+ /*
+ * XXX Workaround: mime_state_update() may invoke multiple call-backs
+ * before returning to the caller.
+ */
+#define MILTER8_MESSAGE_DONE(milter, msg_ctx) \
+ ((milter)->state != MILTER8_STAT_MESSAGE || (msg_ctx)->resp != 0)
+
+ if (MILTER8_MESSAGE_DONE(milter, msg_ctx))
+ return;
+
+ /*
+ * XXX Sendmail compatibility. Don't expose our first (received) header
+ * to mail filter applications. See also cleanup_milter.c for code to
+ * ensure that header replace requests are relative to the message
+ * content as received, that is, without our own first (received) header,
+ * while header insert requests are relative to the message as delivered,
+ * that is, including our own first (received) header.
+ *
+ * XXX But this breaks when they delete our own Received: header with
+ * header_checks before it reaches the queue file. Even then we must not
+ * expose the first header to mail filter applications, otherwise the
+ * dk-filter signature will be inserted at the wrong position. It should
+ * precede the headers that it signs.
+ *
+ * XXX Sendmail compatibility. It eats the first space (not tab) after the
+ * header label and ":".
+ */
+ for (cpp = msg_ctx->auto_hdrs->argv, done = 1; *cpp; cpp++, done <<= 1)
+ if ((msg_ctx->auto_done & done) == 0 && strcmp(*cpp, STR(buf)) == 0) {
+ msg_ctx->auto_done |= done;
+ return;
+ }
+
+ /*
+ * Sendmail 8 sends multi-line headers as text separated by newline.
+ *
+ * We destroy the header buffer to split it into label and value. Changing
+ * the buffer is explicitly allowed by the mime_state(3) interface.
+ */
+ if (msg_verbose > 1)
+ msg_info("%s: header milter %s: %.100s",
+ myname, milter->m.name, STR(buf));
+ cp = STR(buf) + (header_info ? strlen(header_info->name) :
+ is_header(STR(buf)));
+ /* XXX Following matches is_header.c */
+ while (*cp == ' ' || *cp == '\t')
+ *cp++ = 0;
+ if (*cp != ':')
+ msg_panic("%s: header label not followed by ':'", myname);
+ *cp++ = 0;
+ /* XXX Sendmail by default eats one space (not tab) after the colon. */
+ if ((milter->ev_mask & SMFIP_HDR_LEADSPC) == 0 && *cp == ' ')
+ cp++;
+ skip_reply = ((milter->ev_mask & SMFIP_NOHREPL) != 0);
+ msg_ctx->resp =
+ milter8_event(milter, SMFIC_HEADER, SMFIP_NOHDRS,
+ skip_reply, msg_ctx->eoh_macros,
+ MILTER8_DATA_STRING, STR(buf),
+ MILTER8_DATA_STRING, cp,
+ MILTER8_DATA_END);
+}
+
+/* milter8_eoh - milter8_message call-back for end-of-header */
+
+static void milter8_eoh(void *ptr)
+{
+ const char *myname = "milter8_eoh";
+ MILTER_MSG_CONTEXT *msg_ctx = (MILTER_MSG_CONTEXT *) ptr;
+ MILTER8 *milter = msg_ctx->milter;
+ int skip_reply;
+
+ if (MILTER8_MESSAGE_DONE(milter, msg_ctx))
+ return;
+ if (msg_verbose)
+ msg_info("%s: eoh milter %s", myname, milter->m.name);
+ skip_reply = ((milter->ev_mask & SMFIP_NR_EOH) != 0);
+ msg_ctx->resp =
+ milter8_event(milter, SMFIC_EOH, SMFIP_NOEOH,
+ skip_reply, msg_ctx->eoh_macros,
+ MILTER8_DATA_END);
+}
+
+/* milter8_body - milter8_message call-back for body content */
+
+static void milter8_body(void *ptr, int rec_type,
+ const char *buf, ssize_t len,
+ off_t offset)
+{
+ const char *myname = "milter8_body";
+ MILTER_MSG_CONTEXT *msg_ctx = (MILTER_MSG_CONTEXT *) ptr;
+ MILTER8 *milter = msg_ctx->milter;
+ ssize_t todo = len;
+ const char *bp = buf;
+ ssize_t space;
+ ssize_t count;
+ int skip_reply;
+
+ if (MILTER8_MESSAGE_DONE(milter, msg_ctx))
+ return;
+
+ /*
+ * XXX Sendmail compatibility: don't expose our first body line.
+ */
+ if (msg_ctx->first_body) {
+ msg_ctx->first_body = 0;
+ return;
+ }
+
+ /*
+ * XXX I thought I was going to delegate all the on-the-wire formatting
+ * to a common lower layer, but unfortunately it's not practical. If we
+ * were to do MILTER_CHUNK_SIZE buffering in a common lower layer, then
+ * we would have to pass along call-backs and state, so that the
+ * call-back can invoke milter8_event() with the right arguments when the
+ * MILTER_CHUNK_SIZE buffer reaches capacity. That's just too ugly.
+ *
+ * To recover the cost of making an extra copy of body content from Milter
+ * buffer to VSTREAM buffer, we could make vstream_fwrite() a little
+ * smarter so that it does large transfers directly from the user buffer
+ * instead of copying the data one block at a time into a VSTREAM buffer.
+ */
+ if (msg_verbose > 1)
+ msg_info("%s: body milter %s: %.100s", myname, milter->m.name, buf);
+ skip_reply = ((milter->ev_mask & SMFIP_NR_BODY) != 0);
+ /* To append \r\n, simply redirect input to another buffer. */
+ if (rec_type == REC_TYPE_NORM && todo == 0) {
+ bp = "\r\n";
+ todo = 2;
+ rec_type = REC_TYPE_EOF;
+ }
+ while (todo > 0) {
+ /* Append one REC_TYPE_NORM or REC_TYPE_CONT to body chunk buffer. */
+ space = MILTER_CHUNK_SIZE - LEN(milter->body);
+ if (space <= 0)
+ msg_panic("%s: bad buffer size: %ld",
+ myname, (long) LEN(milter->body));
+ count = (todo > space ? space : todo);
+ vstring_memcat(milter->body, bp, count);
+ bp += count;
+ todo -= count;
+ /* Flush body chunk buffer when full. See also milter8_eob(). */
+ if (LEN(milter->body) == MILTER_CHUNK_SIZE) {
+ msg_ctx->resp =
+ milter8_event(milter, SMFIC_BODY, SMFIP_NOBODY,
+ skip_reply, msg_ctx->eod_macros,
+ MILTER8_DATA_BUFFER, milter->body,
+ MILTER8_DATA_END);
+ if (MILTER8_MESSAGE_DONE(milter, msg_ctx))
+ break;
+ VSTRING_RESET(milter->body);
+ }
+ /* To append \r\n, simply redirect input to another buffer. */
+ if (rec_type == REC_TYPE_NORM && todo == 0) {
+ bp = "\r\n";
+ todo = 2;
+ rec_type = REC_TYPE_EOF;
+ }
+ }
+}
+
+/* milter8_eob - milter8_message call-back for end-of-body */
+
+static void milter8_eob(void *ptr)
+{
+ const char *myname = "milter8_eob";
+ MILTER_MSG_CONTEXT *msg_ctx = (MILTER_MSG_CONTEXT *) ptr;
+ MILTER8 *milter = msg_ctx->milter;
+ int skip_reply;
+
+ if (MILTER8_MESSAGE_DONE(milter, msg_ctx))
+ return;
+ if (msg_verbose)
+ msg_info("%s: eob milter %s", myname, milter->m.name);
+
+ /*
+ * Flush partial body chunk buffer. See also milter8_body().
+ *
+ * XXX Sendmail 8 libmilter accepts SMFIC_EOB+data, and delivers it to the
+ * application as two events: SMFIC_BODY+data followed by SMFIC_EOB. This
+ * breaks with the PMilter 0.95 protocol re-implementation, which
+ * delivers the SMFIC_EOB event and ignores the data. To avoid such
+ * compatibility problems we separate the events in the client. With
+ * this, we also prepare for a future where different event types can
+ * have different macro lists.
+ */
+ if (LEN(milter->body) > 0) {
+ skip_reply = ((milter->ev_mask & SMFIP_NR_BODY) != 0);
+ msg_ctx->resp =
+ milter8_event(milter, SMFIC_BODY, SMFIP_NOBODY,
+ skip_reply, msg_ctx->eod_macros,
+ MILTER8_DATA_BUFFER, milter->body,
+ MILTER8_DATA_END);
+ if (MILTER8_MESSAGE_DONE(milter, msg_ctx))
+ return;
+ }
+ msg_ctx->resp =
+ milter8_event(msg_ctx->milter, SMFIC_BODYEOB, 0,
+ DONT_SKIP_REPLY, msg_ctx->eod_macros,
+ MILTER8_DATA_END);
+}
+
+/* milter8_message - send message content and receive reply */
+
+static const char *milter8_message(MILTER *m, VSTREAM *qfile,
+ off_t data_offset,
+ ARGV *eoh_macros,
+ ARGV *eod_macros,
+ ARGV *auto_hdrs)
+{
+ const char *myname = "milter8_message";
+ MILTER8 *milter = (MILTER8 *) m;
+ MIME_STATE *mime_state;
+ int rec_type;
+ const MIME_STATE_DETAIL *detail;
+ int mime_errs = 0;
+ MILTER_MSG_CONTEXT msg_ctx;
+ VSTRING *buf;
+ int saved_errno;
+
+ switch (milter->state) {
+ case MILTER8_STAT_ERROR:
+ case MILTER8_STAT_ACCEPT_CON:
+ case MILTER8_STAT_REJECT_CON:
+ case MILTER8_STAT_ACCEPT_MSG:
+ if (msg_verbose)
+ msg_info("%s: skip message to milter %s", myname, milter->m.name);
+ return (milter->def_reply);
+ case MILTER8_STAT_ENVELOPE:
+ if (msg_verbose)
+ msg_info("%s: message to milter %s", myname, milter->m.name);
+ if (vstream_fseek(qfile, data_offset, SEEK_SET) < 0) {
+ saved_errno = errno;
+ msg_warn("%s: vstream_fseek %s: %m", myname, VSTREAM_PATH(qfile));
+ /* XXX This should be available from cleanup_strerror.c. */
+ return (saved_errno == EFBIG ?
+ "552 5.3.4 Message file too big" :
+ "451 4.3.0 Queue file write error");
+ }
+ msg_ctx.milter = milter;
+ msg_ctx.eoh_macros = eoh_macros;
+ msg_ctx.eod_macros = eod_macros;
+ msg_ctx.auto_hdrs = auto_hdrs;
+ msg_ctx.auto_done = 0;
+ msg_ctx.first_header = 1;
+ msg_ctx.first_body = 1;
+ msg_ctx.resp = 0;
+ mime_state =
+ mime_state_alloc(MIME_OPT_DISABLE_MIME,
+ (milter->ev_mask & SMFIP_NOHDRS) ?
+ (MIME_STATE_HEAD_OUT) 0 : milter8_header,
+ (milter->ev_mask & SMFIP_NOEOH) ?
+ (MIME_STATE_ANY_END) 0 : milter8_eoh,
+ (milter->ev_mask & SMFIP_NOBODY) ?
+ (MIME_STATE_BODY_OUT) 0 : milter8_body,
+ milter8_eob,
+ (MIME_STATE_ERR_PRINT) 0,
+ (void *) &msg_ctx);
+ buf = vstring_alloc(100);
+ milter->state = MILTER8_STAT_MESSAGE;
+ VSTRING_RESET(milter->body);
+ vstream_control(milter->fp,
+ CA_VSTREAM_CTL_DOUBLE,
+ CA_VSTREAM_CTL_TIMEOUT(milter->msg_timeout),
+ CA_VSTREAM_CTL_END);
+
+ /*
+ * XXX When the message (not MIME body part) does not end in CRLF
+ * (i.e. the last record was REC_TYPE_CONT), do we send a CRLF
+ * terminator before triggering the end-of-body condition?
+ */
+ for (;;) {
+ if ((rec_type = rec_get(qfile, buf, 0)) < 0) {
+ msg_warn("%s: error reading %s: %m",
+ myname, VSTREAM_PATH(qfile));
+ msg_ctx.resp = "450 4.3.0 Queue file write error";
+ break;
+ }
+ /* Invoke the appropriate call-back routine. */
+ mime_errs = mime_state_update(mime_state, rec_type,
+ STR(buf), LEN(buf));
+ if (mime_errs) {
+ detail = mime_state_detail(mime_errs);
+ msg_warn("%s: MIME problem %s in %s",
+ myname, detail->text, VSTREAM_PATH(qfile));
+ msg_ctx.resp = "450 4.3.0 Queue file write error";
+ break;
+ }
+ if (MILTER8_MESSAGE_DONE(milter, &msg_ctx))
+ break;
+ if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT)
+ break;
+ }
+ mime_state_free(mime_state);
+ vstring_free(buf);
+ if (milter->fp)
+ vstream_control(milter->fp,
+ CA_VSTREAM_CTL_DOUBLE,
+ CA_VSTREAM_CTL_TIMEOUT(milter->cmd_timeout),
+ CA_VSTREAM_CTL_END);
+ if (milter->state == MILTER8_STAT_MESSAGE
+ || milter->state == MILTER8_STAT_ACCEPT_MSG)
+ milter->state = MILTER8_STAT_ENVELOPE;
+ return (msg_ctx.resp);
+ default:
+ msg_panic("%s: milter %s: bad state %d",
+ myname, milter->m.name, milter->state);
+ }
+}
+
+ /*
+ * Preliminary protocol to send/receive milter instances. This needs to be
+ * extended with type information once we support multiple milter protocols.
+ */
+#define MAIL_ATTR_MILT_NAME "milter_name"
+#define MAIL_ATTR_MILT_VERS "milter_version"
+#define MAIL_ATTR_MILT_ACTS "milter_actions"
+#define MAIL_ATTR_MILT_EVTS "milter_events"
+#define MAIL_ATTR_MILT_NPTS "milter_non_events"
+#define MAIL_ATTR_MILT_STAT "milter_state"
+#define MAIL_ATTR_MILT_CONN "milter_conn_timeout"
+#define MAIL_ATTR_MILT_CMD "milter_cmd_timeout"
+#define MAIL_ATTR_MILT_MSG "milter_msg_timeout"
+#define MAIL_ATTR_MILT_ACT "milter_action"
+#define MAIL_ATTR_MILT_MAC "milter_macro_list"
+
+/* milter8_active - report if this milter still wants events */
+
+static int milter8_active(MILTER *m)
+{
+ MILTER8 *milter = (MILTER8 *) m;
+
+ return (milter->fp != 0
+ && (milter->state == MILTER8_STAT_ENVELOPE
+ || milter->state == MILTER8_STAT_READY));
+}
+
+/* milter8_send - send milter instance */
+
+static int milter8_send(MILTER *m, VSTREAM *stream)
+{
+ const char *myname = "milter8_send";
+ MILTER8 *milter = (MILTER8 *) m;
+
+ if (msg_verbose)
+ msg_info("%s: milter %s", myname, milter->m.name);
+
+ /*
+ * The next read on this Milter socket happens in a different process. It
+ * will not automatically flush the output buffer in this process.
+ */
+ if (milter->fp)
+ vstream_fflush(milter->fp);
+
+ if (attr_print(stream, ATTR_FLAG_MORE,
+ SEND_ATTR_STR(MAIL_ATTR_MILT_NAME, milter->m.name),
+ SEND_ATTR_INT(MAIL_ATTR_MILT_VERS, milter->version),
+ SEND_ATTR_INT(MAIL_ATTR_MILT_ACTS, milter->rq_mask),
+ SEND_ATTR_INT(MAIL_ATTR_MILT_EVTS, milter->ev_mask),
+ SEND_ATTR_INT(MAIL_ATTR_MILT_NPTS, milter->np_mask),
+ SEND_ATTR_INT(MAIL_ATTR_MILT_STAT, milter->state),
+ SEND_ATTR_INT(MAIL_ATTR_MILT_CONN, milter->conn_timeout),
+ SEND_ATTR_INT(MAIL_ATTR_MILT_CMD, milter->cmd_timeout),
+ SEND_ATTR_INT(MAIL_ATTR_MILT_MSG, milter->msg_timeout),
+ SEND_ATTR_STR(MAIL_ATTR_MILT_ACT, milter->def_action),
+ SEND_ATTR_INT(MAIL_ATTR_MILT_MAC, milter->m.macros != 0),
+ ATTR_TYPE_END) != 0
+ || (milter->m.macros != 0
+ && attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_FUNC(milter_macros_print,
+ (const void *) milter->m.macros),
+ ATTR_TYPE_END) != 0)
+ || (milter->m.macros == 0
+ && attr_print(stream, ATTR_FLAG_NONE,
+ ATTR_TYPE_END) != 0)
+ || vstream_fflush(stream) != 0) {
+ return (-1);
+#ifdef CANT_WRITE_BEFORE_SENDING_FD
+ } else if (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_DUMMY, milter->buf),
+ ATTR_TYPE_END) != 1) {
+ return (-1);
+#endif
+ } else if (LOCAL_SEND_FD(vstream_fileno(stream),
+ vstream_fileno(milter->fp)) < 0) {
+ return (-1);
+#ifdef MUST_READ_AFTER_SENDING_FD
+ } else if (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_DUMMY, milter->buf),
+ ATTR_TYPE_END) != 1) {
+ return (-1);
+#endif
+ } else {
+ return (0);
+ }
+}
+
+static MILTER8 *milter8_alloc(const char *, int, int, int, const char *,
+ const char *, MILTERS *);
+
+/* milter8_receive - receive milter instance */
+
+MILTER *milter8_receive(VSTREAM *stream, MILTERS *parent)
+{
+ const char *myname = "milter8_receive";
+ static VSTRING *name_buf;
+ static VSTRING *act_buf;
+ MILTER8 *milter;
+ int version;
+ int rq_mask;
+ int ev_mask;
+ int np_mask;
+ int state;
+ int conn_timeout;
+ int cmd_timeout;
+ int msg_timeout;
+ int fd;
+ int has_macros;
+ MILTER_MACROS *macros = 0;
+
+#define FREE_MACROS_AND_RETURN(x) do { \
+ if (macros) \
+ milter_macros_free(macros); \
+ return (x); \
+ } while (0)
+
+ if (name_buf == 0) {
+ name_buf = vstring_alloc(10);
+ act_buf = vstring_alloc(10);
+ }
+ if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE,
+ RECV_ATTR_STR(MAIL_ATTR_MILT_NAME, name_buf),
+ RECV_ATTR_INT(MAIL_ATTR_MILT_VERS, &version),
+ RECV_ATTR_INT(MAIL_ATTR_MILT_ACTS, &rq_mask),
+ RECV_ATTR_INT(MAIL_ATTR_MILT_EVTS, &ev_mask),
+ RECV_ATTR_INT(MAIL_ATTR_MILT_NPTS, &np_mask),
+ RECV_ATTR_INT(MAIL_ATTR_MILT_STAT, &state),
+ RECV_ATTR_INT(MAIL_ATTR_MILT_CONN, &conn_timeout),
+ RECV_ATTR_INT(MAIL_ATTR_MILT_CMD, &cmd_timeout),
+ RECV_ATTR_INT(MAIL_ATTR_MILT_MSG, &msg_timeout),
+ RECV_ATTR_STR(MAIL_ATTR_MILT_ACT, act_buf),
+ RECV_ATTR_INT(MAIL_ATTR_MILT_MAC, &has_macros),
+ ATTR_TYPE_END) < 10
+ || (has_macros != 0
+ && attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_FUNC(milter_macros_scan,
+ (void *) (macros =
+ milter_macros_alloc(MILTER_MACROS_ALLOC_ZERO))),
+ ATTR_TYPE_END) < 1)
+ || (has_macros == 0
+ && attr_scan(stream, ATTR_FLAG_STRICT,
+ ATTR_TYPE_END) < 0)) {
+ FREE_MACROS_AND_RETURN(0);
+#ifdef CANT_WRITE_BEFORE_SENDING_FD
+ } else if (attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_DUMMY, ""),
+ ATTR_TYPE_END) != 0
+ || vstream_fflush(stream) != 0) {
+ FREE_MACROS_AND_RETURN(0);
+#endif
+ } else if ((fd = LOCAL_RECV_FD(vstream_fileno(stream))) < 0) {
+ FREE_MACROS_AND_RETURN(0);
+ } else {
+#ifdef MUST_READ_AFTER_SENDING_FD
+ (void) attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_DUMMY, ""),
+ ATTR_TYPE_END);
+#endif
+#define NO_PROTOCOL ((char *) 0)
+
+ if (msg_verbose)
+ msg_info("%s: milter %s", myname, STR(name_buf));
+
+ milter = milter8_alloc(STR(name_buf), conn_timeout, cmd_timeout,
+ msg_timeout, NO_PROTOCOL, STR(act_buf), parent);
+ milter->fp = vstream_fdopen(fd, O_RDWR);
+ milter->m.macros = macros;
+ vstream_control(milter->fp, CA_VSTREAM_CTL_DOUBLE, CA_VSTREAM_CTL_END);
+ /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */
+ vstream_tweak_sock(milter->fp);
+ milter->version = version;
+ milter->rq_mask = rq_mask;
+ milter->ev_mask = ev_mask;
+ milter->np_mask = np_mask;
+ milter->state = state;
+ return (&milter->m);
+ }
+}
+
+/* milter8_free - destroy Milter instance */
+
+static void milter8_free(MILTER *m)
+{
+ MILTER8 *milter = (MILTER8 *) m;
+
+ if (msg_verbose)
+ msg_info("free milter %s", milter->m.name);
+ if (milter->fp)
+ (void) vstream_fclose(milter->fp);
+ myfree(milter->m.name);
+ vstring_free(milter->buf);
+ vstring_free(milter->body);
+ if (milter->protocol)
+ myfree(milter->protocol);
+ myfree(milter->def_action);
+ if (milter->def_reply)
+ myfree(milter->def_reply);
+ if (milter->m.macros)
+ milter_macros_free(milter->m.macros);
+ myfree((void *) milter);
+}
+
+/* milter8_alloc - create MTA-side Sendmail 8 Milter instance */
+
+static MILTER8 *milter8_alloc(const char *name, int conn_timeout,
+ int cmd_timeout, int msg_timeout,
+ const char *protocol,
+ const char *def_action,
+ MILTERS *parent)
+{
+ MILTER8 *milter;
+
+ /*
+ * Fill in the structure. Note: all strings must be copied.
+ *
+ * XXX Sendmail 8 libmilter closes the MTA-to-filter socket when it finds
+ * out that the SMTP client has disconnected. Because of this, Postfix
+ * has to open a new MTA-to-filter socket for each SMTP client.
+ */
+ milter = (MILTER8 *) mymalloc(sizeof(*milter));
+ milter->m.name = mystrdup(name);
+ milter->m.flags = 0;
+ milter->m.next = 0;
+ milter->m.parent = parent;
+ milter->m.macros = 0;
+#ifdef LIBMILTER_AUTO_DISCONNECT
+ milter->m.connect_on_demand = (void (*) (struct MILTER *)) milter8_connect;
+#else
+ milter->m.connect_on_demand = 0;
+#endif
+ milter->m.conn_event = milter8_conn_event;
+ milter->m.helo_event = milter8_helo_event;
+ milter->m.mail_event = milter8_mail_event;
+ milter->m.rcpt_event = milter8_rcpt_event;
+ milter->m.data_event = milter8_data_event; /* may be null */
+ milter->m.message = milter8_message;
+ milter->m.unknown_event = milter8_unknown_event; /* may be null */
+ milter->m.other_event = milter8_other_event;
+ milter->m.abort = milter8_abort;
+ milter->m.disc_event = milter8_disc_event;
+ milter->m.active = milter8_active;
+ milter->m.send = milter8_send;
+ milter->m.free = milter8_free;
+ milter->fp = 0;
+ milter->buf = vstring_alloc(100);
+ milter->body = vstring_alloc(100);
+ milter->version = 0;
+ milter->rq_mask = 0;
+ milter->ev_mask = 0;
+ milter->state = MILTER8_STAT_CLOSED;
+ milter->conn_timeout = conn_timeout;
+ milter->cmd_timeout = cmd_timeout;
+ milter->msg_timeout = msg_timeout;
+ milter->protocol = (protocol ? mystrdup(protocol) : 0);
+ milter->def_action = mystrdup(def_action);
+ milter->def_reply = 0;
+ milter->skip_event_type = 0;
+
+ return (milter);
+}
+
+/* milter8_create - create MTA-side Sendmail 8 Milter instance */
+
+MILTER *milter8_create(const char *name, int conn_timeout, int cmd_timeout,
+ int msg_timeout, const char *protocol,
+ const char *def_action, MILTERS *parent)
+{
+ MILTER8 *milter;
+
+ /*
+ * Fill in the structure.
+ */
+ milter = milter8_alloc(name, conn_timeout, cmd_timeout, msg_timeout,
+ protocol, def_action, parent);
+
+ /*
+ * XXX Sendmail 8 libmilter closes the MTA-to-filter socket when it finds
+ * out that the SMTP client has disconnected. Because of this, Postfix
+ * has to open a new MTA-to-filter socket for each SMTP client.
+ */
+#ifndef LIBMILTER_AUTO_DISCONNECT
+ milter8_connect(milter);
+#endif
+ return (&milter->m);
+}
diff --git a/src/milter/milter_macros.c b/src/milter/milter_macros.c
new file mode 100644
index 0000000..27f5509
--- /dev/null
+++ b/src/milter/milter_macros.c
@@ -0,0 +1,303 @@
+/*++
+/* NAME
+/* milter_macros
+/* SUMMARY
+/* manipulate MILTER_MACROS structures
+/* SYNOPSIS
+/* #include <milter.h>
+/*
+/* MILTER_MACROS *milter_macros_create(conn_macros, helo_macros,
+/* mail_macros, rcpt_macros,
+/* data_macros, eoh_macros,
+/* eod_macros, unk_macros)
+/* const char *conn_macros;
+/* const char *helo_macros;
+/* const char *mail_macros;
+/* const char *rcpt_macrps;
+/* const char *data_macros;
+/* const char *eoh_macros;
+/* const char *eod_macros;
+/* const char *unk_macros;
+/*
+/* MILTER_MACROS *milter_macros_alloc(init_mode)
+/* int init_mode;
+/*
+/* void milter_macros_free(mp)
+/* MILTER_MACROS *mp;
+/*
+/* int milter_macros_print(print_fn, stream, flags, ptr)
+/* ATTR_PRINT_COMMON_FN print_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/*
+/* int milter_macros_scan(scan_fn, fp, flags, ptr)
+/* ATTR_SCAN_COMMON_FN scan_fn;
+/* VSTREAM *fp;
+/* int flags;
+/* void *ptr;
+/* DESCRIPTION
+/* Sendmail mail filter (Milter) applications receive sets of
+/* macro name=value pairs with each SMTP or content event.
+/* In Postfix, these macro names are stored in MILTER_MACROS
+/* structures, as one list for each event type. By default,
+/* the same structure is shared by all Milter applications;
+/* it is initialized with information from main.cf. With
+/* Sendmail 8.14 a Milter can override one or more lists of
+/* macro names. Postfix implements this by giving the Milter
+/* its own MILTER_MACROS structure and by storing the per-Milter
+/* information there.
+/*
+/* This module maintains per-event macro name lists as
+/* mystrdup()'ed values. The user is explicitly allowed to
+/* update these values directly, as long as the result is
+/* compatible with mystrdup().
+/*
+/* milter_macros_create() creates a MILTER_MACROS structure
+/* and initializes it with copies of its string arguments.
+/* Null pointers are not valid as input.
+/*
+/* milter_macros_alloc() creates am empty MILTER_MACROS structure
+/* that is initialized according to its init_mode argument.
+/* .IP MILTER_MACROS_ALLOC_ZERO
+/* Initialize all structure members as null pointers. This
+/* mode must be used with milter_macros_scan(), because that
+/* function blindly overwrites all structure members. No other
+/* function except milter_macros_free() allows structure members
+/* with null pointer values.
+/* .IP MILTER_MACROS_ALLOC_EMPTY
+/* Initialize all structure members with mystrdup(""). This
+/* is not as expensive as it appears to be.
+/* .PP
+/* milter_macros_free() destroys a MILTER_MACROS structure and
+/* frees any strings referenced by it.
+/*
+/* milter_macros_print() writes the contents of a MILTER_MACROS
+/* structure to the named stream using the specified attribute
+/* print routine. milter_macros_print() is meant to be passed
+/* as a call-back to attr_print*(), thusly:
+/*
+/* SEND_ATTR_FUNC(milter_macros_print, (const void *) macros),
+/*
+/* milter_macros_scan() reads a MILTER_MACROS structure from
+/* the named stream using the specified attribute scan routine.
+/* No attempt is made to free the memory of existing structure
+/* members. milter_macros_scan() is meant to be passed as a
+/* call-back to attr_scan*(), thusly:
+/*
+/* RECV_ATTR_FUNC(milter_macros_scan, (void *) macros),
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this
+/* software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <attr.h>
+#include <mymalloc.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <milter.h>
+
+ /*
+ * Ad-hoc protocol to send/receive milter macro name lists.
+ */
+#define MAIL_ATTR_MILT_MAC_CONN "conn_macros"
+#define MAIL_ATTR_MILT_MAC_HELO "helo_macros"
+#define MAIL_ATTR_MILT_MAC_MAIL "mail_macros"
+#define MAIL_ATTR_MILT_MAC_RCPT "rcpt_macros"
+#define MAIL_ATTR_MILT_MAC_DATA "data_macros"
+#define MAIL_ATTR_MILT_MAC_EOH "eoh_macros"
+#define MAIL_ATTR_MILT_MAC_EOD "eod_macros"
+#define MAIL_ATTR_MILT_MAC_UNK "unk_macros"
+
+/* milter_macros_print - write macros structure to stream */
+
+int milter_macros_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp,
+ int flags, const void *ptr)
+{
+ MILTER_MACROS *mp = (MILTER_MACROS *) ptr;
+ int ret;
+
+ /*
+ * The attribute order does not matter, except that it must be the same
+ * as in the milter_macros_scan() function.
+ */
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_STR(MAIL_ATTR_MILT_MAC_CONN, mp->conn_macros),
+ SEND_ATTR_STR(MAIL_ATTR_MILT_MAC_HELO, mp->helo_macros),
+ SEND_ATTR_STR(MAIL_ATTR_MILT_MAC_MAIL, mp->mail_macros),
+ SEND_ATTR_STR(MAIL_ATTR_MILT_MAC_RCPT, mp->rcpt_macros),
+ SEND_ATTR_STR(MAIL_ATTR_MILT_MAC_DATA, mp->data_macros),
+ SEND_ATTR_STR(MAIL_ATTR_MILT_MAC_EOH, mp->eoh_macros),
+ SEND_ATTR_STR(MAIL_ATTR_MILT_MAC_EOD, mp->eod_macros),
+ SEND_ATTR_STR(MAIL_ATTR_MILT_MAC_UNK, mp->unk_macros),
+ ATTR_TYPE_END);
+ return (ret);
+}
+
+/* milter_macros_scan - receive macros structure from stream */
+
+int milter_macros_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ MILTER_MACROS *mp = (MILTER_MACROS *) ptr;
+ int ret;
+
+ /*
+ * We could simplify this by moving memory allocation into attr_scan*().
+ */
+ VSTRING *conn_macros = vstring_alloc(10);
+ VSTRING *helo_macros = vstring_alloc(10);
+ VSTRING *mail_macros = vstring_alloc(10);
+ VSTRING *rcpt_macros = vstring_alloc(10);
+ VSTRING *data_macros = vstring_alloc(10);
+ VSTRING *eoh_macros = vstring_alloc(10);
+ VSTRING *eod_macros = vstring_alloc(10);
+ VSTRING *unk_macros = vstring_alloc(10);
+
+ /*
+ * The attribute order does not matter, except that it must be the same
+ * as in the milter_macros_print() function.
+ */
+ ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_STR(MAIL_ATTR_MILT_MAC_CONN, conn_macros),
+ RECV_ATTR_STR(MAIL_ATTR_MILT_MAC_HELO, helo_macros),
+ RECV_ATTR_STR(MAIL_ATTR_MILT_MAC_MAIL, mail_macros),
+ RECV_ATTR_STR(MAIL_ATTR_MILT_MAC_RCPT, rcpt_macros),
+ RECV_ATTR_STR(MAIL_ATTR_MILT_MAC_DATA, data_macros),
+ RECV_ATTR_STR(MAIL_ATTR_MILT_MAC_EOH, eoh_macros),
+ RECV_ATTR_STR(MAIL_ATTR_MILT_MAC_EOD, eod_macros),
+ RECV_ATTR_STR(MAIL_ATTR_MILT_MAC_UNK, unk_macros),
+ ATTR_TYPE_END);
+
+ /*
+ * Don't optimize for error.
+ */
+ mp->conn_macros = vstring_export(conn_macros);
+ mp->helo_macros = vstring_export(helo_macros);
+ mp->mail_macros = vstring_export(mail_macros);
+ mp->rcpt_macros = vstring_export(rcpt_macros);
+ mp->data_macros = vstring_export(data_macros);
+ mp->eoh_macros = vstring_export(eoh_macros);
+ mp->eod_macros = vstring_export(eod_macros);
+ mp->unk_macros = vstring_export(unk_macros);
+
+ return (ret == 8 ? 1 : -1);
+}
+
+/* milter_macros_create - create and initialize macros structure */
+
+MILTER_MACROS *milter_macros_create(const char *conn_macros,
+ const char *helo_macros,
+ const char *mail_macros,
+ const char *rcpt_macros,
+ const char *data_macros,
+ const char *eoh_macros,
+ const char *eod_macros,
+ const char *unk_macros)
+{
+ MILTER_MACROS *mp;
+
+ mp = (MILTER_MACROS *) mymalloc(sizeof(*mp));
+ mp->conn_macros = mystrdup(conn_macros);
+ mp->helo_macros = mystrdup(helo_macros);
+ mp->mail_macros = mystrdup(mail_macros);
+ mp->rcpt_macros = mystrdup(rcpt_macros);
+ mp->data_macros = mystrdup(data_macros);
+ mp->eoh_macros = mystrdup(eoh_macros);
+ mp->eod_macros = mystrdup(eod_macros);
+ mp->unk_macros = mystrdup(unk_macros);
+
+ return (mp);
+}
+
+/* milter_macros_alloc - allocate macros structure with simple initialization */
+
+MILTER_MACROS *milter_macros_alloc(int mode)
+{
+ MILTER_MACROS *mp;
+
+ /*
+ * This macro was originally in milter.h, but no-one else needed it.
+ */
+#define milter_macros_init(mp, expr) do { \
+ MILTER_MACROS *__mp = (mp); \
+ char *__expr = (expr); \
+ __mp->conn_macros = __expr; \
+ __mp->helo_macros = __expr; \
+ __mp->mail_macros = __expr; \
+ __mp->rcpt_macros = __expr; \
+ __mp->data_macros = __expr; \
+ __mp->eoh_macros = __expr; \
+ __mp->eod_macros = __expr; \
+ __mp->unk_macros = __expr; \
+ } while (0)
+
+ mp = (MILTER_MACROS *) mymalloc(sizeof(*mp));
+ switch (mode) {
+ case MILTER_MACROS_ALLOC_ZERO:
+ milter_macros_init(mp, 0);
+ break;
+ case MILTER_MACROS_ALLOC_EMPTY:
+ milter_macros_init(mp, mystrdup(""));
+ break;
+ default:
+ msg_panic("milter_macros_alloc: unknown mode %d", mode);
+ }
+ return (mp);
+}
+
+/* milter_macros_free - destroy memory for MILTER_MACROS structure */
+
+void milter_macros_free(MILTER_MACROS *mp)
+{
+
+ /*
+ * This macro was originally in milter.h, but no-one else needed it.
+ */
+#define milter_macros_wipe(mp) do { \
+ MILTER_MACROS *__mp = mp; \
+ if (__mp->conn_macros) \
+ myfree(__mp->conn_macros); \
+ if (__mp->helo_macros) \
+ myfree(__mp->helo_macros); \
+ if (__mp->mail_macros) \
+ myfree(__mp->mail_macros); \
+ if (__mp->rcpt_macros) \
+ myfree(__mp->rcpt_macros); \
+ if (__mp->data_macros) \
+ myfree(__mp->data_macros); \
+ if (__mp->eoh_macros) \
+ myfree(__mp->eoh_macros); \
+ if (__mp->eod_macros) \
+ myfree(__mp->eod_macros); \
+ if (__mp->unk_macros) \
+ myfree(__mp->unk_macros); \
+ } while (0)
+
+ milter_macros_wipe(mp);
+ myfree((void *) mp);
+}
diff --git a/src/milter/test-list b/src/milter/test-list
new file mode 100644
index 0000000..d4cef7a
--- /dev/null
+++ b/src/milter/test-list
@@ -0,0 +1,49 @@
+# Reject with text
+./test-milter -C 1 -a "554 5.7.1 1% 2%% 3%%%" -c connect -p inet:9999@0.0.0.0
+./test-milter -C 1 -a "554 5.7.1 1% 2%% 3%%%" -c helo -p inet:9999@0.0.0.0
+./test-milter -C 1 -a "554 5.7.1 1% 2%% 3%%%" -c mail -p inet:9999@0.0.0.0
+./test-milter -C 1 -a "554 5.7.1 1% 2%% 3%%%" -c rcpt -p inet:9999@0.0.0.0
+./test-milter -C 1 -a "554 5.7.1 1% 2%% 3%%%" -c header -p inet:9999@0.0.0.0
+./test-milter -C 1 -a "554 5.7.1 1% 2%% 3%%%" -c eoh -p inet:9999@0.0.0.0
+./test-milter -C 1 -a "554 5.7.1 1% 2%% 3%%%" -c body -p inet:9999@0.0.0.0
+./test-milter -C 1 -a "554 5.7.1 1% 2%% 3%%%" -c eom -p inet:9999@0.0.0.0
+
+# Tempfail tests
+./test-milter -C 1 -a tempfail -c connect -p inet:9999@0.0.0.0
+./test-milter -C 1 -a tempfail -c helo -p inet:9999@0.0.0.0
+./test-milter -C 1 -a tempfail -c mail -p inet:9999@0.0.0.0
+./test-milter -C 1 -a tempfail -c rcpt -p inet:9999@0.0.0.0
+./test-milter -C 1 -a tempfail -c header -p inet:9999@0.0.0.0
+./test-milter -C 1 -a tempfail -c eoh -p inet:9999@0.0.0.0
+./test-milter -C 1 -a tempfail -c body -p inet:9999@0.0.0.0
+./test-milter -C 1 -a tempfail -c eom -p inet:9999@0.0.0.0
+
+# Reject tests
+./test-milter -C 1 -a reject -c connect -p inet:9999@0.0.0.0
+./test-milter -C 1 -a reject -c helo -p inet:9999@0.0.0.0
+./test-milter -C 1 -a reject -c mail -p inet:9999@0.0.0.0
+./test-milter -C 1 -a reject -c rcpt -p inet:9999@0.0.0.0
+./test-milter -C 1 -a reject -c header -p inet:9999@0.0.0.0
+./test-milter -C 1 -a reject -c eoh -p inet:9999@0.0.0.0
+./test-milter -C 1 -a reject -c body -p inet:9999@0.0.0.0
+./test-milter -C 1 -a reject -c eom -p inet:9999@0.0.0.0
+
+# Accept tests
+./test-milter -C 1 -a accept -c connect -p inet:9999@0.0.0.0
+./test-milter -C 1 -a accept -c helo -p inet:9999@0.0.0.0
+./test-milter -C 1 -a accept -c rcpt -p inet:9999@0.0.0.0
+./test-milter -C 1 -a accept -c mail -p inet:9999@0.0.0.0
+./test-milter -C 1 -a accept -c header -p inet:9999@0.0.0.0
+./test-milter -C 1 -a accept -c eoh -p inet:9999@0.0.0.0
+./test-milter -C 1 -a accept -c body -p inet:9999@0.0.0.0
+./test-milter -C 1 -a accept -c eom -p inet:9999@0.0.0.0
+
+# discard tests
+./test-milter -C 1 -a discard -c connect -p inet:9999@0.0.0.0
+./test-milter -C 1 -a discard -c helo -p inet:9999@0.0.0.0
+./test-milter -C 1 -a discard -c rcpt -p inet:9999@0.0.0.0
+./test-milter -C 1 -a discard -c mail -p inet:9999@0.0.0.0
+./test-milter -C 1 -a discard -c header -p inet:9999@0.0.0.0
+./test-milter -C 1 -a discard -c eoh -p inet:9999@0.0.0.0
+./test-milter -C 1 -a discard -c body -p inet:9999@0.0.0.0
+./test-milter -C 1 -a discard -c eom -p inet:9999@0.0.0.0
diff --git a/src/milter/test-milter.c b/src/milter/test-milter.c
new file mode 100644
index 0000000..0494ff0
--- /dev/null
+++ b/src/milter/test-milter.c
@@ -0,0 +1,840 @@
+/*++
+/* NAME
+/* test-milter 1
+/* SUMMARY
+/* Simple test mail filter program.
+/* SYNOPSIS
+/* .fi
+/* \fBtest-milter\fR [\fIoptions\fR] -p \fBinet:\fIport\fB@\fIhost\fR
+/*
+/* \fBtest-milter\fR [\fIoptions\fR] -p \fBunix:\fIpathname\fR
+/* DESCRIPTION
+/* \fBtest-milter\fR is a Milter (mail filter) application that
+/* exercises selected features.
+/*
+/* Note: this is an unsupported test program. No attempt is made
+/* to maintain compatibility between successive versions.
+/*
+/* Arguments (multiple alternatives are separated by "\fB|\fR"):
+/* .IP "\fB-a accept|tempfail|reject|discard|skip|\fIddd x.y.z text\fR"
+/* Specifies a non-default reply for the MTA command specified
+/* with \fB-c\fR. The default is \fBtempfail\fR. The \fItext\fR
+/* is repeated once, to produce multi-line reply text.
+/* .IP "\fB-A address\fR"
+/* Add the specified recipient address (specify ESMTP parameters
+/* separated by space). Multiple -A options are supported.
+/* .IP "\fB-b pathname\fR"
+/* Replace the message body by the content of the specified file.
+/* .IP "\fB-c connect|helo|mail|rcpt|data|header|eoh|body|eom|unknown|close|abort\fR"
+/* When to send the non-default reply specified with \fB-a\fR.
+/* The default protocol stage is \fBconnect\fR.
+/* .IP "\fB-C\fI count\fR"
+/* Terminate after \fIcount\fR connections.
+/* .IP "\fB-d\fI level\fR"
+/* Enable libmilter debugging at the specified level.
+/* .IP "\fB-D\fI address\fR"
+/* Delete the specified recipient address. Multiple -D options
+/* are supported.
+/* .IP "\fB-f \fIsender\fR"
+/* Replace the sender by the specified address.
+/* .IP "\fB-h \fI'index header-label header-value'\fR"
+/* Replace the message header at the specified position.
+/* .IP "\fB-i \fI'index header-label header-value'\fR"
+/* Insert header at specified position.
+/* .IP "\fB-l\fR"
+/* Header values include leading space. Specify this option
+/* before \fB-i\fR or \fB-h\fR.
+/* .IP "\fB-m connect|helo|mail|rcpt|data|eoh|eom\fR"
+/* The protocol stage that receives the list of macros specified
+/* with \fB-M\fR. The default protocol stage is \fBconnect\fR.
+/* .IP "\fB-M \fIset_macro_list\fR"
+/* A non-default list of macros that the MTA should send at
+/* the protocol stage specified with \fB-m\fR.
+/* .IP "\fB-n connect|helo|mail|rcpt|data|header|eoh|body|eom|unknown\fR"
+/* The event that the MTA should not send.
+/* .IP "\fB-N connect|helo|mail|rcpt|data|header|eoh|body|eom|unknown\fR"
+/* The event for which the filter will not reply.
+/* .IP "\fB-p inet:\fIport\fB@\fIhost\fB|unix:\fIpathname\fR"
+/* The mail filter listen endpoint.
+/* .IP "\fB-r\fR"
+/* Request rejected recipients from the MTA.
+/* .IP "\fB-v\fR"
+/* Make the program more verbose.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/un.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "libmilter/mfapi.h"
+#include "libmilter/mfdef.h"
+
+static int conn_count;
+static int verbose;
+
+static int test_connect_reply = SMFIS_CONTINUE;
+static int test_helo_reply = SMFIS_CONTINUE;
+static int test_mail_reply = SMFIS_CONTINUE;
+static int test_rcpt_reply = SMFIS_CONTINUE;
+
+#if SMFI_VERSION > 3
+static int test_data_reply = SMFIS_CONTINUE;
+
+#endif
+static int test_header_reply = SMFIS_CONTINUE;
+static int test_eoh_reply = SMFIS_CONTINUE;
+static int test_body_reply = SMFIS_CONTINUE;
+static int test_eom_reply = SMFIS_CONTINUE;
+
+#if SMFI_VERSION > 2
+static int test_unknown_reply = SMFIS_CONTINUE;
+
+#endif
+static int test_close_reply = SMFIS_CONTINUE;
+static int test_abort_reply = SMFIS_CONTINUE;
+
+struct command_map {
+ const char *name;
+ int *reply;
+};
+
+static const struct command_map command_map[] = {
+ "connect", &test_connect_reply,
+ "helo", &test_helo_reply,
+ "mail", &test_mail_reply,
+ "rcpt", &test_rcpt_reply,
+ "header", &test_header_reply,
+ "eoh", &test_eoh_reply,
+ "body", &test_body_reply,
+ "eom", &test_eom_reply,
+ "abort", &test_abort_reply,
+ "close", &test_close_reply,
+#if SMFI_VERSION > 2
+ "unknown", &test_unknown_reply,
+#endif
+#if SMFI_VERSION > 3
+ "data", &test_data_reply,
+#endif
+ 0, 0,
+};
+
+static char *reply_code;
+static char *reply_dsn;
+static char *reply_message;
+
+#ifdef SMFIR_CHGFROM
+static char *chg_from;
+
+#endif
+
+#ifdef SMFIR_INSHEADER
+static char *ins_hdr;
+static int ins_idx;
+static char *ins_val;
+
+#endif
+
+#ifdef SMFIR_CHGHEADER
+static char *chg_hdr;
+static int chg_idx;
+static char *chg_val;
+
+#endif
+
+#ifdef SMFIR_REPLBODY
+static char *body_file;
+
+#endif
+
+#define MAX_RCPT 10
+int add_rcpt_count = 0;
+char *add_rcpt[MAX_RCPT];
+int del_rcpt_count = 0;
+char *del_rcpt[MAX_RCPT];
+
+static const char *macro_names[] = {
+ "_",
+ "i",
+ "j",
+ "v",
+ "{auth_authen}",
+ "{auth_author}",
+ "{auth_type}",
+ "{cert_issuer}",
+ "{cert_subject}",
+ "{cipher}",
+ "{cipher_bits}",
+ "{client_addr}",
+ "{client_connections}",
+ "{client_name}",
+ "{client_port}",
+ "{client_ptr}",
+ "{client_resolve}",
+ "{daemon_addr}",
+ "{daemon_name}",
+ "{daemon_port}",
+ "{if_addr}",
+ "{if_name}",
+ "{mail_addr}",
+ "{mail_host}",
+ "{mail_mailer}",
+ "{rcpt_addr}",
+ "{rcpt_host}",
+ "{rcpt_mailer}",
+ "{tls_version}",
+ 0,
+};
+
+static int test_reply(SMFICTX *ctx, int code)
+{
+ const char **cpp;
+ const char *symval;
+
+ for (cpp = macro_names; *cpp; cpp++)
+ if ((symval = smfi_getsymval(ctx, (char *) *cpp)) != 0)
+ printf("macro: %s=\"%s\"\n", *cpp, symval);
+ (void) fflush(stdout); /* In case output redirected. */
+
+ if (code == SMFIR_REPLYCODE) {
+ if (smfi_setmlreply(ctx, reply_code, reply_dsn, reply_message, reply_message, (char *) 0) == MI_FAILURE)
+ fprintf(stderr, "smfi_setmlreply failed\n");
+ printf("test_reply %s\n\n", reply_code);
+ return (reply_code[0] == '4' ? SMFIS_TEMPFAIL : SMFIS_REJECT);
+ } else {
+ printf("test_reply %d\n\n", code);
+ return (code);
+ }
+}
+
+static sfsistat test_connect(SMFICTX *ctx, char *name, struct sockaddr * sa)
+{
+ const char *print_addr;
+ char buf[BUFSIZ];
+
+ printf("test_connect %s ", name);
+ switch (sa->sa_family) {
+ case AF_INET:
+ {
+ struct sockaddr_in *sin = (struct sockaddr_in *) sa;
+
+ print_addr = inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf));
+ if (print_addr == 0)
+ print_addr = strerror(errno);
+ printf("AF_INET (%s:%d)\n", print_addr, ntohs(sin->sin_port));
+ }
+ break;
+#ifdef HAS_IPV6
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa;
+
+ print_addr = inet_ntop(AF_INET, &sin6->sin6_addr, buf, sizeof(buf));
+ if (print_addr == 0)
+ print_addr = strerror(errno);
+ printf("AF_INET6 (%s:%d)\n", print_addr, ntohs(sin6->sin6_port));
+ }
+ break;
+#endif
+ case AF_UNIX:
+ {
+#undef sun
+ struct sockaddr_un *sun = (struct sockaddr_un *) sa;
+
+ printf("AF_UNIX (%s)\n", sun->sun_path);
+ }
+ break;
+ default:
+ printf(" [unknown address family]\n");
+ break;
+ }
+ return (test_reply(ctx, test_connect_reply));
+}
+
+static sfsistat test_helo(SMFICTX *ctx, char *arg)
+{
+ printf("test_helo \"%s\"\n", arg ? arg : "NULL");
+ return (test_reply(ctx, test_helo_reply));
+}
+
+static sfsistat test_mail(SMFICTX *ctx, char **argv)
+{
+ char **cpp;
+
+ printf("test_mail");
+ for (cpp = argv; *cpp; cpp++)
+ printf(" \"%s\"", *cpp);
+ printf("\n");
+ return (test_reply(ctx, test_mail_reply));
+}
+
+static sfsistat test_rcpt(SMFICTX *ctx, char **argv)
+{
+ char **cpp;
+
+ printf("test_rcpt");
+ for (cpp = argv; *cpp; cpp++)
+ printf(" \"%s\"", *cpp);
+ printf("\n");
+ return (test_reply(ctx, test_rcpt_reply));
+}
+
+
+sfsistat test_header(SMFICTX *ctx, char *name, char *value)
+{
+ printf("test_header \"%s\" \"%s\"\n", name, value);
+ return (test_reply(ctx, test_header_reply));
+}
+
+static sfsistat test_eoh(SMFICTX *ctx)
+{
+ printf("test_eoh\n");
+ return (test_reply(ctx, test_eoh_reply));
+}
+
+static sfsistat test_body(SMFICTX *ctx, unsigned char *data, size_t data_len)
+{
+ if (verbose == 0)
+ printf("test_body %ld bytes\n", (long) data_len);
+ else
+ printf("%.*s", (int) data_len, data);
+ return (test_reply(ctx, test_body_reply));
+}
+
+static sfsistat test_eom(SMFICTX *ctx)
+{
+ printf("test_eom\n");
+#ifdef SMFIR_REPLBODY
+ if (body_file) {
+ char buf[BUFSIZ + 2];
+ FILE *fp;
+ size_t len;
+ int count;
+
+ if ((fp = fopen(body_file, "r")) == 0) {
+ perror(body_file);
+ } else {
+ printf("replace body with content of %s\n", body_file);
+ for (count = 0; fgets(buf, BUFSIZ, fp) != 0; count++) {
+ len = strcspn(buf, "\n");
+ buf[len + 0] = '\r';
+ buf[len + 1] = '\n';
+ if (smfi_replacebody(ctx, buf, len + 2) == MI_FAILURE) {
+ fprintf(stderr, "body replace failure\n");
+ exit(1);
+ }
+ if (verbose)
+ printf("%.*s\n", (int) len, buf);
+ }
+ if (count == 0)
+ perror("fgets");
+ (void) fclose(fp);
+ }
+ }
+#endif
+#ifdef SMFIR_CHGFROM
+ if (chg_from != 0 && smfi_chgfrom(ctx, chg_from, "whatever") == MI_FAILURE)
+ fprintf(stderr, "smfi_chgfrom failed\n");
+#endif
+#ifdef SMFIR_INSHEADER
+ if (ins_hdr && smfi_insheader(ctx, ins_idx, ins_hdr, ins_val) == MI_FAILURE)
+ fprintf(stderr, "smfi_insheader failed\n");
+#endif
+#ifdef SMFIR_CHGHEADER
+ if (chg_hdr && smfi_chgheader(ctx, chg_hdr, chg_idx, chg_val) == MI_FAILURE)
+ fprintf(stderr, "smfi_chgheader failed\n");
+#endif
+ {
+ int count;
+ char *args;
+
+ for (count = 0; count < add_rcpt_count; count++) {
+ if ((args = strchr(add_rcpt[count], ' ')) != 0) {
+ *args++ = 0;
+ if (smfi_addrcpt_par(ctx, add_rcpt[count], args) == MI_FAILURE)
+ fprintf(stderr, "smfi_addrcpt_par `%s' `%s' failed\n",
+ add_rcpt[count], args);
+ } else {
+ if (smfi_addrcpt(ctx, add_rcpt[count]) == MI_FAILURE)
+ fprintf(stderr, "smfi_addrcpt `%s' failed\n",
+ add_rcpt[count]);
+ }
+ }
+
+ for (count = 0; count < del_rcpt_count; count++)
+ if (smfi_delrcpt(ctx, del_rcpt[count]) == MI_FAILURE)
+ fprintf(stderr, "smfi_delrcpt `%s' failed\n", del_rcpt[count]);
+ }
+ return (test_reply(ctx, test_eom_reply));
+}
+
+static sfsistat test_abort(SMFICTX *ctx)
+{
+ printf("test_abort\n");
+ return (test_reply(ctx, test_abort_reply));
+}
+
+static sfsistat test_close(SMFICTX *ctx)
+{
+ printf("test_close\n");
+ if (verbose)
+ printf("conn_count %d\n", conn_count);
+ if (conn_count > 0 && --conn_count == 0)
+ exit(0);
+ return (test_reply(ctx, test_close_reply));
+}
+
+#if SMFI_VERSION > 3
+
+static sfsistat test_data(SMFICTX *ctx)
+{
+ printf("test_data\n");
+ return (test_reply(ctx, test_data_reply));
+}
+
+#endif
+
+#if SMFI_VERSION > 2
+
+static sfsistat test_unknown(SMFICTX *ctx, const char *what)
+{
+ printf("test_unknown %s\n", what);
+ return (test_reply(ctx, test_unknown_reply));
+}
+
+#endif
+
+#if SMFI_VERSION > 5
+
+static sfsistat test_negotiate(SMFICTX *, unsigned long, unsigned long,
+ unsigned long, unsigned long,
+ unsigned long *, unsigned long *,
+ unsigned long *, unsigned long *);
+
+#endif
+
+#ifndef SMFIF_CHGFROM
+#define SMFIF_CHGFROM 0
+#endif
+#ifndef SMFIP_HDR_LEADSPC
+#define SMFIP_HDR_LEADSPC 0
+#define misc_mask 0
+#endif
+
+static struct smfiDesc smfilter =
+{
+ "test-milter",
+ SMFI_VERSION,
+ SMFIF_ADDRCPT | SMFIF_DELRCPT | SMFIF_ADDHDRS | SMFIF_CHGHDRS | SMFIF_CHGBODY | SMFIF_CHGFROM,
+ test_connect,
+ test_helo,
+ test_mail,
+ test_rcpt,
+ test_header,
+ test_eoh,
+ test_body,
+ test_eom,
+ test_abort,
+ test_close,
+#if SMFI_VERSION > 2
+ test_unknown,
+#endif
+#if SMFI_VERSION > 3
+ test_data,
+#endif
+#if SMFI_VERSION > 5
+ test_negotiate,
+#endif
+};
+
+#if SMFI_VERSION > 5
+
+static const char *macro_states[] = {
+ "connect", /* SMFIM_CONNECT */
+ "helo", /* SMFIM_HELO */
+ "mail", /* SMFIM_ENVFROM */
+ "rcpt", /* SMFIM_ENVRCPT */
+ "data", /* SMFIM_DATA */
+ "eom", /* SMFIM_EOM < SMFIM_EOH */
+ "eoh", /* SMFIM_EOH > SMFIM_EOM */
+ 0,
+};
+
+static int set_macro_state;
+static char *set_macro_list;
+
+typedef sfsistat (*FILTER_ACTION) ();
+
+struct noproto_map {
+ const char *name;
+ int send_mask;
+ int reply_mask;
+ int *reply;
+ FILTER_ACTION *action;
+};
+
+static const struct noproto_map noproto_map[] = {
+ "connect", SMFIP_NOCONNECT, SMFIP_NR_CONN, &test_connect_reply, &smfilter.xxfi_connect,
+ "helo", SMFIP_NOHELO, SMFIP_NR_HELO, &test_helo_reply, &smfilter.xxfi_helo,
+ "mail", SMFIP_NOMAIL, SMFIP_NR_MAIL, &test_mail_reply, &smfilter.xxfi_envfrom,
+ "rcpt", SMFIP_NORCPT, SMFIP_NR_RCPT, &test_rcpt_reply, &smfilter.xxfi_envrcpt,
+ "data", SMFIP_NODATA, SMFIP_NR_DATA, &test_data_reply, &smfilter.xxfi_data,
+ "header", SMFIP_NOHDRS, SMFIP_NR_HDR, &test_header_reply, &smfilter.xxfi_header,
+ "eoh", SMFIP_NOEOH, SMFIP_NR_EOH, &test_eoh_reply, &smfilter.xxfi_eoh,
+ "body", SMFIP_NOBODY, SMFIP_NR_BODY, &test_body_reply, &smfilter.xxfi_body,
+ "unknown", SMFIP_NOUNKNOWN, SMFIP_NR_UNKN, &test_connect_reply, &smfilter.xxfi_unknown,
+ 0,
+};
+
+static int nosend_mask;
+static int noreply_mask;
+static int misc_mask;
+
+static sfsistat test_negotiate(SMFICTX *ctx,
+ unsigned long f0,
+ unsigned long f1,
+ unsigned long f2,
+ unsigned long f3,
+ unsigned long *pf0,
+ unsigned long *pf1,
+ unsigned long *pf2,
+ unsigned long *pf3)
+{
+ if (set_macro_list) {
+ if (verbose)
+ printf("set symbol list %s to \"%s\"\n",
+ macro_states[set_macro_state], set_macro_list);
+ smfi_setsymlist(ctx, set_macro_state, set_macro_list);
+ }
+ if (verbose)
+ printf("negotiate f0=%lx *pf0 = %lx f1=%lx *pf1=%lx nosend=%lx noreply=%lx misc=%lx\n",
+ f0, *pf0, f1, *pf1, (long) nosend_mask, (long) noreply_mask, (long) misc_mask);
+ *pf0 = f0;
+ *pf1 = f1 & (nosend_mask | noreply_mask | misc_mask);
+ return (SMFIS_CONTINUE);
+}
+
+#endif
+
+static void parse_hdr_info(const char *optarg, int *idx,
+ char **hdr, char **value)
+{
+ int len;
+
+ len = strlen(optarg) + 1;
+ if ((*hdr = malloc(len)) == 0 || (*value = malloc(len)) == 0) {
+ fprintf(stderr, "out of memory\n");
+ exit(1);
+ }
+ if ((misc_mask & SMFIP_HDR_LEADSPC) == 0 ?
+ sscanf(optarg, "%d %s %[^\n]", idx, *hdr, *value) != 3 :
+ sscanf(optarg, "%d %[^ ]%[^\n]", idx, *hdr, *value) != 3) {
+ fprintf(stderr, "bad header info: %s\n", optarg);
+ exit(1);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ char *action = 0;
+ char *command = 0;
+ const struct command_map *cp;
+ int ch;
+ int code;
+ const char **cpp;
+ char *set_macro_state_arg = 0;
+ char *nosend = 0;
+ char *noreply = 0;
+ const struct noproto_map *np;
+
+ while ((ch = getopt(argc, argv, "a:A:b:c:C:d:D:f:h:i:lm:M:n:N:p:rv")) > 0) {
+ switch (ch) {
+ case 'a':
+ action = optarg;
+ break;
+ case 'A':
+ if (add_rcpt_count >= MAX_RCPT) {
+ fprintf(stderr, "too many -A options\n");
+ exit(1);
+ }
+ add_rcpt[add_rcpt_count++] = optarg;
+ break;
+ case 'b':
+#ifdef SMFIR_REPLBODY
+ if (body_file) {
+ fprintf(stderr, "too many -b options\n");
+ exit(1);
+ }
+ body_file = optarg;
+#else
+ fprintf(stderr, "no libmilter support to replace body\n");
+#endif
+ break;
+ case 'c':
+ command = optarg;
+ break;
+ case 'd':
+ if (smfi_setdbg(atoi(optarg)) == MI_FAILURE) {
+ fprintf(stderr, "smfi_setdbg failed\n");
+ exit(1);
+ }
+ break;
+ case 'D':
+ if (del_rcpt_count >= MAX_RCPT) {
+ fprintf(stderr, "too many -D options\n");
+ exit(1);
+ }
+ del_rcpt[del_rcpt_count++] = optarg;
+ break;
+ case 'f':
+#ifdef SMFIR_CHGFROM
+ if (chg_from) {
+ fprintf(stderr, "too many -f options\n");
+ exit(1);
+ }
+ chg_from = optarg;
+#else
+ fprintf(stderr, "no libmilter support to change sender\n");
+ exit(1);
+#endif
+ break;
+ case 'h':
+#ifdef SMFIR_CHGHEADER
+ if (chg_hdr) {
+ fprintf(stderr, "too many -h options\n");
+ exit(1);
+ }
+ parse_hdr_info(optarg, &chg_idx, &chg_hdr, &chg_val);
+#else
+ fprintf(stderr, "no libmilter support to change header\n");
+ exit(1);
+#endif
+ break;
+ case 'i':
+#ifdef SMFIR_INSHEADER
+ if (ins_hdr) {
+ fprintf(stderr, "too many -i options\n");
+ exit(1);
+ }
+ parse_hdr_info(optarg, &ins_idx, &ins_hdr, &ins_val);
+#else
+ fprintf(stderr, "no libmilter support to insert header\n");
+ exit(1);
+#endif
+ break;
+ case 'l':
+#if SMFI_VERSION > 5
+ if (ins_hdr || chg_hdr) {
+ fprintf(stderr, "specify -l before -i or -r\n");
+ exit(1);
+ }
+ misc_mask |= SMFIP_HDR_LEADSPC;
+#else
+ fprintf(stderr, "no libmilter support for leading space\n");
+ exit(1);
+#endif
+ break;
+ case 'm':
+#if SMFI_VERSION > 5
+ if (set_macro_state_arg) {
+ fprintf(stderr, "too many -m options\n");
+ exit(1);
+ }
+ set_macro_state_arg = optarg;
+#else
+ fprintf(stderr, "no libmilter support to specify macro list\n");
+ exit(1);
+#endif
+ break;
+ case 'M':
+#if SMFI_VERSION > 5
+ if (set_macro_list) {
+ fprintf(stderr, "too many -M options\n");
+ exit(1);
+ }
+ set_macro_list = optarg;
+#else
+ fprintf(stderr, "no libmilter support to specify macro list\n");
+#endif
+ break;
+ case 'n':
+#if SMFI_VERSION > 5
+ if (nosend) {
+ fprintf(stderr, "too many -n options\n");
+ exit(1);
+ }
+ nosend = optarg;
+#else
+ fprintf(stderr, "no libmilter support for negotiate callback\n");
+#endif
+ break;
+ case 'N':
+#if SMFI_VERSION > 5
+ if (noreply) {
+ fprintf(stderr, "too many -n options\n");
+ exit(1);
+ }
+ noreply = optarg;
+#else
+ fprintf(stderr, "no libmilter support for negotiate callback\n");
+#endif
+ break;
+ case 'p':
+ if (smfi_setconn(optarg) == MI_FAILURE) {
+ fprintf(stderr, "smfi_setconn failed\n");
+ exit(1);
+ }
+ break;
+ case 'r':
+#ifdef SMFIP_RCPT_REJ
+ misc_mask |= SMFIP_RCPT_REJ;
+#else
+ fprintf(stderr, "no libmilter support for rejected recipients\n");
+#endif
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'C':
+ conn_count = atoi(optarg);
+ break;
+ default:
+ fprintf(stderr,
+ "usage: %s [-dv] \n"
+ "\t[-a action] non-default action\n"
+ "\t[-b body_text] replace body\n"
+ "\t[-c command] non-default action trigger\n"
+ "\t[-h 'index label value'] replace header\n"
+ "\t[-i 'index label value'] insert header\n"
+ "\t[-m macro_state] non-default macro state\n"
+ "\t[-M macro_list] non-default macro list\n"
+ "\t[-n events] don't receive these events\n"
+ "\t[-N events] don't reply to these events\n"
+ "\t-p port milter application\n"
+ "\t-r request rejected recipients\n"
+ "\t[-C conn_count] when to exit\n",
+ argv[0]);
+ exit(1);
+ }
+ }
+ if (command) {
+ for (cp = command_map; /* see below */ ; cp++) {
+ if (cp->name == 0) {
+ fprintf(stderr, "bad -c argument: %s\n", command);
+ exit(1);
+ }
+ if (strcmp(command, cp->name) == 0)
+ break;
+ }
+ }
+ if (action) {
+ if (command == 0)
+ cp = command_map;
+ if (strcmp(action, "tempfail") == 0) {
+ cp->reply[0] = SMFIS_TEMPFAIL;
+ } else if (strcmp(action, "reject") == 0) {
+ cp->reply[0] = SMFIS_REJECT;
+ } else if (strcmp(action, "accept") == 0) {
+ cp->reply[0] = SMFIS_ACCEPT;
+ } else if (strcmp(action, "discard") == 0) {
+ cp->reply[0] = SMFIS_DISCARD;
+#ifdef SMFIS_SKIP
+ } else if (strcmp(action, "skip") == 0) {
+ cp->reply[0] = SMFIS_SKIP;
+#endif
+ } else if ((code = atoi(action)) >= 400
+ && code <= 599
+ && action[3] == ' ') {
+ cp->reply[0] = SMFIR_REPLYCODE;
+ reply_code = action;
+ reply_dsn = action + 3;
+ if (*reply_dsn != 0) {
+ *reply_dsn++ = 0;
+ reply_dsn += strspn(reply_dsn, " ");
+ }
+ if (*reply_dsn == 0) {
+ reply_dsn = reply_message = 0;
+ } else {
+ reply_message = reply_dsn + strcspn(reply_dsn, " ");
+ if (*reply_message != 0) {
+ *reply_message++ = 0;
+ reply_message += strspn(reply_message, " ");
+ }
+ if (*reply_message == 0)
+ reply_message = 0;
+ }
+ } else {
+ fprintf(stderr, "bad -a argument: %s\n", action);
+ exit(1);
+ }
+ if (verbose) {
+ printf("command %s action %d\n", cp->name, cp->reply[0]);
+ if (reply_code)
+ printf("reply code %s dsn %s message %s\n",
+ reply_code, reply_dsn ? reply_dsn : "(null)",
+ reply_message ? reply_message : "(null)");
+ }
+ }
+#if SMFI_VERSION > 5
+ if (set_macro_state_arg) {
+ for (cpp = macro_states; /* see below */ ; cpp++) {
+ if (*cpp == 0) {
+ fprintf(stderr, "bad -m argument: %s\n", set_macro_state_arg);
+ exit(1);
+ }
+ if (strcmp(set_macro_state_arg, *cpp) == 0)
+ break;
+ }
+ set_macro_state = cpp - macro_states;
+ }
+ if (nosend) {
+ for (np = noproto_map; /* see below */ ; np++) {
+ if (np->name == 0) {
+ fprintf(stderr, "bad -n argument: %s\n", nosend);
+ exit(1);
+ }
+ if (strcmp(nosend, np->name) == 0)
+ break;
+ }
+ nosend_mask = np->send_mask;
+ np->action[0] = 0;
+ }
+ if (noreply) {
+ for (np = noproto_map; /* see below */ ; np++) {
+ if (np->name == 0) {
+ fprintf(stderr, "bad -N argument: %s\n", noreply);
+ exit(1);
+ }
+ if (strcmp(noreply, np->name) == 0)
+ break;
+ }
+ noreply_mask = np->reply_mask;
+ *np->reply = SMFIS_NOREPLY;
+ }
+#endif
+ if (smfi_register(smfilter) == MI_FAILURE) {
+ fprintf(stderr, "smfi_register failed\n");
+ exit(1);
+ }
+ return (smfi_main());
+}