From e57de0df6894d5f3e9f873a08e77808486a90d91 Mon Sep 17 00:00:00 2001 From: Wietse Venema Date: Mon, 17 Mar 2003 00:00:00 -0500 Subject: [PATCH] postfix-2.0.7-20030317 --- postfix/.indent.pro | 1 + postfix/HISTORY | 18 ++++ postfix/conf/master.cf | 2 + postfix/html/proxymap.8.html | 4 +- postfix/html/uce.html | 2 +- postfix/man/man8/proxymap.8 | 2 + postfix/src/global/Makefile.in | 1 + postfix/src/global/mail_version.h | 4 +- postfix/src/global/maps.c | 5 +- postfix/src/global/tok822_parse.c | 4 +- postfix/src/master/mail_flow.c | 1 + postfix/src/postsuper/postsuper.c | 87 ++++++++++------- postfix/src/proxymap/proxymap.c | 2 + postfix/src/smtpd/smtpd.c | 83 +++++++++++++++-- postfix/src/smtpd/smtpd.h | 50 ++++++++++ postfix/src/smtpd/smtpd_check.c | 134 ++++++++++++++++++++------- postfix/src/smtpd/smtpd_check.h | 2 +- postfix/src/smtpd/smtpd_check.in4 | 1 + postfix/src/smtpd/smtpd_check.ref4 | 7 +- postfix/src/smtpd/smtpd_check_access | 1 + postfix/src/smtpd/smtpd_state.c | 6 ++ 21 files changed, 329 insertions(+), 88 deletions(-) diff --git a/postfix/.indent.pro b/postfix/.indent.pro index 132e9b83f..7e8ce4ccf 100644 --- a/postfix/.indent.pro +++ b/postfix/.indent.pro @@ -136,6 +136,7 @@ -TSINK_STATE -TSMTPD_CMD -TSMTPD_DEFER +-TSMTPD_MSG_ACTION -TSMTPD_RBL_EXPAND_CONTEXT -TSMTPD_RBL_STATE -TSMTPD_STATE diff --git a/postfix/HISTORY b/postfix/HISTORY index c25921a28..23f544606 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -7895,6 +7895,24 @@ Apologies for any names omitted. systems against exploitation of the remote buffer overflow vulnerability described in CERT advisory CA-2003-07. +20030311-17 + + Bugfix: the access map actions HOLD, DISCARD, FILTER and + REDIRECT were broken with smtpd_delay_reject=no. This + required re-architecting of the actions code. Files: + smtpd/smtpd.[hc], smtpd/smtpd_check.c, smtpd/smtpd_state.c. + +20030315 + + Bugfix: the postsuper manual page documented support for + the -c command line option, but it was not implemented. + File: postsuper/postsuper.c. + + Bugfix: the Postfix 2.0 recipient map checking code broke + the VRFY command, causing it to reply with status code 252 + for non-existent addresses. This required re-architecting + the recipient table lookup code. File: smtpd/smtpd_check.c. + Open problems: Med: make qmgr recipient bounce/defer activity asynchronous diff --git a/postfix/conf/master.cf b/postfix/conf/master.cf index 2c8d34fc2..a8f973721 100644 --- a/postfix/conf/master.cf +++ b/postfix/conf/master.cf @@ -26,6 +26,8 @@ # directory (pathname is controlled by the queue_directory configuration # variable in the main.cf file). Presently, all Postfix daemons can run # chrooted, except for the pipe, virtual and local delivery daemons. +# The proxymap server can run chrooted, but doing so defeats most of +# the purpose of having that service in the first place. # The files in the examples/chroot-setup subdirectory describe how # to set up a Postfix chroot environment for your type of machine. # diff --git a/postfix/html/proxymap.8.html b/postfix/html/proxymap.8.html index 4ea05c096..0cff6e6a6 100644 --- a/postfix/html/proxymap.8.html +++ b/postfix/html/proxymap.8.html @@ -88,7 +88,9 @@ PROXYMAP(8) PROXYMAP(8) The proxymap server opens only tables that are approved via the proxy_read_maps configuration parameter, does not talk to users, and can run at fixed low privilege, - chrooted or not. + chrooted or not. However, running the proxymap server + chrooted severely limits usability, because it can open + only chrooted tables. The proxymap server is not a trusted daemon process, and must not be used to look up sensitive information such as diff --git a/postfix/html/uce.html b/postfix/html/uce.html index 9ca51eb41..6d0a25c79 100644 --- a/postfix/html/uce.html +++ b/postfix/html/uce.html @@ -880,7 +880,7 @@ and the address contains no sender-specified routing
  • Postfix is the final destination: any destination that matches $mydestination, $inet_interfaces, $virtual_alias_domains, or +href="virtual.5.html">$virtual_alias_domains, or $virtual_mailbox_domains. diff --git a/postfix/man/man8/proxymap.8 b/postfix/man/man8/proxymap.8 index a801de872..73efb7142 100644 --- a/postfix/man/man8/proxymap.8 +++ b/postfix/man/man8/proxymap.8 @@ -88,6 +88,8 @@ of idle time. The proxymap server opens only tables that are approved via the \fBproxy_read_maps\fR configuration parameter, does not talk to users, and can run at fixed low privilege, chrooted or not. +However, running the proxymap server chrooted severely limits +usability, because it can open only chrooted tables. The proxymap server is not a trusted daemon process, and must not be used to look up sensitive information such as user or diff --git a/postfix/src/global/Makefile.in b/postfix/src/global/Makefile.in index cd4ef60af..fb9f9f638 100644 --- a/postfix/src/global/Makefile.in +++ b/postfix/src/global/Makefile.in @@ -1251,6 +1251,7 @@ tok822_parse.o: ../../include/sys_defs.h tok822_parse.o: ../../include/vstring.h tok822_parse.o: ../../include/vbuf.h tok822_parse.o: ../../include/msg.h +tok822_parse.o: ../../include/stringops.h tok822_parse.o: lex_822.h tok822_parse.o: quote_822_local.h tok822_parse.o: quote_flags.h diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 2da4fcccd..4dc8dae25 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -20,10 +20,10 @@ * Patches change the patchlevel and the release date. Snapshots change the * release date only, unless they include the same bugfix as a patch release. */ -#define MAIL_RELEASE_DATE "20030305" +#define MAIL_RELEASE_DATE "20030317" #define VAR_MAIL_VERSION "mail_version" -#define DEF_MAIL_VERSION "2.0.6-" MAIL_RELEASE_DATE +#define DEF_MAIL_VERSION "2.0.7-" MAIL_RELEASE_DATE extern char *var_mail_version; /* diff --git a/postfix/src/global/maps.c b/postfix/src/global/maps.c index fc7dedc32..ae345e3ea 100644 --- a/postfix/src/global/maps.c +++ b/postfix/src/global/maps.c @@ -172,14 +172,15 @@ const char *maps_find(MAPS *maps, const char *name, int flags) continue; if ((expansion = dict_get(dict, name)) != 0) { if (msg_verbose) - msg_info("%s: %s: %s = %s", myname, *map_name, name, expansion); + msg_info("%s: %s: %s: %s = %s", myname, maps->title, + *map_name, name, expansion); return (expansion); } else if (dict_errno != 0) { break; } } if (msg_verbose) - msg_info("%s: %s: %s", myname, name, dict_errno ? + msg_info("%s: %s: %s: %s", myname, maps->title, name, dict_errno ? "search aborted" : "not found"); return (0); } diff --git a/postfix/src/global/tok822_parse.c b/postfix/src/global/tok822_parse.c index fe554bf7a..4866ac5ad 100644 --- a/postfix/src/global/tok822_parse.c +++ b/postfix/src/global/tok822_parse.c @@ -126,6 +126,7 @@ #include #include +#include /* Global library. */ @@ -250,7 +251,7 @@ static void strip_address(VSTRING *vp, int start, TOK822 *addr) * Emit plain
    . Discard any comments or phrases. */ msg_warn("stripping too many comments from address: %.100s...", - vstring_str(vp) + start); + printable(vstring_str(vp) + start, '?')); vstring_truncate(vp, start); VSTRING_ADDCH(vp, '<'); if (addr) { @@ -263,7 +264,6 @@ static void strip_address(VSTRING *vp, int start, TOK822 *addr) VSTRING_ADDCH(vp, '>'); } - /* tok822_externalize - token tree to string, external form */ VSTRING *tok822_externalize(VSTRING *vp, TOK822 *tree, int flags) diff --git a/postfix/src/master/mail_flow.c b/postfix/src/master/mail_flow.c index efb598854..383ba5816 100644 --- a/postfix/src/master/mail_flow.c +++ b/postfix/src/master/mail_flow.c @@ -47,6 +47,7 @@ #include #include #include +#include /* Utility library. */ diff --git a/postfix/src/postsuper/postsuper.c b/postfix/src/postsuper/postsuper.c index 3247a2c00..a5c4ded86 100644 --- a/postfix/src/postsuper/postsuper.c +++ b/postfix/src/postsuper/postsuper.c @@ -987,16 +987,13 @@ int main(int argc, char **argv) msg_fatal("open /dev/null: %m"); /* - * Process environment options as early as we can. We might be called - * from a set-uid (set-gid) program, so be careful with importing - * environment variables. + * Process this environment option as early as we can, to aid debugging. */ if (safe_getenv(CONF_ENV_VERB)) msg_verbose = 1; /* - * Initialize. Set up logging, read the global configuration file and - * extract configuration information. + * Initialize logging. */ if ((slash = strrchr(argv[0], '/')) != 0) argv[0] = slash + 1; @@ -1004,47 +1001,37 @@ int main(int argc, char **argv) msg_syslog_init(mail_task(argv[0]), LOG_PID, LOG_FACILITY); set_mail_conf_str(VAR_PROCNAME, var_procname = mystrdup(argv[0])); - mail_conf_read(); - if (chdir(var_queue_dir)) - msg_fatal("chdir %s: %m", var_queue_dir); - /* - * Be sure to log a warning if we do not finish structural repair. Maybe - * we should have an fsck-style "clean" flag so Postfix will not start - * with a broken queue. - */ - signal(SIGHUP, interrupted); - signal(SIGINT, interrupted); - signal(SIGQUIT, interrupted); - signal(SIGTERM, interrupted); - msg_cleanup(fatal_exit); - - /* - * All file/directory updates must be done as the mail system owner. This - * is because Postfix daemons manipulate the queue with those same - * privileges, so directories must be created with the right ownership. - * - * Running as a non-root user is also required for security reasons. When - * the Postfix queue hierarchy is compromised, an attacker could trick us - * into entering other file hierarchies and afflicting damage. Running as - * a non-root user limits the damage to the already compromised mail - * owner. + * Disallow unsafe practices, and refuse to run set-uid (or as the child + * of a set-uid process). Whenever a privileged wrapper program is + * needed, it must properly sanitize the real/effective/saved UID/GID, + * the secondary groups, the process environment, and so on. Otherwise, + * accidents can happen. If not with Postfix, then with other software. */ + if (unsafe() != 0) + msg_fatal("this postfix command must not run as a set-uid process"); if (getuid()) msg_fatal("use of this command is reserved for the superuser"); - set_ugid(var_owner_uid, var_owner_gid); /* * Parse JCL. */ - while ((c = GETOPT(argc, argv, "d:h:H:pr:sv")) > 0) { + while ((c = GETOPT(argc, argv, "c:d:h:H:pr:sv")) > 0) { switch (c) { default: - msg_fatal("usage: %s [-d queue_id (delete)] " + msg_fatal("usage: %s " + "[-c config_dir] " + "[-d queue_id (delete)] " "[-h queue_id (hold)] [-H queue_id (un-hold)] " "[-p (purge temporary files)] [-r queue_id (requeue)] " "[-s (structure fix)] [-v (verbose)] " "[queue...]", argv[0]); + case 'c': + if (*optarg != '/') + msg_fatal("-c requires absolute pathname"); + if (setenv(CONF_ENV_PATH, optarg, 1) < 0) + msg_fatal("setenv: %m"); + break; case 'd': if (delete_names == 0) delete_names = argv_alloc(1); @@ -1085,6 +1072,42 @@ int main(int argc, char **argv) } } + /* + * Read the global configuration file and extract configuration + * information. The -c command option can override the default + * configuration directory location. + */ + mail_conf_read(); + if (chdir(var_queue_dir)) + msg_fatal("chdir %s: %m", var_queue_dir); + + /* + * All file/directory updates must be done as the mail system owner. This + * is because Postfix daemons manipulate the queue with those same + * privileges, so directories must be created with the right ownership. + * + * Running as a non-root user is also required for security reasons. When + * the Postfix queue hierarchy is compromised, an attacker could trick us + * into entering other file hierarchies and afflicting damage. Running as + * a non-root user limits the damage to the already compromised mail + * owner. + */ + set_ugid(var_owner_uid, var_owner_gid); + + /* + * Be sure to log a warning if we do not finish structural repair. Maybe + * we should have an fsck-style "clean" flag so Postfix will not start + * with a broken queue. + * + * Set up signal handlers after permanently dropping super-user privileges, + * so that signal handlers will always run with the correct privileges. + */ + signal(SIGHUP, interrupted); + signal(SIGINT, interrupted); + signal(SIGQUIT, interrupted); + signal(SIGTERM, interrupted); + msg_cleanup(fatal_exit); + /* * Sanity checks. */ diff --git a/postfix/src/proxymap/proxymap.c b/postfix/src/proxymap/proxymap.c index 618d58595..22d070f3a 100644 --- a/postfix/src/proxymap/proxymap.c +++ b/postfix/src/proxymap/proxymap.c @@ -78,6 +78,8 @@ /* The proxymap server opens only tables that are approved via the /* \fBproxy_read_maps\fR configuration parameter, does not talk to /* users, and can run at fixed low privilege, chrooted or not. +/* However, running the proxymap server chrooted severely limits +/* usability, because it can open only chrooted tables. /* /* The proxymap server is not a trusted daemon process, and must /* not be used to look up sensitive information such as user or diff --git a/postfix/src/smtpd/smtpd.c b/postfix/src/smtpd/smtpd.c index 24aae3d1d..e75bb0d47 100644 --- a/postfix/src/smtpd/smtpd.c +++ b/postfix/src/smtpd/smtpd.c @@ -495,6 +495,8 @@ static int helo_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) smtpd_chat_reply(state, "501 Syntax: HELO hostname"); return (-1); } + if (state->helo_name != 0) + helo_reset(state); if (argc > 2) collapse_args(argc - 1, argv + 1); if (SMTPD_STAND_ALONE(state) == 0 @@ -503,8 +505,6 @@ static int helo_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) smtpd_chat_reply(state, "%s", err); return (-1); } - if (state->helo_name != 0) - helo_reset(state); chat_reset(state, var_smtpd_hist_thrsh); mail_reset(state); rcpt_reset(state); @@ -531,6 +531,8 @@ static int ehlo_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) smtpd_chat_reply(state, "501 Syntax: EHLO hostname"); return (-1); } + if (state->helo_name != 0) + helo_reset(state); if (argc > 2) collapse_args(argc - 1, argv + 1); if (SMTPD_STAND_ALONE(state) == 0 @@ -539,8 +541,6 @@ static int ehlo_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) smtpd_chat_reply(state, "%s", err); return (-1); } - if (state->helo_name != 0) - helo_reset(state); chat_reset(state, var_smtpd_hist_thrsh); mail_reset(state); rcpt_reset(state); @@ -576,6 +576,26 @@ static void helo_reset(SMTPD_STATE *state) if (state->helo_name) myfree(state->helo_name); state->helo_name = 0; + + /* + * With smtpd_delay_reject=yes, smtpd_helo_restrictions is evaluated at + * RCPT TO time, and may be evaluated multiple times per message. + * Per-message smtpd_helo_restrictions side effects (HOLD, DISCARD, etc.) + * accumulate from one evaluation to the next, and may also depend on + * client, sender or recipient information. Therefore, any per-message + * helo restriction side effects need to be reset at the end of a mail + * delivery transaction. + * + * With smtpd_delay_reject=no, smtpd_helo_restrictions is evaluated when + * HELO/EHLO is issued, and its per-message side effects change only when + * another HELO/EHLO command is issued. Therefore, any per-message helo + * restriction side effects must be reset before smtpd_helo_restrictions + * is evaluated. + */ + if (var_smtpd_delay_reject == 0) { + SMTPD_MSG_ACT_FREE(state->action_helo); + SMTPD_MSG_ACT_ZERO(state->action_helo); + } } /* mail_open_stream - open mail destination */ @@ -903,6 +923,20 @@ static void mail_reset(SMTPD_STATE *state) if (var_smtpd_sasl_enable) smtpd_sasl_mail_reset(state); #endif + + /* + * With smtpd_delay_reject=yes, smtpd_sender_restrictions is evaluated at + * RCPT TO time, and may be evaluated multiple times per message. + * Per-message smtpd_sender_restrictions side effects (HOLD, DISCARD, + * etc.) accumulate from one evaluation to the next, and may also depend + * on client, helo or recipient information. + * + * The action_mailrcpt member accumulates both sender and recipient side + * effects. It needs to be reset after each mail delivery, whether or not + * it is actually completed. + */ + SMTPD_MSG_ACT_FREE(state->action_mailrcpt); + SMTPD_MSG_ACT_ZERO(state->action_mailrcpt); } /* rcpt_cmd - process RCPT TO command */ @@ -961,10 +995,6 @@ static int rcpt_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) smtpd_chat_reply(state, "%s", err); return (-1); } - if ((err = smtpd_check_rcptmap(state, argv[2].strval)) != 0) { - smtpd_chat_reply(state, "%s", err); - return (-1); - } } /* @@ -987,6 +1017,26 @@ static void rcpt_reset(SMTPD_STATE *state) state->recipient = 0; } state->rcpt_count = 0; + + /* + * With smtpd_delay_reject=yes, smtpd_{client,helo}_restrictions are + * evaluated at RCPT TO time. They may be evaluated multiple times per + * message delivery. Per-message {client,helo} restriction side effects + * (HOLD, DISCARD, etc.) accumulate from one evaluation to the next, and + * the result may also depend on sender or recipient information. + * Therefore, per-message {client,helo} restriction side effects need to + * be reset between deliveries. + * + * With smtpd_delay_reject=no, smtpd_{client,helo}_restrictions are + * evaluated once and take effect over multiple deliveries. Therefore, + * their per-message side effects must not be reset between deliveries. + */ + if (var_smtpd_delay_reject) { + SMTPD_MSG_ACT_FREE(state->action_client); + SMTPD_MSG_ACT_ZERO(state->action_client); + SMTPD_MSG_ACT_FREE(state->action_helo); + SMTPD_MSG_ACT_ZERO(state->action_helo); + } } /* data_cmd - process DATA command */ @@ -1001,6 +1051,7 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) int first = 1; VSTRING *why = 0; int saved_err; + char *act_value; /* * Sanity checks. With ESMTP command pipelining the client can send DATA @@ -1033,6 +1084,18 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) */ if (*var_always_bcc) rec_fputs(state->cleanup, REC_TYPE_RCPT, var_always_bcc); + if (SMTPD_MSG_ACT_FLAGS(state) & SMTPD_MSG_ACT_DISCARD) { + rec_fprintf(state->dest->stream, REC_TYPE_FLGS, "%d", + CLEANUP_FLAG_DISCARD); + } else { + if (SMTPD_MSG_ACT_FLAGS(state) & SMTPD_MSG_ACT_HOLD) + rec_fprintf(state->cleanup, REC_TYPE_FLGS, "%d", + CLEANUP_FLAG_HOLD); + if ((act_value = SMTPD_MSG_ACT_VALUE(state, filter)) != 0) + rec_fprintf(state->dest->stream, REC_TYPE_FILT, "%s", act_value); + if ((act_value = SMTPD_MSG_ACT_VALUE(state, redirect)) != 0) + rec_fprintf(state->dest->stream, REC_TYPE_RDR, "%s", act_value); + } rec_fputs(state->cleanup, REC_TYPE_MESG, ""); rec_fprintf(state->cleanup, REC_TYPE_NORM, "Received: from %s (%s [%s])", @@ -1269,7 +1332,7 @@ static int vrfy_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) return (-1); } if (SMTPD_STAND_ALONE(state) == 0 - && (err = smtpd_check_rcptmap(state, argv[1].strval)) != 0) { + && (err = smtpd_check_vrfy(state, argv[1].strval)) != 0) { smtpd_chat_reply(state, "%s", err); return (-1); } @@ -1609,7 +1672,7 @@ static void smtpd_service(VSTREAM *stream, char *unused_service, char **argv) static void pre_accept(char *unused_name, char **unused_argv) { const char *table; - + if ((table = dict_changed_name()) != 0) { msg_info("table %s has changed -- restarting", table); exit(0); diff --git a/postfix/src/smtpd/smtpd.h b/postfix/src/smtpd/smtpd.h index 8be9efef1..be0ad3421 100644 --- a/postfix/src/smtpd/smtpd.h +++ b/postfix/src/smtpd/smtpd.h @@ -45,6 +45,43 @@ typedef struct SMTPD_DEFER { int class; /* error notification class */ } SMTPD_DEFER; + /* + * Actions that affect all recipients of a given message. That is, we don't + * "undo" results from one recipient when evaluating the next recipient. + */ +typedef struct SMTPD_MSG_ACTION { + int flags; /* see below */ + char *filter; /* filter destination */ + char *redirect; /* redirect destination */ +} SMTPD_MSG_ACTION; + +#define SMTPD_MSG_ACT_HOLD (1<<0) /* place message on hold */ +#define SMTPD_MSG_ACT_DISCARD (1<<1) /* discard message */ +#define SMTPD_MSG_ACT_FILTER (1<<2) /* filter message */ +#define SMTPD_MSG_ACT_REDIRECT (1<<3) /* redirect message */ + + /* + * Short-hand for when to stop searching restriction lists. + */ +#define SMTPD_MSG_ACT_FINAL (SMTPD_MSG_ACT_DISCARD) + +#define SMTPD_MSG_ACT_ZERO(a) do { \ + (a).flags = 0; \ + (a).filter = 0; \ + (a).redirect = 0; \ + } while (0) + +#define SMTPD_MSG_ACT_FREE(a) do { \ + if ((a).filter) myfree((a).filter); \ + if ((a).redirect) myfree((a).redirect); \ + } while (0) + +#define SMTPD_MSG_ACT_COPY(d, s) do { \ + SMTPD_MSG_ACT_FREE(d); \ + (d).filter = ((s).filter ? mystrdup((s).filter) : 0); \ + (d).redirect = ((s).redirect ? mystrdup((s).redirect) : 0); \ + } while (0) + typedef struct SMTPD_STATE { int err; VSTREAM *client; @@ -86,6 +123,10 @@ typedef struct SMTPD_STATE { VSTRING *sasl_encoded; VSTRING *sasl_decoded; #endif + SMTPD_MSG_ACTION *action; /* action from access map */ + SMTPD_MSG_ACTION action_client; /* action after connect */ + SMTPD_MSG_ACTION action_helo; /* action after helo/ehlo */ + SMTPD_MSG_ACTION action_mailrcpt; /* action after mail from/rcpt to */ int rcptmap_checked; int warn_if_reject; /* force reject into warning */ SMTPD_DEFER defer_if_reject; /* force reject into deferral */ @@ -96,6 +137,15 @@ typedef struct SMTPD_STATE { VSTRING *expand_buf; /* scratch space for $name expansion */ } SMTPD_STATE; +#define SMTPD_MSG_ACT_FLAGS(s) \ + ((s)->action_client.flags | (s)->action_helo.flags \ + | (s)->action_mailrcpt.flags) + +#define SMTPD_MSG_ACT_VALUE(s,m) \ + ((s)->action_mailrcpt.m ? (s)->action_mailrcpt.m : \ + (s)->action_helo.m ? (s)->action_helo.m : \ + (s)->action_client.m) + extern void smtpd_state_init(SMTPD_STATE *, VSTREAM *); extern void smtpd_state_reset(SMTPD_STATE *); diff --git a/postfix/src/smtpd/smtpd_check.c b/postfix/src/smtpd/smtpd_check.c index 1db47f3d9..4b030da86 100644 --- a/postfix/src/smtpd/smtpd_check.c +++ b/postfix/src/smtpd/smtpd_check.c @@ -24,7 +24,7 @@ /* SMTPD_STATE *state; /* char *recipient; /* -/* char *smtpd_check_rcptmap(state, recipient) +/* char *smtpd_check_vrfy(state, recipient) /* SMTPD_STATE *state; /* char *recipient; /* @@ -202,11 +202,13 @@ /* .IP smtpd_recipient_restrictions /* Restrictions on the recipient address that is sent with the RCPT /* TO command. +/* .IP local_recipient_maps +/* Tables of user names (not addresses) that exist in $mydestination. +/* Mail for local users not in these tables is rejected. /* .PP -/* smtpd_check_rcptmap() validates the recipient address provided -/* with an RCPT TO request and sets the rcptmap_checked flag. -/* Relevant configuration parameters: -/* .IP local_recipients_map +/* smtpd_check_vrfy() validates the recipient address provided +/* with a VRFY request. Relevant configuration parameters: +/* .IP local_recipient_maps /* Tables of user names (not addresses) that exist in $mydestination. /* Mail for local users not in these tables is rejected. /* .PP @@ -1751,6 +1753,8 @@ static int check_table_result(SMTPD_STATE *state, const char *table, * mind, and reject/discard the message for other reasons. */ if (STREQUAL(value, "FILTER", cmd_len)) { + if (state->action == 0) + return (SMTPD_CHECK_DUNNO); if (*cmd_text == 0) { msg_warn("access map %s entry \"%s\" has FILTER entry without value", table, datum); @@ -1763,9 +1767,10 @@ static int check_table_result(SMTPD_STATE *state, const char *table, vstring_sprintf(error_text, "<%s>: %s triggers FILTER %s", reply_name, reply_class, cmd_text); log_whatsup(state, "filter", STR(error_text)); -#ifndef TEST - rec_fprintf(state->dest->stream, REC_TYPE_FILT, "%s", cmd_text); -#endif + state->action->flags |= SMTPD_MSG_ACT_FILTER; + if (state->action->filter) + myfree(state->action->filter); + state->action->filter = mystrdup(cmd_text); return (SMTPD_CHECK_DUNNO); } } @@ -1775,30 +1780,25 @@ static int check_table_result(SMTPD_STATE *state, const char *table, * reject/discard the message for other reasons. */ if (STREQUAL(value, "HOLD", cmd_len)) { + if (state->action == 0) + return (SMTPD_CHECK_DUNNO); vstring_sprintf(error_text, "<%s>: %s %s", reply_name, reply_class, *cmd_text ? cmd_text : "triggers HOLD action"); log_whatsup(state, "hold", STR(error_text)); -#ifndef TEST - rec_fprintf(state->dest->stream, REC_TYPE_FLGS, "%d", - CLEANUP_FLAG_HOLD); -#endif + state->action->flags |= SMTPD_MSG_ACT_HOLD; return (SMTPD_CHECK_DUNNO); } /* * DISCARD means silently discard and claim successful delivery. - * - * XXX Set some global flag that disables all further restrictions. - * Triggering a "reject" or "hold" action after "discard" is silly. */ if (STREQUAL(value, "DISCARD", cmd_len)) { + if (state->action == 0) + return (SMTPD_CHECK_DUNNO); vstring_sprintf(error_text, "<%s>: %s %s", reply_name, reply_class, *cmd_text ? cmd_text : "triggers DISCARD action"); log_whatsup(state, "discard", STR(error_text)); -#ifndef TEST - rec_fprintf(state->dest->stream, REC_TYPE_FLGS, "%d", - CLEANUP_FLAG_DISCARD); -#endif + state->action->flags |= SMTPD_MSG_ACT_DISCARD; return (SMTPD_CHECK_OK); } @@ -1807,6 +1807,8 @@ static int check_table_result(SMTPD_STATE *state, const char *table, * change our mind, and reject/discard the message for other reasons. */ if (STREQUAL(value, "REDIRECT", cmd_len)) { + if (state->action == 0) + return (SMTPD_CHECK_DUNNO); if (strchr(cmd_text, '@') == 0) { msg_warn("access map %s entry \"%s\" requires user@domain target", table, datum); @@ -1815,9 +1817,10 @@ static int check_table_result(SMTPD_STATE *state, const char *table, vstring_sprintf(error_text, "<%s>: %s triggers REDIRECT %s", reply_name, reply_class, cmd_text); log_whatsup(state, "redirect", STR(error_text)); -#ifndef TEST - rec_fprintf(state->dest->stream, REC_TYPE_RDR, "%s", cmd_text); -#endif + state->action->flags |= SMTPD_MSG_ACT_REDIRECT; + if (state->action->redirect) + myfree(state->action->redirect); + state->action->redirect = mystrdup(cmd_text); return (SMTPD_CHECK_DUNNO); } } @@ -2646,6 +2649,9 @@ static int generic_checks(SMTPD_STATE *state, ARGV *restrictions, for (cpp = restrictions->argv; (name = *cpp) != 0; cpp++) { + if (state->action && SMTPD_MSG_ACT_FLAGS(state) & SMTPD_MSG_ACT_FINAL) + break; + if (msg_verbose) msg_info("%s: name=%s", myname, name); @@ -2966,6 +2972,20 @@ char *smtpd_check_client(SMTPD_STATE *state) */ state->defer_if_permit.active = 0; + /* + * With smtpd_delay_reject=yes, smtpd_client_restrictions is evaluated at + * RCPT TO time, and may be evaluated multiple times. Per-message side + * effects (HOLD, DISCARD, etc.) accumulate from one evaluation to the + * next, and may also depend on helo, sender or recipient information. + * + * With smtpd_delay_reject=no, smtpd_{client,helo}_restrictions are + * evaluated immediately, and HELO may be given multiple times. The same + * {client,helo} per-message side effects (HOLD, DISCARD, etc.) apply to + * multiple deliveries. Therefore, we need separate per-message side + * effect storage for client, helo, and for sender+recipients. + */ + state->action = &state->action_client; + /* * Apply restrictions in the order as specified. */ @@ -3020,6 +3040,20 @@ char *smtpd_check_helo(SMTPD_STATE *state, char *helohost) */ state->defer_if_permit.active = state->defer_if_permit_client; + /* + * With smtpd_delay_reject=yes, smtpd_helo_restrictions is evaluated at + * RCPT TO time, and may be evaluated multiple times. Per-message side + * effects (HOLD, DISCARD, etc.) accumulate from one evaluation to the + * next, and may also depend on sender or recipient information. + * + * With smtpd_delay_reject=no, smtpd_{client,helo}_restrictions are + * evaluated immediately, and HELO may be given multiple times. The same + * {client,helo} per-message side effects (HOLD, DISCARD, etc.) apply to + * multiple deliveries. Therefore, we need separate per-message side + * effect storage for client, helo, and for sender+recipients. + */ + state->action = &state->action_helo; + /* * Apply restrictions in the order as specified. */ @@ -3065,6 +3099,14 @@ char *smtpd_check_mail(SMTPD_STATE *state, char *sender) state->defer_if_permit.active = state->defer_if_permit_client | state->defer_if_permit_helo; + /* + * With smtpd_delay_reject=yes, smtpd_sender_restrictions is evaluated at + * RCPT TO time, and may be evaluated multiple times. Per-message side + * effects (HOLD, DISCARD, etc.) accumulate from one evaluation to the + * next, and may also depend on client, helo or recipient information. + */ + state->action = &state->action_mailrcpt; + /* * Apply restrictions in the order as specified. */ @@ -3152,6 +3194,13 @@ char *smtpd_check_rcpt(SMTPD_STATE *state, char *recipient) */ state->defer_if_permit.active = state->defer_if_permit_sender; + /* + * Per-message side effects (HOLD, DISCARD, etc.) accumulate from one + * recipient to the next, and may also depend on client, helo or sender + * information. + */ + state->action = &state->action_mailrcpt; + /* * Apply restrictions in the order as specified. */ @@ -3169,6 +3218,14 @@ char *smtpd_check_rcpt(SMTPD_STATE *state, char *recipient) status = smtpd_check_reject(state, state->defer_if_permit.class, "%s", STR(state->defer_if_permit.reason)); + /* + * If the "check_recipient_maps" restriction was not applied, and if mail + * is not being rejected or discarded, validate the recipient here. + */ + if (status == 0 && state->rcptmap_checked == 0 + && (SMTPD_MSG_ACT_FLAGS(state) & SMTPD_MSG_ACT_FINAL) == 0) + status = check_rcpt_maps(state, recipient); + SMTPD_CHECK_RCPT_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0); } @@ -3213,6 +3270,11 @@ char *smtpd_check_etrn(SMTPD_STATE *state, char *domain) state->defer_if_permit.active = state->defer_if_permit_client | state->defer_if_permit_helo; + /* + * HOLD, DISCARD, FILTER, etc. are meaningless. + */ + state->action = 0; + /* * Apply restrictions in the order as specified. */ @@ -3233,11 +3295,11 @@ char *smtpd_check_etrn(SMTPD_STATE *state, char *domain) SMTPD_CHECK_ETRN_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0); } -/* smtpd_check_rcptmap - permit if recipient address matches lookup table */ +/* smtpd_check_vrfy - permit if recipient address matches lookup table */ -char *smtpd_check_rcptmap(SMTPD_STATE *state, char *recipient) +char *smtpd_check_vrfy(SMTPD_STATE *state, char *recipient) { - char *myname = "smtpd_check_rcptmap"; + char *myname = "smtpd_check_vrfy"; int status; if (msg_verbose) @@ -3246,6 +3308,7 @@ char *smtpd_check_rcptmap(SMTPD_STATE *state, char *recipient) /* * Return here in case of serious trouble. */ + SMTPD_CHECK_RESET(); if ((status = setjmp(smtpd_check_buf)) == 0) status = check_rcpt_maps(state, recipient); @@ -3258,14 +3321,6 @@ static int check_rcpt_maps(SMTPD_STATE *state, const char *recipient) { const RESOLVE_REPLY *reply; - /* - * Duplicate suppression. There's an implicit check_recipient_maps - * restriction at the end of all recipient restrictions. - */ - if (state->rcptmap_checked == 1) - return (0); - state->rcptmap_checked = 1; - /* * Resolve the address. */ @@ -3622,6 +3677,7 @@ int var_local_rcpt_code; int var_relay_rcpt_code; int var_virt_mailbox_code; int var_virt_alias_code; +int var_show_unk_rcpt_table; static INT_TABLE int_table[] = { "msg_verbose", 0, &msg_verbose, @@ -3644,6 +3700,7 @@ static INT_TABLE int_table[] = { VAR_RELAY_RCPT_CODE, DEF_RELAY_RCPT_CODE, &var_relay_rcpt_code, VAR_VIRT_ALIAS_CODE, DEF_VIRT_ALIAS_CODE, &var_virt_alias_code, VAR_VIRT_MAILBOX_CODE, DEF_VIRT_MAILBOX_CODE, &var_virt_mailbox_code, + VAR_SHOW_UNK_RCPT_TABLE, DEF_SHOW_UNK_RCPT_TABLE, &var_show_unk_rcpt_table, 0, }; @@ -3902,6 +3959,8 @@ int main(int argc, char **argv) state.namaddr = concatenate(state.name, "[", state.addr, "]", (char *) 0); resp = smtpd_check_client(&state); + SMTPD_MSG_ACT_FREE(state.action_client); + SMTPD_MSG_ACT_ZERO(state.action_client); } break; @@ -4013,17 +4072,22 @@ int main(int argc, char **argv) if (strcasecmp(args->argv[0], "helo") == 0) { state.where = "HELO"; resp = smtpd_check_helo(&state, args->argv[1]); + SMTPD_MSG_ACT_FREE(state.action_helo); + SMTPD_MSG_ACT_ZERO(state.action_helo); UPDATE_STRING(state.helo_name, args->argv[1]); } else if (strcasecmp(args->argv[0], "mail") == 0) { state.where = "MAIL"; TRIM_ADDR(args->argv[1], addr); UPDATE_STRING(state.sender, addr); resp = smtpd_check_mail(&state, addr); + SMTPD_MSG_ACT_FREE(state.action_mailrcpt); + SMTPD_MSG_ACT_ZERO(state.action_mailrcpt); } else if (strcasecmp(args->argv[0], "rcpt") == 0) { state.where = "RCPT"; TRIM_ADDR(args->argv[1], addr); - (resp = smtpd_check_rcpt(&state, addr)) - || (resp = smtpd_check_rcptmap(&state, addr)); + resp = smtpd_check_rcpt(&state, addr); + SMTPD_MSG_ACT_FREE(state.action_mailrcpt); + SMTPD_MSG_ACT_ZERO(state.action_mailrcpt); } break; diff --git a/postfix/src/smtpd/smtpd_check.h b/postfix/src/smtpd/smtpd_check.h index 60348c40d..e05b80a7d 100644 --- a/postfix/src/smtpd/smtpd_check.h +++ b/postfix/src/smtpd/smtpd_check.h @@ -16,7 +16,7 @@ extern void smtpd_check_init(void); extern char *smtpd_check_client(SMTPD_STATE *); extern char *smtpd_check_helo(SMTPD_STATE *, char *); extern char *smtpd_check_mail(SMTPD_STATE *, char *); -extern char *smtpd_check_rcptmap(SMTPD_STATE *, char *); +extern char *smtpd_check_vrfy(SMTPD_STATE *, char *); extern char *smtpd_check_size(SMTPD_STATE *, off_t); extern char *smtpd_check_rcpt(SMTPD_STATE *, char *); extern char *smtpd_check_etrn(SMTPD_STATE *, char *); diff --git a/postfix/src/smtpd/smtpd_check.in4 b/postfix/src/smtpd/smtpd_check.in4 index 024bb77ff..abffa1ff0 100644 --- a/postfix/src/smtpd/smtpd_check.in4 +++ b/postfix/src/smtpd/smtpd_check.in4 @@ -11,6 +11,7 @@ sender_restrictions hash:./smtpd_check_access mail rejecttext@bad.domain mail filter@filter.domain mail filtertext@filter.domain +mail filtertexttext@filter.domain mail hold@hold.domain mail holdtext@hold.domain mail discard@hold.domain diff --git a/postfix/src/smtpd/smtpd_check.ref4 b/postfix/src/smtpd/smtpd_check.ref4 index e47600177..a73b5a1fc 100644 --- a/postfix/src/smtpd/smtpd_check.ref4 +++ b/postfix/src/smtpd/smtpd_check.ref4 @@ -14,10 +14,13 @@ OK ./smtpd_check: : reject: MAIL from localhost[127.0.0.1]: 554 : Sender address rejected: text; from= proto=SMTP 554 : Sender address rejected: text >>> mail filter@filter.domain -./smtpd_check: warning: access map hash:./smtpd_check_access entry filter@filter.domain has FILTER entry without value +./smtpd_check: warning: access map hash:./smtpd_check_access entry "filter@filter.domain" has FILTER entry without value OK >>> mail filtertext@filter.domain -./smtpd_check: : filter: MAIL from localhost[127.0.0.1]: : Sender address triggers FILTER text; from= proto=SMTP +./smtpd_check: warning: access map hash:./smtpd_check_access entry "filtertext@filter.domain" requires transport:destination +OK +>>> mail filtertexttext@filter.domain +./smtpd_check: : filter: MAIL from localhost[127.0.0.1]: : Sender address triggers FILTER text:text; from= proto=SMTP OK >>> mail hold@hold.domain ./smtpd_check: : hold: MAIL from localhost[127.0.0.1]: : Sender address triggers HOLD action; from= proto=SMTP diff --git a/postfix/src/smtpd/smtpd_check_access b/postfix/src/smtpd/smtpd_check_access index 41405ef4f..bfcb3d35f 100644 --- a/postfix/src/smtpd/smtpd_check_access +++ b/postfix/src/smtpd/smtpd_check_access @@ -50,6 +50,7 @@ dsn.rfc-ignorant.org $rbl_code client=$client rejecttext@bad.domain reject text filter@filter.domain filter filtertext@filter.domain filter text +filtertexttext@filter.domain filter text:text hold@hold.domain hold holdtext@hold.domain hold text discard@hold.domain discard diff --git a/postfix/src/smtpd/smtpd_state.c b/postfix/src/smtpd/smtpd_state.c index 53ef27580..d3694839f 100644 --- a/postfix/src/smtpd/smtpd_state.c +++ b/postfix/src/smtpd/smtpd_state.c @@ -92,6 +92,9 @@ void smtpd_state_init(SMTPD_STATE *state, VSTREAM *stream) state->recursion = 0; state->msg_size = 0; state->junk_cmds = 0; + SMTPD_MSG_ACT_ZERO(state->action_client); + SMTPD_MSG_ACT_ZERO(state->action_helo); + SMTPD_MSG_ACT_ZERO(state->action_mailrcpt); state->defer_if_permit_client = 0; state->defer_if_permit_helo = 0; state->defer_if_permit_sender = 0; @@ -136,6 +139,9 @@ void smtpd_state_reset(SMTPD_STATE *state) vstring_free(state->defer_if_reject.reason); if (state->expand_buf) vstring_free(state->expand_buf); + SMTPD_MSG_ACT_FREE(state->action_client); + SMTPD_MSG_ACT_FREE(state->action_helo); + SMTPD_MSG_ACT_FREE(state->action_mailrcpt); #ifdef USE_SASL_AUTH if (var_smtpd_sasl_enable)