it is adjusted to maximal_queue_lifetime, and a warning is
logged. Files: *qmgr/qmgr.c.
- Cleanup: trivial-rewrite detects changes to maps even in
- the absence of connection events. File: trivial-rewrite.c.
+20050203
+
+ Cleanup: trivial-rewrite now restarts more timely after
+ changes in lookup tables. Of the all the alternatives
+ tested, the simplest one produces the most bang for the
+ buck. The other code is left in place for illustrative
+ purposes. File: trivial-rewrite/trivial-rewrite.c.
+
+ Cleanup: sendmail no longer ignores null command-line
+ recipients. File: sendmail/sendmail.c.
+
+ Cleanup: "postfix start" background checks moved back
+ to the foreground so they can be stopped more easily.
+ File: conf/postfix-script.
Open problems:
- Med: SunOS 4 has no strtoul(). Instead of sprintf/strtoul()
- use hexen/decode() for the process generation number.
+ Med: local and remote source port and IP address for
+ smtpd policy hook.
+
+ Med: smtp_connect_timeout_budget (default: 2x smtp_connect_timeout)
+ to limit the total time spent trying to connect.
Low: pointers to postfinger and saslfinger. postfinger
is now bundled.
smtpd, qmgr, local, etc. use unquoted address forms as
keys. cleanup uses quoted forms.
- Low: sendmail does not store null command-line recipients.
-
Low: have a configurable list of errno values for mailbox
or maildir delivery that result in deferral rather than
bouncing mail.
and change the patchlevel and the release date. Patches are never
issued for snapshot releases.
-Incompatible changes with snapshot Postfix-2.2-20050202
+Incompatible changes with snapshot Postfix-2.2-20050203
=======================================================
Postfix rewrites message header addresses only in mail that originates
from the local machine. Specify "local_header_rewrite_clients =
static:all" to get the old behavior of Postfix 2.1 and earlier.
-Major changes with snapshot Postfix-2.2-20050202
+All "postfix start" file permission checks are run in the foreground
+while Postfix is started.
+
+Major changes with snapshot Postfix-2.2-20050203
================================================
To create a ready-to-install package for distribution to other
New "permit_inet_interfaces" access restriction to allow access
from local IP addresses only. This is used for the default, purist,
-setting of local_header_rewrite_clients.
+setting of local_header_rewrite_clients in the previous paragraph.
New "sleep time-in-seconds" pseudo access restriction to block
zombie clients with reject_unauthorized_pipelining before the
Safety: Postfix no longer tries to send mail to the fallback_relay
when the local machine is MX host for the mail destination. See
-postconf(5) description of fallback_relay for details.
+the postconf(5) description of fallback_relay for details.
Incompatible changes with snapshot Postfix-2.2-20050117
=======================================================
deferred queue directories, because those contain lots of mail when
undeliverable mail is backing up.
-In order to speed up start-up, some Postfix file permission checks
-are run in the background after Postfix is started.
-
The SMTP server now requires that IPv6 addresses in SMTP commands
are specified as [ipv6:ipv6address], as described in RFC 2821.
$FATAL Postfix integrity check failed!
exit 1
}
- # Warning checks proceed in the background.
- $INFO starting background file permission checks in 60 seconds
- (sleep 60; $config_directory/postfix-script check-warn) &
+ # Foreground this so it can be stopped. All inodes are cached.
+ $config_directory/postfix-script check-warn
fi
$INFO starting the Postfix mail system
$daemon_directory/master &
max_rcpt = 0;
}
if (max_cache > 0) {
- msg_info("statistics: max ident cache size %d at %.15s",
+ msg_info("statistics: max cache size %d at %.15s",
max_cache, ctime(&max_cache_time) + 4);
max_cache = 0;
}
/* Utility library. */
#include <name_mask.h>
+#include <msg.h>
/* Global library. */
int input_transp_cleanup(int cleanup_flags, int transp_mask)
{
+ const char *myname = "input_transp_cleanup";
+
+ if (msg_verbose)
+ msg_info("before %s: cleanup flags = %s",
+ myname, cleanup_strflags(cleanup_flags));
if (transp_mask & INPUT_TRANSP_ADDRESS_MAPPING)
cleanup_flags &= ~(CLEANUP_FLAG_BCC_OK | CLEANUP_FLAG_MAP_OK);
if (transp_mask & INPUT_TRANSP_HEADER_BODY)
cleanup_flags &= ~CLEANUP_FLAG_FILTER;
+ if (msg_verbose)
+ msg_info("after %s: cleanup flags = %s",
+ myname, cleanup_strflags(cleanup_flags));
return (cleanup_flags);
}
* Patches change the patchlevel and the release date. Snapshots change the
* release date only.
*/
-#define MAIL_RELEASE_DATE "20050202"
+#define MAIL_RELEASE_DATE "20050203"
#define MAIL_VERSION_NUMBER "2.2"
#define VAR_MAIL_VERSION "mail_version"
{
char *myname = "resolve_clnt";
VSTREAM *stream;
+ int server_flags;
/*
* One-entry cache.
ATTR_TYPE_END) != 0
|| vstream_fflush(stream)
|| attr_scan(stream, ATTR_FLAG_STRICT,
+ ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, &server_flags,
ATTR_TYPE_STR, MAIL_ATTR_TRANSPORT, reply->transport,
ATTR_TYPE_STR, MAIL_ATTR_NEXTHOP, reply->nexthop,
ATTR_TYPE_STR, MAIL_ATTR_RECIP, reply->recipient,
ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, &reply->flags,
- ATTR_TYPE_END) != 4) {
+ ATTR_TYPE_END) != 5) {
if (msg_verbose || (errno != EPIPE && errno != ENOENT))
msg_warn("problem talking to service %s: %m",
var_rewrite_service);
IFSET(RESOLVE_CLASS_VIRTUAL, "virtual"),
IFSET(RESOLVE_CLASS_RELAY, "relay"),
IFSET(RESOLVE_CLASS_DEFAULT, "default"));
+ /* Server-requested disconnect. */
+ if (server_flags != 0)
+ clnt_stream_recover(rewrite_clnt_stream);
if (STR(reply->transport)[0] == 0)
msg_warn("%s: null transport result for: <%s>", myname, addr);
else if (STR(reply->recipient)[0] == 0 && *addr != 0)
VSTRING *rewrite_clnt(const char *rule, const char *addr, VSTRING *result)
{
VSTREAM *stream;
+ int server_flags;
/*
* One-entry cache.
ATTR_TYPE_END) != 0
|| vstream_fflush(stream)
|| attr_scan(stream, ATTR_FLAG_STRICT,
+ ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, &server_flags,
ATTR_TYPE_STR, MAIL_ATTR_ADDR, result,
- ATTR_TYPE_END) != 1) {
+ ATTR_TYPE_END) != 2) {
if (msg_verbose || (errno != EPIPE && errno != ENOENT))
msg_warn("problem talking to service %s: %m",
var_rewrite_service);
if (msg_verbose)
msg_info("rewrite_clnt: %s: %s -> %s",
rule, addr, vstring_str(result));
+ /* Server-requested disconnect. */
+ if (server_flags != 0)
+ clnt_stream_recover(rewrite_clnt_stream);
break;
}
sleep(1); /* XXX make configurable */
typedef void (*MULTI_SERVER_FN) (VSTREAM *, char *, char **);
extern NORETURN multi_server_main(int, char **, MULTI_SERVER_FN,...);
extern void multi_server_disconnect(VSTREAM *);
+extern int multi_server_drain(void);
/*
* trigger_server.c
watchdog_start(watchdog); /* same as trigger servers */
event_loop(-1);
if (master_gotsighup) {
- msg_info("reload configuration");
+ msg_info("reload configuration %s", var_config_dir);
master_gotsighup = 0; /* this first */
master_vars_init(); /* then this */
master_refresh(); /* then this */
/* void multi_server_disconnect(stream, argv)
/* VSTREAM *stream;
/* char **argv;
+/*
+/* void multi_server_drain()
/* DESCRIPTION
/* This module implements a skeleton for multi-threaded
/* mail subsystems: mail subsystem programs that service multiple
/* multi_server_disconnect() should be called by the application
/* when a client disconnects.
/*
+/* multi_server_drain() should be called when the application
+/* no longer wishes to accept new client connections. Existing
+/* clients are handled in a background process. A non-zero
+/* result means this call should be tried again later.
+/*
/* The var_use_limit variable limits the number of clients that
/* a server can service before it commits suicide.
/* This value is taken from the global \fBmain.cf\fR configuration
*/
static int client_count;
static int use_count;
+static int socket_count = 1;
static void (*multi_server_service) (VSTREAM *, char *, char **);
static char *multi_server_name;
multi_server_exit();
}
+/* multi_server_drain - stop accepting new clients */
+
+int multi_server_drain(void)
+{
+ int fd;
+
+ switch (fork()) {
+ /* Try again later. */
+ case -1:
+ return (-1);
+ /* Finish existing clients in the background, then terminate. */
+ case 0:
+ for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++)
+ event_disable_readwrite(fd);
+ var_use_limit = 1;
+ return (0);
+ /* Let the master start a new process. */
+ default:
+ exit(0);
+ }
+}
+
/* multi_server_disconnect - terminate client session */
void multi_server_disconnect(VSTREAM *stream)
char *service_name = basename(argv[0]);
int delay;
int c;
- int socket_count = 1;
int fd;
va_list ap;
MAIL_SERVER_INIT_FN pre_init = 0;
if ((generation = getenv(MASTER_GEN_NAME)) != 0) {
if (!alldig(generation))
msg_fatal("bad generation: %s", generation);
- multi_server_generation = strtoul(generation, (char **) 0, 8);
+ OCTAL_TO_UNSIGNED(multi_server_generation, generation);
+ if (msg_verbose)
+ msg_info("process generation: %s (%o)",
+ generation, multi_server_generation);
}
/*
if ((generation = getenv(MASTER_GEN_NAME)) != 0) {
if (!alldig(generation))
msg_fatal("bad generation: %s", generation);
- single_server_generation = strtoul(generation, (char **) 0, 8);
+ OCTAL_TO_UNSIGNED(single_server_generation, generation);
+ if (msg_verbose)
+ msg_info("process generation: %s (%o)",
+ generation, single_server_generation);
}
/*
if ((generation = getenv(MASTER_GEN_NAME)) != 0) {
if (!alldig(generation))
msg_fatal("bad generation: %s", generation);
- trigger_server_generation = strtoul(generation, (char **) 0, 8);
+ OCTAL_TO_UNSIGNED(trigger_server_generation, generation);
+ if (msg_verbose)
+ msg_info("process generation: %s (%o)",
+ generation, trigger_server_generation);
}
/*
SM_STATE state;
int mime_errs;
const char *errstr;
+ int addr_count;
/*
* Access control is enforced in the postdrop command. The code here
if (recipients) {
for (cpp = recipients; *cpp != 0; cpp++) {
tree = tok822_parse(*cpp);
- for (tp = tree; tp != 0; tp = tp->next) {
+ for (addr_count = 0, tp = tree; tp != 0; tp = tp->next) {
if (tp->type == TOK822_ADDR) {
tok822_internalize(buf, tp->head, TOK822_STR_DEFL);
if (REC_PUT_BUF(dst, REC_TYPE_RCPT, buf) < 0)
"%s(%ld): error writing queue file: %m",
saved_sender, (long) uid);
++rcpt_count;
+ ++addr_count;
}
}
tok822_free_tree(tree);
+ if (addr_count == 0) {
+ if (rec_put(dst, REC_TYPE_RCPT, "", 0) < 0)
+ msg_fatal_status(EX_TEMPFAIL,
+ "%s(%ld): error writing queue file: %m",
+ saved_sender, (long) uid);
+ ++rcpt_count;
+ }
}
}
resolve.o: ../../include/dict.h
resolve.o: ../../include/argv.h
resolve.o: ../../include/mail_addr_find.h
+resolve.o: ../../include/valid_mailhost_addr.h
resolve.o: trivial-rewrite.h
resolve.o: transport.h
rewrite.o: rewrite.c
trivial-rewrite.o: ../../include/stringops.h
trivial-rewrite.o: ../../include/dict.h
trivial-rewrite.o: ../../include/argv.h
+trivial-rewrite.o: ../../include/events.h
trivial-rewrite.o: ../../include/mail_params.h
trivial-rewrite.o: ../../include/mail_proto.h
trivial-rewrite.o: ../../include/iostuff.h
#include <match_parent_style.h>
#include <maps.h>
#include <mail_addr_find.h>
+#include <valid_mailhost_addr.h>
/* Application-specific. */
STR(nexthop), STR(nextrcpt), flags);
attr_print(stream, ATTR_FLAG_NONE,
+ ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, server_flags,
ATTR_TYPE_STR, MAIL_ATTR_TRANSPORT, STR(channel),
ATTR_TYPE_STR, MAIL_ATTR_NEXTHOP, STR(nexthop),
ATTR_TYPE_STR, MAIL_ATTR_RECIP, STR(nextrcpt),
vstring_str(address), vstring_str(result));
attr_print(stream, ATTR_FLAG_NONE,
+ ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, server_flags,
ATTR_TYPE_STR, MAIL_ATTR_ADDR, vstring_str(result),
ATTR_TYPE_END);
/* The \fBtrivial-rewrite\fR daemon processes three types of client
/* service requests:
/* .IP "\fBrewrite \fIcontext address\fR"
-/* Rewrite an address to standard form, according to the
+/* Rewrite an address to standard form, according to the
/* address rewriting context:
/* .RS
/* .IP \fBlocal\fR
/* .IP \fIrecipient\fR
/* The envelope recipient address that is passed on to \fInexthop\fR.
/* .IP \fIflags\fR
-/* The address class, whether the address requires relaying,
+/* The address class, whether the address requires relaying,
/* whether the address has problems, and whether the request failed.
/* .RE
/* .IP "\fBverify \fIaddress\fR"
#include <split_at.h>
#include <stringops.h>
#include <dict.h>
+#include <events.h>
/* Global library. */
VAR_VRFY_XPORT_MAPS, &var_vrfy_xport_maps, 0
};
+ /*
+ * Connection management. When file-based lookup tables change we should
+ * restart at our convenience, but avoid client read errors. We restart
+ * rather than reopen, because the process may be chrooted (and if it isn't
+ * we still need code that handles the chrooted case anyway).
+ *
+ * Three variants are implemented. Only one should be used.
+ *
+ * ifdef DETACH_AND_ASK_CLIENTS_TO_RECONNECT
+ *
+ * This code detaches the trivial-rewrite process from the master, stops
+ * accepting new clients, and handles established clients in the background,
+ * asking them to reconnect the next time they send a request. The master
+ * create a new process that accepts connections. This is reasonably safe
+ * because the number of trivial-rewrite server processes is small compared
+ * to the number of trivial-rewrite client processes. The few extra
+ * background processes should not make a difference in Postfix's footprint.
+ * However, once a daemon detaches from the master, its exit status will be
+ * lost, and abnormal termination may remain undetected. Timely restart is
+ * achieved by checking the table changed status every 10 seconds or so
+ * before responding to a client request.
+ *
+ * ifdef CHECK_TABLE_STATS_PERIODICALLY
+ *
+ * This code runs every 10 seconds and terminates the process when lookup
+ * tables have changed. This is subject to race conditions when established
+ * clients send a request while the server exits; those clients may read EOF
+ * instead of a server reply. If the experience with the oldest option
+ * (below) is anything to go by, however, then this is unlikely to be a
+ * problem during real deployment.
+ *
+ * ifdef CHECK_TABLE_STATS_BEFORE_ACCEPT
+ *
+ * This is the old code. It checks the table changed status when a new client
+ * connects (i.e. before the server calls accept()), and terminates
+ * immediately. This is invisible for the connecting client, but is subject
+ * to race conditions when established clients send a request while the
+ * server exits; those clients may read EOF instead of a server reply. This
+ * has, however, not been a problem in real deployment. With the old code,
+ * timely restart is achieved by setting the ipc_ttl parameter to 60
+ * seconds, so that the table change status is checked several times a
+ * minute.
+ */
+int server_flags;
+
+ /*
+ * Define exactly one of these.
+ */
+/* #define DETACH_AND_ASK_CLIENTS_TO_RECONNECT /* correct and complex */
+#define CHECK_TABLE_STATS_PERIODICALLY /* quick */
+/* #define CHECK_TABLE_STATS_BEFORE_ACCEPT /* slow */
+
/* rewrite_service - read request and send reply */
static void rewrite_service(VSTREAM *stream, char *unused_service, char **argv)
{
int status = -1;
+
+#ifdef DETACH_AND_ASK_CLIENTS_TO_RECONNECT
static time_t last;
- time_t now = event_time();
+ time_t now;
const char *table;
+#endif
+
/*
* Sanity check. This service takes no command-line arguments.
*/
msg_fatal("unexpected command-line argument: %s", argv[0]);
/*
- * Connections are persistent. Be sure to refesh timely.
+ * Client connections are long-lived. Be sure to refesh timely.
*/
- if (now - last > 10) {
+#ifdef DETACH_AND_ASK_CLIENTS_TO_RECONNECT
+ if (server_flags == 0 && (now = event_time()) - last > 10) {
if ((table = dict_changed_name()) != 0) {
msg_info("table %s has changed -- restarting", table);
- exit(0);
+ if (multi_server_drain() == 0)
+ server_flags = 1;
}
last = now;
}
+#endif
/*
* This routine runs whenever a client connects to the UNIX-domain socket
multi_server_disconnect(stream);
}
+/* pre_accept - see if tables have changed */
+
+#ifdef CHECK_TABLE_STATS_BEFORE_ACCEPT
+
+static void pre_accept(char *unused_name, char **unused_argv)
+{
+ const char *table;
+
+ if ((table = dict_changed_name()) != 0) {
+ msg_info("table %s has changed -- restarting", table);
+ exit(0);
+ }
+}
+
+#endif
+
+#ifdef CHECK_TABLE_STATS_PERIODICALLY
+
+static void check_table_stats(int unused_event, char *unused_context)
+{
+ const char *table;
+
+ if ((table = dict_changed_name()) != 0) {
+ msg_info("table %s has changed -- restarting", table);
+ exit(0);
+ }
+ event_request_timer(check_table_stats, (char *) 0, 10);
+}
+
+#endif
+
/* pre_jail_init - initialize before entering chroot jail */
static void pre_jail_init(char *unused_name, char **unused_argv)
transport_post_init(resolve_regular.transport_info);
if (resolve_verify.transport_info)
transport_post_init(resolve_verify.transport_info);
+#ifdef CHECK_TABLE_STATS_PERIODICALLY
+ check_table_stats(0, (char *) 0);
+#endif
}
/* main - pass control to the multi-threaded skeleton code */
MAIL_SERVER_BOOL_TABLE, bool_table,
MAIL_SERVER_PRE_INIT, pre_jail_init,
MAIL_SERVER_POST_INIT, post_jail_init,
+#ifdef CHECK_TABLE_STATS_BEFORE_ACCEPT
+ MAIL_SERVER_PRE_ACCEPT, pre_accept,
+#endif
0);
}
*/
#include <tok822.h>
+ /*
+ * Connection management.
+ */
+int server_flags;
+
/*
* rewrite.c
*/
char **origin; /* default origin */
const char *domain_name; /* name of variable */
char **domain; /* default domain */
-} RWR_CONTEXT;
+} RWR_CONTEXT;
#define REW_PARAM_VALUE(x) (*(x)) /* make it easy to do it right */
#define NATIVE_COMMAND_DIR "/usr/etc"
#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
#define STRCASECMP_IN_STRINGS_H
+#define OCTAL_TO_UNSIGNED(res, str) sscanf((str), "%o", &(res))
#endif
/*
typedef int WAIT_STATUS_T;
#define NORMAL_EXIT_STATUS(status) ((status) == 0)
+#endif
+
+#ifndef OCTAL_TO_UNSIGNED
+#define OCTAL_TO_UNSIGNED(res, str) ((res) = strtoul((str), (char **) 0, 8))
#endif
/*