diff --git a/postfix/HISTORY b/postfix/HISTORY index 67ed9c452..af0d3efe2 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -7796,6 +7796,22 @@ Apologies for any names omitted. Postfix now uses TIME.DEVICE_INODE.HOST. Files: local/maildir.c, virtual/maildir.c. +20030124 + + Cleanup: queue structures no longer overload queue name + and nexthop destination. Files: *qmgr/qmgr_message.c, + *qmgr/qmgr_queue.c, *qmgr/qmgr_deliver.c. + +20030125 + + Feature: "REDIRECT user@domain" action in access maps or + in header/body_checks causes mail to be sent to the specified + address instead of the intended recipient(s). I would never + recommend that people use this to redirect (bounced) SPAM + to the beneficiaries of an advertisement campaign. Files: + smtpd/smtpd_check.c, cleanup/cleanup_message.c, + *qmgr/qmgr_message.c. + Open problems: Med: make qmgr recipient bounce/defer activity asynchronous diff --git a/postfix/RELEASE_NOTES b/postfix/RELEASE_NOTES index e4e8cf33b..342194db4 100644 --- a/postfix/RELEASE_NOTES +++ b/postfix/RELEASE_NOTES @@ -22,6 +22,27 @@ snapshot release). Patches change the patchlevel and the release date. Snapshots change only the release date, unless they include the same bugfixes as a patch release. +Incompatible changes with Postfix snapshot 2.0.3-20030125 +========================================================= + +This release adds a new queue file record type for the address +specified in "REDIRECT user@domain" actions in access maps or +header/body_checks. + +Major changes with Postfix snapshot 2.0.3-20030125 +================================================== + +Code cleanup up of queue manager internals. Queue names are no +longer mixed up with the next-hop destination, and the address +resolver loop is now easier to understand. + +New "REDIRECT user@domain" action for access maps and header/body_checks +that overrides all the originally specified recipients of a message. +I would never recommend that people use this to redirect (bounced) +SPAM to the beneficiaries of an advertisement campaign. It would +have helped when someone began spamming the network with sender +addresses in one of my domains, and I got all the bounces. + Incompatible changes with Postfix snapshot 2.0.1-20030112 ========================================================= diff --git a/postfix/conf/access b/postfix/conf/access index 0b4a8ad63..1f4b38ba7 100644 --- a/postfix/conf/access +++ b/postfix/conf/access @@ -164,8 +164,17 @@ # about content filters is in the Postfix FIL- # TER_README file. # -# Note: this action currently affects all recipients -# of the message. +# Note: this action overrides the main.cf con- +# tent_filter setting, and currently affects all +# recipients of the message. +# +# REDIRECT user@domain +# After the message is queued, send the message to +# the specified address instead of the intended +# recipient(s). +# +# Note: this action overrides the FILTER action, and +# currently affects all recipients of the message. # # restriction... # Apply the named UCE restriction(s) (permit, reject, diff --git a/postfix/conf/sample-filter.cf b/postfix/conf/sample-filter.cf index 3ab0d02e4..115fdcb7f 100644 --- a/postfix/conf/sample-filter.cf +++ b/postfix/conf/sample-filter.cf @@ -38,6 +38,9 @@ # off in the second cleanup server. More info about content # filtering is in the Postfix FILTER_README file. This feature # overrides the main.cf content_filter setting. +# REDIRECT user@domain +# Send the message to the specified address instead of the +# intended recipient(s). This feature overrides the FILTER action. # # By default, these patterns apply the primary message headers, to # MIME headers, and to the headers of attached messages. With older @@ -82,6 +85,9 @@ header_checks = regexp:/etc/postfix/header_checks # off in the second cleanup server. More info about content # filtering is in the Postfix FILTER_README file. This feature # overrides the main.cf content_filter setting. +# REDIRECT user@domain +# Send the message to the specified address instead of the +# intended recipient(s). This feature overrides the FILTER action. # # By default, the same patterns are applied as for header_checks. # diff --git a/postfix/conf/sample-pcre-body.cf b/postfix/conf/sample-pcre-body.cf index 14e151225..0ece30ca0 100644 --- a/postfix/conf/sample-pcre-body.cf +++ b/postfix/conf/sample-pcre-body.cf @@ -51,7 +51,12 @@ # and after the filter, with header/body # checks turned off in the second cleanup # server. More information about content filters -# is in the Postfix FILTER_README file. +# is in the Postfix FILTER_README file. This feature +# overrides the main.cf content_filter setting. +# REDIRECT user@domain +# Send the message to the specified address instead +# of the intended recipient(s). This feature overrides +# the FILTER action. # # Substitution of sub-strings from the matched expression is # possible using the conventional perl syntax. The macros in the diff --git a/postfix/conf/sample-pcre-header.cf b/postfix/conf/sample-pcre-header.cf index 9dc72813a..58fde1226 100644 --- a/postfix/conf/sample-pcre-header.cf +++ b/postfix/conf/sample-pcre-header.cf @@ -52,7 +52,12 @@ # and after the filter, with header/body # checks turned off in the second cleanup # server. More information about content filters -# is in the Postfix FILTER_README file. +# is in the Postfix FILTER_README file. This feature +# overrides the main.cf content_filter setting. +# REDIRECT user@domain +# Send the message to the specified address instead +# of the intended recipient(s). This feature overrides +# the FILTER action. # # Substitution of sub-strings from the matched expression is # possible using the conventional perl syntax. The macros in the diff --git a/postfix/conf/sample-regexp-body.cf b/postfix/conf/sample-regexp-body.cf index e86c306fb..89357f397 100644 --- a/postfix/conf/sample-regexp-body.cf +++ b/postfix/conf/sample-regexp-body.cf @@ -43,7 +43,11 @@ # After the message is queued, send the entire message through # a content filter. This requires different cleanup servers # before and after the filter, with header/body checks turned -# off in the second cleanup server. +# off in the second cleanup server. This overrides the main.cf +# content filter setting. +# REDIRECT user@domain +# Send the message to the specified address instead of the +# intended recipient(s). This overrides the FILTER action. # Skip over base 64 encoded blocks. This saves lots of CPU cycles. # Expressions by Liviu Daia. Amended by Victor Duchovni. diff --git a/postfix/conf/sample-regexp-header.cf b/postfix/conf/sample-regexp-header.cf index bc29fdf6d..10e529009 100644 --- a/postfix/conf/sample-regexp-header.cf +++ b/postfix/conf/sample-regexp-header.cf @@ -43,7 +43,11 @@ # After the message is queued, send the entire message through # a content filter. This requires different cleanup servers # before and after the filter, with header/body checks turned -# off in the second cleanup server. +# off in the second cleanup server. This overrides the main.cf +# content filter setting. +# REDIRECT user@domain +# Send the message to the specified address instead of the +# intended recipient(s). This overrides the FILTER action. /^Subject: Make Money Fast/ REJECT /^To: friend@public.com/ REJECT diff --git a/postfix/conf/sample-smtpd.cf b/postfix/conf/sample-smtpd.cf index 73c3cd797..b6a6643d2 100644 --- a/postfix/conf/sample-smtpd.cf +++ b/postfix/conf/sample-smtpd.cf @@ -266,6 +266,8 @@ mynetworks_style = subnet # Discard the message if the result is DISCARD text... # Hold the message in the queue if the result is HOLD text... # Release mail "on hold" with the postsuper(1) command. +# Filter the message if the result is FILTER transport:nexthop. +# Redirect the message if the result is REDIRECT user@domain. # Permit the SMTP client if the result is OK or all numerical. # reject_rbl_client domain.tld: reject if the reversed client IP address # is listed in an A record under domain.tld. @@ -312,6 +314,8 @@ smtpd_helo_required = no # Discard the message if the result is DISCARD text... # Hold the message in the queue if the result is HOLD text... # Release mail "on hold" with the postsuper(1) command. +# Filter the message if the result is FILTER transport:nexthop. +# Redirect the message if the result is REDIRECT user@domain. # Permit the HELO command if the result is OK or all numerical. # reject: reject the request. Place this at the end of a restriction. # permit: permit the request. Place this at the end of a restriction. @@ -349,6 +353,8 @@ smtpd_helo_restrictions = # Discard the message if the result is DISCARD text... # Hold the message in the queue if the result is HOLD text... # Release mail "on hold" with the postsuper(1) command. +# Filter the message if the result is FILTER transport:nexthop. +# Redirect the message if the result is REDIRECT user@domain. # Permit the sender if the result is OK or all numerical. # reject_sender_login_mismatch: reject if $smtpd_sender_login_maps specifies # a MAIL FROM address owner, but the client is not (SASL) logged in as @@ -423,6 +429,8 @@ smtpd_sender_restrictions = # Discard the message if the result is DISCARD text... # Hold the message in the queue if the result is HOLD text... # Release mail "on hold" with the postsuper(1) command. +# Filter the message if the result is FILTER transport:nexthop. +# Redirect the message if the result is REDIRECT user@domain. # Permit the recipient if the result is OK or all numerical. # reject_non_fqdn_recipient: reject recipient address that is not in FQDN form # reject: reject the request. Place this at the end of a restriction. diff --git a/postfix/html/access.5.html b/postfix/html/access.5.html index 5158b390c..b841dfeab 100644 --- a/postfix/html/access.5.html +++ b/postfix/html/access.5.html @@ -165,8 +165,17 @@ ACCESS(5) ACCESS(5) about content filters is in the Postfix FIL- TER_README file. - Note: this action currently affects all recipients - of the message. + Note: this action overrides the main.cf con- + tent_filter setting, and currently affects all + recipients of the message. + + REDIRECT user@domain + After the message is queued, send the message to + the specified address instead of the intended + recipient(s). + + Note: this action overrides the FILTER action, and + currently affects all recipients of the message. restriction... Apply the named UCE restriction(s) (permit, reject, diff --git a/postfix/html/uce.html b/postfix/html/uce.html index eee2d501d..9ca51eb41 100644 --- a/postfix/html/uce.html +++ b/postfix/html/uce.html @@ -168,6 +168,11 @@ off in the second cleanup server. More details about content filtering are in the Postfix FILTER_README file. This feature overrides the main.cf content_filter setting. +
REDIRECT user@domain
+After the message is queued, send the message to the +specified address instead of the intended recipients. +overrides the FILTER action. +

@@ -267,6 +272,11 @@ off in the second cleanup server. More details about content filtering are in the Postfix FILTER_README file. This feature overrides the main.cf content_filter setting. +

REDIRECT user@domain
+After the message is queued, send the message to the +specified address instead of the intended recipients. +overrides the FILTER action. +

diff --git a/postfix/man/man5/access.5 b/postfix/man/man5/access.5 index 12528f659..fcdca96b4 100644 --- a/postfix/man/man5/access.5 +++ b/postfix/man/man5/access.5 @@ -152,7 +152,14 @@ After the message is queued, send the entire message through a content filter. More information about content filters is in the Postfix FILTER_README file. .sp -Note: this action currently affects all recipients of the message. +Note: this action overrides the \fBmain.cf content_filter\fR setting, +and currently affects all recipients of the message. +.IP "\fBREDIRECT \fIuser@domain\fR" +After the message is queued, send the message to the specified +address instead of the intended recipient(s). +.sp +Note: this action overrides the FILTER action, and currently affects +all recipients of the message. .IP \fIrestriction...\fR Apply the named UCE restriction(s) (\fBpermit\fR, \fBreject\fR, \fBreject_unauth_destination\fR, and so on). diff --git a/postfix/proto/access b/postfix/proto/access index eef4a4bf1..f1bbc99f1 100644 --- a/postfix/proto/access +++ b/postfix/proto/access @@ -63,7 +63,7 @@ # Note: lookup of the null sender address is not possible with # some types of lookup table. By default, Postfix uses \fB<>\fR # as the lookup key for such addresses. The value is specified with -# the \fBsmtpd_null_access_lookup_key\fR parameter in the Postfix +# the \fBsmtpd_null_access_lookup_key\fR parameter in the Postfix # \fBmain.cf\fR file. # ADDRESS EXTENSION # .fi @@ -100,8 +100,8 @@ # the numerical code and text. # .IP \fBREJECT\fR # .IP "\fBREJECT \fIoptional text...\fR -# Reject the address etc. that matches the pattern. Reply with -# \fI$reject_code optional text...\fR when the optional text is +# Reject the address etc. that matches the pattern. Reply with +# \fI$reject_code optional text...\fR when the optional text is # specified, otherwise reply with a generic error response message. # .IP \fBOK\fR # Accept the address etc. that matches the pattern. @@ -136,7 +136,14 @@ # a content filter. More information about content filters # is in the Postfix FILTER_README file. # .sp -# Note: this action currently affects all recipients of the message. +# Note: this action overrides the \fBmain.cf content_filter\fR setting, +# and currently affects all recipients of the message. +# .IP "\fBREDIRECT \fIuser@domain\fR" +# After the message is queued, send the message to the specified +# address instead of the intended recipient(s). +# .sp +# Note: this action overrides the FILTER action, and currently affects +# all recipients of the message. # .IP \fIrestriction...\fR # Apply the named UCE restriction(s) (\fBpermit\fR, \fBreject\fR, # \fBreject_unauth_destination\fR, and so on). diff --git a/postfix/src/cleanup/cleanup.h b/postfix/src/cleanup/cleanup.h index 8a4083d3c..88636ef12 100644 --- a/postfix/src/cleanup/cleanup.h +++ b/postfix/src/cleanup/cleanup.h @@ -67,6 +67,7 @@ typedef struct CLEANUP_STATE { MIME_STATE *mime_state; /* MIME state engine */ int mime_errs; /* MIME error flags */ char *filter; /* from header/body patterns */ + char *redirect; /* from header/body patterns */ } CLEANUP_STATE; /* diff --git a/postfix/src/cleanup/cleanup_extracted.c b/postfix/src/cleanup/cleanup_extracted.c index 19131642d..50b3d12ed 100644 --- a/postfix/src/cleanup/cleanup_extracted.c +++ b/postfix/src/cleanup/cleanup_extracted.c @@ -89,6 +89,14 @@ void cleanup_extracted(CLEANUP_STATE *state, int type, char *buf, int len) if (state->filter != 0) cleanup_out_string(state, REC_TYPE_FILT, state->filter); + /* + * Output the optional redirect target address before the mandatory + * Return-Receipt-To and Errors-To queue file records so that the queue + * manager will pick up the address before starting deliveries. + */ + if (state->redirect != 0) + cleanup_out_string(state, REC_TYPE_RDR, state->redirect); + /* * Older Postfix versions didn't emit encoding information, so this * record can only be optional. Putting this before the mandatory diff --git a/postfix/src/cleanup/cleanup_message.c b/postfix/src/cleanup/cleanup_message.c index b7283fde0..28ac89b10 100644 --- a/postfix/src/cleanup/cleanup_message.c +++ b/postfix/src/cleanup/cleanup_message.c @@ -322,8 +322,8 @@ static int cleanup_act(CLEANUP_STATE *state, char *context, const char *buf, } else { if (state->filter) myfree(state->filter); - /* XXX should log something? */ state->filter = mystrdup(optional_text); + cleanup_act_log(state, "filter", context, buf, optional_text); } return (CLEANUP_ACT_KEEP); } @@ -338,6 +338,19 @@ static int cleanup_act(CLEANUP_STATE *state, char *context, const char *buf, state->flags |= CLEANUP_FLAG_HOLD; return (CLEANUP_ACT_KEEP); } + if (STREQUAL(value, "REDIRECT", command_len)) { + if (strchr(optional_text, '@') == 0) { + msg_warn("bad REDIRECT target \"%s\" in %s map, need user@domain", + optional_text, map_class); + } else { + if (state->redirect) + myfree(state->redirect); + state->redirect = mystrdup(optional_text); + cleanup_act_log(state, "redirect", context, buf, optional_text); + state->flags &= ~CLEANUP_FLAG_FILTER; + } + return (CLEANUP_ACT_KEEP); + } if (*optional_text) msg_warn("unexpected text after command in %s map: %s", map_class, value); diff --git a/postfix/src/cleanup/cleanup_state.c b/postfix/src/cleanup/cleanup_state.c index 522db49c2..2f17fd050 100644 --- a/postfix/src/cleanup/cleanup_state.c +++ b/postfix/src/cleanup/cleanup_state.c @@ -92,6 +92,7 @@ CLEANUP_STATE *cleanup_state_alloc(void) state->mime_state = 0; state->mime_errs = 0; state->filter = 0; + state->redirect = 0; return (state); } @@ -131,5 +132,7 @@ void cleanup_state_free(CLEANUP_STATE *state) mime_state_free(state->mime_state); if (state->filter) myfree(state->filter); + if (state->redirect) + myfree(state->redirect); myfree((char *) state); } diff --git a/postfix/src/global/Makefile.in b/postfix/src/global/Makefile.in index c88e30bdf..267ee712b 100644 --- a/postfix/src/global/Makefile.in +++ b/postfix/src/global/Makefile.in @@ -1157,6 +1157,12 @@ resolve_local.o: mail_params.h resolve_local.o: own_inet_addr.h resolve_local.o: resolve_local.h resolve_local.o: match_parent_style.h +resover.o: resover.c +resover.o: ../../include/sys_defs.h +resover.o: ../../include/msg.h +resover.o: ../../include/vstring.h +resover.o: ../../include/vbuf.h +resover.o: ../../include/split_at.h rewrite_clnt.o: rewrite_clnt.c rewrite_clnt.o: ../../include/sys_defs.h rewrite_clnt.o: ../../include/msg.h diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 9eb5d9876..718590173 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -20,7 +20,7 @@ * 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 "20030124" +#define MAIL_RELEASE_DATE "20030125" #define VAR_MAIL_VERSION "mail_version" #define DEF_MAIL_VERSION "2.0.3-" MAIL_RELEASE_DATE diff --git a/postfix/src/global/rec_type.h b/postfix/src/global/rec_type.h index a14656915..0e54d7ee9 100644 --- a/postfix/src/global/rec_type.h +++ b/postfix/src/global/rec_type.h @@ -37,6 +37,7 @@ #define REC_TYPE_WARN 'W' /* warning message time */ #define REC_TYPE_ATTR 'A' /* named attribute for extensions */ +#define REC_TYPE_RDR '>' /* redirect target */ #define REC_TYPE_FLGS 'f' /* cleanup processing flags */ #define REC_TYPE_MESG 'M' /* start message records */ @@ -64,9 +65,9 @@ * allow for the presence of A records in the extracted segment, because it * can be requested to re-process already queued mail with `postsuper -r'. */ -#define REC_TYPE_ENVELOPE "MCTFILSDROWVA" +#define REC_TYPE_ENVELOPE "MCTFILSDROWVA>" #define REC_TYPE_CONTENT "XLN" -#define REC_TYPE_EXTRACT "EDROPreAFI" +#define REC_TYPE_EXTRACT "EDROPreAFI>" /* * The record at the beginning of the envelope segment specifies the message diff --git a/postfix/src/nqmgr/Makefile.in b/postfix/src/nqmgr/Makefile.in index 1b1b139b8..c9a01a6f3 100644 --- a/postfix/src/nqmgr/Makefile.in +++ b/postfix/src/nqmgr/Makefile.in @@ -199,6 +199,7 @@ qmgr_message.o: ../../include/verp_sender.h qmgr_message.o: ../../include/mail_proto.h qmgr_message.o: ../../include/iostuff.h qmgr_message.o: ../../include/attr.h +qmgr_message.o: ../../include/rewrite_clnt.h qmgr_message.o: ../../include/resolve_clnt.h qmgr_message.o: qmgr.h qmgr_message.o: ../../include/scan_dir.h diff --git a/postfix/src/nqmgr/qmgr.h b/postfix/src/nqmgr/qmgr.h index 54d91e588..65037f657 100644 --- a/postfix/src/nqmgr/qmgr.h +++ b/postfix/src/nqmgr/qmgr.h @@ -176,7 +176,8 @@ struct QMGR_ENTRY_LIST { }; struct QMGR_QUEUE { - char *name; /* domain name */ + char *name; /* domain name or address */ + char *nexthop; /* domain name */ int todo_refcount; /* queue entries (todo list) */ int busy_refcount; /* queue entries (busy list) */ int window; /* slow open algorithm */ @@ -194,7 +195,7 @@ struct QMGR_QUEUE { extern int qmgr_queue_count; -extern QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *, const char *); +extern QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *, const char *, const char *); extern void qmgr_queue_done(QMGR_QUEUE *); extern void qmgr_queue_throttle(QMGR_QUEUE *, const char *); extern void qmgr_queue_unthrottle(QMGR_QUEUE *); @@ -271,6 +272,7 @@ struct QMGR_MESSAGE { char *return_receipt; /* confirm receipt address */ char *filter_xport; /* filtering transport */ char *inspect_xport; /* inspecting transport */ + char *redirect_addr; /* info@spammer.tld */ long data_size; /* message content size */ long rcpt_offset; /* more recipients here */ long unread_offset; /* more unread recipients here */ diff --git a/postfix/src/nqmgr/qmgr_deliver.c b/postfix/src/nqmgr/qmgr_deliver.c index c26efff61..3f7e78bb5 100644 --- a/postfix/src/nqmgr/qmgr_deliver.c +++ b/postfix/src/nqmgr/qmgr_deliver.c @@ -129,11 +129,9 @@ static int qmgr_deliver_send_request(QMGR_ENTRY *entry, VSTREAM *stream) QMGR_RCPT_LIST list = entry->rcpt_list; QMGR_RCPT *recipient; QMGR_MESSAGE *message = entry->message; - char *cp; VSTRING *sender_buf = 0; char *sender; int flags; - char *nexthop; /* * If variable envelope return path is requested, change prefix+@origin @@ -149,28 +147,15 @@ static int qmgr_deliver_send_request(QMGR_ENTRY *entry, VSTREAM *stream) sender = vstring_str(sender_buf); } - /* - * With mail transports that accept only one recipient per delivery, the - * queue name is user@nexthop, so that we can implement per-recipient - * concurrency limits. However, the delivery agent protocol expects - * nexthop only, so we must strip off the recipient local part. - * - * XXX Should have separate fields for queue name and for destination, so - * that we don't have to make a special case for the error delivery agent - * (where nexthop is arbitrary text). See also: qmgr_message.c. - */ flags = message->tflags | (message->inspect_xport ? DEL_REQ_FLAG_BOUNCE : DEL_REQ_FLAG_DEFLT); - nexthop = strcmp(entry->queue->transport->name, MAIL_SERVICE_ERROR) != 0 - && (cp = strrchr(entry->queue->name, '@')) != 0 && cp[1] ? - cp + 1 : entry->queue->name; attr_print(stream, ATTR_FLAG_MORE, ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, flags, ATTR_TYPE_STR, MAIL_ATTR_QUEUE, message->queue_name, ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, message->queue_id, ATTR_TYPE_LONG, MAIL_ATTR_OFFSET, message->data_offset, ATTR_TYPE_LONG, MAIL_ATTR_SIZE, message->data_size, - ATTR_TYPE_STR, MAIL_ATTR_NEXTHOP, nexthop, + ATTR_TYPE_STR, MAIL_ATTR_NEXTHOP, entry->queue->nexthop, ATTR_TYPE_STR, MAIL_ATTR_ENCODING, message->encoding, ATTR_TYPE_STR, MAIL_ATTR_SENDER, sender, ATTR_TYPE_STR, MAIL_ATTR_ERRTO, message->errors_to, diff --git a/postfix/src/nqmgr/qmgr_entry.c b/postfix/src/nqmgr/qmgr_entry.c index 5032a4556..c26d29325 100644 --- a/postfix/src/nqmgr/qmgr_entry.c +++ b/postfix/src/nqmgr/qmgr_entry.c @@ -300,7 +300,7 @@ QMGR_ENTRY *qmgr_entry_create(QMGR_PEER *peer, QMGR_MESSAGE *message) && (now = event_time()) >= queue->clog_time_to_warn) { active_share = queue_length / (double) qmgr_message_count; msg_warn("mail for %s is using up %d of %d active queue entries", - queue->name, queue_length, qmgr_message_count); + queue->nexthop, queue_length, qmgr_message_count); if (active_share < 0.9) msg_warn("this may slow down other mail deliveries"); transport = queue->transport; @@ -314,7 +314,7 @@ QMGR_ENTRY *qmgr_entry_create(QMGR_PEER *peer, QMGR_MESSAGE *message) VAR_QMGR_ACT_LIMIT, var_qmgr_active_limit); else if (queue->peers.next != queue->peers.prev) msg_warn("you may need a separate master.cf transport for %s", - queue->name); + queue->nexthop); else { msg_warn("you may need to reduce %s connect and helo timeouts", transport->name); diff --git a/postfix/src/nqmgr/qmgr_message.c b/postfix/src/nqmgr/qmgr_message.c index 60d051c9f..3be89bfcf 100644 --- a/postfix/src/nqmgr/qmgr_message.c +++ b/postfix/src/nqmgr/qmgr_message.c @@ -124,6 +124,7 @@ /* Client stubs. */ +#include #include /* Application-specific. */ @@ -159,6 +160,7 @@ static QMGR_MESSAGE *qmgr_message_create(const char *queue_name, message->return_receipt = 0; message->filter_xport = 0; message->inspect_xport = 0; + message->redirect_addr = 0; message->data_size = 0; message->warn_offset = 0; message->warn_time = 0; @@ -385,6 +387,10 @@ static int qmgr_message_read(QMGR_MESSAGE *message) if (message->inspect_xport != 0) myfree(message->inspect_xport); message->inspect_xport = mystrdup(start); + } else if (rec_type == REC_TYPE_RDR) { + if (message->redirect_addr != 0) + myfree(message->redirect_addr); + message->redirect_addr = mystrdup(start); } else if (rec_type == REC_TYPE_FROM) { if (message->sender == 0) { message->sender = mystrdup(start); @@ -577,7 +583,7 @@ static int qmgr_message_sort_compare(const void *p1, const void *p2) return (result); /* - * Compare (already lowercased) next-hop hostname. + * Compare queue name (nexthop or recipient@nexthop). */ if ((result = strcmp(queue1->name, queue2->name)) != 0) return (result); @@ -619,6 +625,24 @@ static void qmgr_message_sort(QMGR_MESSAGE *message) } } +/* qmgr_resolve_one - resolve or skip one recipient */ + +static int qmgr_resolve_one(QMGR_MESSAGE *message, QMGR_RCPT *recipient, + const char *addr, RESOLVE_REPLY *reply) +{ + resolve_clnt_query(addr, reply); + if (reply->flags & RESOLVE_FLAG_FAIL) { + qmgr_defer_recipient(message, recipient, "address resolver failure"); + return (-1); + } else if (reply->flags & RESOLVE_FLAG_ERROR) { + qmgr_bounce_recipient(message, recipient, + "bad address syntax: \"%s\"", addr); + return (-1); + } else { + return (0); + } +} + /* qmgr_message_resolve - resolve recipients */ static void qmgr_message_resolve(QMGR_MESSAGE *message) @@ -629,6 +653,7 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message) QMGR_TRANSPORT *transport = 0; QMGR_QUEUE *queue = 0; RESOLVE_REPLY reply; + VSTRING *queue_name; char *at; char **cpp; char *nexthop; @@ -641,63 +666,75 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message) #define UPDATE(ptr,new) { myfree(ptr); ptr = mystrdup(new); } resolve_clnt_init(&reply); + queue_name = vstring_alloc(1); for (recipient = list.info; recipient < list.info + list.len; recipient++) { /* - * Resolve the destination to (transport, nexthop, address). The - * result address may differ from the one specified by the sender. + * Redirect overrides all else. But only once (per batch of + * recipients). For consistency with the remainder of Postfix, + * rewrite the address to canonical form before resolving it. */ - if (var_sender_routing == 0) { - resolve_clnt_query(recipient->address, &reply); - if (reply.flags & RESOLVE_FLAG_FAIL) { - qmgr_defer_recipient(message, recipient, - "address resolver failure"); + if (message->redirect_addr) { + if (recipient > list.info) { + recipient->queue = 0; continue; } - if (reply.flags & RESOLVE_FLAG_ERROR) { - qmgr_bounce_recipient(message, recipient, - "bad address syntax: \"%s\"", - recipient->address); + rewrite_clnt_internal(REWRITE_CANON, message->redirect_addr, + reply.recipient); + UPDATE(recipient->address, STR(reply.recipient)); + if (qmgr_resolve_one(message, recipient, + recipient->address, &reply) < 0) continue; - } - } else { - resolve_clnt_query(message->sender, &reply); - if (reply.flags & RESOLVE_FLAG_FAIL) { - qmgr_defer_recipient(message, recipient, - "address resolver failure"); - continue; - } - if (reply.flags & RESOLVE_FLAG_ERROR) { - qmgr_bounce_recipient(message, recipient, - "bad address syntax: \"%s\"", - message->sender); - continue; - } - vstring_strcpy(reply.recipient, recipient->address); + if (!STREQ(recipient->address, STR(reply.recipient))) + UPDATE(recipient->address, STR(reply.recipient)); } - if (message->filter_xport) { + + /* + * Content filtering overrides the address resolver. + */ + else if (message->filter_xport) { vstring_strcpy(reply.transport, message->filter_xport); if ((nexthop = split_at(STR(reply.transport), ':')) == 0 || *nexthop == 0) nexthop = var_myhostname; vstring_strcpy(reply.nexthop, nexthop); - } else { + vstring_strcpy(reply.recipient, recipient->address); + } + + /* + * Resolve the destination to (transport, nexthop, address). The + * result address may differ from the one specified by the sender. + */ + else if (var_sender_routing == 0) { + if (qmgr_resolve_one(message, recipient, + recipient->address, &reply) < 0) + continue; if (!STREQ(recipient->address, STR(reply.recipient))) UPDATE(recipient->address, STR(reply.recipient)); } + + /* + * XXX Sender-based routing does not work very well, because it has + * problems with sending bounces. + */ + else { + if (qmgr_resolve_one(message, recipient, + message->sender, &reply) < 0) + continue; + vstring_strcpy(reply.recipient, recipient->address); + } + + /* + * Bounce null recipients. This should never happen, but is most + * likely the result of a fault in a different program, so aborting + * the queue manager process does not help. + */ if (recipient->address[0] == 0) { qmgr_bounce_recipient(message, recipient, "null recipient address"); continue; } - /* - * XXX The nexthop destination is also used as lookup key for the - * per-destination queue. Fold the nexthop to lower case so that we - * don't have multiple queues for the same site. - */ - lowercase(STR(reply.nexthop)); - /* * Bounce recipient addresses that start with `-'. External commands * may misinterpret such addresses as command-line options. @@ -716,46 +753,6 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message) continue; } - /* - * Queues are identified by the transport name and by the next-hop - * hostname. When the delivery agent accepts only one recipient per - * delivery, give each recipient its own queue, so that deliveries to - * different recipients of the same message can happen in parallel. - * This also has the benefit that one bad recipient cannot interfere - * with deliveries to other recipients. XXX Should split the address - * on the recipient delimiter if one is defined, but doing a proper - * job requires knowledge of local aliases. Yuck! I don't want to - * duplicate delivery-agent specific knowledge in the queue manager. - * - * XXX The nexthop field is overloaded to serve as destination and as - * queue name. Should have separate fields for queue name and for - * destination, so that we don't have to make a special case for the - * error delivery agent (where nexthop is arbitrary text). See also: - * qmgr_deliver.c. - */ - at = strrchr(STR(reply.recipient), '@'); - len = (at ? (at - STR(reply.recipient)) : strlen(STR(reply.recipient))); - - /* - * Look up or instantiate the proper transport. We're working a - * little ahead, doing queue management stuff that used to be done - * way down. - */ - if (transport == 0 || !STREQ(transport->name, STR(reply.transport))) { - if ((transport = qmgr_transport_find(STR(reply.transport))) == 0) - transport = qmgr_transport_create(STR(reply.transport)); - queue = 0; - } - if (strcmp(transport->name, MAIL_SERVICE_ERROR) != 0 - && transport->recipient_limit == 1) { - VSTRING_SPACE(reply.nexthop, len + 2); - memmove(STR(reply.nexthop) + len + 1, STR(reply.nexthop), - LEN(reply.nexthop) + 1); - memcpy(STR(reply.nexthop), STR(reply.recipient), len); - STR(reply.nexthop)[len] = '@'; - lowercase(STR(reply.nexthop)); - } - /* * Discard mail to the local double bounce address here, so this * system can run without a local delivery agent. They'd still have @@ -766,6 +763,9 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message) * be directed to a general-purpose null delivery agent. */ if (reply.flags & RESOLVE_CLASS_LOCAL) { + at = strrchr(STR(reply.recipient), '@'); + len = (at ? (at - STR(reply.recipient)) + : strlen(STR(reply.recipient))); if (strncasecmp(STR(reply.recipient), var_double_bounce_sender, len) == 0 && !var_double_bounce_sender[len]) { @@ -798,16 +798,6 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message) } } - /* - * XXX Gross hack alert. We want to group recipients by transport and - * by next-hop hostname, in order to minimize the number of network - * transactions. However, it would be wasteful to have an in-memory - * resolver reply structure for each in-core recipient. Instead, we - * bind each recipient to an in-core queue instance which is needed - * anyway. That gives all information needed for recipient grouping. - */ -#if 0 - /* * Look up or instantiate the proper transport. */ @@ -816,7 +806,6 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message) transport = qmgr_transport_create(STR(reply.transport)); queue = 0; } -#endif /* * This transport is dead. Defer delivery to this recipient. @@ -826,13 +815,50 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message) continue; } + /* + * The nexthop destination provides the default name for the + * per-destination queue. When the delivery agent accepts only one + * recipient per delivery, give each recipient its own queue, so that + * deliveries to different recipients of the same message can happen + * in parallel, and so that we can enforce per-recipient concurrency + * limits and prevent one recipient from tying up all the delivery + * agent resources. We use recipient@nexthop as queue name rather + * than the actual recipient domain name, so that one recipient in + * multiple equivalent domains cannot evade the per-recipient + * concurrency limit. XXX Should split the address on the recipient + * delimiter if one is defined, but doing a proper job requires + * knowledge of local aliases. Yuck! I don't want to duplicate + * delivery-agent specific knowledge in the queue manager. + * + * Fold the result to lower case so that we don't have multiple queues + * for the same name. + * + * Important! All recipients in a queue must have the same nexthop + * value. It is OK to have multiple queues with the same nexthop + * value, but only when those queues are named after recipients. + */ + vstring_strcpy(queue_name, STR(reply.nexthop)); + if (strcmp(transport->name, MAIL_SERVICE_ERROR) != 0 + && transport->recipient_limit == 1) { + at = strrchr(STR(reply.recipient), '@'); + len = (at ? (at - STR(reply.recipient)) + : strlen(STR(reply.recipient))); + VSTRING_SPACE(queue_name, len + 2); + memmove(STR(queue_name) + len + 1, STR(queue_name), + LEN(queue_name) + 1); + memcpy(STR(queue_name), STR(reply.recipient), len); + STR(queue_name)[len] = '@'; + } + lowercase(STR(queue_name)); + /* * This transport is alive. Find or instantiate a queue for this * recipient. */ - if (queue == 0 || !STREQ(queue->name, STR(reply.nexthop))) { - if ((queue = qmgr_queue_find(transport, STR(reply.nexthop))) == 0) - queue = qmgr_queue_create(transport, STR(reply.nexthop)); + if (queue == 0 || !STREQ(queue->name, STR(queue_name))) { + if ((queue = qmgr_queue_find(transport, STR(queue_name))) == 0) + queue = qmgr_queue_create(transport, STR(queue_name), + STR(reply.nexthop)); } /* @@ -849,6 +875,7 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message) recipient->queue = queue; } resolve_clnt_free(&reply); + vstring_free(queue_name); } /* qmgr_message_assign - assign recipients to specific delivery requests */ @@ -973,6 +1000,8 @@ void qmgr_message_free(QMGR_MESSAGE *message) myfree(message->filter_xport); if (message->inspect_xport) myfree(message->inspect_xport); + if (message->redirect_addr) + myfree(message->redirect_addr); qmgr_rcpt_list_free(&message->rcpt_list); qmgr_message_count--; myfree((char *) message); diff --git a/postfix/src/nqmgr/qmgr_queue.c b/postfix/src/nqmgr/qmgr_queue.c index 9854011b6..b86034a57 100644 --- a/postfix/src/nqmgr/qmgr_queue.c +++ b/postfix/src/nqmgr/qmgr_queue.c @@ -8,16 +8,17 @@ /* /* int qmgr_queue_count; /* -/* QMGR_QUEUE *qmgr_queue_create(transport, site) +/* QMGR_QUEUE *qmgr_queue_create(transport, name, nexthop) /* QMGR_TRANSPORT *transport; -/* const char *site; +/* const char *name; +/* const char *nexthop; /* /* void qmgr_queue_done(queue) /* QMGR_QUEUE *queue; /* -/* QMGR_QUEUE *qmgr_queue_find(transport, site) +/* QMGR_QUEUE *qmgr_queue_find(transport, name) /* QMGR_TRANSPORT *transport; -/* const char *site; +/* const char *name; /* /* void qmgr_queue_throttle(queue, reason) /* QMGR_QUEUE *queue; @@ -34,7 +35,7 @@ /* qmgr_queue_count is a global counter for the total number /* of in-core queue structures. /* -/* qmgr_queue_create() creates an empty queue for the named +/* qmgr_queue_create() creates an empty named queue for the named /* transport and destination. The queue is given an initial /* concurrency limit as specified with the /* \fIinitial_destination_concurrency\fR configuration parameter, @@ -45,9 +46,8 @@ /* its entries have been taken care of. It is an error to dispose /* of a dead queue. /* -/* qmgr_queue_find() looks up the queue for the named destination -/* for the named transport. A null result means that the queue -/* was not found. +/* qmgr_queue_find() looks up the named queue for the named +/* transport. A null result means that the queue was not found. /* /* qmgr_queue_throttle() handles a delivery error, and decrements the /* concurrency limit for the destination. When the concurrency limit @@ -212,13 +212,15 @@ void qmgr_queue_done(QMGR_QUEUE *queue) QMGR_LIST_UNLINK(transport->queue_list, QMGR_QUEUE *, queue, peers); htable_delete(transport->queue_byname, queue->name, (void (*) (char *)) 0); myfree(queue->name); + myfree(queue->nexthop); qmgr_queue_count--; myfree((char *) queue); } /* qmgr_queue_create - create in-core queue for site */ -QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *transport, const char *site) +QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *transport, const char *name, + const char *nexthop) { QMGR_QUEUE *queue; @@ -229,7 +231,8 @@ QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *transport, const char *site) queue = (QMGR_QUEUE *) mymalloc(sizeof(QMGR_QUEUE)); qmgr_queue_count++; - queue->name = mystrdup(site); + queue->name = mystrdup(name); + queue->nexthop = mystrdup(nexthop); queue->todo_refcount = 0; queue->busy_refcount = 0; queue->transport = transport; @@ -240,13 +243,13 @@ QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *transport, const char *site) queue->clog_time_to_warn = 0; queue->blocker_tag = 0; QMGR_LIST_APPEND(transport->queue_list, queue, peers); - htable_enter(transport->queue_byname, site, (char *) queue); + htable_enter(transport->queue_byname, name, (char *) queue); return (queue); } -/* qmgr_queue_find - find in-core queue for site */ +/* qmgr_queue_find - find in-core named queue */ -QMGR_QUEUE *qmgr_queue_find(QMGR_TRANSPORT *transport, const char *site) +QMGR_QUEUE *qmgr_queue_find(QMGR_TRANSPORT *transport, const char *name) { - return ((QMGR_QUEUE *) htable_find(transport->queue_byname, site)); + return ((QMGR_QUEUE *) htable_find(transport->queue_byname, name)); } diff --git a/postfix/src/proxymap/Makefile.in b/postfix/src/proxymap/Makefile.in index 5ee7d59d8..91d20d838 100644 --- a/postfix/src/proxymap/Makefile.in +++ b/postfix/src/proxymap/Makefile.in @@ -61,6 +61,8 @@ proxymap.o: ../../include/msg.h proxymap.o: ../../include/mymalloc.h proxymap.o: ../../include/vstring.h proxymap.o: ../../include/vbuf.h +proxymap.o: ../../include/htable.h +proxymap.o: ../../include/stringops.h proxymap.o: ../../include/dict.h proxymap.o: ../../include/vstream.h proxymap.o: ../../include/argv.h diff --git a/postfix/src/proxymap/proxymap.c b/postfix/src/proxymap/proxymap.c index e81e5e852..0caed2983 100644 --- a/postfix/src/proxymap/proxymap.c +++ b/postfix/src/proxymap/proxymap.c @@ -149,14 +149,14 @@ char *var_local_rcpt_maps; char *var_virt_alias_maps; char *var_virt_alias_doms; -char *var_virt_mbox_maps; -char *var_virt_mbox_doms; +char *var_virt_mailbox_maps; +char *var_virt_mailbox_doms; char *var_relay_rcpt_maps; char *var_relay_domains; char *var_canonical_maps; char *var_send_canon_maps; char *var_rcpt_canon_maps; -char *var_relocatedmaps; +char *var_relocated_maps; char *var_transport_maps; char *var_proxy_read_maps; @@ -385,14 +385,14 @@ int main(int argc, char **argv) VAR_LOCAL_RCPT_MAPS, DEF_LOCAL_RCPT_MAPS, &var_local_rcpt_maps, 0, 0, VAR_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps, 0, 0, VAR_VIRT_ALIAS_DOMS, DEF_VIRT_ALIAS_DOMS, &var_virt_alias_doms, 0, 0, - VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mbox_maps, 0, 0, - VAR_VIRT_MAILBOX_DOMS, DEF_VIRT_MAILBOX_DOMS, &var_virt_mbox_doms, 0, 0, + VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps, 0, 0, + VAR_VIRT_MAILBOX_DOMS, DEF_VIRT_MAILBOX_DOMS, &var_virt_mailbox_doms, 0, 0, VAR_RELAY_RCPT_MAPS, DEF_RELAY_RCPT_MAPS, &var_relay_rcpt_maps, 0, 0, VAR_RELAY_DOMAINS, DEF_RELAY_DOMAINS, &var_relay_domains, 0, 0, VAR_CANONICAL_MAPS, DEF_CANONICAL_MAPS, &var_canonical_maps, 0, 0, VAR_SEND_CANON_MAPS, DEF_SEND_CANON_MAPS, &var_send_canon_maps, 0, 0, VAR_RCPT_CANON_MAPS, DEF_RCPT_CANON_MAPS, &var_rcpt_canon_maps, 0, 0, - VAR_RELOCATED_MAPS, DEF_RELOCATED_MAPS, &var_relocatedmaps, 0, 0, + VAR_RELOCATED_MAPS, DEF_RELOCATED_MAPS, &var_relocated_maps, 0, 0, VAR_TRANSPORT_MAPS, DEF_TRANSPORT_MAPS, &var_transport_maps, 0, 0, VAR_PROXY_READ_MAPS, DEF_PROXY_READ_MAPS, &var_proxy_read_maps, 0, 0, 0, diff --git a/postfix/src/qmgr/Makefile.in b/postfix/src/qmgr/Makefile.in index e128810a7..f4a1419c8 100644 --- a/postfix/src/qmgr/Makefile.in +++ b/postfix/src/qmgr/Makefile.in @@ -186,6 +186,7 @@ qmgr_message.o: ../../include/verp_sender.h qmgr_message.o: ../../include/mail_proto.h qmgr_message.o: ../../include/iostuff.h qmgr_message.o: ../../include/attr.h +qmgr_message.o: ../../include/rewrite_clnt.h qmgr_message.o: ../../include/resolve_clnt.h qmgr_message.o: qmgr.h qmgr_message.o: ../../include/scan_dir.h diff --git a/postfix/src/qmgr/qmgr.h b/postfix/src/qmgr/qmgr.h index e553e1793..67a1adfce 100644 --- a/postfix/src/qmgr/qmgr.h +++ b/postfix/src/qmgr/qmgr.h @@ -140,7 +140,8 @@ struct QMGR_ENTRY_LIST { }; struct QMGR_QUEUE { - char *name; /* domain name */ + char *name; /* domain name or address */ + char *nexthop; /* domain name */ int todo_refcount; /* queue entries (todo list) */ int busy_refcount; /* queue entries (busy list) */ int window; /* slow open algorithm */ @@ -157,7 +158,7 @@ struct QMGR_QUEUE { extern int qmgr_queue_count; -extern QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *, const char *); +extern QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *, const char *, const char *); extern QMGR_QUEUE *qmgr_queue_select(QMGR_TRANSPORT *); extern void qmgr_queue_done(QMGR_QUEUE *); extern void qmgr_queue_throttle(QMGR_QUEUE *, const char *); @@ -231,6 +232,7 @@ struct QMGR_MESSAGE { char *return_receipt; /* confirm receipt address */ char *filter_xport; /* filtering transport */ char *inspect_xport; /* inspecting transport */ + char *redirect_addr; /* info@spammer.tld */ long data_size; /* message content size */ long rcpt_offset; /* more recipients here */ QMGR_RCPT_LIST rcpt_list; /* complete addresses */ diff --git a/postfix/src/qmgr/qmgr_deliver.c b/postfix/src/qmgr/qmgr_deliver.c index dbdcfd75d..c1e6ab749 100644 --- a/postfix/src/qmgr/qmgr_deliver.c +++ b/postfix/src/qmgr/qmgr_deliver.c @@ -2,7 +2,7 @@ /* NAME /* qmgr_deliver 3 /* SUMMARY -/* deliver one pe-site queue entry to that site +/* deliver one per-site queue entry to that site /* SYNOPSIS /* #include "qmgr.h" /* @@ -124,11 +124,9 @@ static int qmgr_deliver_send_request(QMGR_ENTRY *entry, VSTREAM *stream) QMGR_RCPT_LIST list = entry->rcpt_list; QMGR_RCPT *recipient; QMGR_MESSAGE *message = entry->message; - char *cp; VSTRING *sender_buf = 0; char *sender; int flags; - char *nexthop; /* * If variable envelope return path is requested, change prefix+@origin @@ -144,28 +142,15 @@ static int qmgr_deliver_send_request(QMGR_ENTRY *entry, VSTREAM *stream) sender = vstring_str(sender_buf); } - /* - * With mail transports that accept only one recipient per delivery, the - * queue name is user@nexthop, so that we can implement per-recipient - * concurrency limits. However, the delivery agent protocol expects - * nexthop only, so we must strip off the recipient local part. - * - * XXX Should have separate fields for queue name and for destination, so - * that we don't have to make a special case for the error delivery agent - * (where nexthop is arbitrary text). See also: qmgr_message.c. - */ flags = message->tflags | (message->inspect_xport ? DEL_REQ_FLAG_BOUNCE : DEL_REQ_FLAG_DEFLT); - nexthop = strcmp(entry->queue->transport->name, MAIL_SERVICE_ERROR) != 0 - && (cp = strrchr(entry->queue->name, '@')) != 0 && cp[1] ? - cp + 1 : entry->queue->name; attr_print(stream, ATTR_FLAG_MORE, ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, flags, ATTR_TYPE_STR, MAIL_ATTR_QUEUE, message->queue_name, ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, message->queue_id, ATTR_TYPE_LONG, MAIL_ATTR_OFFSET, message->data_offset, ATTR_TYPE_LONG, MAIL_ATTR_SIZE, message->data_size, - ATTR_TYPE_STR, MAIL_ATTR_NEXTHOP, nexthop, + ATTR_TYPE_STR, MAIL_ATTR_NEXTHOP, entry->queue->nexthop, ATTR_TYPE_STR, MAIL_ATTR_ENCODING, message->encoding, ATTR_TYPE_STR, MAIL_ATTR_SENDER, sender, ATTR_TYPE_STR, MAIL_ATTR_ERRTO, message->errors_to, diff --git a/postfix/src/qmgr/qmgr_entry.c b/postfix/src/qmgr/qmgr_entry.c index 57de8ebd6..3d3f9f461 100644 --- a/postfix/src/qmgr/qmgr_entry.c +++ b/postfix/src/qmgr/qmgr_entry.c @@ -239,7 +239,7 @@ QMGR_ENTRY *qmgr_entry_create(QMGR_QUEUE *queue, QMGR_MESSAGE *message) && (now = event_time()) >= queue->clog_time_to_warn) { active_share = queue_length / (double) qmgr_message_count; msg_warn("mail for %s is using up %d of %d active queue entries", - queue->name, queue_length, qmgr_message_count); + queue->nexthop, queue_length, qmgr_message_count); if (active_share < 0.9) msg_warn("this may slow down other mail deliveries"); transport = queue->transport; @@ -253,7 +253,7 @@ QMGR_ENTRY *qmgr_entry_create(QMGR_QUEUE *queue, QMGR_MESSAGE *message) VAR_QMGR_ACT_LIMIT, var_qmgr_active_limit); else if (queue->peers.next != queue->peers.prev) msg_warn("you may need a separate master.cf transport for %s", - queue->name); + queue->nexthop); else { msg_warn("you may need to reduce %s connect and helo timeouts", transport->name); diff --git a/postfix/src/qmgr/qmgr_message.c b/postfix/src/qmgr/qmgr_message.c index 15919b3fa..f3bc58e49 100644 --- a/postfix/src/qmgr/qmgr_message.c +++ b/postfix/src/qmgr/qmgr_message.c @@ -115,6 +115,7 @@ /* Client stubs. */ +#include #include /* Application-specific. */ @@ -149,6 +150,7 @@ static QMGR_MESSAGE *qmgr_message_create(const char *queue_name, message->return_receipt = 0; message->filter_xport = 0; message->inspect_xport = 0; + message->redirect_addr = 0; message->data_size = 0; message->warn_offset = 0; message->warn_time = 0; @@ -266,6 +268,10 @@ static int qmgr_message_read(QMGR_MESSAGE *message) if (message->inspect_xport != 0) myfree(message->inspect_xport); message->inspect_xport = mystrdup(start); + } else if (rec_type == REC_TYPE_RDR) { + if (message->redirect_addr != 0) + myfree(message->redirect_addr); + message->redirect_addr = mystrdup(start); } else if (rec_type == REC_TYPE_FROM) { if (message->sender == 0) { message->sender = mystrdup(start); @@ -452,14 +458,14 @@ static int qmgr_message_sort_compare(const void *p1, const void *p2) /* * Compare message transport. */ - if ((result = strcasecmp(queue1->transport->name, - queue2->transport->name)) != 0) + if ((result = strcmp(queue1->transport->name, + queue2->transport->name)) != 0) return (result); /* - * Compare next-hop hostname. + * Compare queue name (nexthop or recipient@nexthop). */ - if ((result = strcasecmp(queue1->name, queue2->name)) != 0) + if ((result = strcmp(queue1->name, queue2->name)) != 0) return (result); } @@ -499,6 +505,24 @@ static void qmgr_message_sort(QMGR_MESSAGE *message) } } +/* qmgr_resolve_one - resolve or skip one recipient */ + +static int qmgr_resolve_one(QMGR_MESSAGE *message, QMGR_RCPT *recipient, + const char *addr, RESOLVE_REPLY *reply) +{ + resolve_clnt_query(addr, reply); + if (reply->flags & RESOLVE_FLAG_FAIL) { + qmgr_defer_recipient(message, recipient, "address resolver failure"); + return (-1); + } else if (reply->flags & RESOLVE_FLAG_ERROR) { + qmgr_bounce_recipient(message, recipient, + "bad address syntax: \"%s\"", addr); + return (-1); + } else { + return (0); + } +} + /* qmgr_message_resolve - resolve recipients */ static void qmgr_message_resolve(QMGR_MESSAGE *message) @@ -509,6 +533,7 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message) QMGR_TRANSPORT *transport = 0; QMGR_QUEUE *queue = 0; RESOLVE_REPLY reply; + VSTRING *queue_name; char *at; char **cpp; char *nexthop; @@ -521,63 +546,75 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message) #define UPDATE(ptr,new) { myfree(ptr); ptr = mystrdup(new); } resolve_clnt_init(&reply); + queue_name = vstring_alloc(1); for (recipient = list.info; recipient < list.info + list.len; recipient++) { /* - * Resolve the destination to (transport, nexthop, address). The - * result address may differ from the one specified by the sender. + * Redirect overrides all else. But only once (per batch of + * recipients). For consistency with the remainder of Postfix, + * rewrite the address to canonical form before resolving it. */ - if (var_sender_routing == 0) { - resolve_clnt_query(recipient->address, &reply); - if (reply.flags & RESOLVE_FLAG_FAIL) { - qmgr_defer_recipient(message, recipient, - "address resolver failure"); + if (message->redirect_addr) { + if (recipient > list.info) { + recipient->queue = 0; continue; } - if (reply.flags & RESOLVE_FLAG_ERROR) { - qmgr_bounce_recipient(message, recipient, - "bad address syntax: \"%s\"", - recipient->address); + rewrite_clnt_internal(REWRITE_CANON, message->redirect_addr, + reply.recipient); + UPDATE(recipient->address, STR(reply.recipient)); + if (qmgr_resolve_one(message, recipient, + recipient->address, &reply) < 0) continue; - } - } else { - resolve_clnt_query(message->sender, &reply); - if (reply.flags & RESOLVE_FLAG_FAIL) { - qmgr_defer_recipient(message, recipient, - "address resolver failure"); - continue; - } - if (reply.flags & RESOLVE_FLAG_ERROR) { - qmgr_bounce_recipient(message, recipient, - "bad address syntax: \"%s\"", - message->sender); - continue; - } - vstring_strcpy(reply.recipient, recipient->address); + if (!STREQ(recipient->address, STR(reply.recipient))) + UPDATE(recipient->address, STR(reply.recipient)); } - if (message->filter_xport) { + + /* + * Content filtering overrides the address resolver. + */ + else if (message->filter_xport) { vstring_strcpy(reply.transport, message->filter_xport); if ((nexthop = split_at(STR(reply.transport), ':')) == 0 || *nexthop == 0) nexthop = var_myhostname; vstring_strcpy(reply.nexthop, nexthop); - } else { + vstring_strcpy(reply.recipient, recipient->address); + } + + /* + * Resolve the destination to (transport, nexthop, address). The + * result address may differ from the one specified by the sender. + */ + else if (var_sender_routing == 0) { + if (qmgr_resolve_one(message, recipient, + recipient->address, &reply) < 0) + continue; if (!STREQ(recipient->address, STR(reply.recipient))) UPDATE(recipient->address, STR(reply.recipient)); } + + /* + * XXX Sender-based routing does not work very well, because it has + * problems with sending bounces. + */ + else { + if (qmgr_resolve_one(message, recipient, + message->sender, &reply) < 0) + continue; + vstring_strcpy(reply.recipient, recipient->address); + } + + /* + * Bounce null recipients. This should never happen, but is most + * likely the result of a fault in a different program, so aborting + * the queue manager process does not help. + */ if (recipient->address[0] == 0) { qmgr_bounce_recipient(message, recipient, "null recipient address"); continue; } - /* - * XXX The nexthop destination is also used as lookup key for the - * per-destination queue. Fold the nexthop to lower case so that we - * don't have multiple queues for the same site. - */ - lowercase(STR(reply.nexthop)); - /* * Bounce recipient addresses that start with `-'. External commands * may misinterpret such addresses as command-line options. @@ -596,46 +633,6 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message) continue; } - /* - * Queues are identified by the transport name and by the next-hop - * hostname. When the delivery agent accepts only one recipient per - * delivery, give each recipient its own queue, so that deliveries to - * different recipients of the same message can happen in parallel. - * This also has the benefit that one bad recipient cannot interfere - * with deliveries to other recipients. XXX Should split the address - * on the recipient delimiter if one is defined, but doing a proper - * job requires knowledge of local aliases. Yuck! I don't want to - * duplicate delivery-agent specific knowledge in the queue manager. - * - * XXX The nexthop field is overloaded to serve as destination and as - * queue name. Should have separate fields for queue name and for - * destination, so that we don't have to make a special case for the - * error delivery agent (where nexthop is arbitrary text). See also: - * qmgr_deliver.c. - */ - at = strrchr(STR(reply.recipient), '@'); - len = (at ? (at - STR(reply.recipient)) : strlen(STR(reply.recipient))); - - /* - * Look up or instantiate the proper transport. We're working a - * little ahead, doing queue management stuff that used to be done - * way down. - */ - if (transport == 0 || !STREQ(transport->name, STR(reply.transport))) { - if ((transport = qmgr_transport_find(STR(reply.transport))) == 0) - transport = qmgr_transport_create(STR(reply.transport)); - queue = 0; - } - if (strcmp(transport->name, MAIL_SERVICE_ERROR) != 0 - && transport->recipient_limit == 1) { - VSTRING_SPACE(reply.nexthop, len + 2); - memmove(STR(reply.nexthop) + len + 1, STR(reply.nexthop), - LEN(reply.nexthop) + 1); - memcpy(STR(reply.nexthop), STR(reply.recipient), len); - STR(reply.nexthop)[len] = '@'; - lowercase(STR(reply.nexthop)); - } - /* * Discard mail to the local double bounce address here, so this * system can run without a local delivery agent. They'd still have @@ -646,6 +643,9 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message) * be directed to a general-purpose null delivery agent. */ if (reply.flags & RESOLVE_CLASS_LOCAL) { + at = strrchr(STR(reply.recipient), '@'); + len = (at ? (at - STR(reply.recipient)) + : strlen(STR(reply.recipient))); if (strncasecmp(STR(reply.recipient), var_double_bounce_sender, len) == 0 && !var_double_bounce_sender[len]) { @@ -678,16 +678,6 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message) } } - /* - * XXX Gross hack alert. We want to group recipients by transport and - * by next-hop hostname, in order to minimize the number of network - * transactions. However, it would be wasteful to have an in-memory - * resolver reply structure for each in-core recipient. Instead, we - * bind each recipient to an in-core queue instance which is needed - * anyway. That gives all information needed for recipient grouping. - */ -#if 0 - /* * Look up or instantiate the proper transport. */ @@ -696,7 +686,6 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message) transport = qmgr_transport_create(STR(reply.transport)); queue = 0; } -#endif /* * This transport is dead. Defer delivery to this recipient. @@ -706,13 +695,50 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message) continue; } + /* + * The nexthop destination provides the default name for the + * per-destination queue. When the delivery agent accepts only one + * recipient per delivery, give each recipient its own queue, so that + * deliveries to different recipients of the same message can happen + * in parallel, and so that we can enforce per-recipient concurrency + * limits and prevent one recipient from tying up all the delivery + * agent resources. We use recipient@nexthop as queue name rather + * than the actual recipient domain name, so that one recipient in + * multiple equivalent domains cannot evade the per-recipient + * concurrency limit. XXX Should split the address on the recipient + * delimiter if one is defined, but doing a proper job requires + * knowledge of local aliases. Yuck! I don't want to duplicate + * delivery-agent specific knowledge in the queue manager. + * + * Fold the result to lower case so that we don't have multiple queues + * for the same name. + * + * Important! All recipients in a queue must have the same nexthop + * value. It is OK to have multiple queues with the same nexthop + * value, but only when those queues are named after recipients. + */ + vstring_strcpy(queue_name, STR(reply.nexthop)); + if (strcmp(transport->name, MAIL_SERVICE_ERROR) != 0 + && transport->recipient_limit == 1) { + at = strrchr(STR(reply.recipient), '@'); + len = (at ? (at - STR(reply.recipient)) + : strlen(STR(reply.recipient))); + VSTRING_SPACE(queue_name, len + 2); + memmove(STR(queue_name) + len + 1, STR(queue_name), + LEN(queue_name) + 1); + memcpy(STR(queue_name), STR(reply.recipient), len); + STR(queue_name)[len] = '@'; + } + lowercase(STR(queue_name)); + /* * This transport is alive. Find or instantiate a queue for this * recipient. */ - if (queue == 0 || !STREQ(queue->name, STR(reply.nexthop))) { - if ((queue = qmgr_queue_find(transport, STR(reply.nexthop))) == 0) - queue = qmgr_queue_create(transport, STR(reply.nexthop)); + if (queue == 0 || !STREQ(queue->name, STR(queue_name))) { + if ((queue = qmgr_queue_find(transport, STR(queue_name))) == 0) + queue = qmgr_queue_create(transport, STR(queue_name), + STR(reply.nexthop)); } /* @@ -729,6 +755,7 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message) recipient->queue = queue; } resolve_clnt_free(&reply); + vstring_free(queue_name); } /* qmgr_message_assign - assign recipients to specific delivery requests */ @@ -791,6 +818,8 @@ void qmgr_message_free(QMGR_MESSAGE *message) myfree(message->filter_xport); if (message->inspect_xport) myfree(message->inspect_xport); + if (message->redirect_addr) + myfree(message->redirect_addr); qmgr_rcpt_list_free(&message->rcpt_list); qmgr_message_count--; myfree((char *) message); diff --git a/postfix/src/qmgr/qmgr_queue.c b/postfix/src/qmgr/qmgr_queue.c index 7d4214f70..3c88b7eda 100644 --- a/postfix/src/qmgr/qmgr_queue.c +++ b/postfix/src/qmgr/qmgr_queue.c @@ -8,16 +8,17 @@ /* /* int qmgr_queue_count; /* -/* QMGR_QUEUE *qmgr_queue_create(transport, site) +/* QMGR_QUEUE *qmgr_queue_create(transport, name, nexthop) /* QMGR_TRANSPORT *transport; -/* const char *site; +/* const char *name; +/* const char *nexthop; /* /* void qmgr_queue_done(queue) /* QMGR_QUEUE *queue; /* -/* QMGR_QUEUE *qmgr_queue_find(transport, site) +/* QMGR_QUEUE *qmgr_queue_find(transport, name) /* QMGR_TRANSPORT *transport; -/* const char *site; +/* const char *name; /* /* QMGR_QUEUE *qmgr_queue_select(transport) /* QMGR_TRANSPORT *transport; @@ -37,7 +38,7 @@ /* qmgr_queue_count is a global counter for the total number /* of in-core queue structures. /* -/* qmgr_queue_create() creates an empty queue for the named +/* qmgr_queue_create() creates an empty named queue for the named /* transport and destination. The queue is given an initial /* concurrency limit as specified with the /* \fIinitial_destination_concurrency\fR configuration parameter, @@ -48,9 +49,8 @@ /* its entries have been taken care of. It is an error to dispose /* of a dead queue. /* -/* qmgr_queue_find() looks up the queue for the named destination -/* for the named transport. A null result means that the queue -/* was not found. +/* qmgr_queue_find() looks up the named queue for the named +/* transport. A null result means that the queue was not found. /* /* qmgr_queue_select() uses a round-robin strategy to select /* from the named transport one per-destination queue with a @@ -235,13 +235,15 @@ void qmgr_queue_done(QMGR_QUEUE *queue) QMGR_LIST_UNLINK(transport->queue_list, QMGR_QUEUE *, queue); htable_delete(transport->queue_byname, queue->name, (void (*) (char *)) 0); myfree(queue->name); + myfree(queue->nexthop); qmgr_queue_count--; myfree((char *) queue); } /* qmgr_queue_create - create in-core queue for site */ -QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *transport, const char *site) +QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *transport, const char *name, + const char *nexthop) { QMGR_QUEUE *queue; @@ -252,7 +254,8 @@ QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *transport, const char *site) queue = (QMGR_QUEUE *) mymalloc(sizeof(QMGR_QUEUE)); qmgr_queue_count++; - queue->name = mystrdup(site); + queue->name = mystrdup(name); + queue->nexthop = mystrdup(nexthop); queue->todo_refcount = 0; queue->busy_refcount = 0; queue->transport = transport; @@ -262,13 +265,13 @@ QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *transport, const char *site) queue->reason = 0; queue->clog_time_to_warn = 0; QMGR_LIST_PREPEND(transport->queue_list, queue); - htable_enter(transport->queue_byname, site, (char *) queue); + htable_enter(transport->queue_byname, name, (char *) queue); return (queue); } -/* qmgr_queue_find - find in-core queue for site */ +/* qmgr_queue_find - find in-core named queue */ -QMGR_QUEUE *qmgr_queue_find(QMGR_TRANSPORT *transport, const char *site) +QMGR_QUEUE *qmgr_queue_find(QMGR_TRANSPORT *transport, const char *name) { - return ((QMGR_QUEUE *) htable_find(transport->queue_byname, site)); + return ((QMGR_QUEUE *) htable_find(transport->queue_byname, name)); } diff --git a/postfix/src/smtpd/Makefile.in b/postfix/src/smtpd/Makefile.in index 7550d3d73..59772ff3a 100644 --- a/postfix/src/smtpd/Makefile.in +++ b/postfix/src/smtpd/Makefile.in @@ -221,6 +221,7 @@ smtpd_check.o: ../../include/rec_type.h smtpd_check.o: ../../include/mail_proto.h smtpd_check.o: ../../include/iostuff.h smtpd_check.o: ../../include/attr.h +smtpd_check.o: ../../include/mail_addr.h smtpd_check.o: ../../include/verify_clnt.h smtpd_check.o: ../../include/deliver_request.h smtpd_check.o: ../../include/recipient_list.h diff --git a/postfix/src/smtpd/smtpd_check.c b/postfix/src/smtpd/smtpd_check.c index 49b328cf5..1db47f3d9 100644 --- a/postfix/src/smtpd/smtpd_check.c +++ b/postfix/src/smtpd/smtpd_check.c @@ -1752,11 +1752,11 @@ static int check_table_result(SMTPD_STATE *state, const char *table, */ if (STREQUAL(value, "FILTER", cmd_len)) { if (*cmd_text == 0) { - msg_warn("access map %s entry %s has FILTER entry without value", + msg_warn("access map %s entry \"%s\" has FILTER entry without value", table, datum); return (SMTPD_CHECK_DUNNO); } else if (strchr(cmd_text, ':') == 0) { - msg_warn("access map %s entry %s requires transport:destination", + msg_warn("access map %s entry \"%s\" requires transport:destination", table, datum); return (SMTPD_CHECK_DUNNO); } else { @@ -1802,6 +1802,26 @@ static int check_table_result(SMTPD_STATE *state, const char *table, return (SMTPD_CHECK_OK); } + /* + * REDIRECT means deliver to designated recipient. But we may still + * change our mind, and reject/discard the message for other reasons. + */ + if (STREQUAL(value, "REDIRECT", cmd_len)) { + if (strchr(cmd_text, '@') == 0) { + msg_warn("access map %s entry \"%s\" requires user@domain target", + table, datum); + return (SMTPD_CHECK_DUNNO); + } else { + 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 + return (SMTPD_CHECK_DUNNO); + } + } + /* * All-numeric result probably means OK - some out-of-band authentication * mechanism uses this as time stamp. @@ -3309,13 +3329,13 @@ static int check_rcpt_maps(SMTPD_STATE *state, const char *recipient) if ((reply->flags & RESOLVE_CLASS_LOCAL) && *var_local_rcpt_maps - /* Generated by bounce, absorbed by qmgr. */ + /* Generated by bounce, absorbed by qmgr. */ && !MATCH_LEFT(var_double_bounce_sender, CONST_STR(reply->recipient), strlen(var_double_bounce_sender)) - /* Absorbed by qmgr. */ + /* Absorbed by qmgr. */ && !MATCH_LEFT(MAIL_ADDR_POSTMASTER, CONST_STR(reply->recipient), strlen(MAIL_ADDR_POSTMASTER)) - /* Generated by bounce. */ + /* Generated by bounce. */ && !MATCH_LEFT(MAIL_ADDR_MAIL_DAEMON, CONST_STR(reply->recipient), strlen(MAIL_ADDR_MAIL_DAEMON)) && NOMATCH(local_rcpt_maps, CONST_STR(reply->recipient))) diff --git a/postfix/src/util/Makefile.in b/postfix/src/util/Makefile.in index bce2c74e6..4167dfff0 100644 --- a/postfix/src/util/Makefile.in +++ b/postfix/src/util/Makefile.in @@ -624,6 +624,18 @@ dict_open.o: split_at.h dict_open.o: htable.h dict_pcre.o: dict_pcre.c dict_pcre.o: sys_defs.h +dict_pcre.o: mymalloc.h +dict_pcre.o: msg.h +dict_pcre.o: safe.h +dict_pcre.o: vstream.h +dict_pcre.o: vbuf.h +dict_pcre.o: vstring.h +dict_pcre.o: stringops.h +dict_pcre.o: readlline.h +dict_pcre.o: dict.h +dict_pcre.o: argv.h +dict_pcre.o: dict_pcre.h +dict_pcre.o: mac_parse.h dict_regexp.o: dict_regexp.c dict_regexp.o: sys_defs.h dict_regexp.o: mymalloc.h