diff options
Diffstat (limited to 'src/milter/milter.c')
-rw-r--r-- | src/milter/milter.c | 1121 |
1 files changed, 1121 insertions, 0 deletions
diff --git a/src/milter/milter.c b/src/milter/milter.c new file mode 100644 index 0000000..dfd5e1c --- /dev/null +++ b/src/milter/milter.c @@ -0,0 +1,1121 @@ +/*++ +/* NAME +/* milter 3 +/* SUMMARY +/* generic MTA-side mail filter interface +/* SYNOPSIS +/* #include <milter.h> +/* +/* MILTERS *milter_create(milter_names, conn_timeout, cmd_timeout, +/* msg_timeout, protocol, def_action, +/* conn_macros, helo_macros, +/* mail_macros, rcpt_macros, +/* data_macros, eoh_macros, +/* eod_macros, unk_macros, +/* macro_deflts) +/* const char *milter_names; +/* int conn_timeout; +/* int cmd_timeout; +/* int msg_timeout; +/* const char *protocol; +/* const char *def_action; +/* const char *conn_macros; +/* const char *helo_macros; +/* const char *mail_macros; +/* const char *rcpt_macrps; +/* const char *data_macros; +/* const char *eoh_macros; +/* const char *eod_macros; +/* const char *unk_macros; +/* const char *macro_deflts; +/* +/* void milter_free(milters) +/* MILTERS *milters; +/* +/* void milter_macro_callback(milters, mac_lookup, mac_context) +/* const char *(*mac_lookup)(const char *name, void *context); +/* void *mac_context; +/* +/* void milter_edit_callback(milters, add_header, upd_header, +/* ins_header, del_header, chg_from, +/* add_rcpt, add_rcpt_par, del_rcpt, +/* repl_body, context) +/* MILTERS *milters; +/* MILTER_ADD_HEADER_FN add_header; +/* MILTER_EDIT_HEADER_FN upd_header; +/* MILTER_EDIT_HEADER_FN ins_header; +/* MILTER_DEL_HEADER_FN del_header; +/* MILTER_EDIT_FROM_FN chg_from; +/* MILTER_EDIT_RCPT_FN add_rcpt; +/* MILTER_EDIT_RCPT_PAR_FN add_rcpt_par; +/* MILTER_EDIT_RCPT_FN del_rcpt; +/* MILTER_EDIT_BODY_FN repl_body; +/* void *context; +/* +/* const char *milter_conn_event(milters, client_name, client_addr, +/* client_port, addr_family) +/* MILTERS *milters; +/* const char *client_name; +/* const char *client_addr; +/* const char *client_port; +/* int addr_family; +/* +/* const char *milter_disc_event(milters) +/* MILTERS *milters; +/* +/* const char *milter_helo_event(milters, helo_name, esmtp_flag) +/* MILTERS *milters; +/* const char *helo_name; +/* int esmtp_flag; +/* +/* const char *milter_mail_event(milters, argv) +/* MILTERS *milters; +/* const char **argv; +/* +/* const char *milter_rcpt_event(milters, flags, argv) +/* MILTERS *milters; +/* int flags; +/* const char **argv; +/* +/* const char *milter_data_event(milters) +/* MILTERS *milters; +/* +/* const char *milter_unknown_event(milters, command) +/* MILTERS *milters; +/* const char *command; +/* +/* const char *milter_other_event(milters) +/* MILTERS *milters; +/* +/* const char *milter_message(milters, qfile, data_offset, auto_hdrs) +/* MILTERS *milters; +/* VSTREAM *qfile; +/* off_t data_offset; +/* ARGV *auto_hdrs; +/* +/* const char *milter_abort(milters) +/* MILTERS *milters; +/* +/* int milter_send(milters, fp) +/* MILTERS *milters; +/* VSTREAM *fp; +/* +/* MILTERS *milter_receive(fp, count) +/* VSTREAM *fp; +/* int count; +/* +/* int milter_dummy(milters, fp) +/* MILTERS *milters; +/* VSTREAM *fp; +/* DESCRIPTION +/* The functions in this module manage one or more milter (mail +/* filter) clients. Currently, only the Sendmail 8 filter +/* protocol is supported. +/* +/* The functions that inspect content or envelope commands +/* return either an SMTP reply ([45]XX followed by enhanced +/* status code and text), "D" (discard), "H" (quarantine), +/* "S" (shutdown connection), or a null pointer, which means +/* "no news is good news". +/* +/* milter_create() instantiates the milter clients specified +/* with the milter_names argument. The conn_macros etc. +/* arguments specify the names of macros that are sent to the +/* mail filter applications upon a connect etc. event, and the +/* macro_deflts argument specifies macro defaults that will be used +/* only if the application's lookup call-back returns null. This +/* function should be called during process initialization, +/* before entering a chroot jail. The timeout parameters specify +/* time limits for the completion of the specified request +/* classes. The protocol parameter specifies a protocol version +/* and optional extensions. When the milter application is +/* unavailable, the milter client will go into a suitable error +/* state as specified with the def_action parameter (i.e. +/* reject, tempfail or accept all subsequent events). +/* +/* milter_free() disconnects from the milter instances that +/* are still opened, and destroys the data structures created +/* by milter_create(). This function is safe to call at any +/* point after milter_create(). +/* +/* milter_macro_callback() specifies a call-back function and +/* context for macro lookup. This function must be called +/* before milter_conn_event(). +/* +/* milter_edit_callback() specifies call-back functions and +/* context for editing the queue file after the end-of-data +/* is received. This function must be called before milter_message(); +/* +/* milter_conn_event() reports an SMTP client connection event +/* to the specified milter instances, after sending the macros +/* specified with the milter_create() conn_macros argument. +/* This function must be called before reporting any other +/* events. +/* +/* milter_disc_event() reports an SMTP client disconnection +/* event to the specified milter instances. No events can +/* reported after this call. To simplify usage, redundant calls +/* of this function are NO-OPs and don't raise a run-time +/* error. +/* +/* milter_helo_event() reports a HELO or EHLO event to the +/* specified milter instances, after sending the macros that +/* were specified with the milter_create() helo_macros argument. +/* +/* milter_mail_event() reports a MAIL FROM event to the specified +/* milter instances, after sending the macros that were specified +/* with the milter_create() mail_macros argument. +/* +/* milter_rcpt_event() reports an RCPT TO event to the specified +/* milter instances, after sending the macros that were specified +/* with the milter_create() rcpt_macros argument. The flags +/* argument supports the following: +/* .IP MILTER_FLAG_WANT_RCPT_REJ +/* When this flag is cleared, invoke all milters. When this +/* flag is set, invoke only milters that want to receive +/* rejected recipients; with Sendmail V8 Milters, {rcpt_mailer} +/* is set to "error", {rcpt_host} is set to an enhanced status +/* code, and {rcpt_addr} is set to descriptive text. +/* .PP +/* milter_data_event() reports a DATA event to the specified +/* milter instances, after sending the macros that were specified +/* with the milter_create() data_macros argument. +/* +/* milter_unknown_event() reports an unknown command event to +/* the specified milter instances, after sending the macros +/* that were specified with the milter_create() unk_macros +/* argument. +/* +/* milter_other_event() returns the current default mail filter +/* reply for the current SMTP connection state; it does not +/* change milter states. A null pointer result means that all +/* is well. This function can be used for SMTP commands such +/* as AUTH, STARTTLS that don't have their own milter event +/* routine. +/* +/* milter_message() sends the message header and body to the +/* to the specified milter instances, and sends the macros +/* specified with the milter_create() eoh_macros after the +/* message header, and with the eod_macros argument at +/* the end. Each milter sees the result of any changes made +/* by a preceding milter. This function must be called with +/* as argument an open Postfix queue file. +/* +/* milter_abort() cancels a mail transaction in progress. To +/* simplify usage, redundant calls of this function are NO-OPs +/* and don't raise a run-time error. +/* +/* milter_send() sends a list of mail filters over the specified +/* stream. When given a null list pointer, a "no filter" +/* indication is sent. The result is non-zero in case of +/* error. +/* +/* milter_receive() receives the specified number of mail +/* filters over the specified stream. The result is a null +/* pointer when no milters were sent, or when an error happened. +/* +/* milter_dummy() is like milter_send(), except that it sends +/* a dummy, but entirely valid, mail filter list. +/* SEE ALSO +/* milter8(3) Sendmail 8 Milter protocol +/* DIAGNOSTICS +/* Panic: interface violation. +/* Fatal errors: memory allocation problem. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <stringops.h> +#include <argv.h> +#include <attr.h> +#include <htable.h> + +/* Global library. */ + +#include <mail_proto.h> +#include <record.h> +#include <rec_type.h> +#include <mail_params.h> +#include <attr_override.h> + +/* Postfix Milter library. */ + +#include <milter.h> + +/* Application-specific. */ + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) + +/* milter_macro_defaults_create - parse default macro entries */ + +HTABLE *milter_macro_defaults_create(const char *macro_defaults) +{ + const char myname[] = "milter_macro_defaults_create"; + char *saved_defaults = mystrdup(macro_defaults); + char *cp = saved_defaults; + HTABLE *table = 0; + VSTRING *canon_buf = 0; + char *nameval; + + while ((nameval = mystrtokq(&cp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) { + const char *err; + char *name; + char *value; + + /* + * Split the input into (name, value) pairs. Allow the forms + * name=value and { name = value }, where the last form ignores + * whitespace after the opening "{", around the "=", and before the + * closing "}". A name may also be specified as {name}. + * + * Use the form {name} for table lookups, because that is the form of + * the S8_MAC_* macro names. + */ + if (*nameval == CHARS_BRACE[0] + && nameval[balpar(nameval, CHARS_BRACE)] != '=' + && (err = extpar(&nameval, CHARS_BRACE, EXTPAR_FLAG_NONE)) != 0) + msg_fatal("malformed default macro entry: %s in \"%s\"", + err, macro_defaults); + if ((err = split_nameval(nameval, &name, &value)) != 0) + msg_fatal("malformed default macro entry: %s in \"%s\"", + err, macro_defaults); + if (*name != '{') /* } */ + name = STR(vstring_sprintf(canon_buf ? canon_buf : + (canon_buf = vstring_alloc(20)), "{%s}", name)); + if (table == 0) + table = htable_create(1); + if (htable_find(table, name) != 0) { + msg_warn("ignoring multiple default macro entries for %s in \"%s\"", + name, macro_defaults); + } else { + (void) htable_enter(table, name, mystrdup(value)); + if (msg_verbose) + msg_info("%s: add name=%s default=%s", myname, name, value); + } + } + myfree(saved_defaults); + if (canon_buf) + vstring_free(canon_buf); + return (table); +} + +/* milter_macro_lookup - look up macros */ + +static ARGV *milter_macro_lookup(MILTERS *milters, const char *macro_names) +{ + const char *myname = "milter_macro_lookup"; + char *saved_names = mystrdup(macro_names); + char *cp = saved_names; + ARGV *argv = argv_alloc(10); + VSTRING *canon_buf = vstring_alloc(20); + const char *value; + const char *name; + const char *cname; + + while ((name = mystrtok(&cp, CHARS_COMMA_SP)) != 0) { + if (msg_verbose) + msg_info("%s: \"%s\"", myname, name); + if (*name != '{') /* } */ + cname = STR(vstring_sprintf(canon_buf, "{%s}", name)); + else + cname = name; + if ((value = milters->mac_lookup(cname, milters->mac_context)) != 0) { + if (msg_verbose) + msg_info("%s: result \"%s\"", myname, value); + argv_add(argv, name, value, (char *) 0); + } else if (milters->macro_defaults != 0 + && (value = htable_find(milters->macro_defaults, cname)) != 0) { + if (msg_verbose) + msg_info("%s: using default \"%s\"", myname, value); + argv_add(argv, name, value, (char *) 0); + } + } + myfree(saved_names); + vstring_free(canon_buf); + return (argv); +} + +/* milter_macro_callback - specify macro lookup */ + +void milter_macro_callback(MILTERS *milters, + const char *(*mac_lookup) (const char *, void *), + void *mac_context) +{ + milters->mac_lookup = mac_lookup; + milters->mac_context = mac_context; +} + +/* milter_edit_callback - specify queue file edit call-back information */ + +void milter_edit_callback(MILTERS *milters, + MILTER_ADD_HEADER_FN add_header, + MILTER_EDIT_HEADER_FN upd_header, + MILTER_EDIT_HEADER_FN ins_header, + MILTER_DEL_HEADER_FN del_header, + MILTER_EDIT_FROM_FN chg_from, + MILTER_EDIT_RCPT_FN add_rcpt, + MILTER_EDIT_RCPT_PAR_FN add_rcpt_par, + MILTER_EDIT_RCPT_FN del_rcpt, + MILTER_EDIT_BODY_FN repl_body, + void *chg_context) +{ + milters->add_header = add_header; + milters->upd_header = upd_header; + milters->ins_header = ins_header; + milters->del_header = del_header; + milters->chg_from = chg_from; + milters->add_rcpt = add_rcpt; + milters->add_rcpt_par = add_rcpt_par; + milters->del_rcpt = del_rcpt; + milters->repl_body = repl_body; + milters->chg_context = chg_context; +} + +/* milter_conn_event - report connect event */ + +const char *milter_conn_event(MILTERS *milters, + const char *client_name, + const char *client_addr, + const char *client_port, + unsigned addr_family) +{ + const char *resp; + MILTER *m; + ARGV *global_macros = 0; + ARGV *any_macros; + +#define MILTER_MACRO_EVAL(global_macros, m, milters, member) \ + ((m->macros && m->macros->member[0]) ? \ + milter_macro_lookup(milters, m->macros->member) : \ + global_macros ? global_macros : \ + (global_macros = \ + milter_macro_lookup(milters, milters->macros->member))) + + if (msg_verbose) + msg_info("report connect to all milters"); + for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) { + if (m->connect_on_demand != 0) + m->connect_on_demand(m); + any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, conn_macros); + resp = m->conn_event(m, client_name, client_addr, client_port, + addr_family, any_macros); + if (any_macros != global_macros) + argv_free(any_macros); + } + if (global_macros) + argv_free(global_macros); + return (resp); +} + +/* milter_helo_event - report helo event */ + +const char *milter_helo_event(MILTERS *milters, const char *helo_name, + int esmtp_flag) +{ + const char *resp; + MILTER *m; + ARGV *global_macros = 0; + ARGV *any_macros; + + if (msg_verbose) + msg_info("report helo to all milters"); + for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) { + any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, helo_macros); + resp = m->helo_event(m, helo_name, esmtp_flag, any_macros); + if (any_macros != global_macros) + argv_free(any_macros); + } + if (global_macros) + argv_free(global_macros); + return (resp); +} + +/* milter_mail_event - report mail from event */ + +const char *milter_mail_event(MILTERS *milters, const char **argv) +{ + const char *resp; + MILTER *m; + ARGV *global_macros = 0; + ARGV *any_macros; + + if (msg_verbose) + msg_info("report sender to all milters"); + for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) { + any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, mail_macros); + resp = m->mail_event(m, argv, any_macros); + if (any_macros != global_macros) + argv_free(any_macros); + } + if (global_macros) + argv_free(global_macros); + return (resp); +} + +/* milter_rcpt_event - report rcpt to event */ + +const char *milter_rcpt_event(MILTERS *milters, int flags, const char **argv) +{ + const char *resp; + MILTER *m; + ARGV *global_macros = 0; + ARGV *any_macros; + + if (msg_verbose) + msg_info("report recipient to all milters (flags=0x%x)", flags); + for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) { + if ((flags & MILTER_FLAG_WANT_RCPT_REJ) == 0 + || (m->flags & MILTER_FLAG_WANT_RCPT_REJ) != 0) { + any_macros = + MILTER_MACRO_EVAL(global_macros, m, milters, rcpt_macros); + resp = m->rcpt_event(m, argv, any_macros); + if (any_macros != global_macros) + argv_free(any_macros); + } + } + if (global_macros) + argv_free(global_macros); + return (resp); +} + +/* milter_data_event - report data event */ + +const char *milter_data_event(MILTERS *milters) +{ + const char *resp; + MILTER *m; + ARGV *global_macros = 0; + ARGV *any_macros; + + if (msg_verbose) + msg_info("report data to all milters"); + for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) { + any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, data_macros); + resp = m->data_event(m, any_macros); + if (any_macros != global_macros) + argv_free(any_macros); + } + if (global_macros) + argv_free(global_macros); + return (resp); +} + +/* milter_unknown_event - report unknown command */ + +const char *milter_unknown_event(MILTERS *milters, const char *command) +{ + const char *resp; + MILTER *m; + ARGV *global_macros = 0; + ARGV *any_macros; + + if (msg_verbose) + msg_info("report unknown command to all milters"); + for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) { + any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, unk_macros); + resp = m->unknown_event(m, command, any_macros); + if (any_macros != global_macros) + argv_free(any_macros); + } + if (global_macros) + argv_free(global_macros); + return (resp); +} + +/* milter_other_event - other SMTP event */ + +const char *milter_other_event(MILTERS *milters) +{ + const char *resp; + MILTER *m; + + if (msg_verbose) + msg_info("query milter states for other event"); + for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) + resp = m->other_event(m); + return (resp); +} + +/* milter_message - inspect message content */ + +const char *milter_message(MILTERS *milters, VSTREAM *fp, off_t data_offset, + ARGV *auto_hdrs) +{ + const char *resp; + MILTER *m; + ARGV *global_eoh_macros = 0; + ARGV *global_eod_macros = 0; + ARGV *any_eoh_macros; + ARGV *any_eod_macros; + + if (msg_verbose) + msg_info("inspect content by all milters"); + for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) { + any_eoh_macros = MILTER_MACRO_EVAL(global_eoh_macros, m, milters, eoh_macros); + any_eod_macros = MILTER_MACRO_EVAL(global_eod_macros, m, milters, eod_macros); + resp = m->message(m, fp, data_offset, any_eoh_macros, any_eod_macros, + auto_hdrs); + if (any_eoh_macros != global_eoh_macros) + argv_free(any_eoh_macros); + if (any_eod_macros != global_eod_macros) + argv_free(any_eod_macros); + } + if (global_eoh_macros) + argv_free(global_eoh_macros); + if (global_eod_macros) + argv_free(global_eod_macros); + return (resp); +} + +/* milter_abort - cancel message receiving state, all milters */ + +void milter_abort(MILTERS *milters) +{ + MILTER *m; + + if (msg_verbose) + msg_info("abort all milters"); + for (m = milters->milter_list; m != 0; m = m->next) + m->abort(m); +} + +/* milter_disc_event - report client disconnect event to all milters */ + +void milter_disc_event(MILTERS *milters) +{ + MILTER *m; + + if (msg_verbose) + msg_info("disconnect event to all milters"); + for (m = milters->milter_list; m != 0; m = m->next) + m->disc_event(m); +} + + /* + * Table-driven parsing of main.cf parameter overrides for specific Milters. + * We derive the override names from the corresponding main.cf parameter + * names by skipping the redundant "milter_" prefix. + */ +static ATTR_OVER_TIME time_table[] = { + 7 + (const char *) VAR_MILT_CONN_TIME, DEF_MILT_CONN_TIME, 0, 1, 0, + 7 + (const char *) VAR_MILT_CMD_TIME, DEF_MILT_CMD_TIME, 0, 1, 0, + 7 + (const char *) VAR_MILT_MSG_TIME, DEF_MILT_MSG_TIME, 0, 1, 0, + 0, +}; +static ATTR_OVER_STR str_table[] = { + 7 + (const char *) VAR_MILT_PROTOCOL, 0, 1, 0, + 7 + (const char *) VAR_MILT_DEF_ACTION, 0, 1, 0, + 0, +}; + +#define link_override_table_to_variable(table, var) \ + do { table[var##_offset].target = &var; } while (0) + +#define my_conn_timeout_offset 0 +#define my_cmd_timeout_offset 1 +#define my_msg_timeout_offset 2 + +#define my_protocol_offset 0 +#define my_def_action_offset 1 + +/* milter_new - create milter list */ + +MILTERS *milter_new(const char *names, + int conn_timeout, + int cmd_timeout, + int msg_timeout, + const char *protocol, + const char *def_action, + MILTER_MACROS *macros, + HTABLE *macro_defaults) +{ + MILTERS *milters; + MILTER *head = 0; + MILTER *tail = 0; + char *name; + MILTER *milter; + const char *sep = CHARS_COMMA_SP; + const char *parens = CHARS_BRACE; + int my_conn_timeout; + int my_cmd_timeout; + int my_msg_timeout; + const char *my_protocol; + const char *my_def_action; + + /* + * Initialize. + */ + link_override_table_to_variable(time_table, my_conn_timeout); + link_override_table_to_variable(time_table, my_cmd_timeout); + link_override_table_to_variable(time_table, my_msg_timeout); + link_override_table_to_variable(str_table, my_protocol); + link_override_table_to_variable(str_table, my_def_action); + + /* + * Parse the milter list. + */ + milters = (MILTERS *) mymalloc(sizeof(*milters)); + if (names != 0 && *names != 0) { + char *saved_names = mystrdup(names); + char *cp = saved_names; + char *op; + char *err; + + /* + * Instantiate Milters, allowing for per-Milter overrides. + */ + while ((name = mystrtokq(&cp, sep, parens)) != 0) { + my_conn_timeout = conn_timeout; + my_cmd_timeout = cmd_timeout; + my_msg_timeout = msg_timeout; + my_protocol = protocol; + my_def_action = def_action; + if (name[0] == parens[0]) { + op = name; + if ((err = extpar(&op, parens, EXTPAR_FLAG_NONE)) != 0) + msg_fatal("milter service syntax error: %s", err); + if ((name = mystrtok(&op, sep)) == 0) + msg_fatal("empty milter definition: \"%s\"", names); + attr_override(op, sep, parens, + CA_ATTR_OVER_STR_TABLE(str_table), + CA_ATTR_OVER_TIME_TABLE(time_table), + CA_ATTR_OVER_END); + } + milter = milter8_create(name, my_conn_timeout, my_cmd_timeout, + my_msg_timeout, my_protocol, + my_def_action, milters); + if (head == 0) { + head = milter; + } else { + tail->next = milter; + } + tail = milter; + } + myfree(saved_names); + } + milters->milter_list = head; + milters->mac_lookup = 0; + milters->mac_context = 0; + milters->macros = macros; + milters->macro_defaults = macro_defaults; + milters->add_header = 0; + milters->upd_header = milters->ins_header = 0; + milters->del_header = 0; + milters->add_rcpt = milters->del_rcpt = 0; + milters->repl_body = 0; + milters->chg_context = 0; + return (milters); +} + +/* milter_free - destroy all milters */ + +void milter_free(MILTERS *milters) +{ + MILTER *m; + MILTER *next; + + if (msg_verbose) + msg_info("free all milters"); + for (m = milters->milter_list; m != 0; m = next) + next = m->next, m->free(m); + if (milters->macros) + milter_macros_free(milters->macros); + if (milters->macro_defaults) + htable_free(milters->macro_defaults, myfree); + myfree((void *) milters); +} + +/* milter_dummy - send empty milter list */ + +int milter_dummy(MILTERS *milters, VSTREAM *stream) +{ + MILTERS dummy = *milters; + + dummy.milter_list = 0; + return (milter_send(&dummy, stream)); +} + +/* milter_send - send Milter instances over stream */ + +int milter_send(MILTERS *milters, VSTREAM *stream) +{ + MILTER *m; + int status = 0; + int count = 0; + + /* + * XXX Optimization: send only the filters that are actually used in the + * remote process. No point sending a filter that looks at HELO commands + * to a cleanup server. For now we skip only the filters that are known + * to be disabled (either in global error state or in global accept + * state). + * + * XXX We must send *some* information, even when there are no active + * filters, otherwise the cleanup server would try to apply its own + * non_smtpd_milters settings. + */ + if (milters != 0) + for (m = milters->milter_list; m != 0; m = m->next) + if (m->active(m)) + count++; + (void) rec_fprintf(stream, REC_TYPE_MILT_COUNT, "%d", count); + + if (msg_verbose) + msg_info("send %d milters", count); + + /* + * XXX Optimization: don't send or receive further information when there + * aren't any active filters. + */ + if (count <= 0) + return (0); + + /* + * Send the filter macro name lists. + */ + (void) attr_print(stream, ATTR_FLAG_MORE, + SEND_ATTR_FUNC(milter_macros_print, + (const void *) milters->macros), + ATTR_TYPE_END); + + /* + * Send the filter macro defaults. + */ + count = milters->macro_defaults ? milters->macro_defaults->used : 0; + (void) attr_print(stream, ATTR_FLAG_MORE, + SEND_ATTR_INT(MAIL_ATTR_SIZE, count), + ATTR_TYPE_END); + if (count > 0) + (void) attr_print(stream, ATTR_FLAG_MORE, + SEND_ATTR_HASH(milters->macro_defaults), + ATTR_TYPE_END); + + /* + * Send the filter instances. + */ + for (m = milters->milter_list; m != 0; m = m->next) + if (m->active(m) && (status = m->send(m, stream)) != 0) + break; + + /* + * Over to you. + */ + if (status != 0 + || attr_scan(stream, ATTR_FLAG_STRICT, + RECV_ATTR_INT(MAIL_ATTR_STATUS, &status), + ATTR_TYPE_END) != 1 + || status != 0) { + msg_warn("cannot send milters to service %s", VSTREAM_PATH(stream)); + return (-1); + } + return (0); +} + +/* milter_receive - receive milters from stream */ + +MILTERS *milter_receive(VSTREAM *stream, int count) +{ + MILTERS *milters; + MILTER *head = 0; + MILTER *tail = 0; + MILTER *milter = 0; + int macro_default_count; + + if (msg_verbose) + msg_info("receive %d milters", count); + + /* + * XXX We must instantiate a MILTERS structure even when the sender has + * no active filters, otherwise the cleanup server would try to use its + * own non_smtpd_milters settings. + */ +#define NO_MILTERS ((char *) 0) +#define NO_TIMEOUTS 0, 0, 0 +#define NO_PROTOCOL ((char *) 0) +#define NO_ACTION ((char *) 0) +#define NO_MACROS ((MILTER_MACROS *) 0) +#define NO_MACRO_DEFLTS ((HTABLE *) 0) + + milters = milter_new(NO_MILTERS, NO_TIMEOUTS, NO_PROTOCOL, NO_ACTION, + NO_MACROS, NO_MACRO_DEFLTS); + + /* + * XXX Optimization: don't send or receive further information when there + * aren't any active filters. + */ + if (count <= 0) + return (milters); + + /* + * Receive the global macro name lists. + */ + milters->macros = milter_macros_alloc(MILTER_MACROS_ALLOC_ZERO); + if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE, + RECV_ATTR_FUNC(milter_macros_scan, + (void *) milters->macros), + ATTR_TYPE_END) != 1) { + milter_free(milters); + return (0); + } + + /* + * Receive the filter macro defaults. + */ + if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE, + RECV_ATTR_INT(MAIL_ATTR_SIZE, ¯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 |