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
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
=========================================================
# 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,
# 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
# 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.
#
# 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
# 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
# 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.
# 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
# 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.
# 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.
# 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
# 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.
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 <b>main.cf</b> <b>con-</b>
+ <b>tent</b><i>_</i><b>filter</b> setting, and currently affects all
+ recipients of the message.
+
+ <b>REDIRECT</b> <i>user@domain</i>
+ 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.
<i>restriction...</i>
Apply the named UCE restriction(s) (<b>permit</b>, <b>reject</b>,
filtering are in the Postfix FILTER_README file. This feature
overrides the main.cf <b>content_filter</b> setting.
+<dt>REDIRECT <i>user</i>@<i>domain</i> <dd>
+After the message is queued, send the message to the
+specified address instead of the intended recipients.
+overrides the FILTER action.
+
</dl>
<p>
filtering are in the Postfix FILTER_README file. This feature
overrides the main.cf <b>content_filter</b> setting.
+<dt>REDIRECT <i>user</i>@<i>domain</i> <dd>
+After the message is queued, send the message to the
+specified address instead of the intended recipients.
+overrides the FILTER action.
+
</dl>
<p>
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).
# 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
# 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.
# 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).
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;
/*
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
} 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);
}
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);
state->mime_state = 0;
state->mime_errs = 0;
state->filter = 0;
+ state->redirect = 0;
return (state);
}
mime_state_free(state->mime_state);
if (state->filter)
myfree(state->filter);
+ if (state->redirect)
+ myfree(state->redirect);
myfree((char *) state);
}
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
* 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
#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 */
* 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
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
};
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 */
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 *);
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 */
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
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,
&& (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;
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);
/* Client stubs. */
+#include <rewrite_clnt.h>
#include <resolve_clnt.h>
/* Application-specific. */
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;
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);
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);
}
}
+/* 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)
QMGR_TRANSPORT *transport = 0;
QMGR_QUEUE *queue = 0;
RESOLVE_REPLY reply;
+ VSTRING *queue_name;
char *at;
char **cpp;
char *nexthop;
#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.
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
* 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]) {
}
}
- /*
- * 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.
*/
transport = qmgr_transport_create(STR(reply.transport));
queue = 0;
}
-#endif
/*
* This transport is dead. Defer delivery to this recipient.
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));
}
/*
recipient->queue = queue;
}
resolve_clnt_free(&reply);
+ vstring_free(queue_name);
}
/* qmgr_message_assign - assign recipients to specific delivery requests */
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);
/*
/* 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;
/* 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,
/* 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
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;
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;
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));
}
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
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;
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,
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
};
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 */
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 *);
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 */
/* 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"
/*
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
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,
&& (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;
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);
/* Client stubs. */
+#include <rewrite_clnt.h>
#include <resolve_clnt.h>
/* Application-specific. */
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;
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);
/*
* 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);
}
}
}
+/* 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)
QMGR_TRANSPORT *transport = 0;
QMGR_QUEUE *queue = 0;
RESOLVE_REPLY reply;
+ VSTRING *queue_name;
char *at;
char **cpp;
char *nexthop;
#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.
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
* 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]) {
}
}
- /*
- * 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.
*/
transport = qmgr_transport_create(STR(reply.transport));
queue = 0;
}
-#endif
/*
* This transport is dead. Defer delivery to this recipient.
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));
}
/*
recipient->queue = queue;
}
resolve_clnt_free(&reply);
+ vstring_free(queue_name);
}
/* qmgr_message_assign - assign recipients to specific delivery requests */
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);
/*
/* 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;
/* 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,
/* 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
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;
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;
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));
}
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
*/
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 {
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.
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)))
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