diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 12:06:34 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 12:06:34 +0000 |
commit | 5e61585d76ae77fd5e9e96ebabb57afa4d74880d (patch) | |
tree | 2b467823aaeebc7ef8bc9e3cabe8074eaef1666d /src/milter | |
parent | Initial commit. (diff) | |
download | postfix-5e61585d76ae77fd5e9e96ebabb57afa4d74880d.tar.xz postfix-5e61585d76ae77fd5e9e96ebabb57afa4d74880d.zip |
Adding upstream version 3.5.24.upstream/3.5.24
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/milter')
l--------- | src/milter/.indent.pro | 1 | ||||
-rw-r--r-- | src/milter/Makefile.in | 145 | ||||
-rw-r--r-- | src/milter/milter.c | 1121 | ||||
-rw-r--r-- | src/milter/milter.h | 223 | ||||
-rw-r--r-- | src/milter/milter8.c | 2911 | ||||
-rw-r--r-- | src/milter/milter_macros.c | 298 | ||||
-rw-r--r-- | src/milter/test-list | 49 | ||||
-rw-r--r-- | src/milter/test-milter.c | 840 |
8 files changed, 5588 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..3d71cc6 --- /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, + (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, ¯o_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..601adf4 --- /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_MASTER_FN, VSTREAM *, int, void *); +extern int milter_macros_scan(ATTR_SCAN_MASTER_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..6344734 --- /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, + (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..95126b1 --- /dev/null +++ b/src/milter/milter_macros.c @@ -0,0 +1,298 @@ +/*++ +/* 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_MASTER_FN print_fn; +/* VSTREAM *stream; +/* int flags; +/* void *ptr; +/* +/* int milter_macros_scan(scan_fn, fp, flags, ptr) +/* ATTR_SCAN_MASTER_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, (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 +/*--*/ + +/* 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_MASTER_FN print_fn, VSTREAM *fp, + int flags, 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_MASTER_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()); +} |