shooting a little easier. Files: smtpd/smtpd_check.c,
trivial-rewrite/resolve.c.
+20021213
+
+ Cleanup: transport map entries with null nexthop ignored
+ relayhost settings. Making the code simpler also made it
+ more correct. Files: trivial-rewrite/resolve.c,
+ trivial-rewrite/transport.c.
+
+ Feature: "helpful_warnings" (default: yes) that can be
+ turned off if you really know what you're doing and want
+ to eliminate some unnecessary work.
+
+ Feature: enforcement of master.cf process limits for
+ processes such as qmgr and pickup that must run alone, and
+ processes such as cleanup and bounce that must run without
+ explicit process count limit. If an incorrect process limit
+ is specified in master.cf the service aborts.
+
Open problems:
Low: after successful delivery, per-queue window += 1/window,
<p>
-Important: do not specify a relayhost entry, or else mail for
+Important: do not specify a relayhost in main.cf, or else mail for
internal destinations will still be given to the relayhost.
<p>
MAIL_SERVER_STR_TABLE, str_table,
MAIL_SERVER_TIME_TABLE, time_table,
MAIL_SERVER_POST_INIT, post_jail_init,
+ MAIL_SERVER_UNLIMITED,
0);
}
MAIL_SERVER_POST_INIT, cleanup_post_jail,
MAIL_SERVER_PRE_ACCEPT, pre_accept,
MAIL_SERVER_IN_FLOW_DELAY,
+ MAIL_SERVER_UNLIMITED,
0);
}
single_server_main(argc, argv, flush_service,
MAIL_SERVER_TIME_TABLE, time_table,
MAIL_SERVER_PRE_INIT, pre_jail_init,
+ MAIL_SERVER_UNLIMITED,
0);
}
/* char *var_transit_origin;
/* char *var_transit_dest;
/* char *var_mail_name;
+/* int var_helpful_warnings;
/* char *var_syslog_name;
/* char *var_mail_owner;
/* uid_t var_owner_uid;
char *var_transit_origin;
char *var_transit_dest;
char *var_mail_name;
+int var_helpful_warnings;
char *var_syslog_name;
char *var_mail_owner;
uid_t var_owner_uid;
VAR_STRICT_ENCODING, DEF_STRICT_ENCODING, &var_strict_encoding,
VAR_DISABLE_MIME_INPUT, DEF_DISABLE_MIME_INPUT, &var_disable_mime_input,
VAR_DISABLE_MIME_OCONV, DEF_DISABLE_MIME_OCONV, &var_disable_mime_oconv,
+ VAR_HELPFUL_WARNINGS, DEF_HELPFUL_WARNINGS, &var_helpful_warnings,
0,
};
const char *cp;
#define DEF_MAIL_NAME "Postfix"
extern char *var_mail_name;
+ /*
+ * You want to be helped or not.
+ */
+#define VAR_HELPFUL_WARNINGS "helpful_warnings"
+#define DEF_HELPFUL_WARNINGS 1
+extern bool var_helpful_warnings;
+
/*
* What problem classes should be reported to the postmaster via email.
* Default is bad problems only. See mail_error(3). Even when mail notices
* 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 "20021212"
+#define MAIL_RELEASE_DATE "20021213"
#define VAR_MAIL_VERSION "mail_version"
#define DEF_MAIL_VERSION "1.1.12-" MAIL_RELEASE_DATE
#define MAIL_SERVER_LOOP 12
#define MAIL_SERVER_EXIT 13
#define MAIL_SERVER_PRE_ACCEPT 14
+#define MAIL_SERVER_SOLITARY 15
+#define MAIL_SERVER_UNLIMITED 16
#define MAIL_SERVER_IN_FLOW_DELAY 20
argv_add(serv->args, command, (char *) 0);
if (serv->max_proc == 1)
argv_add(serv->args, "-l", (char *) 0);
+ if (serv->max_proc == 0)
+ argv_add(serv->args, "-z", (char *) 0);
if (strcmp(basename(command), name) != 0)
argv_add(serv->args, "-n", name, (char *) 0);
argv_add(serv->args, "-t", transport, (char *) 0);
/* .IP "MAIL_SERVER_IN_FLOW_DELAY (none)"
/* Pause $in_flow_delay seconds when no "mail flow control token"
/* is available. A token is consumed for each connection request.
+/* .IP MAIL_SERVER_SOLITARY
+/* This service must be configured with process limit of 1.
+/* .IP MAIL_SERVER_UNLIMITED
+/* This service must be configured with process limit of 0.
/* .PP
/* multi_server_disconnect() should be called by the application
/* when a client disconnects.
char *lock_path;
VSTRING *why;
int alone = 0;
+ int zerolimit = 0;
WATCHDOG *watchdog;
char *oval;
* stderr, because no-one is going to see them.
*/
opterr = 0;
- while ((c = GETOPT(argc, argv, "cDi:lm:n:o:s:St:uv")) > 0) {
+ while ((c = GETOPT(argc, argv, "cDi:lm:n:o:s:St:uvz")) > 0) {
switch (c) {
case 'c':
root_dir = "setme";
case 'v':
msg_verbose++;
break;
+ case 'z':
+ zerolimit = 1;
+ break;
default:
msg_fatal("invalid option: %c", c);
break;
case MAIL_SERVER_IN_FLOW_DELAY:
multi_server_in_flow_delay = 1;
break;
+ case MAIL_SERVER_SOLITARY:
+ if (!alone)
+ msg_fatal("service %s requires a process limit of 1",
+ service_name);
+ break;
+ case MAIL_SERVER_UNLIMITED:
+ if (!zerolimit)
+ msg_fatal("service %s requires a process limit of 0",
+ service_name);
+ break;
default:
msg_panic("%s: unknown argument type: %d", myname, key);
}
/* .IP "MAIL_SERVER_IN_FLOW_DELAY (none)"
/* Pause $in_flow_delay seconds when no "mail flow control token"
/* is available. A token is consumed for each connection request.
+/* .IP MAIL_SERVER_SOLITARY
+/* This service must be configured with process limit of 1.
+/* .IP MAIL_SERVER_UNLIMITED
+/* This service must be configured with process limit of 0.
/* .PP
/* The var_use_limit variable limits the number of clients that
/* a server can service before it commits suicide.
close_on_exec(fd, CLOSE_ON_EXEC);
stream = vstream_fdopen(fd, O_RDWR);
tmp = concatenate(single_server_name, " socket", (char *) 0);
- vstream_control(stream, VSTREAM_CTL_PATH, tmp, VSTREAM_CTL_END);
+ vstream_control(stream, VSTREAM_CTL_PATH, tmp, VSTREAM_CTL_END);
myfree(tmp);
timed_ipc_setup(stream);
if (master_notify(var_pid, MASTER_STAT_TAKEN) < 0)
char *lock_path;
VSTRING *why;
int alone = 0;
+ int zerolimit = 0;
WATCHDOG *watchdog;
char *oval;
* stderr, because no-one is going to see them.
*/
opterr = 0;
- while ((c = GETOPT(argc, argv, "cDi:lm:n:o:s:St:uv")) > 0) {
+ while ((c = GETOPT(argc, argv, "cDi:lm:n:o:s:St:uvz")) > 0) {
switch (c) {
case 'c':
root_dir = "setme";
case 'v':
msg_verbose++;
break;
+ case 'z':
+ zerolimit = 1;
+ break;
default:
msg_fatal("invalid option: %c", c);
break;
case MAIL_SERVER_IN_FLOW_DELAY:
single_server_in_flow_delay = 1;
break;
+ case MAIL_SERVER_SOLITARY:
+ if (!alone)
+ msg_fatal("service %s requires a process limit of 1",
+ service_name);
+ break;
+ case MAIL_SERVER_UNLIMITED:
+ if (!zerolimit)
+ msg_fatal("service %s requires a process limit of 0",
+ service_name);
+ break;
default:
msg_panic("%s: unknown argument type: %d", myname, key);
}
/* .IP "MAIL_SERVER_IN_FLOW_DELAY (none)"
/* Pause $in_flow_delay seconds when no "mail flow control token"
/* is available. A token is consumed for each connection request.
+/* .IP MAIL_SERVER_SOLITARY
+/* This service must be configured with process limit of 1.
+/* .IP MAIL_SERVER_UNLIMITED
+/* This service must be configured with process limit of 0.
/* .PP
/* The var_use_limit variable limits the number of clients that
/* a server can service before it commits suicide.
char *lock_path;
VSTRING *why;
int alone = 0;
+ int zerolimit = 0;
WATCHDOG *watchdog;
char *oval;
* stderr, because no-one is going to see them.
*/
opterr = 0;
- while ((c = GETOPT(argc, argv, "cDi:lm:n:o:s:St:uv")) > 0) {
+ while ((c = GETOPT(argc, argv, "cDi:lm:n:o:s:St:uvz")) > 0) {
switch (c) {
case 'c':
root_dir = "setme";
case 'v':
msg_verbose++;
break;
+ case 'z':
+ zerolimit = 1;
+ break;
default:
msg_fatal("invalid option: %c", c);
break;
case MAIL_SERVER_IN_FLOW_DELAY:
trigger_server_in_flow_delay = 1;
break;
+ case MAIL_SERVER_SOLITARY:
+ if (!alone)
+ msg_fatal("service %s requires a process limit of 1",
+ service_name);
+ break;
+ case MAIL_SERVER_UNLIMITED:
+ if (!zerolimit)
+ msg_fatal("service %s requires a process limit of 0",
+ service_name);
+ break;
default:
msg_panic("%s: unknown argument type: %d", myname, key);
}
MAIL_SERVER_POST_INIT, qmgr_post_init,
MAIL_SERVER_LOOP, qmgr_loop,
MAIL_SERVER_PRE_ACCEPT, pre_accept,
+ MAIL_SERVER_SOLITARY,
0);
}
* XXX This code does not detect the case that the active queue is being
* starved because incoming mail is pounding the disk.
*/
- if (var_qmgr_clog_warn_time > 0) {
+ if (var_helpful_warnings && var_qmgr_clog_warn_time > 0) {
int queue_length = queue->todo_refcount + queue->busy_refcount;
time_t now;
QMGR_TRANSPORT *transport;
trigger_server_main(argc, argv, pickup_service,
MAIL_SERVER_STR_TABLE, str_table,
MAIL_SERVER_POST_INIT, drop_privileges,
+ MAIL_SERVER_SOLITARY,
0);
}
MAIL_SERVER_POST_INIT, qmgr_post_init,
MAIL_SERVER_LOOP, qmgr_loop,
MAIL_SERVER_PRE_ACCEPT, pre_accept,
+ MAIL_SERVER_SOLITARY,
0);
}
* XXX This code does not detect the case that the active queue is being
* starved because incoming mail is pounding the disk.
*/
- if (var_qmgr_clog_warn_time > 0) {
+ if (var_helpful_warnings && var_qmgr_clog_warn_time > 0) {
int queue_length = queue->todo_refcount + queue->busy_refcount;
time_t now;
QMGR_TRANSPORT *transport;
* With virtual, relay, or other non-local destinations, give the highest
* precedence to delivery transport associated next-hop information.
*
+ * XXX With the virtual mailbox transport, set the nexthop information to
+ * $myhostname, so that in default configurations the virtual delivery
+ * agent will not use separate queues for every $virtual_mailbox_domains
+ * domain name. That prevents anomalies where many low-traffic domains
+ * starve a high-traffic domain.
+ *
* XXX Nag if the domain is listed in multiple domain lists. The effect is
* implementation defined, and may break when internals change.
*/
+#define VIRT_ALIAS_TRANSPORT var_error_transport
+#define VIRT_ALIAS_NEXTHOP "User unknown in virtual alias table"
+
dict_errno = 0;
if (domain != 0) {
tok822_internalize(nexthop, domain->next, TOK822_STR_DEFL);
*flags |= RESOLVE_FLAG_ERROR;
if (virt_alias_doms
&& string_list_match(virt_alias_doms, STR(nexthop))) {
- if (virt_mailbox_doms
+ if (var_helpful_warnings
+ && virt_mailbox_doms
&& string_list_match(virt_mailbox_doms, STR(nexthop)))
msg_warn("do not list domain %s in BOTH %s and %s",
STR(nexthop), VAR_VIRT_ALIAS_DOMS, VAR_VIRT_MAILBOX_DOMS);
- vstring_strcpy(channel, var_error_transport);
- vstring_strcpy(nexthop, "User unknown in virtual alias table");
+ vstring_strcpy(channel, VIRT_ALIAS_TRANSPORT);
+ vstring_strcpy(nexthop, VIRT_ALIAS_NEXTHOP);
blame = VAR_ERROR_TRANSPORT;
*flags |= RESOLVE_CLASS_ALIAS;
} else if (dict_errno != 0) {
} else if (virt_mailbox_doms
&& string_list_match(virt_mailbox_doms, STR(nexthop))) {
vstring_strcpy(channel, var_virt_transport);
+ vstring_strcpy(nexthop, var_myhostname);
blame = VAR_VIRT_TRANSPORT;
*flags |= RESOLVE_CLASS_VIRTUAL;
} else if (dict_errno != 0) {
blame = VAR_DEF_TRANSPORT;
*flags |= RESOLVE_CLASS_DEFAULT;
}
- if (*var_relayhost)
+ if (*var_relayhost) {
vstring_strcpy(nexthop, var_relayhost);
+ lowercase(STR(nexthop));
+ }
}
- if ((destination = split_at(STR(channel), ':')) != 0 && *destination)
+ if ((destination = split_at(STR(channel), ':')) != 0 && *destination) {
vstring_strcpy(nexthop, destination);
+ lowercase(STR(nexthop));
+ }
}
/*
* Local delivery. Set up the default local transport and the default
* next-hop hostname (myself).
*
+ * XXX Set the nexthop information to myhostname, so that the local delivery
+ * agent does not get a queue for every domain name in $mydestination or
+ * for every network address in $inet_interfaces.
+ *
* XXX Nag if the domain is listed in multiple domain lists. The effect is
* implementation defined, and may break when internals change.
*/
else {
if ((rcpt_domain = strrchr(STR(nextrcpt), '@')) != 0) {
rcpt_domain++;
- if (virt_alias_doms
+ if (var_helpful_warnings
+ && virt_alias_doms
&& string_list_match(virt_alias_doms, rcpt_domain))
msg_warn("do not list domain %s in BOTH %s and %s",
rcpt_domain, VAR_MYDEST, VAR_VIRT_ALIAS_DOMS);
- if (virt_mailbox_doms
+ if (var_helpful_warnings
+ && virt_mailbox_doms
&& string_list_match(virt_mailbox_doms, rcpt_domain))
msg_warn("do not list domain %s in BOTH %s and %s",
rcpt_domain, VAR_MYDEST, VAR_VIRT_MAILBOX_DOMS);
}
}
+ /*
+ * Kludge for virtual alias domains. Their next-hop info is arbitrary
+ * text that must not be passed on to regular delivery agents. So, if the
+ * transport was changed, but the nexthop was not, copy over the local
+ * hostname instead.
+ */
+#define STREQ(x, y) (strcmp((x), (y)) == 0)
+
+ if ((*flags & RESOLVE_FLAG_FAIL) == 0
+ && (*flags & RESOLVE_CLASS_ALIAS) != 0
+ && !STREQ(STR(channel), VIRT_ALIAS_TRANSPORT)
+ && STREQ(STR(nexthop), VIRT_ALIAS_NEXTHOP))
+ vstring_strcpy(nexthop, var_myhostname);
+
/*
* Bounce recipients that have moved, regardless of domain address class.
* The downside of doing this here is that this table has no effect on
*/
#define IGNORE_ADDR_EXTENSION ((char **) 0)
- if ((*flags & RESOLVE_FLAG_FAIL) == 0 && *var_relocated_maps != 0) {
+ if ((*flags & RESOLVE_FLAG_FAIL) == 0 && relocated_maps != 0) {
const char *newloc;
if ((newloc = mail_addr_find(relocated_maps, STR(nextrcpt),
IGNORE_ADDR_EXTENSION)) != 0) {
vstring_strcpy(channel, var_error_transport);
- vstring_sprintf(nexthop, "user has moved to %s", newloc);
+ vstring_sprintf(nexthop, "User has moved to %s", newloc);
} else if (dict_errno != 0) {
msg_warn("%s lookup failure", VAR_RELOCATED_MAPS);
*flags |= RESOLVE_FLAG_FAIL;
static VSTRING *wildcard_channel;
static VSTRING *wildcard_nexthop;
+#define STR(x) vstring_str(x)
+
+ /*
+ * Macro for consistent updates of the transport and nexthop results.
+ */
+#define UPDATE_IF_SPECIFIED(dst, src) do { \
+ if ((src) && *(src)) vstring_strcpy((dst), (src)); \
+ } while (0)
+
/* transport_init - pre-jail initialization */
void transport_init(void)
transport_path = maps_create("transport", var_transport_maps,
DICT_FLAG_LOCK);
transport_match_parent_style = match_parent_style(VAR_TRANSPORT_MAPS);
-
}
/* find_transport_entry - look up and parse transport table entry */
static int find_transport_entry(const char *key, int flags,
- VSTRING *channel, VSTRING *nexthop,
- const char *def_nexthop)
+ VSTRING *channel, VSTRING *nexthop)
{
char *saved_value;
const char *host;
}
/*
- * Can't do transport:user@domain until we have a way to pass the
- * recipient back to the application, and until we have verified that
- * this does not open security holes via, for example, regexp maps.
- * Nothing is supposed to trust a transport name, envelope recipient
- * address or next-hop hostname, but to be on the safe side we probably
- * should add some syntax sanity checks.
+ * Can't do transport:user@domain because the right-hand side can have
+ * arbitrary content (especially in the case of the error mailer).
*/
else {
saved_value = mystrdup(value);
- if ((host = split_at(saved_value, ':')) != 0 && *host != 0) {
-#if 0
- if ((ratsign = strrchr(host, '@'))) {
- vstring_strcpy(recipient, host);
- vstring_strcpy(nexthop, ratsign + 1);
- } else
-#endif
- vstring_strcpy(nexthop, host);
- } else if (def_nexthop != 0)
- vstring_strcpy(nexthop, def_nexthop);
- if (*saved_value != 0)
- vstring_strcpy(channel, saved_value);
+ host = split_at(saved_value, ':');
+ UPDATE_IF_SPECIFIED(nexthop, host);
+ UPDATE_IF_SPECIFIED(channel, saved_value);
myfree(saved_value);
return (FOUND);
}
VSTRING *channel = vstring_alloc(10);
VSTRING *nexthop = vstring_alloc(10);
+ /*
+ * Technically, the wildcard lookup pattern is redundant. A static map
+ * (keys always match, result is fixed string) could achieve the same:
+ *
+ * transport_maps = hash:/etc/postfix/transport static:xxx:yyy
+ *
+ * But the user interface of such an approach would be less intuitive. We
+ * tolerate the continued existence of wildcard lookup patterns because
+ * of human interface considerations.
+ */
#define WILDCARD "*"
#define FULL 0
#define PARTIAL DICT_FLAG_FIXED
- if (find_transport_entry(WILDCARD, FULL, channel, nexthop, (char *) 0)) {
+ if (find_transport_entry(WILDCARD, FULL, channel, nexthop)) {
wildcard_channel = channel;
wildcard_nexthop = nexthop;
if (msg_verbose)
const char *name;
const char *next;
int found;
- const char *def_nexthop = 0;
#define STREQ(x,y) (strcmp((x), (y)) == 0)
#define DISCARD_EXTENSION ((char **) 0)
* string. Specify the FULL flag to include regexp maps in the query.
*/
if (STREQ(full_addr, var_xport_null_key)) {
- if (find_transport_entry(full_addr, FULL, channel, nexthop, def_nexthop))
+ if (find_transport_entry(full_addr, FULL, channel, nexthop))
RETURN_FREE(FOUND);
RETURN_FREE(NOTFOUND);
}
if ((ratsign = strrchr(full_addr, '@')) == 0 || ratsign[1] == 0)
msg_panic("transport_lookup: bad address: \"%s\"", full_addr);
- def_nexthop = ratsign + 1;
-
- if (find_transport_entry(full_addr, FULL, channel, nexthop, def_nexthop))
+ if (find_transport_entry(full_addr, FULL, channel, nexthop))
RETURN_FREE(FOUND);
/*
*/
if ((stripped_addr = strip_addr(full_addr, DISCARD_EXTENSION,
*var_rcpt_delim)) != 0) {
- if (find_transport_entry(stripped_addr, PARTIAL, channel, nexthop,
- def_nexthop)) {
+ if (find_transport_entry(stripped_addr, PARTIAL,
+ channel, nexthop)) {
myfree(stripped_addr);
RETURN_FREE(FOUND);
} else {
* with regular expressions.
*/
for (found = 0, name = ratsign + 1; /* void */ ; name = next) {
- if (find_transport_entry(name, PARTIAL, channel, nexthop, def_nexthop))
+ if (find_transport_entry(name, PARTIAL, channel, nexthop))
RETURN_FREE(FOUND);
if ((next = strchr(name + 1, '.')) == 0)
break;
* Fall back to the wild-card entry.
*/
if (wildcard_channel) {
- if (*vstring_str(wildcard_channel))
- vstring_strcpy(channel, vstring_str(wildcard_channel));
- if (*vstring_str(wildcard_nexthop))
- vstring_strcpy(nexthop, vstring_str(wildcard_nexthop));
+ UPDATE_IF_SPECIFIED(channel, STR(wildcard_channel));
+ UPDATE_IF_SPECIFIED(nexthop, STR(wildcard_nexthop));
RETURN_FREE(FOUND);
}