From: Wietse Venema Date: Sat, 14 Jan 2012 05:00:00 +0000 (-0500) Subject: postfix-2.9-20120114 X-Git-Tag: v2.9.0-RC1~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=98d8efe9af1df2731204d344d7a63eab99977894;p=thirdparty%2Fpostfix.git postfix-2.9-20120114 --- diff --git a/postfix/.indent.pro b/postfix/.indent.pro index 9e679a2ca..e7009d5d4 100644 --- a/postfix/.indent.pro +++ b/postfix/.indent.pro @@ -100,6 +100,7 @@ -TDICT_SDBM -TDICT_SQLITE -TDICT_STACK +-TDICT_SURROGATE -TDICT_TCP -TDICT_TEXT -TDICT_THASH diff --git a/postfix/HISTORY b/postfix/HISTORY index 9f3659ac1..1d43a28cb 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -17502,7 +17502,7 @@ Apologies for any names omitted. 20120110 - Cleanup: added logging for failed table lookups and replaced + Cleanup: added logging for failed table lookups, and replaced some "fatal" errors by warnings. Files: cleanup/cleanup_addr.c, cleanup/cleanup_message.c, cleanup/cleanup_milter.c, cleanup/cleanup_masquerade.c, global/header_body_checks.c, @@ -17511,3 +17511,36 @@ Apologies for any names omitted. smtp/smtp_proto.c, smtp/smtp_sasl_auth_cache.c, smtp/smtp_sasl_glue.c, smtp/smtp_session.c, smtp/smtp_trouble.c, smtpd/smtpd.c, smtpd/smtpd_check.c. + +20120114 + + Cleanup: gradual degradation after database file open errors. + Instead of terminating immediately with a "fatal" error, a + Postfix daemon logs an error and continues execution with + reduced functionality. In other words, features that don't + depend on the unavailable table will keep working. However, + for the sake of sanity, the number of such errors over the + life of a process is limited to 13. Files: + src/global/cfg_parser.c, src/util/dict_thash.c, + src/util/dict_cidr.c, src/util/dict_nis.c, src/util/dict_nisplus.c, + src/global/dict_ldap.c, src/global/dict_mysql.c, + src/global/dict_pgsql.c, src/global/dict_sqlite.c, + src/postconf/postconf_main.c, src/global/mail_conf.c, + src/util/dict.h, src/util/dict.c, src/global/dict_memcache.c, + src/util/dict_tcp.c, src/util/dict_unix.c, src/util/dict_pcre.c, + src/util/dict_regexp.c, src/master/trigger_server.c, + src/master/single_server.c, src/master/multi_server.c, + src/master/event_server.c, src/util/dict_test.c, + src/util/dict_surrogate.c, src/util/dict_alloc.c, src/util/msg.c, + src/util/dict_cdb.c, src/util/dict_dbm.c, src/util/msg.h, + src/util/dict_db.c. + + Incompatibility: the Postfix SMTP server no longer reports + transcripts of sessions where a client command is rejected + because a table is unavailable. To receive such reports, + add the new "data" class to the notify_classes parameter + value. The reports will be sent to the error_notice_recipient + address as before. This class is also used by the Postfix + SMTP client to report about sessions that fail because a + table is unavailable. Files: global/mail_error.[hc], + smtpd/smtpd_check.c, smtp/smtp_trouble.c. diff --git a/postfix/RELEASE_NOTES b/postfix/RELEASE_NOTES index 4492293bd..dfb86dc67 100644 --- a/postfix/RELEASE_NOTES +++ b/postfix/RELEASE_NOTES @@ -14,16 +14,57 @@ specifies the release date of a stable release or snapshot release. If you upgrade from Postfix 2.7 or earlier, read RELEASE_NOTES-2.8 before proceeding. -Major changes with snapshot 20111208 +Incompatible changes with snapshot 20120114 +=========================================== + +Instead of terminating immediately with a "fatal" message when a +database file can't be opened, a Postfix daemon program now logs +an "error" message, and continues execution with reduced functionality. + +Specify "daemon_table_open_error_is_fatal = yes" to get the +historical behavior (immediate termination with "fatal" message). + +Logfile-based alerting systems may need to be updated to look for +"error" messages in addition to "fatal" messages. + +By default the Postfix SMTP server no longer reports transcripts +of sessions where a client command is rejected because a table is +unavailable. To receive such reports, add the new "data" class to +the notify_classes parameter value. The reports will be sent to the +error_notice_recipient address as before. This class is also used +by the Postfix SMTP client to report about sessions that fail because +a table is unavailable + +Major changes with snapshot 20120114 +==================================== + +Gradual degradation after database file open error. Instead of +terminating immediately with a "fatal" message, a Postfix daemon +program logs an "error" message, and continues execution with reduced +functionality. + +Features that don't depend on the unavailable table will keep +working; features that depend on the table will fail, and will be +logged with a "warning" message. For the sake of sanity, the number +of "error"s over the life of a process is limited to 13. + +Specify "daemon_table_open_error_is_fatal = yes" to get the +historical behavior (immediate termination with "fatal" message). + +When the notify_classes parameter contains the new "data" class, +the Postfix SMTP client and server will report transcripts of +sessions with errors because a table is unavailable. + +Major changes with snapshot 20120108 ==================================== The LDAP, *SQL and memcache clients now "catch" table lookup errors in the "domain" feature, instead of terminating with a fatal error. -Major changes with snapshot 20111202 +Major changes with snapshot 20120102 ==================================== -Degrade gracefully when some or all network protocols specified +Degrade gradually when some or all network protocols specified with inet_protocols are unavailable, instead of terminating with a fatal error. This eliminates build errors on non-standard systems where opening an IPv4 socket results in an error, and on non-standard diff --git a/postfix/WISHLIST b/postfix/WISHLIST index 394eea47c..c201dae89 100644 --- a/postfix/WISHLIST +++ b/postfix/WISHLIST @@ -4,9 +4,6 @@ Wish list: Remove this file from the stable release. - In daemons, open surrogate map when real map is unavailable, - effectively redirecting to fail:. - Things to do after the stable release: Before proxymap can be exposed to the network to share, diff --git a/postfix/html/memcache_table.5.html b/postfix/html/memcache_table.5.html index 1dd8ba15f..11ab0ff7e 100644 --- a/postfix/html/memcache_table.5.html +++ b/postfix/html/memcache_table.5.html @@ -162,12 +162,13 @@ MEMCACHE_TABLE(5) MEMCACHE_TABLE(5) domain (default: no domain list) This feature can significantly reduce database server load. Specify a list of domain names, paths - to files, or dictionaries. When specified, only - fully qualified search keys with a *non-empty* - localpart and a matching domain are eligible for - lookup or update: bare 'user' lookups, bare domain - lookups and "@domain" lookups are silently skipped - (updates are skipped with a warning). Example: + to files, or "type:table" databases. When speci- + fied, only fully qualified search keys with a *non- + empty* localpart and a matching domain are eligible + for lookup or update: bare 'user' lookups, bare + domain lookups and "@domain" lookups are silently + skipped (updates are skipped with a warning). + Example: domain = example.com, hash:/etc/postfix/searchdomains @@ -179,31 +180,31 @@ MEMCACHE_TABLE(5) MEMCACHE_TABLE(5) The maximal memcache reply line length in bytes. max_try (default: 2) - The number of times to try a memcache command + The number of times to try a memcache command before giving up. retry_pause (default: 1) - The time in seconds to wait after a memcache com- + The time in seconds to wait after a memcache com- mand fails. timeout (default: 2) - The time limit for sending a memcache command and + The time limit for sending a memcache command and for receiving a memcache reply. BUGS - The Postfix memcache client cannot be used for security- - sensitive tables such as alias_maps (these may contain - "|command and "/file/name" destinations), or vir- - tual_uid_maps, virtual_gid_maps and virtual_mailbox_maps - (these specify UNIX process privileges or "/file/name" - destinations). In a typical deployment a memcache data- - base is writable by any process that can talk to the mem- - cache server; in contrast, security-sensitive tables must + The Postfix memcache client cannot be used for security- + sensitive tables such as alias_maps (these may contain + "|command and "/file/name" destinations), or vir- + tual_uid_maps, virtual_gid_maps and virtual_mailbox_maps + (these specify UNIX process privileges or "/file/name" + destinations). In a typical deployment a memcache data- + base is writable by any process that can talk to the mem- + cache server; in contrast, security-sensitive tables must not be writable by the unprivileged Postfix user. The Postfix memcache client requires additional configura- - tion when used as postscreen(8) or verify(8) cache. For - details see the backup and ttl parameter discussions in + tion when used as postscreen(8) or verify(8) cache. For + details see the backup and ttl parameter discussions in the MEMCACHE MAIN PARAMETERS section above. SEE ALSO @@ -215,13 +216,13 @@ MEMCACHE_TABLE(5) MEMCACHE_TABLE(5) MEMCACHE_README, Postfix memcache client guide LICENSE - The Secure Mailer license must be distributed with this + The Secure Mailer license must be distributed with this software. HISTORY - The first memcache client for Postfix was written by Omar - Kilani, and was based on libmemcache. The Postfix imple- - mentation does not use libmemcache, and bears no resem- + The first memcache client for Postfix was written by Omar + Kilani, and was based on libmemcache. The Postfix imple- + mentation does not use libmemcache, and bears no resem- blance to earlier work. AUTHOR(S) diff --git a/postfix/html/postconf.5.html b/postfix/html/postconf.5.html index 5ef3c7bd0..ec54b00c4 100644 --- a/postfix/html/postconf.5.html +++ b/postfix/html/postconf.5.html @@ -1618,6 +1618,39 @@ be owned by root.

+ + +
daemon_table_open_error_is_fatal +(default: no)
+ +

How a Postfix daemon process handles errors while opening lookup +tables: gradual degradation or immediate termination.

+ +
+ +
no (default)

Gradual degradation: a +daemon process logs a message of type "error" and continues execution +with reduced functionality. Features that do not depend on the +unavailable table will work normally, while features that depend +on the table will result in a type "warning" message.
When +the notify_classes parameter value contains the "data" class, the +Postfix SMTP server and client will report transcripts of sessions +with an error because a table is unavailable.

+ +
yes (historical behavior)

Immediate +termination: a daemon process logs a type "fatal" message and +terminates immediately. This option reduces the number of possible +code paths through Postfix, and may therefore be slightly more +secure than the default.

+ +
+ +

For the sake of sanity, the number of type "error" messages is +limited to 13 over the lifetime of a daemon process.

+ +

This feature is available in Postfix 2.9 and later.

+ +
daemon_timeout @@ -6653,6 +6686,14 @@ notification is sent to the address specified with the is sent to the address specified with the 2bounce_notice_recipient configuration parameter (default: postmaster). +
data
+ +
Send the postmaster a transcript of the SMTP session with an +error because a critical data file was unavailable. The notification +is sent to the address specified with the error_notice_recipient +configuration parameter (default: postmaster).
This feature +is available in Postfix 2.9 and later.
+
delay
Send the postmaster copies of the headers of delayed mail. The diff --git a/postfix/man/man5/memcache_table.5 b/postfix/man/man5/memcache_table.5 index 5ceb73d1d..002766859 100644 --- a/postfix/man/man5/memcache_table.5 +++ b/postfix/man/man5/memcache_table.5 @@ -160,7 +160,8 @@ is skipped with a warning). .RE .IP "\fBdomain (default: no domain list)\fR" This feature can significantly reduce database server load. -Specify a list of domain names, paths to files, or dictionaries. +Specify a list of domain names, paths to files, or "type:table" +databases. When specified, only fully qualified search keys with a *non-empty* localpart and a matching domain are eligible for lookup or update: bare 'user' lookups, bare domain diff --git a/postfix/man/man5/postconf.5 b/postfix/man/man5/postconf.5 index d5b5e17f1..09506eae2 100644 --- a/postfix/man/man5/postconf.5 +++ b/postfix/man/man5/postconf.5 @@ -906,6 +906,31 @@ with Cyrus SASL 2.1.22 or later. The directory with Postfix support programs and daemon programs. These should not be invoked directly by humans. The directory must be owned by root. +.SH daemon_table_open_error_is_fatal (default: no) +How a Postfix daemon process handles errors while opening lookup +tables: gradual degradation or immediate termination. +.IP "\fB no \fR (default)" +Gradual degradation: a +daemon process logs a message of type "error" and continues execution +with reduced functionality. Features that do not depend on the +unavailable table will work normally, while features that depend +on the table will result in a type "warning" message. +.br +When +the notify_classes parameter value contains the "data" class, the +Postfix SMTP server and client will report transcripts of sessions +with an error because a table is unavailable. +.IP "\fB yes \fR (historical behavior)" +Immediate +termination: a daemon process logs a type "fatal" message and +terminates immediately. This option reduces the number of possible +code paths through Postfix, and may therefore be slightly more +secure than the default. +.PP +For the sake of sanity, the number of type "error" messages is +limited to 13 over the lifetime of a daemon process. +.PP +This feature is available in Postfix 2.9 and later. .SH daemon_timeout (default: 18000s) How much time a Postfix daemon process may take to handle a request before it is terminated by a built-in watchdog timer. @@ -3792,6 +3817,14 @@ bounce_notice_recipient configuration parameter (default: postmaster). Send undeliverable bounced mail to the postmaster. The notification is sent to the address specified with the 2bounce_notice_recipient configuration parameter (default: postmaster). +.IP "\fBdata\fR" +Send the postmaster a transcript of the SMTP session with an +error because a critical data file was unavailable. The notification +is sent to the address specified with the error_notice_recipient +configuration parameter (default: postmaster). +.br +This feature +is available in Postfix 2.9 and later. .IP "\fBdelay\fR" Send the postmaster copies of the headers of delayed mail. The notification is sent to the address specified with the diff --git a/postfix/proto/memcache_table b/postfix/proto/memcache_table index 45dbe2942..fc5827c13 100644 --- a/postfix/proto/memcache_table +++ b/postfix/proto/memcache_table @@ -150,7 +150,8 @@ # .RE # .IP "\fBdomain (default: no domain list)\fR" # This feature can significantly reduce database server load. -# Specify a list of domain names, paths to files, or dictionaries. +# Specify a list of domain names, paths to files, or "type:table" +# databases. # When specified, only fully qualified search keys with a # *non-empty* localpart and a matching domain are eligible # for lookup or update: bare 'user' lookups, bare domain diff --git a/postfix/proto/postconf.proto b/postfix/proto/postconf.proto index 638e81205..8e5e47a36 100644 --- a/postfix/proto/postconf.proto +++ b/postfix/proto/postconf.proto @@ -3045,6 +3045,14 @@ bounce_notice_recipient configuration parameter (default: postmaster). is sent to the address specified with the 2bounce_notice_recipient configuration parameter (default: postmaster).
+
data
+ +
Send the postmaster a transcript of the SMTP session with an +error because a critical data file was unavailable. The notification +is sent to the address specified with the error_notice_recipient +configuration parameter (default: postmaster).
This feature +is available in Postfix 2.9 and later.
+
delay
Send the postmaster copies of the headers of delayed mail. The @@ -14455,3 +14463,32 @@ units are: s (seconds), m (minutes), h (hours), d (days), w (weeks).

This feature is available in Postfix 2.9 and later.

+ +%PARAM daemon_table_open_error_is_fatal no + +

How a Postfix daemon process handles errors while opening lookup +tables: gradual degradation or immediate termination.

+ +
+ +
no (default)

Gradual degradation: a +daemon process logs a message of type "error" and continues execution +with reduced functionality. Features that do not depend on the +unavailable table will work normally, while features that depend +on the table will result in a type "warning" message.
When +the notify_classes parameter value contains the "data" class, the +Postfix SMTP server and client will report transcripts of sessions +with an error because a table is unavailable.

+ +
yes (historical behavior)

Immediate +termination: a daemon process logs a type "fatal" message and +terminates immediately. This option reduces the number of possible +code paths through Postfix, and may therefore be slightly more +secure than the default.

+ +
+ +

For the sake of sanity, the number of type "error" messages is +limited to 13 over the lifetime of a daemon process.

+ +

This feature is available in Postfix 2.9 and later.

diff --git a/postfix/src/cleanup/cleanup_milter.c b/postfix/src/cleanup/cleanup_milter.c index cc63313f1..18957fe1d 100644 --- a/postfix/src/cleanup/cleanup_milter.c +++ b/postfix/src/cleanup/cleanup_milter.c @@ -2155,7 +2155,7 @@ char *var_milt_head_checks = ""; /* Dummies to satisfy unused external references. */ -int cleanup_masquerade_internal(VSTRING *addr, ARGV *masq_domains) +int cleanup_masquerade_internal(CLEANUP_STATE *state, VSTRING *addr, ARGV *masq_domains) { msg_panic("cleanup_masquerade_internal dummy"); } diff --git a/postfix/src/global/cfg_parser.c b/postfix/src/global/cfg_parser.c index 73c81d025..89ebab5d3 100644 --- a/postfix/src/global/cfg_parser.c +++ b/postfix/src/global/cfg_parser.c @@ -40,7 +40,8 @@ /* \fBvalue\fR" in main.cf (the old LDAP style). It unifies the /* two styles and provides support for range checking. /* -/* \fIcfg_parser_alloc\fR initializes the parser. +/* \fIcfg_parser_alloc\fR initializes the parser. The result +/* is NULL if a configuration file could not be opened. /* /* \fIcfg_parser_free\fR releases the parser. /* @@ -234,7 +235,11 @@ CFG_PARSER *cfg_parser_alloc(const char *pname) parser = (CFG_PARSER *) mymalloc(sizeof(*parser)); parser->name = mystrdup(pname); if (*parser->name == '/' || *parser->name == '.') { - dict_load_file(parser->name, parser->name); + if (dict_load_file_xt(parser->name, parser->name) == 0) { + myfree(parser->name); + myfree((char *) parser); + return (0); + } parser->get_str = get_dict_str; parser->get_int = get_dict_int; parser->get_bool = get_dict_bool; diff --git a/postfix/src/global/dict_ldap.c b/postfix/src/global/dict_ldap.c index 6398dcc89..3d6ddeaaa 100644 --- a/postfix/src/global/dict_ldap.c +++ b/postfix/src/global/dict_ldap.c @@ -1606,7 +1606,7 @@ static void dict_ldap_close(DICT *dict) /* dict_ldap_open - create association with data base */ -DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags) +DICT *dict_ldap_open(const char *ldapsource, int open_flags, int dict_flags) { const char *myname = "dict_ldap_open"; DICT_LDAP *dict_ldap; @@ -1619,10 +1619,26 @@ DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags) char *bindopt; int tmp; int vendor_version = dict_ldap_vendor_version(); + CFG_PARSER *parser; if (msg_verbose) msg_info("%s: Using LDAP source %s", myname, ldapsource); + /* + * Sanity check. + */ + if (open_flags != O_RDONLY) + return (dict_surrogate(DICT_TYPE_LDAP, ldapsource, open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_LDAP, ldapsource)); + + /* + * Open the configuration file. + */ + if ((parser = cfg_parser_alloc(ldapsource)) == 0) + return (dict_surrogate(DICT_TYPE_LDAP, ldapsource, open_flags, dict_flags, + "open %s: %m", ldapsource)); + dict_ldap = (DICT_LDAP *) dict_alloc(DICT_TYPE_LDAP, ldapsource, sizeof(*dict_ldap)); dict_ldap->dict.lookup = dict_ldap_lookup; @@ -1630,7 +1646,7 @@ DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags) dict_ldap->dict.flags = dict_flags; dict_ldap->ld = NULL; - dict_ldap->parser = cfg_parser_alloc(ldapsource); + dict_ldap->parser = parser; server_host = cfg_get_str(dict_ldap->parser, "server_host", "localhost", 1, 0); diff --git a/postfix/src/global/dict_memcache.c b/postfix/src/global/dict_memcache.c index d6df42996..e3ce92538 100644 --- a/postfix/src/global/dict_memcache.c +++ b/postfix/src/global/dict_memcache.c @@ -500,17 +500,27 @@ DICT *dict_memcache_open(const char *name, int open_flags, int dict_flags) { DICT_MC *dict_mc; char *backup; + CFG_PARSER *parser; /* * Sanity checks. */ if (dict_flags & DICT_FLAG_NO_UNAUTH) - msg_fatal("%s:%s map is not allowed for security-sensitive data", - DICT_TYPE_MEMCACHE, name); + return (dict_surrogate(DICT_TYPE_MEMCACHE, name, open_flags, dict_flags, + "%s:%s map is not allowed for security-sensitive data", + DICT_TYPE_MEMCACHE, name)); open_flags &= (O_RDONLY | O_RDWR | O_WRONLY | O_APPEND); if (open_flags != O_RDONLY && open_flags != O_RDWR) - msg_fatal("%s:%s map requires O_RDONLY or O_RDWR access mode", - DICT_TYPE_MEMCACHE, name); + return (dict_surrogate(DICT_TYPE_MEMCACHE, name, open_flags, dict_flags, + "%s:%s map requires O_RDONLY or O_RDWR access mode", + DICT_TYPE_MEMCACHE, name)); + + /* + * Open the configuration file. + */ + if ((parser = cfg_parser_alloc(name)) == 0) + return (dict_surrogate(DICT_TYPE_MEMCACHE, name, open_flags, dict_flags, + "open %s: %m", name)); /* * Create the dictionary object. @@ -531,7 +541,7 @@ DICT *dict_memcache_open(const char *name, int open_flags, int dict_flags) /* * Parse the configuration file. */ - dict_mc->parser = cfg_parser_alloc(name); + dict_mc->parser = parser; dict_mc->key_format = cfg_get_str(dict_mc->parser, DICT_MC_NAME_KEY_FMT, DICT_MC_DEF_KEY_FMT, 0, 0); dict_mc->timeout = cfg_get_int(dict_mc->parser, DICT_MC_NAME_MC_TIMEOUT, diff --git a/postfix/src/global/dict_mysql.c b/postfix/src/global/dict_mysql.c index 8f7285234..a3e231a4e 100644 --- a/postfix/src/global/dict_mysql.c +++ b/postfix/src/global/dict_mysql.c @@ -583,11 +583,10 @@ static void plmysql_down_host(HOST *host) static void mysql_parse_config(DICT_MYSQL *dict_mysql, const char *mysqlcf) { const char *myname = "mysqlname_parse"; - CFG_PARSER *p; + CFG_PARSER *p = dict_mysql->parser; VSTRING *buf; char *hosts; - p = dict_mysql->parser = cfg_parser_alloc(mysqlcf); dict_mysql->username = cfg_get_str(p, "user", "", 0, 0); dict_mysql->password = cfg_get_str(p, "password", "", 0, 0); dict_mysql->dbname = cfg_get_str(p, "dbname", "", 1, 0); @@ -649,19 +648,29 @@ static void mysql_parse_config(DICT_MYSQL *dict_mysql, const char *mysqlcf) DICT *dict_mysql_open(const char *name, int open_flags, int dict_flags) { DICT_MYSQL *dict_mysql; + CFG_PARSER *parser; /* * Sanity checks. */ if (open_flags != O_RDONLY) - msg_fatal("%s:%s map requires O_RDONLY access mode", - DICT_TYPE_MYSQL, name); + return (dict_surrogate(DICT_TYPE_MYSQL, name, open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_MYSQL, name)); + + /* + * Open the configuration file. + */ + if ((parser = cfg_parser_alloc(name)) == 0) + return (dict_surrogate(DICT_TYPE_MYSQL, name, open_flags, dict_flags, + "open %s: %m", name)); dict_mysql = (DICT_MYSQL *) dict_alloc(DICT_TYPE_MYSQL, name, sizeof(DICT_MYSQL)); dict_mysql->dict.lookup = dict_mysql_lookup; dict_mysql->dict.close = dict_mysql_close; dict_mysql->dict.flags = dict_flags; + dict_mysql->parser = parser; mysql_parse_config(dict_mysql, name); #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000 dict_mysql->active_host = 0; diff --git a/postfix/src/global/dict_pgsql.c b/postfix/src/global/dict_pgsql.c index a98a0c979..b96a81fe3 100644 --- a/postfix/src/global/dict_pgsql.c +++ b/postfix/src/global/dict_pgsql.c @@ -680,12 +680,11 @@ static void plpgsql_down_host(HOST *host) static void pgsql_parse_config(DICT_PGSQL *dict_pgsql, const char *pgsqlcf) { const char *myname = "pgsql_parse_config"; - CFG_PARSER *p; + CFG_PARSER *p = dict_pgsql->parser; char *hosts; VSTRING *query; char *select_function; - p = dict_pgsql->parser = cfg_parser_alloc(pgsqlcf); dict_pgsql->username = cfg_get_str(p, "user", "", 0, 0); dict_pgsql->password = cfg_get_str(p, "password", "", 0, 0); dict_pgsql->dbname = cfg_get_str(p, "dbname", "", 1, 0); @@ -752,16 +751,29 @@ static void pgsql_parse_config(DICT_PGSQL *dict_pgsql, const char *pgsqlcf) DICT *dict_pgsql_open(const char *name, int open_flags, int dict_flags) { DICT_PGSQL *dict_pgsql; + CFG_PARSER *parser; + /* + * Sanity check. + */ if (open_flags != O_RDONLY) - msg_fatal("%s:%s map requires O_RDONLY access mode", - DICT_TYPE_PGSQL, name); + return (dict_surrogate(DICT_TYPE_PGSQL, name, open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_PGSQL, name)); + + /* + * Open the configuration file. + */ + if ((parser = cfg_parser_alloc(name)) == 0) + return (dict_surrogate(DICT_TYPE_PGSQL, name, open_flags, dict_flags, + "open %s: %m", name)); dict_pgsql = (DICT_PGSQL *) dict_alloc(DICT_TYPE_PGSQL, name, sizeof(DICT_PGSQL)); dict_pgsql->dict.lookup = dict_pgsql_lookup; dict_pgsql->dict.close = dict_pgsql_close; dict_pgsql->dict.flags = dict_flags; + dict_pgsql->parser = parser; pgsql_parse_config(dict_pgsql, name); dict_pgsql->active_host = 0; dict_pgsql->pldb = plpgsql_init(dict_pgsql->hosts); diff --git a/postfix/src/global/dict_sqlite.c b/postfix/src/global/dict_sqlite.c index acfcd4084..7f56cdf30 100644 --- a/postfix/src/global/dict_sqlite.c +++ b/postfix/src/global/dict_sqlite.c @@ -268,7 +268,6 @@ static void sqlite_parse_config(DICT_SQLITE *dict_sqlite, const char *sqlitecf) * query interface if necessary. This simplifies migration from one SQL * database type to another. */ - dict_sqlite->parser = cfg_parser_alloc(sqlitecf); dict_sqlite->dbpath = cfg_get_str(dict_sqlite->parser, "dbpath", "", 1, 0); dict_sqlite->query = cfg_get_str(dict_sqlite->parser, "query", NULL, 0, 0); if (dict_sqlite->query == 0) { @@ -305,13 +304,22 @@ static void sqlite_parse_config(DICT_SQLITE *dict_sqlite, const char *sqlitecf) DICT *dict_sqlite_open(const char *name, int open_flags, int dict_flags) { DICT_SQLITE *dict_sqlite; + CFG_PARSER *parser; /* * Sanity checks. */ if (open_flags != O_RDONLY) - msg_fatal("%s:%s map requires O_RDONLY access mode", - DICT_TYPE_SQLITE, name); + return (dict_surrogate(DICT_TYPE_SQLITE, name, open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_SQLITE, name)); + + /* + * Open the configuration file. + */ + if ((parser = cfg_parser_alloc(name)) == 0) + return (dict_surrogate(DICT_TYPE_SQLITE, name, open_flags, dict_flags, + "open %s: %m", name)); dict_sqlite = (DICT_SQLITE *) dict_alloc(DICT_TYPE_SQLITE, name, sizeof(DICT_SQLITE)); @@ -319,6 +327,7 @@ DICT *dict_sqlite_open(const char *name, int open_flags, int dict_flags) dict_sqlite->dict.close = dict_sqlite_close; dict_sqlite->dict.flags = dict_flags; + dict_sqlite->parser = parser; sqlite_parse_config(dict_sqlite, name); if (sqlite3_open(dict_sqlite->dbpath, &dict_sqlite->db)) diff --git a/postfix/src/global/mail_conf.c b/postfix/src/global/mail_conf.c index ed1b336d1..ae0ae9886 100644 --- a/postfix/src/global/mail_conf.c +++ b/postfix/src/global/mail_conf.c @@ -187,7 +187,8 @@ void mail_conf_suck(void) && geteuid() != 0) /* untrusted */ mail_conf_checkdir(var_config_dir); path = concatenate(var_config_dir, "/", "main.cf", (char *) 0); - dict_load_file(CONFIG_DICT, path); + if (dict_load_file_xt(CONFIG_DICT, path) == 0) + msg_fatal("open %s: %m", path); myfree(path); } diff --git a/postfix/src/global/mail_error.c b/postfix/src/global/mail_error.c index 8e9d40936..0a239b816 100644 --- a/postfix/src/global/mail_error.c +++ b/postfix/src/global/mail_error.c @@ -21,6 +21,9 @@ /* does not exist, and so on. /* .IP "2bounce (MAIL_ERROR_2BOUNCE)" /* A bounce message could not be delivered. +/* .IP "dat (MAIL_ERROR_DATA)" +/* A message could not be delivered because a critical data +/* file was unavailable. /* .IP "policy (MAIL_ERROR_POLICY)" /* Policy violation. This depends on what restrictions have /* been configured locally. @@ -67,6 +70,7 @@ const NAME_MASK mail_error_masks[] = { "bounce", MAIL_ERROR_BOUNCE, "2bounce", MAIL_ERROR_2BOUNCE, + "data", MAIL_ERROR_DATA, "delay", MAIL_ERROR_DELAY, "policy", MAIL_ERROR_POLICY, "protocol", MAIL_ERROR_PROTOCOL, diff --git a/postfix/src/global/mail_error.h b/postfix/src/global/mail_error.h index 89aee07e4..f90c0ef1c 100644 --- a/postfix/src/global/mail_error.h +++ b/postfix/src/global/mail_error.h @@ -26,6 +26,7 @@ #define MAIL_ERROR_RESOURCE (1<<4) #define MAIL_ERROR_2BOUNCE (1<<5) #define MAIL_ERROR_DELAY (1<<6) +#define MAIL_ERROR_DATA (1<<7) extern const NAME_MASK mail_error_masks[]; diff --git a/postfix/src/global/mail_params.c b/postfix/src/global/mail_params.c index dff3b72b8..e312f56e5 100644 --- a/postfix/src/global/mail_params.c +++ b/postfix/src/global/mail_params.c @@ -118,6 +118,7 @@ /* char *var_multi_name; /* bool var_multi_enable; /* bool var_long_queue_ids; +/* bool var_daemon_open_fatal; /* /* void mail_params_init() /* @@ -304,6 +305,7 @@ char *var_multi_group; char *var_multi_name; bool var_multi_enable; bool var_long_queue_ids; +bool var_daemon_open_fatal; const char null_format_string[1] = ""; @@ -517,6 +519,11 @@ void mail_params_init() VAR_MULTI_NAME, DEF_MULTI_NAME, &var_multi_name, 0, 0, 0, }; + static const CONFIG_BOOL_TABLE first_bool_defaults[] = { + /* read and process the following before opening tables. */ + VAR_DAEMON_OPEN_FATAL, DEF_DAEMON_OPEN_FATAL, &var_daemon_open_fatal, + 0, + }; static const CONFIG_STR_FN_TABLE function_str_defaults[] = { VAR_MYHOSTNAME, check_myhostname, &var_myhostname, 1, 0, VAR_MYDOMAIN, check_mydomainname, &var_mydomain, 1, 0, @@ -646,6 +653,14 @@ void mail_params_init() var_config_dir, MAIN_CONF_FILE, VAR_SYSLOG_FACILITY, var_syslog_facility); + /* + * Should daemons terminate after table open error, or should they + * continue execution with reduced functionality? + */ + get_mail_conf_bool_table(first_bool_defaults); + if (var_daemon_open_fatal) + dict_allow_surrogate = 0; + /* * What protocols should we attempt to support? The result is stored in * the global inet_proto_table variable. diff --git a/postfix/src/global/mail_params.h b/postfix/src/global/mail_params.h index 936835e70..912c012c8 100644 --- a/postfix/src/global/mail_params.h +++ b/postfix/src/global/mail_params.h @@ -3623,6 +3623,13 @@ extern bool var_smtp_rec_deadline; #define DEF_SM_FIX_EOL SM_FIX_EOL_ALWAYS extern char *var_sm_fix_eol; + /* + * Gradual degradation, or fatal exit after table open error? + */ +#define VAR_DAEMON_OPEN_FATAL "daemon_table_open_error_is_fatal" +#define DEF_DAEMON_OPEN_FATAL 0 +extern bool var_daemon_open_fatal; + /* LICENSE /* .ad /* .fi diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index a69bbad89..cbb946c71 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -20,7 +20,7 @@ * Patches change both the patchlevel and the release date. Snapshots have no * patchlevel; they change the release date only. */ -#define MAIL_RELEASE_DATE "20120110" +#define MAIL_RELEASE_DATE "20120114" #define MAIL_VERSION_NUMBER "2.9" #ifdef SNAPSHOT diff --git a/postfix/src/global/maps.ref b/postfix/src/global/maps.ref index 8ae750a25..84033c7d8 100644 --- a/postfix/src/global/maps.ref +++ b/postfix/src/global/maps.ref @@ -1,7 +1,7 @@ unknown: dict_open: fail:1maps unknown: dict_register: fail:1maps(0,lock) 1 "": not found -unknown: warning: fail:1maps lookup of foobar failed +unknown: warning: fail:1maps lookup error for "foobar" unknown: maps_find: whatever: foobar: search aborted "foobar": lookup error unknown: maps_free: fail:1maps(0,lock) diff --git a/postfix/src/global/namadr_list.c b/postfix/src/global/namadr_list.c index 66a1fb934..fc7f681ed 100644 --- a/postfix/src/global/namadr_list.c +++ b/postfix/src/global/namadr_list.c @@ -94,6 +94,7 @@ #include #include #include +#include static void usage(char *progname) { @@ -120,6 +121,7 @@ int main(int argc, char **argv) } if (argc != optind + 3) usage(argv[0]); + dict_allow_surrogate = 1; list = namadr_list_init(MATCH_FLAG_PARENT | MATCH_FLAG_RETURN, argv[optind]); host = argv[optind + 1]; addr = argv[optind + 2]; diff --git a/postfix/src/global/namadr_list.in b/postfix/src/global/namadr_list.in index c1cd8f2c4..dfe6ed20c 100644 --- a/postfix/src/global/namadr_list.in +++ b/postfix/src/global/namadr_list.in @@ -39,3 +39,4 @@ env foo=x ./namadr_list !!environ:junk foo 168.100.189.3 env foo=x ./namadr_list !!environ:junk bar 168.100.189.3 ./namadr_list fail:1 bar 168.100.189.3 ./namadr_list !fail:1 bar 168.100.189.3 +./namadr_list `pwd`/nosuchfile bar 168.100.189.3 diff --git a/postfix/src/global/namadr_list.ref b/postfix/src/global/namadr_list.ref index 0515ecea5..1f62aa216 100644 --- a/postfix/src/global/namadr_list.ref +++ b/postfix/src/global/namadr_list.ref @@ -44,3 +44,7 @@ bar/168.100.189.3: NO bar/168.100.189.3: ERROR ./namadr_list: warning: fail:1: table lookup problem bar/168.100.189.3: ERROR +./namadr_list: error: open file /home/wietse/postfix-2.9-20120114/src/global/nosuchfile: No such file or directory +./namadr_list: warning: non-existent:/home/wietse/postfix-2.9-20120114/src/global/nosuchfile is unavailable. open file /home/wietse/postfix-2.9-20120114/src/global/nosuchfile: No such file or directory +./namadr_list: warning: non-existent:/home/wietse/postfix-2.9-20120114/src/global/nosuchfile: table lookup problem +bar/168.100.189.3: ERROR diff --git a/postfix/src/global/smtp_stream.c b/postfix/src/global/smtp_stream.c index fb68e5057..4893f4082 100644 --- a/postfix/src/global/smtp_stream.c +++ b/postfix/src/global/smtp_stream.c @@ -122,8 +122,8 @@ /* the application. /* This error is never generated by the smtp_stream(3) module, but /* is defined for application-specific use. -/* .IP SMTP_ERR_APPL -/* Application error - the program cannot proceed with this +/* .IP SMTP_ERR_DATA +/* Application data error - the program cannot proceed with this /* SMTP session. /* .IP SMTP_ERR_NONE /* A non-error code that makes setjmp()/longjmp() convenient diff --git a/postfix/src/global/smtp_stream.h b/postfix/src/global/smtp_stream.h index 599bcfa29..bdb143675 100644 --- a/postfix/src/global/smtp_stream.h +++ b/postfix/src/global/smtp_stream.h @@ -31,7 +31,7 @@ #define SMTP_ERR_TIME 2 /* time out */ #define SMTP_ERR_QUIET 3 /* silent cleanup (application) */ #define SMTP_ERR_NONE 4 /* non-error case */ -#define SMTP_ERR_APPL 5 /* application error - can't proceed */ +#define SMTP_ERR_DATA 5 /* application data error */ extern void smtp_stream_setup(VSTREAM *, int, int); extern void PRINTFLIKE(2, 3) smtp_printf(VSTREAM *, const char *,...); diff --git a/postfix/src/master/event_server.c b/postfix/src/master/event_server.c index 37d7e0970..b58d22578 100644 --- a/postfix/src/master/event_server.c +++ b/postfix/src/master/event_server.c @@ -594,6 +594,12 @@ NORETURN event_server_main(int argc, char **argv, MULTI_SERVER_FN service,...) */ mail_dict_init(); + /* + * After database open error, continue execution with reduced + * functionality. + */ + dict_allow_surrogate = 1; + /* * Pick up policy settings from master process. Shut up error messages to * stderr, because no-one is going to see them. diff --git a/postfix/src/master/multi_server.c b/postfix/src/master/multi_server.c index 0b5e678c6..c16118db3 100644 --- a/postfix/src/master/multi_server.c +++ b/postfix/src/master/multi_server.c @@ -589,6 +589,12 @@ NORETURN multi_server_main(int argc, char **argv, MULTI_SERVER_FN service,...) * Register dictionaries that use higher-level interfaces and protocols. */ mail_dict_init(); + + /* + * After database open error, continue execution with reduced + * functionality. + */ + dict_allow_surrogate = 1; /* * Pick up policy settings from master process. Shut up error messages to diff --git a/postfix/src/master/single_server.c b/postfix/src/master/single_server.c index aba479cd5..7b6d8bfd7 100644 --- a/postfix/src/master/single_server.c +++ b/postfix/src/master/single_server.c @@ -472,6 +472,12 @@ NORETURN single_server_main(int argc, char **argv, SINGLE_SERVER_FN service,...) * Register dictionaries that use higher-level interfaces and protocols. */ mail_dict_init(); + + /* + * After database open error, continue execution with reduced + * functionality. + */ + dict_allow_surrogate = 1; /* * Pick up policy settings from master process. Shut up error messages to diff --git a/postfix/src/master/trigger_server.c b/postfix/src/master/trigger_server.c index df3bf2054..bc4a16cea 100644 --- a/postfix/src/master/trigger_server.c +++ b/postfix/src/master/trigger_server.c @@ -483,6 +483,12 @@ NORETURN trigger_server_main(int argc, char **argv, TRIGGER_SERVER_FN service,.. * Register dictionaries that use higher-level interfaces and protocols. */ mail_dict_init(); + + /* + * After database open error, continue execution with reduced + * functionality. + */ + dict_allow_surrogate = 1; /* * Pick up policy settings from master process. Shut up error messages to diff --git a/postfix/src/postconf/postconf_main.c b/postfix/src/postconf/postconf_main.c index 9411d7dc4..ad96c9afb 100644 --- a/postfix/src/postconf/postconf_main.c +++ b/postfix/src/postconf/postconf_main.c @@ -107,7 +107,8 @@ void read_parameters(void) */ set_config_dir(); path = concatenate(var_config_dir, "/", MAIN_CONF_FILE, (char *) 0); - dict_load_file(CONFIG_DICT, path); + if (dict_load_file_xt(CONFIG_DICT, path) == 0) + msg_fatal("open %s: %m", path); myfree(path); } diff --git a/postfix/src/smtp/smtp.c b/postfix/src/smtp/smtp.c index 831cfc7bf..a13cd2eef 100644 --- a/postfix/src/smtp/smtp.c +++ b/postfix/src/smtp/smtp.c @@ -1103,7 +1103,7 @@ static void pre_init(char *unused_name, char **unused_argv) * Session cache domain list. */ if (*var_smtp_cache_dest) - smtp_cache_dest = string_list_init(MATCH_FLAG_NONE, var_smtp_cache_dest); + smtp_cache_dest = string_list_init(MATCH_FLAG_RETURN, var_smtp_cache_dest); /* * EHLO keyword filter. diff --git a/postfix/src/smtp/smtp_chat.c b/postfix/src/smtp/smtp_chat.c index 1aefaf412..cad15c4bf 100644 --- a/postfix/src/smtp/smtp_chat.c +++ b/postfix/src/smtp/smtp_chat.c @@ -306,7 +306,7 @@ SMTP_RESP *smtp_chat_resp(SMTP_SESSION *session) smtp_chat_resp_filter->type, smtp_chat_resp_filter->name, printable(STR(session->buffer), '?')); - vstream_longjmp(session->stream, SMTP_ERR_APPL); + vstream_longjmp(session->stream, SMTP_ERR_DATA); } } if (chat_append_flag) { diff --git a/postfix/src/smtp/smtp_proto.c b/postfix/src/smtp/smtp_proto.c index e348da80f..e1775b1cf 100644 --- a/postfix/src/smtp/smtp_proto.c +++ b/postfix/src/smtp/smtp_proto.c @@ -456,7 +456,7 @@ int smtp_helo(SMTP_STATE *state) msg_warn("%s: %s map lookup error for %s", session->state->request->queue_id, smtp_ehlo_dis_maps->title, state->session->addr); - vstream_longjmp(session->stream, SMTP_ERR_APPL); + vstream_longjmp(session->stream, SMTP_ERR_DATA); } discard_mask = ehlo_mask(ehlo_words); if (discard_mask && !(discard_mask & EHLO_MASK_SILENT)) @@ -996,7 +996,7 @@ static void smtp_header_rewrite(void *context, int header_class, if (result == HBC_CHECKS_STAT_ERROR) { msg_warn("%s: smtp header checks lookup error", state->request->queue_id); - vstream_longjmp(state->session->stream, SMTP_ERR_APPL); + vstream_longjmp(state->session->stream, SMTP_ERR_DATA); } if (result != STR(buf)) { vstring_strcpy(buf, result); @@ -1098,7 +1098,7 @@ static void smtp_body_rewrite(void *context, int type, } else if (result == HBC_CHECKS_STAT_ERROR) { msg_warn("%s: smtp body checks lookup error", state->request->queue_id); - vstream_longjmp(state->session->stream, SMTP_ERR_APPL); + vstream_longjmp(state->session->stream, SMTP_ERR_DATA); } else if (result != 0) { smtp_text_out(state, type, result, strlen(result), offset); myfree(result); diff --git a/postfix/src/smtp/smtp_sasl_glue.c b/postfix/src/smtp/smtp_sasl_glue.c index 16fc940f4..7ba70bb1d 100644 --- a/postfix/src/smtp/smtp_sasl_glue.c +++ b/postfix/src/smtp/smtp_sasl_glue.c @@ -206,7 +206,7 @@ int smtp_sasl_passwd_lookup(SMTP_SESSION *session) } else if (smtp_sasl_passwd_map->error) { msg_warn("%s: %s lookup error", state->request->queue_id, smtp_sasl_passwd_map->title); - vstream_longjmp(session->stream, SMTP_ERR_APPL); + vstream_longjmp(session->stream, SMTP_ERR_DATA); } else { if (msg_verbose) msg_info("%s: no auth info found (sender=`%s', host=`%s')", diff --git a/postfix/src/smtp/smtp_session.c b/postfix/src/smtp/smtp_session.c index 4ee8e872a..e98695450 100644 --- a/postfix/src/smtp/smtp_session.c +++ b/postfix/src/smtp/smtp_session.c @@ -227,7 +227,7 @@ static int tls_policy_lookup_one(SMTP_SESSION *session, int *site_level, msg_warn("%s: %s lookup error for %s", session->state->request->queue_id, tls_policy->title, site_name); - vstream_longjmp(session->stream, SMTP_ERR_APPL); + vstream_longjmp(session->stream, SMTP_ERR_DATA); } if (cbuf == 0) cbuf = vstring_alloc(10); diff --git a/postfix/src/smtp/smtp_trouble.c b/postfix/src/smtp/smtp_trouble.c index 706bc5d4d..0e7fafd46 100644 --- a/postfix/src/smtp/smtp_trouble.c +++ b/postfix/src/smtp/smtp_trouble.c @@ -431,7 +431,8 @@ int smtp_stream_except(SMTP_STATE *state, int code, const char *description) dsb_simple(why, "4.4.2", "conversation with %s timed out while %s", session->namaddr, description); break; - case SMTP_ERR_APPL: + case SMTP_ERR_DATA: + session->error_mask |= MAIL_ERROR_DATA; dsb_simple(why, "4.3.0", "local data error while talking to %s", session->namaddr); } diff --git a/postfix/src/smtpd/smtpd.c b/postfix/src/smtpd/smtpd.c index d4b30a6d2..b2bfd231d 100644 --- a/postfix/src/smtpd/smtpd.c +++ b/postfix/src/smtpd/smtpd.c @@ -1390,8 +1390,9 @@ static int sasl_client_exception(SMTPD_STATE *state) if (sasl_exceptions_networks == 0) return (0); - match = namadr_list_match(sasl_exceptions_networks, - state->name, state->addr); + if ((match = namadr_list_match(sasl_exceptions_networks, + state->name, state->addr)) == 0) + match = sasl_exceptions_networks->error; if (msg_verbose) msg_info("sasl_exceptions: %s, match=%d", @@ -1554,13 +1555,31 @@ static int helo_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) return (0); } +/* cant_announce_feature - explain and terminate this session */ + +static NORETURN cant_announce_feature(SMTPD_STATE *state, const char *feature) +{ + msg_warn("don't know if feature %s should be announced to %s", + feature, state->namaddr); + vstream_longjmp(state->client, SMTP_ERR_DATA); +} + +/* cant_permit_command - explain and terminate this session */ + +static NORETURN cant_permit_command(SMTPD_STATE *state, const char *command) +{ + msg_warn("don't know if command %s should be allowed from %s", + command, state->namaddr); + vstream_longjmp(state->client, SMTP_ERR_DATA); +} + /* ehlo_cmd - process EHLO command */ static int ehlo_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) { const char *err; int discard_mask; - VSTRING *reply_buf; + char **cpp; /* * XXX 2821 new feature: Section 4.1.4 specifies that a server must clear @@ -1627,23 +1646,21 @@ static int ehlo_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) } /* - * Build the EHLO response, suppressing features as requested. We store - * each output line in a one-element output queue, where it sits until we - * know if we need to prepend "250-" or "250 " to it. Each time we - * enqueue a reply line we flush the one that sits in the queue. We use a - * couple ugly macros to avoid making mistakes in code that repeats a - * lot. + * Build the EHLO response, producing no output until we know what to + * send - this simplifies exception handling. The CRLF record boundaries + * don't exist at this level in the code, so we represent multi-line + * output as an array of single-line responses. */ -#define ENQUEUE_FIX_REPLY(state, reply_buf, cmd) \ +#define EHLO_APPEND(state, cmd) \ do { \ - smtpd_chat_reply((state), "250-%s", STR(reply_buf)); \ - vstring_strcpy((reply_buf), (cmd)); \ + vstring_sprintf((state)->ehlo_buf, (cmd)); \ + argv_add((state)->ehlo_argv, STR((state)->ehlo_buf), (char *) 0); \ } while (0) -#define ENQUEUE_FMT_REPLY(state, reply_buf, fmt, arg) \ +#define EHLO_APPEND1(state, cmd, arg) \ do { \ - smtpd_chat_reply((state), "250-%s", STR(reply_buf)); \ - vstring_sprintf((reply_buf), (fmt), (arg)); \ + vstring_sprintf((state)->ehlo_buf, (cmd), (arg)); \ + argv_add((state)->ehlo_argv, STR((state)->ehlo_buf), (char *) 0); \ } while (0) /* @@ -1659,72 +1676,103 @@ static int ehlo_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) if ((discard_mask & EHLO_MASK_ENHANCEDSTATUSCODES) == 0) if (discard_mask && !(discard_mask & EHLO_MASK_SILENT)) msg_info("discarding EHLO keywords: %s", str_ehlo_mask(discard_mask)); + if (ehlo_discard_maps && ehlo_discard_maps->error) { + msg_warn("don't know what features to announce in EHLO"); + vstream_longjmp(state->client, SMTP_ERR_DATA); + } + + /* + * These may still exist after a prior exception. + */ + if (state->ehlo_argv == 0) { + state->ehlo_argv = argv_alloc(10); + state->ehlo_buf = vstring_alloc(10); + } else + argv_truncate(state->ehlo_argv, 0); - reply_buf = vstring_alloc(10); - vstring_strcpy(reply_buf, var_myhostname); + EHLO_APPEND1(state, "%s", var_myhostname); if ((discard_mask & EHLO_MASK_PIPELINING) == 0) - ENQUEUE_FIX_REPLY(state, reply_buf, "PIPELINING"); + EHLO_APPEND(state, "PIPELINING"); if ((discard_mask & EHLO_MASK_SIZE) == 0) { if (var_message_limit) - ENQUEUE_FMT_REPLY(state, reply_buf, "SIZE %lu", - (unsigned long) var_message_limit); /* XXX */ + EHLO_APPEND1(state, "SIZE %lu", + (unsigned long) var_message_limit); /* XXX */ else - ENQUEUE_FIX_REPLY(state, reply_buf, "SIZE"); + EHLO_APPEND(state, "SIZE"); } if ((discard_mask & EHLO_MASK_VRFY) == 0) if (var_disable_vrfy_cmd == 0) - ENQUEUE_FIX_REPLY(state, reply_buf, SMTPD_CMD_VRFY); + EHLO_APPEND(state, SMTPD_CMD_VRFY); if ((discard_mask & EHLO_MASK_ETRN) == 0) - ENQUEUE_FIX_REPLY(state, reply_buf, SMTPD_CMD_ETRN); + EHLO_APPEND(state, SMTPD_CMD_ETRN); #ifdef USE_TLS if ((discard_mask & EHLO_MASK_STARTTLS) == 0) if (var_smtpd_use_tls && (!state->tls_context)) - ENQUEUE_FIX_REPLY(state, reply_buf, SMTPD_CMD_STARTTLS); + EHLO_APPEND(state, SMTPD_CMD_STARTTLS); #endif #ifdef USE_SASL_AUTH +#ifndef AUTH_CMD +#define AUTH_CMD "AUTH" +#endif if ((discard_mask & EHLO_MASK_AUTH) == 0) { if (smtpd_sasl_is_active(state) && !sasl_client_exception(state)) { - ENQUEUE_FMT_REPLY(state, reply_buf, "AUTH %s", - state->sasl_mechanism_list); + EHLO_APPEND1(state, "AUTH %s", state->sasl_mechanism_list); if (var_broken_auth_clients) - ENQUEUE_FMT_REPLY(state, reply_buf, "AUTH=%s", - state->sasl_mechanism_list); - } + EHLO_APPEND1(state, "AUTH=%s", state->sasl_mechanism_list); + } else if (sasl_exceptions_networks && sasl_exceptions_networks->error) + cant_announce_feature(state, AUTH_CMD); } #define XCLIENT_LOGIN_KLUDGE " " XCLIENT_LOGIN #else #define XCLIENT_LOGIN_KLUDGE "" #endif - if ((discard_mask & EHLO_MASK_VERP) == 0) + if ((discard_mask & EHLO_MASK_VERP) == 0) { if (namadr_list_match(verp_clients, state->name, state->addr)) - ENQUEUE_FIX_REPLY(state, reply_buf, VERP_CMD); + EHLO_APPEND(state, VERP_CMD); + else if (verp_clients && verp_clients->error) + cant_announce_feature(state, VERP_CMD); + } /* XCLIENT must not override its own access control. */ - if ((discard_mask & EHLO_MASK_XCLIENT) == 0) + if ((discard_mask & EHLO_MASK_XCLIENT) == 0) { if (xclient_allowed) - ENQUEUE_FIX_REPLY(state, reply_buf, XCLIENT_CMD - " " XCLIENT_NAME " " XCLIENT_ADDR - " " XCLIENT_PROTO " " XCLIENT_HELO - " " XCLIENT_REVERSE_NAME " " XCLIENT_PORT - XCLIENT_LOGIN_KLUDGE); - if ((discard_mask & EHLO_MASK_XFORWARD) == 0) + EHLO_APPEND(state, XCLIENT_CMD + " " XCLIENT_NAME " " XCLIENT_ADDR + " " XCLIENT_PROTO " " XCLIENT_HELO + " " XCLIENT_REVERSE_NAME " " XCLIENT_PORT + XCLIENT_LOGIN_KLUDGE); + else if (xclient_hosts && xclient_hosts->error) + cant_announce_feature(state, XCLIENT_CMD); + } + if ((discard_mask & EHLO_MASK_XFORWARD) == 0) { if (xforward_allowed) - ENQUEUE_FIX_REPLY(state, reply_buf, XFORWARD_CMD - " " XFORWARD_NAME " " XFORWARD_ADDR - " " XFORWARD_PROTO " " XFORWARD_HELO - " " XFORWARD_DOMAIN " " XFORWARD_PORT - " " XFORWARD_IDENT); + EHLO_APPEND(state, XFORWARD_CMD + " " XFORWARD_NAME " " XFORWARD_ADDR + " " XFORWARD_PROTO " " XFORWARD_HELO + " " XFORWARD_DOMAIN " " XFORWARD_PORT + " " XFORWARD_IDENT); + else if (xforward_hosts && xforward_hosts->error) + cant_announce_feature(state, XFORWARD_CMD); + } if ((discard_mask & EHLO_MASK_ENHANCEDSTATUSCODES) == 0) - ENQUEUE_FIX_REPLY(state, reply_buf, "ENHANCEDSTATUSCODES"); + EHLO_APPEND(state, "ENHANCEDSTATUSCODES"); if ((discard_mask & EHLO_MASK_8BITMIME) == 0) - ENQUEUE_FIX_REPLY(state, reply_buf, "8BITMIME"); + EHLO_APPEND(state, "8BITMIME"); if ((discard_mask & EHLO_MASK_DSN) == 0) - ENQUEUE_FIX_REPLY(state, reply_buf, "DSN"); - smtpd_chat_reply(state, "250 %s", STR(reply_buf)); + EHLO_APPEND(state, "DSN"); + + /* + * Send the reply. + */ + for (cpp = state->ehlo_argv->argv; *cpp; cpp++) + smtpd_chat_reply(state, "250%c%s", cpp[1] ? '-' : ' ', *cpp); /* * Clean up. */ - vstring_free(reply_buf); + argv_free(state->ehlo_argv); + state->ehlo_argv = 0; + vstring_free(state->ehlo_buf); + state->ehlo_buf = 0; return (0); } @@ -1739,6 +1787,14 @@ static void helo_reset(SMTPD_STATE *state) if (SMTPD_STAND_ALONE(state) == 0 && smtpd_milters != 0) milter_abort(smtpd_milters); } + if (state->ehlo_argv) { + argv_free(state->ehlo_argv); + state->ehlo_argv = 0; + } + if (state->ehlo_buf) { + vstring_free(state->ehlo_buf); + state->ehlo_buf = 0; + } } /* mail_open_stream - open mail queue file or IPC stream */ @@ -3486,6 +3542,8 @@ static int xclient_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) XCLIENT_CMD); return (-1); } + if (xclient_hosts && xclient_hosts->error) + cant_permit_command(state, XCLIENT_CMD); if (!xclient_allowed) { state->error_mask |= MAIL_ERROR_POLICY; smtpd_chat_reply(state, "550 5.7.0 Error: insufficient authorization"); @@ -3780,6 +3838,8 @@ static int xforward_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) XFORWARD_CMD); return (-1); } + if (xforward_hosts && xforward_hosts->error) + cant_permit_command(state, XFORWARD_CMD); if (!xforward_allowed) { state->error_mask |= MAIL_ERROR_POLICY; smtpd_chat_reply(state, "550 5.7.0 Error: insufficient authorization"); @@ -4429,11 +4489,12 @@ static void smtpd_proto(SMTPD_STATE *state) case SMTP_ERR_QUIET: break; - case SMTP_ERR_APPL: + case SMTP_ERR_DATA: msg_info("%s: reject: %s from %s: " "421 4.3.0 %s Server configuration error", (state->queue_id ? state->queue_id : "NOQUEUE"), state->where, state->namaddr, var_myhostname); + state->error_mask |= MAIL_ERROR_DATA; if (vstream_setjmp(state->client) == 0) smtpd_chat_reply(state, "421 4.3.0 %s Server configuration error", var_myhostname); @@ -4538,8 +4599,6 @@ static void smtpd_proto(SMTPD_STATE *state) if (ehlo_discard_maps == 0 || (ehlo_words = maps_find(ehlo_discard_maps, state->addr, 0)) == 0) ehlo_words = var_smtpd_ehlo_dis_words; - if (ehlo_discard_maps && ehlo_discard_maps->error) - vstream_longjmp(state->client, SMTP_ERR_APPL); state->ehlo_discard_mask = ehlo_mask(ehlo_words); /* XXX We use the real client for connect access control. */ @@ -4638,7 +4697,7 @@ static void smtpd_proto(SMTPD_STATE *state) msg_warn("%s:%s lookup error for \"%.100s\"", smtpd_cmd_filter->type, smtpd_cmd_filter->name, printable(STR(state->buffer), '?')); - vstream_longjmp(state->client, SMTP_ERR_APPL); + vstream_longjmp(state->client, SMTP_ERR_DATA); } } if ((argc = smtpd_token(vstring_str(state->buffer), &argv)) == 0) { @@ -4647,6 +4706,7 @@ static void smtpd_proto(SMTPD_STATE *state) state->error_count++; continue; } + /* Ignore smtpd_noop_cmds lookup errors. Non-critical feature. */ if (*var_smtpd_noop_cmds && string_list_match(smtpd_noop_cmds, argv[0].strval)) { smtpd_chat_reply(state, "250 2.0.0 Ok"); @@ -4657,6 +4717,7 @@ static void smtpd_proto(SMTPD_STATE *state) for (cmdp = smtpd_cmd_table; cmdp->name != 0; cmdp++) if (strcasecmp(argv[0].strval, cmdp->name) == 0) break; + /* Ignore smtpd_forbid_cmds lookup errors. Non-critical feature. */ if (cmdp->name == 0) { state->where = SMTPD_CMD_UNKNOWN; if (is_header(argv[0].strval) @@ -4886,11 +4947,11 @@ static void pre_jail_init(char *unused_name, char **unused_argv) * Initialize blacklist/etc. patterns before entering the chroot jail, in * case they specify a filename pattern. */ - smtpd_noop_cmds = string_list_init(MATCH_FLAG_NONE, var_smtpd_noop_cmds); - smtpd_forbid_cmds = string_list_init(MATCH_FLAG_NONE, var_smtpd_forbid_cmds); - verp_clients = namadr_list_init(MATCH_FLAG_NONE, var_verp_clients); - xclient_hosts = namadr_list_init(MATCH_FLAG_NONE, var_xclient_hosts); - xforward_hosts = namadr_list_init(MATCH_FLAG_NONE, var_xforward_hosts); + smtpd_noop_cmds = string_list_init(MATCH_FLAG_RETURN, var_smtpd_noop_cmds); + smtpd_forbid_cmds = string_list_init(MATCH_FLAG_RETURN, var_smtpd_forbid_cmds); + verp_clients = namadr_list_init(MATCH_FLAG_RETURN, var_verp_clients); + xclient_hosts = namadr_list_init(MATCH_FLAG_RETURN, var_xclient_hosts); + xforward_hosts = namadr_list_init(MATCH_FLAG_RETURN, var_xforward_hosts); hogger_list = namadr_list_init(MATCH_FLAG_RETURN, var_smtpd_hoggers); /* @@ -4914,7 +4975,7 @@ static void pre_jail_init(char *unused_name, char **unused_argv) if (*var_smtpd_sasl_exceptions_networks) sasl_exceptions_networks = - namadr_list_init(MATCH_FLAG_NONE, + namadr_list_init(MATCH_FLAG_RETURN, var_smtpd_sasl_exceptions_networks); #else msg_warn("%s is true, but SASL support is not compiled in", diff --git a/postfix/src/smtpd/smtpd.h b/postfix/src/smtpd/smtpd.h index 4001a8145..cc4590682 100644 --- a/postfix/src/smtpd/smtpd.h +++ b/postfix/src/smtpd/smtpd.h @@ -177,6 +177,12 @@ typedef struct { const char **milter_argv; /* SMTP command vector */ ssize_t milter_argc; /* SMTP command vector */ const char *milter_reject_text; /* input to call-back from Milter */ + + /* + * EHLO temporary space. + */ + VSTRING *ehlo_buf; + ARGV *ehlo_argv; } SMTPD_STATE; #define SMTPD_FLAG_HANGUP (1<<0) /* 421/521 disconnect */ diff --git a/postfix/src/smtpd/smtpd_chat.c b/postfix/src/smtpd/smtpd_chat.c index 9cc30385e..a1016cd9c 100644 --- a/postfix/src/smtpd/smtpd_chat.c +++ b/postfix/src/smtpd/smtpd_chat.c @@ -182,7 +182,10 @@ void smtpd_chat_reply(SMTPD_STATE *state, const char *format,...) /* This is why we use strlen() above instead of VSTRING_LEN(). */ if ((next = strstr(cp, "\r\n")) != 0) { *next = 0; - cp[3] = '-'; /* contact footer kludge */ + if (next[2] != 0) + cp[3] = '-'; /* contact footer kludge */ + else + next = end; /* strip trailing \r\n */ } else { next = end; } diff --git a/postfix/src/smtpd/smtpd_check.c b/postfix/src/smtpd/smtpd_check.c index 5bfaf7357..341ae3d42 100644 --- a/postfix/src/smtpd/smtpd_check.c +++ b/postfix/src/smtpd/smtpd_check.c @@ -897,7 +897,7 @@ static int defer_if(SMTPD_DEFER *defer, int error_class, static NORETURN reject_dict_retry(SMTPD_STATE *state, const char *reply_name) { - longjmp(smtpd_check_buf, smtpd_check_reject(state, MAIL_ERROR_RESOURCE, + longjmp(smtpd_check_buf, smtpd_check_reject(state, MAIL_ERROR_DATA, 451, "4.3.0", "<%s>: Temporary lookup failure", reply_name)); diff --git a/postfix/src/smtpd/smtpd_error.ref b/postfix/src/smtpd/smtpd_error.ref index b30f7c300..44675cb2e 100644 --- a/postfix/src/smtpd/smtpd_error.ref +++ b/postfix/src/smtpd/smtpd_error.ref @@ -70,8 +70,8 @@ OK >>> # >>> rcpt reject@dunno.domain ./smtpd_check: warning: non-null host address bits in "168.100.189.1/27", perhaps you should use "168.100.189.0/27" instead -./smtpd_check: : reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.5 Server configuration error; from= to= proto=SMTP helo= -451 4.3.5 Server configuration error +./smtpd_check: : reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 : Temporary lookup failure; from= to= proto=SMTP helo= +451 4.3.0 : Temporary lookup failure >>> # >>> # check_sender_access specific >>> # @@ -91,7 +91,7 @@ OK >>> recipient_restrictions permit_tls_clientcerts OK >>> rcpt reject@dunno.domain -./smtpd_check: warning: fail:1_certs lookup of abcdef failed +./smtpd_check: warning: fail:1_certs lookup error for "abcdef" ./smtpd_check: warning: relay_clientcerts: lookup error for fingerprint 'abcdef', pkey fingerprint abcdef ./smtpd_check: : reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 : Temporary lookup failure; from=<> to= proto=SMTP helo= 451 4.3.0 : Temporary lookup failure @@ -130,6 +130,6 @@ OK >>> virtual_alias_maps fail:1_virtual OK >>> rcpt user@example.com -./smtpd_check: warning: fail:1_virtual lookup of user@example.com failed +./smtpd_check: warning: fail:1_virtual lookup error for "user@example.com" ./smtpd_check: : reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 : Temporary lookup failure; from=<> to= proto=SMTP helo= 451 4.3.0 : Temporary lookup failure diff --git a/postfix/src/smtpd/smtpd_state.c b/postfix/src/smtpd/smtpd_state.c index 43baa74bc..c03dfbef8 100644 --- a/postfix/src/smtpd/smtpd_state.c +++ b/postfix/src/smtpd/smtpd_state.c @@ -174,6 +174,9 @@ void smtpd_state_init(SMTPD_STATE *state, VSTREAM *stream, * Initialize the conversation history. */ smtpd_chat_reset(state); + + state->ehlo_argv = 0; + state->ehlo_buf = 0; } /* smtpd_state_reset - cleanup after disconnect */ diff --git a/postfix/src/util/Makefile.in b/postfix/src/util/Makefile.in index 368a1cea8..cff7d8a05 100644 --- a/postfix/src/util/Makefile.in +++ b/postfix/src/util/Makefile.in @@ -34,7 +34,7 @@ SRCS = alldig.c allprint.c argv.c argv_split.c attr_clnt.c attr_print0.c \ unix_pass_listen.c unix_pass_trigger.c edit_file.c inet_windowsize.c \ unix_pass_fd_fix.c dict_cache.c valid_utf_8.c dict_thash.c \ ip_match.c nbbio.c stream_pass_connect.c base32_code.c dict_test.c \ - dict_fail.c msg_rate_delay.c + dict_fail.c msg_rate_delay.c dict_surrogate.c OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \ attr_print64.o attr_print_plain.o attr_scan0.o attr_scan64.o \ attr_scan_plain.o auto_clnt.o base64_code.o basename.o binhash.o \ @@ -70,7 +70,7 @@ OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \ unix_pass_listen.o unix_pass_trigger.o edit_file.o inet_windowsize.o \ unix_pass_fd_fix.o dict_cache.o valid_utf_8.o dict_thash.o \ ip_match.o nbbio.o stream_pass_connect.o base32_code.o dict_test.o \ - dict_fail.o msg_rate_delay.o + dict_fail.o msg_rate_delay.o dict_surrogate.o HDRS = argv.h attr.h attr_clnt.h auto_clnt.h base64_code.h binhash.h \ chroot_uid.h cidr_match.h clean_env.h connect.h ctable.h dict.h \ dict_cdb.h dict_cidr.h dict_db.h dict_dbm.h dict_env.h dict_ht.h \ @@ -444,7 +444,7 @@ tests: valid_hostname_test mac_expand_test dict_test unescape_test \ attr_scan64_test attr_scan0_test dict_pcre_test host_port_test \ dict_cidr_test attr_scan_plain_test htable_test hex_code_test \ myaddrinfo_test format_tv_test ip_match_test name_mask_tests \ - base32_code_test + base32_code_test dict_thash_test root_tests: @@ -628,6 +628,11 @@ name_mask_test9: name_mask name_mask.in name_mask.ref9 base32_code_test: base32_code ./base32_code +dict_thash_test: ../postmap/postmap dict_thash.map + ../postmap/postmap -s texthash:dict_thash.map >dict_thash.tmp 2>&1 + sort dict_thash.tmp | diff -b dict_thash.map - + rm -f dict_thash.tmp + depend: $(MAKES) (sed '1,/^# do not edit/!d' Makefile.in; \ set -e; for i in [a-z][a-z0-9]*.c; do \ @@ -1042,6 +1047,15 @@ dict_static.o: sys_defs.h dict_static.o: vbuf.h dict_static.o: vstream.h dict_static.o: vstring.h +dict_surrogate.o: argv.h +dict_surrogate.o: dict.h +dict_surrogate.o: dict_surrogate.c +dict_surrogate.o: msg.h +dict_surrogate.o: mymalloc.h +dict_surrogate.o: sys_defs.h +dict_surrogate.o: vbuf.h +dict_surrogate.o: vstream.h +dict_surrogate.o: vstring.h dict_tcp.o: argv.h dict_tcp.o: connect.h dict_tcp.o: dict.h diff --git a/postfix/src/util/dict.c b/postfix/src/util/dict.c index baeff9d20..875c5c513 100644 --- a/postfix/src/util/dict.c +++ b/postfix/src/util/dict.c @@ -49,7 +49,7 @@ /* /* const char *dict_changed_name() /* AUXILIARY FUNCTIONS -/* void dict_load_file(dict_name, path) +/* int dict_load_file_xt(dict_name, path) /* const char *dict_name; /* const char *path; /* @@ -141,13 +141,14 @@ /* be re-opened because it has changed or because it was unlinked. /* A non-zero result is the name of a changed dictionary. /* -/* dict_load_file() reads name-value entries from the named file. +/* dict_load_file_xt() reads name-value entries from the named file. /* Lines that begin with whitespace are concatenated to the preceding /* line (the newline is deleted). /* Each entry is stored in the dictionary named by \fIdict_name\fR. +/* The result is zero if the file could not be opened. /* /* dict_load_fp() reads name-value entries from an open stream. -/* It has the same semantics as the dict_load_file() function. +/* It has the same semantics as the dict_load_file_xt() function. /* /* dict_flags_str() returns a printable representation of the /* specified dictionary flags. The result is overwritten upon @@ -383,9 +384,9 @@ int dict_error(const char *dict_name) return (dict ? dict->error : DICT_ERR_NONE); } -/* dict_load_file - read entries from text file */ +/* dict_load_file_xt - read entries from text file */ -void dict_load_file(const char *dict_name, const char *path) +int dict_load_file_xt(const char *dict_name, const char *path) { VSTREAM *fp; struct stat st; @@ -398,7 +399,7 @@ void dict_load_file(const char *dict_name, const char *path) */ for (before = time((time_t *) 0); /* see below */ ; before = after) { if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0) - msg_fatal("open %s: %m", path); + return (0); dict_load_fp(dict_name, fp); if (fstat(vstream_fileno(fp), &st) < 0) msg_fatal("fstat %s: %m", path); @@ -411,6 +412,7 @@ void dict_load_file(const char *dict_name, const char *path) msg_info("pausing to let %s cool down", path); doze(300000); } + return (1); } /* dict_load_fp - read entries from open stream */ diff --git a/postfix/src/util/dict.h b/postfix/src/util/dict.h index 90536a226..f8e91a405 100644 --- a/postfix/src/util/dict.h +++ b/postfix/src/util/dict.h @@ -96,7 +96,8 @@ extern DICT *dict_debug(DICT *); * map implementation itself upon open, lookup etc. requests. * * DICT_FLAG_RQST_MASK - all requestor flags, including paranoid flags, that - * the requestor may change between open, lookup etc. requests. + * the requestor may change between open, lookup etc. requests. These + * specify requestor properties, not map properties. * * DICT_FLAG_INST_MASK - none of the above flags. The requestor may not change * these flags between open, lookup, etc. requests (although a map may make @@ -154,7 +155,7 @@ extern int dict_update(const char *, const char *, const char *); extern const char *dict_lookup(const char *, const char *); extern int dict_delete(const char *, const char *); extern int dict_sequence(const char *, const int, const char **, const char **); -extern void dict_load_file(const char *, const char *); +extern int dict_load_file_xt(const char *, const char *); extern void dict_load_fp(const char *, VSTREAM *); extern const char *dict_eval(const char *, const char *, int); extern int dict_error(const char *); @@ -182,6 +183,18 @@ extern const char *dict_flags_str(int); */ void dict_test(int, char **); + /* + * Behind-the-scenes support to continue execution with reduced + * functionality. + */ +extern int dict_allow_surrogate; +extern DICT *dict_surrogate(const char *, const char *, int, int, const char *,...); + + /* + * This name is reserved for matchlist error handling. + */ +#define DICT_TYPE_NOFILE "non-existent" + /* LICENSE /* .ad /* .fi diff --git a/postfix/src/util/dict_alloc.c b/postfix/src/util/dict_alloc.c index b68421021..03321f1da 100644 --- a/postfix/src/util/dict_alloc.c +++ b/postfix/src/util/dict_alloc.c @@ -65,7 +65,7 @@ static const char *dict_default_lookup(DICT *dict, const char *unused_key) { - msg_fatal("%s table %s: lookup operation is not supported", + msg_fatal("table %s:%s: lookup operation is not supported", dict->type, dict->name); } @@ -74,7 +74,7 @@ static const char *dict_default_lookup(DICT *dict, const char *unused_key) static int dict_default_update(DICT *dict, const char *unused_key, const char *unused_value) { - msg_fatal("%s table %s: update operation is not supported", + msg_fatal("table %s:%s: update operation is not supported", dict->type, dict->name); } @@ -82,7 +82,7 @@ static int dict_default_update(DICT *dict, const char *unused_key, static int dict_default_delete(DICT *dict, const char *unused_key) { - msg_fatal("%s table %s: delete operation is not supported", + msg_fatal("table %s:%s: delete operation is not supported", dict->type, dict->name); } @@ -91,7 +91,7 @@ static int dict_default_delete(DICT *dict, const char *unused_key) static int dict_default_sequence(DICT *dict, int unused_function, const char **unused_key, const char **unused_value) { - msg_fatal("%s table %s: sequence operation is not supported", + msg_fatal("table %s:%s: sequence operation is not supported", dict->type, dict->name); } @@ -99,7 +99,7 @@ static int dict_default_sequence(DICT *dict, int unused_function, static void dict_default_close(DICT *dict) { - msg_fatal("%s table %s: close operation is not supported", + msg_fatal("table %s:%s: close operation is not supported", dict->type, dict->name); } diff --git a/postfix/src/util/dict_cdb.c b/postfix/src/util/dict_cdb.c index 099ecc99d..9de10fdf0 100644 --- a/postfix/src/util/dict_cdb.c +++ b/postfix/src/util/dict_cdb.c @@ -184,7 +184,8 @@ static DICT *dict_cdbq_open(const char *path, int dict_flags) cdb_path = concatenate(path, CDB_SUFFIX, (char *) 0); if ((fd = open(cdb_path, O_RDONLY)) < 0) - msg_fatal("open database %s: %m", cdb_path); + return (dict_surrogate(DICT_TYPE_CDB, path, O_RDONLY, dict_flags, + "open database %s: %m", cdb_path)); dict_cdbq = (DICT_CDBQ *) dict_alloc(DICT_TYPE_CDB, cdb_path, sizeof(*dict_cdbq)); @@ -339,9 +340,11 @@ static DICT *dict_cdbm_open(const char *path, int dict_flags) * isn't creating it at the same time. */ for (;;) { - if ((fd = open(tmp_path, O_RDWR | O_CREAT, 0644)) < 0 - || fstat(fd, &st0) < 0) - msg_fatal("open database %s: %m", tmp_path); + if ((fd = open(tmp_path, O_RDWR | O_CREAT, 0644)) < 0) + return (dict_surrogate(DICT_TYPE_CDB, path, O_RDWR, dict_flags, + "open database %s: %m", tmp_path)); + if (fstat(fd, &st0) < 0) + msg_fatal("fstat(%s): %m", tmp_path); /* * Get an exclusive lock - we're going to change the database so we diff --git a/postfix/src/util/dict_cidr.c b/postfix/src/util/dict_cidr.c index 97f4867db..5731e58d4 100644 --- a/postfix/src/util/dict_cidr.c +++ b/postfix/src/util/dict_cidr.c @@ -176,8 +176,18 @@ DICT *dict_cidr_open(const char *mapname, int open_flags, int dict_flags) * Sanity checks. */ if (open_flags != O_RDONLY) - msg_fatal("%s:%s map requires O_RDONLY access mode", - DICT_TYPE_CIDR, mapname); + return (dict_surrogate(DICT_TYPE_CIDR, mapname, open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_CIDR, mapname)); + + /* + * Open the configuration file. + */ + if ((map_fp = vstream_fopen(mapname, O_RDONLY, 0)) == 0) + return (dict_surrogate(DICT_TYPE_CIDR, mapname, open_flags, dict_flags, + "open %s: %m", mapname)); + if (fstat(vstream_fileno(map_fp), &st) < 0) + msg_fatal("fstat %s: %m", mapname); /* * XXX Eliminate unnecessary queries by setting a flag that says "this @@ -190,10 +200,6 @@ DICT *dict_cidr_open(const char *mapname, int open_flags, int dict_flags) dict_cidr->dict.flags = dict_flags | DICT_FLAG_PATTERN; dict_cidr->head = 0; - if ((map_fp = vstream_fopen(mapname, O_RDONLY, 0)) == 0) - msg_fatal("open %s: %m", mapname); - if (fstat(vstream_fileno(map_fp), &st) < 0) - msg_fatal("fstat %s: %m", mapname); dict_cidr->dict.owner.uid = st.st_uid; dict_cidr->dict.owner.status = (st.st_uid != 0); diff --git a/postfix/src/util/dict_db.c b/postfix/src/util/dict_db.c index 898dab53a..29e4cda09 100644 --- a/postfix/src/util/dict_db.c +++ b/postfix/src/util/dict_db.c @@ -594,10 +594,11 @@ static DICT *dict_db_open(const char *class, const char *path, int open_flags, (void) db_version(&major_version, &minor_version, &patch_version); if (major_version != DB_VERSION_MAJOR || minor_version != DB_VERSION_MINOR) - msg_fatal("incorrect version of Berkeley DB: " + return (dict_surrogate(class, path, open_flags, dict_flags, + "incorrect version of Berkeley DB: " "compiled against %d.%d.%d, run-time linked against %d.%d.%d", - DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH, - major_version, minor_version, patch_version); + DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH, + major_version, minor_version, patch_version)); if (msg_verbose) { msg_info("Compiled against Berkeley DB: %d.%d.%d\n", DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH); @@ -627,7 +628,8 @@ static DICT *dict_db_open(const char *class, const char *path, int open_flags, if (dict_flags & DICT_FLAG_LOCK) { if ((lock_fd = open(db_path, LOCK_OPEN_FLAGS(open_flags), 0644)) < 0) { if (errno != ENOENT) - msg_fatal("open database %s: %m", db_path); + return (dict_surrogate(class, path, open_flags, dict_flags, + "open database %s: %m", db_path)); } else { if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) msg_fatal("shared-lock database %s for open: %m", db_path); @@ -642,7 +644,8 @@ static DICT *dict_db_open(const char *class, const char *path, int open_flags, */ #if DB_VERSION_MAJOR < 2 if ((db = dbopen(db_path, open_flags, 0644, type, tweak)) == 0) - msg_fatal("open database %s: %m", db_path); + return (dict_surrogate(class, path, open_flags, dict_flags, + "open database %s: %m", db_path)); dbfd = db->fd(db); #endif @@ -658,7 +661,8 @@ static DICT *dict_db_open(const char *class, const char *path, int open_flags, if (open_flags & O_TRUNC) db_flags |= DB_TRUNCATE; if ((errno = db_open(db_path, type, db_flags, 0644, 0, tweak, &db)) != 0) - msg_fatal("open database %s: %m", db_path); + return (dict_surrogate(class, path, open_flags, dict_flags, + "open database %s: %m", db_path)); if (db == 0) msg_panic("db_open null result"); if ((errno = db->fd(db, &dbfd)) != 0) @@ -686,10 +690,12 @@ static DICT *dict_db_open(const char *class, const char *path, int open_flags, msg_fatal("set DB hash element count %d: %m", DICT_DB_NELM); #if DB_VERSION_MAJOR == 5 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR > 0) if ((errno = db->open(db, 0, db_path, 0, type, db_flags, 0644)) != 0) - msg_fatal("open database %s: %m", db_path); + return (dict_surrogate(class, path, open_flags, dict_flags, + "open database %s: %m", db_path)); #elif (DB_VERSION_MAJOR == 3 || DB_VERSION_MAJOR == 4) if ((errno = db->open(db, db_path, 0, type, db_flags, 0644)) != 0) - msg_fatal("open database %s: %m", db_path); + return (dict_surrogate(class, path, open_flags, dict_flags, + "open database %s: %m", db_path)); #else #error "Unsupported Berkeley DB version" #endif diff --git a/postfix/src/util/dict_dbm.c b/postfix/src/util/dict_dbm.c index bdc4c3888..c0627d87b 100644 --- a/postfix/src/util/dict_dbm.c +++ b/postfix/src/util/dict_dbm.c @@ -426,7 +426,8 @@ DICT *dict_dbm_open(const char *path, int open_flags, int dict_flags) if (dict_flags & DICT_FLAG_LOCK) { dbm_path = concatenate(path, ".dir", (char *) 0); if ((lock_fd = open(dbm_path, open_flags, 0644)) < 0) - msg_fatal("open database %s: %m", dbm_path); + return (dict_surrogate(DICT_TYPE_DBM, path, open_flags, dict_flags, + "open database %s: %m", dbm_path)); if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) msg_fatal("shared-lock database %s for open: %m", dbm_path); } @@ -435,7 +436,8 @@ DICT *dict_dbm_open(const char *path, int open_flags, int dict_flags) * XXX SunOS 5.x has no const in dbm_open() prototype. */ if ((dbm = dbm_open((char *) path, open_flags, 0644)) == 0) - msg_fatal("open database %s.{dir,pag}: %m", path); + return (dict_surrogate(DICT_TYPE_DBM, path, open_flags, dict_flags, + "open database %s.{dir,pag}: %m", path)); if (dict_flags & DICT_FLAG_LOCK) { if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) diff --git a/postfix/src/util/dict_fail.c b/postfix/src/util/dict_fail.c index c4f8f54d5..06c10f50c 100644 --- a/postfix/src/util/dict_fail.c +++ b/postfix/src/util/dict_fail.c @@ -6,9 +6,9 @@ /* SYNOPSIS /* #include /* -/* DICT *dict_fail_open(name, name, dict_flags) +/* DICT *dict_fail_open(name, open_flags, dict_flags) /* const char *name; -/* int dummy; +/* int open_flags; /* int dict_flags; /* DESCRIPTION /* dict_fail_open() implements a dummy dictionary that fails diff --git a/postfix/src/util/dict_nis.c b/postfix/src/util/dict_nis.c index a06e5f359..357011f80 100644 --- a/postfix/src/util/dict_nis.c +++ b/postfix/src/util/dict_nis.c @@ -226,8 +226,9 @@ DICT *dict_nis_open(const char *map, int open_flags, int dict_flags) DICT_NIS *dict_nis; if (open_flags != O_RDONLY) - msg_fatal("%s:%s map requires O_RDONLY access mode", - DICT_TYPE_NIS, map); + return (dict_surrogate(DICT_TYPE_NIS, map, open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_NIS, map)); dict_nis = (DICT_NIS *) dict_alloc(DICT_TYPE_NIS, map, sizeof(*dict_nis)); dict_nis->dict.lookup = dict_nis_lookup; diff --git a/postfix/src/util/dict_nisplus.c b/postfix/src/util/dict_nisplus.c index 819bf0750..1f5e1266a 100644 --- a/postfix/src/util/dict_nisplus.c +++ b/postfix/src/util/dict_nisplus.c @@ -257,8 +257,9 @@ DICT *dict_nisplus_open(const char *map, int open_flags, int dict_flags) * Sanity check. */ if (open_flags != O_RDONLY) - msg_fatal("%s:%s map requires O_RDONLY access mode", - DICT_TYPE_NISPLUS, map); + return (dict_surrogate(DICT_TYPE_NISPLUS, map, open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_NISPLUS, map)); /* * Initialize. This is a read-only map with fixed strings, not with diff --git a/postfix/src/util/dict_pcre.c b/postfix/src/util/dict_pcre.c index 067416442..d53d4b6c8 100644 --- a/postfix/src/util/dict_pcre.c +++ b/postfix/src/util/dict_pcre.c @@ -794,7 +794,7 @@ static DICT_PCRE_RULE *dict_pcre_parse_rule(const char *mapname, int lineno, /* dict_pcre_open - load and compile a file containing regular expressions */ -DICT *dict_pcre_open(const char *mapname, int unused_flags, int dict_flags) +DICT *dict_pcre_open(const char *mapname, int open_flags, int dict_flags) { DICT_PCRE *dict_pcre; VSTREAM *map_fp; @@ -806,6 +806,23 @@ DICT *dict_pcre_open(const char *mapname, int unused_flags, int dict_flags) int nesting = 0; char *p; + /* + * Sanity checks. + */ + if (open_flags != O_RDONLY) + return (dict_surrogate(DICT_TYPE_PCRE, mapname, open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_PCRE, mapname)); + + /* + * Open the configuration file. + */ + if ((map_fp = vstream_fopen(mapname, O_RDONLY, 0)) == 0) + return (dict_surrogate(DICT_TYPE_PCRE, mapname, open_flags, dict_flags, + "open %s: %m", mapname)); + if (fstat(vstream_fileno(map_fp), &st) < 0) + msg_fatal("fstat %s: %m", mapname); + line_buffer = vstring_alloc(100); dict_pcre = (DICT_PCRE *) dict_alloc(DICT_TYPE_PCRE, mapname, @@ -823,17 +840,12 @@ DICT *dict_pcre_open(const char *mapname, int unused_flags, int dict_flags) pcre_free = (void (*) (void *)) myfree; dict_pcre_init = 1; } + dict_pcre->dict.owner.uid = st.st_uid; + dict_pcre->dict.owner.status = (st.st_uid != 0); /* * Parse the pcre table. */ - if ((map_fp = vstream_fopen(mapname, O_RDONLY, 0)) == 0) - msg_fatal("open %s: %m", mapname); - if (fstat(vstream_fileno(map_fp), &st) < 0) - msg_fatal("fstat %s: %m", mapname); - dict_pcre->dict.owner.uid = st.st_uid; - dict_pcre->dict.owner.status = (st.st_uid != 0); - while (readlline(line_buffer, map_fp, &lineno)) { p = vstring_str(line_buffer); trimblanks(p, 0)[0] = 0; /* Trim space at end */ diff --git a/postfix/src/util/dict_regexp.c b/postfix/src/util/dict_regexp.c index b7b08c62f..2cab3b4e0 100644 --- a/postfix/src/util/dict_regexp.c +++ b/postfix/src/util/dict_regexp.c @@ -734,7 +734,7 @@ static DICT_REGEXP_RULE *dict_regexp_parseline(const char *mapname, int lineno, /* dict_regexp_open - load and compile a file containing regular expressions */ -DICT *dict_regexp_open(const char *mapname, int unused_flags, int dict_flags) +DICT *dict_regexp_open(const char *mapname, int open_flags, int dict_flags) { DICT_REGEXP *dict_regexp; VSTREAM *map_fp; @@ -747,6 +747,23 @@ DICT *dict_regexp_open(const char *mapname, int unused_flags, int dict_flags) int nesting = 0; char *p; + /* + * Sanity checks. + */ + if (open_flags != O_RDONLY) + return (dict_surrogate(DICT_TYPE_REGEXP, mapname, open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_REGEXP, mapname)); + + /* + * Open the configuration file. + */ + if ((map_fp = vstream_fopen(mapname, O_RDONLY, 0)) == 0) + return (dict_surrogate(DICT_TYPE_REGEXP, mapname, open_flags, dict_flags, + "open %s: %m", mapname)); + if (fstat(vstream_fileno(map_fp), &st) < 0) + msg_fatal("fstat %s: %m", mapname); + line_buffer = vstring_alloc(100); dict_regexp = (DICT_REGEXP *) dict_alloc(DICT_TYPE_REGEXP, mapname, @@ -759,17 +776,12 @@ DICT *dict_regexp_open(const char *mapname, int unused_flags, int dict_flags) dict_regexp->head = 0; dict_regexp->pmatch = 0; dict_regexp->expansion_buf = 0; + dict_regexp->dict.owner.uid = st.st_uid; + dict_regexp->dict.owner.status = (st.st_uid != 0); /* * Parse the regexp table. */ - if ((map_fp = vstream_fopen(mapname, O_RDONLY, 0)) == 0) - msg_fatal("open %s: %m", mapname); - if (fstat(vstream_fileno(map_fp), &st) < 0) - msg_fatal("fstat %s: %m", mapname); - dict_regexp->dict.owner.uid = st.st_uid; - dict_regexp->dict.owner.status = (st.st_uid != 0); - while (readlline(line_buffer, map_fp, &lineno)) { p = vstring_str(line_buffer); trimblanks(p, 0)[0] = 0; diff --git a/postfix/src/util/dict_surrogate.c b/postfix/src/util/dict_surrogate.c new file mode 100644 index 000000000..42dbe8fad --- /dev/null +++ b/postfix/src/util/dict_surrogate.c @@ -0,0 +1,169 @@ +/*++ +/* NAME +/* dict_surrogate 3 +/* SUMMARY +/* surrogate table for graceful "open" failure +/* SYNOPSIS +/* #include +/* +/* DICT *dict_surrogate(dict_type, dict_name, +/* open_flags, dict_flags, +/* format, ...) +/* const char *dict_type; +/* const char *dict_name; +/* int open_flags; +/* int dict_flags; +/* const char *format; +/* +/* int dict_allow_surrogate; +/* DESCRIPTION +/* dict_surrogate() either terminates the program with a fatal +/* error, or provides a dummy dictionary that fails all +/* operations with an error message, allowing the program to +/* continue with reduced functionality. +/* +/* The global dict_allow_surrogate variable controls the choice +/* between fatal error or reduced functionality. The default +/* value is zero (fatal error). +/* +/* Arguments: +/* .IP dict_type +/* .IP dict_name +/* .IP open_flags +/* .IP dict_flags +/* The parameters to the failed dictionary open() request. +/* .IP format, ... +/* The reason why the table could not be opened. This text is +/* logged immediately, and upon every attempt to access the +/* surrogate dictionary, before returning a "failed" completion +/* status. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include +#include + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + char *reason; /* open failure reason */ +} DICT_SURROGATE; + +/* dict_surrogate_sequence - fail lookup */ + +static int dict_surrogate_sequence(DICT *dict, int unused_func, + const char **key, const char **value) +{ + DICT_SURROGATE *dp = (DICT_SURROGATE *) dict; + + msg_warn("%s:%s is unavailable. %s", + dict->type, dict->name, dp->reason); + DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR); +} + +/* dict_surrogate_update - fail lookup */ + +static int dict_surrogate_update(DICT *dict, const char *unused_name, + const char *unused_value) +{ + DICT_SURROGATE *dp = (DICT_SURROGATE *) dict; + + msg_warn("%s:%s is unavailable. %s", + dict->type, dict->name, dp->reason); + DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR); +} + +/* dict_surrogate_lookup - fail lookup */ + +static const char *dict_surrogate_lookup(DICT *dict, const char *unused_name) +{ + DICT_SURROGATE *dp = (DICT_SURROGATE *) dict; + + msg_warn("%s:%s is unavailable. %s", + dict->type, dict->name, dp->reason); + DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, (char *) 0); +} + +/* dict_surrogate_delete - fail delete */ + +static int dict_surrogate_delete(DICT *dict, const char *unused_name) +{ + DICT_SURROGATE *dp = (DICT_SURROGATE *) dict; + + msg_warn("%s:%s is unavailable. %s", + dict->type, dict->name, dp->reason); + DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR); +} + +/* dict_surrogate_close - close fail dictionary */ + +static void dict_surrogate_close(DICT *dict) +{ + DICT_SURROGATE *dp = (DICT_SURROGATE *) dict; + + myfree((char *) dp->reason); + dict_free(dict); +} + +int dict_allow_surrogate = 0; + +/* dict_surrogate - terminate or provide surrogate dictionary */ + +DICT *dict_surrogate(const char *dict_type, const char *dict_name, + int open_flags, int dict_flags, + const char *fmt,...) +{ + va_list ap; + DICT_SURROGATE *dp; + VSTRING *buf; + void (*log_fn) (const char *, va_list); + + /* + * Log the problem immediately when it is detected. The table may not be + * accessed in every program execution (that is the whole point of + * continuing with reduced functionality) but we don't want the problem + * to remain unnoticed until long after a configuration mistake is made. + */ + log_fn = dict_allow_surrogate ? vmsg_error : vmsg_fatal; + va_start(ap, fmt); + log_fn(fmt, ap); + va_end(ap); + + /* + * Log the problem upon each access. + */ + dp = (DICT_SURROGATE *) dict_alloc(dict_type, dict_name, sizeof(*dp)); + dp->dict.lookup = dict_surrogate_lookup; + if (open_flags & O_RDWR) { + dp->dict.update = dict_surrogate_update; + dp->dict.delete = dict_surrogate_delete; + } + dp->dict.sequence = dict_surrogate_sequence; + dp->dict.close = dict_surrogate_close; + dp->dict.flags = dict_flags | DICT_FLAG_PATTERN; + dp->dict.owner.status = DICT_OWNER_TRUSTED; + buf = vstring_alloc(10); + va_start(ap, fmt); + vstring_vsprintf(buf, fmt, ap); + va_end(ap); + dp->reason = vstring_export(buf); + return (DICT_DEBUG (&dp->dict)); +} diff --git a/postfix/src/util/dict_tcp.c b/postfix/src/util/dict_tcp.c index 1f85b5f10..a20956538 100644 --- a/postfix/src/util/dict_tcp.c +++ b/postfix/src/util/dict_tcp.c @@ -290,11 +290,13 @@ DICT *dict_tcp_open(const char *map, int open_flags, int dict_flags) * Sanity checks. */ if (dict_flags & DICT_FLAG_NO_UNAUTH) - msg_fatal("%s:%s map is not allowed for security sensitive data", - DICT_TYPE_TCP, map); + return (dict_surrogate(DICT_TYPE_TCP, map, open_flags, dict_flags, + "%s:%s map is not allowed for security sensitive data", + DICT_TYPE_TCP, map)); if (open_flags != O_RDONLY) - msg_fatal("%s:%s map requires O_RDONLY access mode", - DICT_TYPE_TCP, map); + return (dict_surrogate(DICT_TYPE_TCP, map, open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_TCP, map)); /* * Create the dictionary handle. Do not open the connection until the diff --git a/postfix/src/util/dict_test.c b/postfix/src/util/dict_test.c index 68165e2fb..3d60b7a92 100644 --- a/postfix/src/util/dict_test.c +++ b/postfix/src/util/dict_test.c @@ -77,6 +77,7 @@ void dict_test(int argc, char **argv) usage(argv[0]); } dict_name = argv[optind]; + dict_allow_surrogate = 1; dict = dict_open(dict_name, open_flags, dict_flags); dict_register(dict_name, dict); while (vstring_fgets_nonl(inbuf, VSTREAM_IN)) { diff --git a/postfix/src/util/dict_thash.c b/postfix/src/util/dict_thash.c index a03dc3f4b..2c35db9a3 100644 --- a/postfix/src/util/dict_thash.c +++ b/postfix/src/util/dict_thash.c @@ -152,37 +152,28 @@ DICT *dict_thash_open(const char *path, int open_flags, int dict_flags) int lineno; char *key; char *value; + HTABLE *table; HTABLE_INFO *ht; /* * Sanity checks. */ if (open_flags != O_RDONLY) - msg_fatal("%s:%s map requires O_RDONLY access mode", - DICT_TYPE_THASH, path); - - /* - * Create the in-memory table. - */ - dict_thash = (DICT_THASH *) - dict_alloc(DICT_TYPE_THASH, path, sizeof(*dict_thash)); - dict_thash->dict.lookup = dict_thash_lookup; - dict_thash->dict.sequence = dict_thash_sequence; - dict_thash->dict.close = dict_thash_close; - dict_thash->dict.flags = dict_flags | DICT_FLAG_DUP_WARN | DICT_FLAG_FIXED; - if (dict_flags & DICT_FLAG_FOLD_FIX) - dict_thash->dict.fold_buf = vstring_alloc(10); - dict_thash->info = 0; + return (dict_surrogate(DICT_TYPE_THASH, path, open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_THASH, path)); /* * Read the flat text file into in-memory hash. Read the file again if it * may have changed while we were reading. */ for (before = time((time_t *) 0); /* see below */ ; before = after) { - if ((fp = vstream_fopen(path, open_flags, 0644)) == 0) - msg_fatal("open database %s: %m", path); + if ((fp = vstream_fopen(path, open_flags, 0644)) == 0) { + return (dict_surrogate(DICT_TYPE_THASH, path, open_flags, dict_flags, + "open database %s: %m", path)); + } lineno = 0; - dict_thash->table = htable_create(13); + table = htable_create(13); while (readlline(line_buffer, fp, &lineno)) { /* @@ -214,20 +205,20 @@ DICT *dict_thash_open(const char *path, int open_flags, int dict_flags) /* * Optionally fold the key. */ - if (dict_thash->dict.flags & DICT_FLAG_FOLD_FIX) + if (dict_flags & DICT_FLAG_FOLD_FIX) lowercase(key); /* * Store the value under the key. Handle duplicates * appropriately. */ - if ((ht = htable_locate(dict_thash->table, key)) != 0) { - if (dict_thash->dict.flags & DICT_FLAG_DUP_IGNORE) { + if ((ht = htable_locate(table, key)) != 0) { + if (dict_flags & DICT_FLAG_DUP_IGNORE) { /* void */ ; - } else if (dict_thash->dict.flags & DICT_FLAG_DUP_REPLACE) { + } else if (dict_flags & DICT_FLAG_DUP_REPLACE) { myfree(ht->value); ht->value = mystrdup(value); - } else if (dict_thash->dict.flags & DICT_FLAG_DUP_WARN) { + } else if (dict_flags & DICT_FLAG_DUP_WARN) { msg_warn("%s, line %d: duplicate entry: \"%s\"", path, lineno, key); } else { @@ -235,7 +226,7 @@ DICT *dict_thash_open(const char *path, int open_flags, int dict_flags) path, lineno, key); } } else { - htable_enter(dict_thash->table, key, mystrdup(value)); + htable_enter(table, key, mystrdup(value)); } } @@ -253,12 +244,26 @@ DICT *dict_thash_open(const char *path, int open_flags, int dict_flags) /* * Yes, it is hot. Discard the result and read the file again. */ - htable_free(dict_thash->table, myfree); + htable_free(table, myfree); if (msg_verbose > 1) msg_info("pausing to let file %s cool down", path); doze(300000); } vstring_free(line_buffer); + + /* + * Create the in-memory table. + */ + dict_thash = (DICT_THASH *) + dict_alloc(DICT_TYPE_THASH, path, sizeof(*dict_thash)); + dict_thash->dict.lookup = dict_thash_lookup; + dict_thash->dict.sequence = dict_thash_sequence; + dict_thash->dict.close = dict_thash_close; + dict_thash->dict.flags = dict_flags | DICT_FLAG_DUP_WARN | DICT_FLAG_FIXED; + if (dict_flags & DICT_FLAG_FOLD_FIX) + dict_thash->dict.fold_buf = vstring_alloc(10); + dict_thash->info = 0; + dict_thash->table = table; dict_thash->dict.owner.uid = st.st_uid; dict_thash->dict.owner.status = (st.st_uid != 0); diff --git a/postfix/src/util/dict_thash.map b/postfix/src/util/dict_thash.map new file mode 100644 index 000000000..67e75784f --- /dev/null +++ b/postfix/src/util/dict_thash.map @@ -0,0 +1,14 @@ +allascii.c 915 +alldig.c 928 +allprint.c 943 +allspace.c 931 +argv.c 5271 +argv_split.c 2780 +attr_clnt.c 5813 +attr_print0.c 7243 +attr_print64.c 8104 +attr_print_plain.c 7086 +attr_scan0.c 15454 +attr_scan64.c 17256 +attr_scan_plain.c 16924 +auto_clnt.c 9819 diff --git a/postfix/src/util/dict_unix.c b/postfix/src/util/dict_unix.c index cd103cb40..463534430 100644 --- a/postfix/src/util/dict_unix.c +++ b/postfix/src/util/dict_unix.c @@ -159,7 +159,7 @@ static void dict_unix_close(DICT *dict) /* dict_unix_open - open UNIX map */ -DICT *dict_unix_open(const char *map, int unused_flags, int dict_flags) +DICT *dict_unix_open(const char *map, int open_flags, int dict_flags) { DICT_UNIX *dict_unix; struct dict_unix_lookup { @@ -173,14 +173,26 @@ DICT *dict_unix_open(const char *map, int unused_flags, int dict_flags) }; struct dict_unix_lookup *lp; - dict_unix = (DICT_UNIX *) dict_alloc(DICT_TYPE_UNIX, map, - sizeof(*dict_unix)); + /* + * Sanity checks. + */ + if (open_flags != O_RDONLY) + return (dict_surrogate(DICT_TYPE_UNIX, map, open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_UNIX, map)); + + /* + * "Open" the database. + */ for (lp = dict_unix_lookup; /* void */ ; lp++) { if (lp->name == 0) - msg_fatal("dict_unix_open: unknown map name: %s", map); + return (dict_surrogate(DICT_TYPE_UNIX, map, open_flags, dict_flags, + "unknown table: %s:%s", DICT_TYPE_UNIX, map)); if (strcmp(map, lp->name) == 0) break; } + dict_unix = (DICT_UNIX *) dict_alloc(DICT_TYPE_UNIX, map, + sizeof(*dict_unix)); dict_unix->dict.lookup = lp->lookup; dict_unix->dict.close = dict_unix_close; dict_unix->dict.flags = dict_flags | DICT_FLAG_FIXED; diff --git a/postfix/src/util/match_list.c b/postfix/src/util/match_list.c index f6f7fc34d..ca7091efb 100644 --- a/postfix/src/util/match_list.c +++ b/postfix/src/util/match_list.c @@ -106,6 +106,10 @@ static ARGV *match_list_parse(ARGV *list, char *string, int init_match) char *map_type_name_flags; int match; +#define OPEN_FLAGS O_RDONLY +#define DICT_FLAGS (DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX) +#define STR(x) vstring_str(x) + /* * /filename contents are expanded in-line. To support !/filename we * prepend the negation operator to each item from the file. @@ -121,17 +125,23 @@ static ARGV *match_list_parse(ARGV *list, char *string, int init_match) if (*item == 0) msg_fatal("%s: no pattern after '!'", myname); if (*item == '/') { /* /file/name */ - if ((fp = vstream_fopen(item, O_RDONLY, 0)) == 0) - msg_fatal("%s: open file %s: %m", myname, item); - while (vstring_fgets(buf, fp)) - if (vstring_str(buf)[0] != '#') - list = match_list_parse(list, vstring_str(buf), match); - if (vstream_fclose(fp)) - msg_fatal("%s: read file %s: %m", myname, item); + if ((fp = vstream_fopen(item, O_RDONLY, 0)) == 0) { + vstring_sprintf(buf, "%s:%s", DICT_TYPE_NOFILE, item); + /* XXX Should increment existing map refcount. */ + if (dict_handle(STR(buf)) == 0) + dict_register(STR(buf), + dict_surrogate(DICT_TYPE_NOFILE, item, + OPEN_FLAGS, DICT_FLAGS, + "open file %s: %m", item)); + argv_add(list, STR(buf), (char *) 0); + } else { + while (vstring_fgets(buf, fp)) + if (vstring_str(buf)[0] != '#') + list = match_list_parse(list, vstring_str(buf), match); + if (vstream_fclose(fp)) + msg_fatal("%s: read file %s: %m", myname, item); + } } else if (MATCH_DICTIONARY(item)) { /* type:table */ -#define OPEN_FLAGS O_RDONLY -#define DICT_FLAGS (DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX) -#define STR(x) vstring_str(x) vstring_sprintf(buf, "%s%s(%o,%s)", match ? "" : "!", item, OPEN_FLAGS, dict_flags_str(DICT_FLAGS)); map_type_name_flags = STR(buf) + (match == 0); diff --git a/postfix/src/util/match_ops.c b/postfix/src/util/match_ops.c index d138d0579..f978d2629 100644 --- a/postfix/src/util/match_ops.c +++ b/postfix/src/util/match_ops.c @@ -297,7 +297,7 @@ int match_hostaddr(MATCH_LIST *list, const char *addr, const char *pattern) err = cidr_match_parse(&match_info, saved_patt, (VSTRING *) 0); myfree(saved_patt); if (err != 0) { - list->error = DICT_ERR_CONFIG; + list->error = DICT_ERR_RETRY; rc = match_error(list, "%s", vstring_str(err)); vstring_free(err); return (rc); diff --git a/postfix/src/util/msg.c b/postfix/src/util/msg.c index ae7798017..70c6eab08 100644 --- a/postfix/src/util/msg.c +++ b/postfix/src/util/msg.c @@ -11,22 +11,47 @@ /* void msg_info(format, ...) /* const char *format; /* +/* void vmsg_info(format, ap) +/* const char *format; +/* va_list ap; +/* /* void msg_warn(format, ...) /* const char *format; /* +/* void vmsg_warn(format, ap) +/* const char *format; +/* va_list ap; +/* /* void msg_error(format, ...) /* const char *format; /* +/* void vmsg_error(format, ap) +/* const char *format; +/* va_list ap; +/* /* NORETURN msg_fatal(format, ...) /* const char *format; /* +/* NORETURN vmsg_fatal(format, ap) +/* const char *format; +/* va_list ap; +/* /* NORETURN msg_fatal_status(status, format, ...) /* int status; /* const char *format; /* +/* NORETURN vmsg_fatal_status(status, format, ap) +/* int status; +/* const char *format; +/* va_list ap; +/* /* NORETURN msg_panic(format, ...) /* const char *format; /* +/* NORETURN vmsg_panic(format, ap) +/* const char *format; +/* va_list ap; +/* /* MSG_CLEANUP_FN msg_cleanup(cleanup) /* void (*cleanup)(void); /* AUXILIARY FUNCTIONS @@ -176,10 +201,15 @@ void msg_info(const char *fmt,...) va_list ap; va_start(ap, fmt); - msg_vprintf(MSG_INFO, fmt, ap); + vmsg_info(fmt, ap); va_end(ap); } +void vmsg_info(const char *fmt, va_list ap) +{ + msg_vprintf(MSG_INFO, fmt, ap); +} + /* msg_warn - report warning message */ void msg_warn(const char *fmt,...) @@ -187,10 +217,15 @@ void msg_warn(const char *fmt,...) va_list ap; va_start(ap, fmt); - msg_vprintf(MSG_WARN, fmt, ap); + vmsg_warn(fmt, ap); va_end(ap); } +void vmsg_warn(const char *fmt, va_list ap) +{ + msg_vprintf(MSG_WARN, fmt, ap); +} + /* msg_error - report recoverable error */ void msg_error(const char *fmt,...) @@ -198,8 +233,13 @@ void msg_error(const char *fmt,...) va_list ap; va_start(ap, fmt); - msg_vprintf(MSG_ERROR, fmt, ap); + vmsg_error(fmt, ap); va_end(ap); +} + +void vmsg_error(const char *fmt, va_list ap) +{ + msg_vprintf(MSG_ERROR, fmt, ap); if (++msg_error_count >= msg_error_bound) msg_fatal("too many errors - program terminated"); } @@ -210,10 +250,15 @@ NORETURN msg_fatal(const char *fmt,...) { va_list ap; + va_start(ap, fmt); + vmsg_fatal(fmt, ap); + /* NOTREACHED */ +} + +NORETURN vmsg_fatal(const char *fmt, va_list ap) +{ if (msg_exiting++ == 0) { - va_start(ap, fmt); msg_vprintf(MSG_FATAL, fmt, ap); - va_end(ap); if (msg_cleanup_fn) msg_cleanup_fn(); } @@ -228,10 +273,15 @@ NORETURN msg_fatal_status(int status, const char *fmt,...) { va_list ap; + va_start(ap, fmt); + vmsg_fatal_status(status, fmt, ap); + /* NOTREACHED */ +} + +NORETURN vmsg_fatal_status(int status, const char *fmt, va_list ap) +{ if (msg_exiting++ == 0) { - va_start(ap, fmt); msg_vprintf(MSG_FATAL, fmt, ap); - va_end(ap); if (msg_cleanup_fn) msg_cleanup_fn(); } @@ -246,10 +296,15 @@ NORETURN msg_panic(const char *fmt,...) { va_list ap; + va_start(ap, fmt); + vmsg_panic(fmt, ap); + /* NOTREACHED */ +} + +NORETURN vmsg_panic(const char *fmt, va_list ap) +{ if (msg_exiting++ == 0) { - va_start(ap, fmt); msg_vprintf(MSG_PANIC, fmt, ap); - va_end(ap); } sleep(1); abort(); /* Die! */ diff --git a/postfix/src/util/msg.h b/postfix/src/util/msg.h index 4b1c6d0bf..599c69a57 100644 --- a/postfix/src/util/msg.h +++ b/postfix/src/util/msg.h @@ -14,6 +14,7 @@ /* * System library. */ +#include #include /* @@ -30,6 +31,13 @@ extern NORETURN PRINTFLIKE(1, 2) msg_fatal(const char *,...); extern NORETURN PRINTFLIKE(2, 3) msg_fatal_status(int, const char *,...); extern NORETURN PRINTFLIKE(1, 2) msg_panic(const char *,...); +extern void vmsg_info(const char *, va_list); +extern void vmsg_warn(const char *, va_list); +extern void vmsg_error(const char *, va_list); +extern NORETURN vmsg_fatal(const char *, va_list); +extern NORETURN vmsg_fatal_status(int, const char *, va_list); +extern NORETURN vmsg_panic(const char *, va_list); + extern int msg_error_limit(int); extern void msg_error_clear(void); extern MSG_CLEANUP_FN msg_cleanup(MSG_CLEANUP_FN);