]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-2.9-20120114
authorWietse Venema <wietse@porcupine.org>
Sat, 14 Jan 2012 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <viktor@dukhovni.org>
Tue, 5 Feb 2013 06:37:55 +0000 (06:37 +0000)
69 files changed:
postfix/.indent.pro
postfix/HISTORY
postfix/RELEASE_NOTES
postfix/WISHLIST
postfix/html/memcache_table.5.html
postfix/html/postconf.5.html
postfix/man/man5/memcache_table.5
postfix/man/man5/postconf.5
postfix/proto/memcache_table
postfix/proto/postconf.proto
postfix/src/cleanup/cleanup_milter.c
postfix/src/global/cfg_parser.c
postfix/src/global/dict_ldap.c
postfix/src/global/dict_memcache.c
postfix/src/global/dict_mysql.c
postfix/src/global/dict_pgsql.c
postfix/src/global/dict_sqlite.c
postfix/src/global/mail_conf.c
postfix/src/global/mail_error.c
postfix/src/global/mail_error.h
postfix/src/global/mail_params.c
postfix/src/global/mail_params.h
postfix/src/global/mail_version.h
postfix/src/global/maps.ref
postfix/src/global/namadr_list.c
postfix/src/global/namadr_list.in
postfix/src/global/namadr_list.ref
postfix/src/global/smtp_stream.c
postfix/src/global/smtp_stream.h
postfix/src/master/event_server.c
postfix/src/master/multi_server.c
postfix/src/master/single_server.c
postfix/src/master/trigger_server.c
postfix/src/postconf/postconf_main.c
postfix/src/smtp/smtp.c
postfix/src/smtp/smtp_chat.c
postfix/src/smtp/smtp_proto.c
postfix/src/smtp/smtp_sasl_glue.c
postfix/src/smtp/smtp_session.c
postfix/src/smtp/smtp_trouble.c
postfix/src/smtpd/smtpd.c
postfix/src/smtpd/smtpd.h
postfix/src/smtpd/smtpd_chat.c
postfix/src/smtpd/smtpd_check.c
postfix/src/smtpd/smtpd_error.ref
postfix/src/smtpd/smtpd_state.c
postfix/src/util/Makefile.in
postfix/src/util/dict.c
postfix/src/util/dict.h
postfix/src/util/dict_alloc.c
postfix/src/util/dict_cdb.c
postfix/src/util/dict_cidr.c
postfix/src/util/dict_db.c
postfix/src/util/dict_dbm.c
postfix/src/util/dict_fail.c
postfix/src/util/dict_nis.c
postfix/src/util/dict_nisplus.c
postfix/src/util/dict_pcre.c
postfix/src/util/dict_regexp.c
postfix/src/util/dict_surrogate.c [new file with mode: 0644]
postfix/src/util/dict_tcp.c
postfix/src/util/dict_test.c
postfix/src/util/dict_thash.c
postfix/src/util/dict_thash.map [new file with mode: 0644]
postfix/src/util/dict_unix.c
postfix/src/util/match_list.c
postfix/src/util/match_ops.c
postfix/src/util/msg.c
postfix/src/util/msg.h

index 9e679a2cab22a8dc177ad155fb93be2affc42119..e7009d5d405e344b9ec5357992a6e4eec9e4a3a7 100644 (file)
 -TDICT_SDBM
 -TDICT_SQLITE
 -TDICT_STACK
+-TDICT_SURROGATE
 -TDICT_TCP
 -TDICT_TEXT
 -TDICT_THASH
index 9f3659ac18aedc482b298b9b2c33a5446ac41568..1d43a28cb6dd39238576c65d3f9147a868fa128a 100644 (file)
@@ -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.
index 4492293bde4a0f2fdb8f1d78d12bc640c24b5b39..dfb86dc67662d4e8a588d78ce1f0e78853c64b37 100644 (file)
@@ -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
index 394eea47cd073f94a859bf972405d54514933856..c201dae89bb888392fda4d050ec3ce8a969104f5 100644 (file)
@@ -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,
index 1dd8ba15f27d3ac5b11e2e60b09c680b4390ebb0..11ab0ff7e6ec2090da078f0faed59ff9844c05ce 100644 (file)
@@ -162,12 +162,13 @@ MEMCACHE_TABLE(5)                                            MEMCACHE_TABLE(5)
        <b>domain (default: no domain list)</b>
               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 "<a href="DATABASE_README.html">type:table</a>" 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.
 
        <b>max_try (default: 2)</b>
-              The  number  of  times  to  try  a memcache command
+              The number of  times  to  try  a  memcache  command
               before giving up.
 
        <b>retry_pause (default: 1)</b>
-              The time in seconds to wait after a  memcache  com-
+              The  time  in seconds to wait after a memcache com-
               mand fails.
 
        <b>timeout (default: 2)</b>
-              The  time  limit for sending a memcache command and
+              The time limit for sending a memcache  command  and
               for receiving a memcache reply.
 
 <b>BUGS</b>
-       The Postfix memcache client cannot be used  for  security-
-       sensitive  tables  such  as  <b><a href="postconf.5.html#alias_maps">alias_maps</a></b> (these may contain
-       "<i>|command</i>  and   "<i>/file/name</i>"   destinations),   or   <b><a href="postconf.5.html#virtual_uid_maps">vir</a>-</b>
-       <b><a href="postconf.5.html#virtual_uid_maps">tual_uid_maps</a></b>,  <b><a href="postconf.5.html#virtual_gid_maps">virtual_gid_maps</a></b>  and <b><a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a></b>
-       (these specify UNIX  process  privileges  or  "<i>/file/name</i>"
-       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  <b><a href="postconf.5.html#alias_maps">alias_maps</a></b>  (these  may  contain
+       "<i>|command</i>   and   "<i>/file/name</i>"   destinations),   or  <b><a href="postconf.5.html#virtual_uid_maps">vir</a>-</b>
+       <b><a href="postconf.5.html#virtual_uid_maps">tual_uid_maps</a></b>, <b><a href="postconf.5.html#virtual_gid_maps">virtual_gid_maps</a></b>  and  <b><a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a></b>
+       (these  specify  UNIX  process  privileges or "<i>/file/name</i>"
+       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 <a href="postscreen.8.html"><b>postscreen</b>(8)</a> or <a href="verify.8.html"><b>verify</b>(8)</a> cache.  For
-       details see the <b>backup</b> and <b>ttl</b>  parameter  discussions  in
+       tion when used as <a href="postscreen.8.html"><b>postscreen</b>(8)</a> or <a href="verify.8.html"><b>verify</b>(8)</a>  cache.   For
+       details  see  the  <b>backup</b> and <b>ttl</b> parameter discussions in
        the MEMCACHE MAIN PARAMETERS section above.
 
 <b>SEE ALSO</b>
@@ -215,13 +216,13 @@ MEMCACHE_TABLE(5)                                            MEMCACHE_TABLE(5)
        <a href="MEMCACHE_README.html">MEMCACHE_README</a>, Postfix memcache client guide
 
 <b>LICENSE</b>
-       The Secure Mailer license must be  distributed  with  this
+       The  Secure  Mailer  license must be distributed with this
        software.
 
 <b>HISTORY</b>
-       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.
 
 <b>AUTHOR(S)</b>
index 5ef3c7bd0e41e4db3c252ca3de5c1f428bdf7968..ec54b00c4f4a27a3ef0c93c88b6fd425fa21b32d 100644 (file)
@@ -1618,6 +1618,39 @@ be owned by root.
 </p>
 
 
+</DD>
+
+<DT><b><a name="daemon_table_open_error_is_fatal">daemon_table_open_error_is_fatal</a>
+(default: no)</b></DT><DD>
+
+<p> How a Postfix daemon process handles errors while opening lookup
+tables: gradual degradation or immediate termination. </p>
+
+<dl>
+
+<dt> <b> no </b> (default) </dt> <dd> <p> 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 <a href="postconf.5.html#notify_classes">notify_classes</a> 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.  </p> </dd>
+
+<dt> <b> yes </b> (historical behavior) </dt> <dd> <p> 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.  </p> </dd>
+
+</dl>
+
+<p> For the sake of sanity, the number of type "error" messages is
+limited to 13 over the lifetime of a daemon process. </p>
+
+<p> This feature is available in Postfix 2.9 and later.  </p>
+
+
 </DD>
 
 <DT><b><a name="daemon_timeout">daemon_timeout</a>
@@ -6653,6 +6686,14 @@ notification is sent to the address specified with the
 is sent to the address specified with the <a href="postconf.5.html#2bounce_notice_recipient">2bounce_notice_recipient</a>
 configuration parameter (default: postmaster). </dd>
 
+<dt><b>data</b></dt>
+
+<dd>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 <a href="postconf.5.html#error_notice_recipient">error_notice_recipient</a>
+configuration parameter (default: postmaster). <br> This feature
+is available in Postfix 2.9 and later.  </dd>
+
 <dt><b>delay</b></dt>
 
 <dd>Send the postmaster copies of the headers of delayed mail. The
index 5ceb73d1d3e0fd44b285120fb9a566217b09baf7..00276685964aa576b923a413b9edf889870fd41b 100644 (file)
@@ -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
index d5b5e17f172f567eb5a52d3ecc76ce625a646dfb..09506eae2cb5110ec38768f6226a4de1e90d4cff 100644 (file)
@@ -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
index 45dbe2942502bd5cb4cf008ec617da0780d363db..fc5827c13fcc1692c1e4a03eee178d7dff534d27 100644 (file)
 # .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
index 638e81205a0433a820703491577b589cf7a26e43..8e5e47a365f5812937ed3333f9c63092dcb779e5 100644 (file)
@@ -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). </dd>
 
+<dt><b>data</b></dt>
+
+<dd>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.  </dd>
+
 <dt><b>delay</b></dt>
 
 <dd>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).
 </p>
 
 <p> This feature is available in Postfix 2.9 and later.  </p>
+
+%PARAM daemon_table_open_error_is_fatal no
+
+<p> How a Postfix daemon process handles errors while opening lookup
+tables: gradual degradation or immediate termination. </p>
+
+<dl>
+
+<dt> <b> no </b> (default) </dt> <dd> <p> 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.  </p> </dd>
+
+<dt> <b> yes </b> (historical behavior) </dt> <dd> <p> 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.  </p> </dd>
+
+</dl>
+
+<p> For the sake of sanity, the number of type "error" messages is
+limited to 13 over the lifetime of a daemon process. </p>
+
+<p> This feature is available in Postfix 2.9 and later.  </p>
index cc63313f1d9c1cdb783045ef8ecf9bd874441d4f..18957fe1dd4d5f94d48a12b076de3a4565b65ba5 100644 (file)
@@ -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");
 }
index 73c81d025b2e6967801261f57820b6ce8b98a5c3..89ebab5d321f34d0c86f11462b2d59aef1b0bc6b 100644 (file)
@@ -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;
index 6398dcc890df419990cfaf5b80b623aafc46f667..3d6ddeaaab2082d0792dc2b62f487e98a3c5ce41 100644 (file)
@@ -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);
index d6df42996963e20193beb63172a4762ce197892e..e3ce925381cc72643af1303554a72f646b435f32 100644 (file)
@@ -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,
index 8f728523472b9e301abc4c169d2b154d0264c0f9..a3e231a4ed204b569fb1b48953bd960944095edb 100644 (file)
@@ -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;
index a98a0c9798f537558bc6d2a93f435b3110c00963..b96a81fe3a50ff452b6bf5edca086b7d1017dd9c 100644 (file)
@@ -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);
index acfcd408459e89b5e2251dd693a8c7f98d656ec8..7f56cdf30cf9d5d3856764e51012dc5bc841c5fb 100644 (file)
@@ -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))
index ed1b336d1a059b6e82592f45177594778d1b0755..ae0ae9886caf7a493ce1dd1eb1771a745aeab626 100644 (file)
@@ -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);
 }
 
index 8e9d40936a74baa4edf643f97ecbc2e3df89158e..0a239b81678ad17612d553a3df61507087499b94 100644 (file)
@@ -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,
index 89aee07e43f3f9837dcb1cd95698415282100a7b..f90c0ef1c98f2db9406e24f1a4fb5362c3aa3d48 100644 (file)
@@ -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[];
 
index dff3b72b87aed0597b7143d988a7f432648ea15d..e312f56e56133ee97e2790359d8aa18825b49d6a 100644 (file)
 /*     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.
index 936835e7013012606ab2c88a7cd68078647a04a8..912c012c8eb4d74ee4aacf116a460ee5a22dd0fa 100644 (file)
@@ -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
index a69bbad89481b916f34cf7828d35b17e9b261e59..cbb946c714f20c7d8ff29a1afef687644c298383 100644 (file)
@@ -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
index 8ae750a254f6f0a64e3b1c2f847c0aa4faf9ca4f..84033c7d848efdddc0fa85331899092c0233b744 100644 (file)
@@ -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)
index 66a1fb934827a4c4d2eb4441758e7c58a5d22988..fc7f681ed239706f3c7b950faddd049eca589212 100644 (file)
@@ -94,6 +94,7 @@
 #include <unistd.h>
 #include <vstream.h>
 #include <msg_vstream.h>
+#include <dict.h>
 
 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];
index c1cd8f2c4cd1fb00f261031aebd8b68eb7c303b2..dfe6ed20c15ea10e363d9229f5fbf0994d15d281 100644 (file)
@@ -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
index 0515ecea5628cfc2ee2532f2d23f818236cb2297..1f62aa216616ad7e4cd7fd34777563b8fc1f56be 100644 (file)
@@ -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
index fb68e505775d8b1c6e3ddb3f5765b41edae88b98..4893f40822c67e46670b7a3d5ab754303945590d 100644 (file)
 /*     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
index 599bcfa297c0f702fe8fa8874279ae77db0f3b27..bdb143675ee49f00ed75933534586415be7de408 100644 (file)
@@ -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 *,...);
index 37d7e097059850a8181f6727ee52302c9b8e51a1..b58d22578b785d2e8490bd7461260a99d9b84d3f 100644 (file)
@@ -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.
index 0b5e678c614d60b2ea35c2ba13fe1eda02ffc92e..c16118db3761fc78104089ff4cfe401f7d1d0bd1 100644 (file)
@@ -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
index aba479cd51225838ac71caa3bdca93904bd8e9c5..7b6d8bfd7eb179957d7bf6687bbfb6e6f82f069f 100644 (file)
@@ -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
index df3bf2054ff782a87cdd631caef8481a4fdb8f27..bc4a16ceae7bbcd9f0d0289407e5016186325dfd 100644 (file)
@@ -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
index 9411d7dc4a677208bf5834def894fd377e99451e..ad96c9afbcde1851270aaf8bfc1fe53e5940b7e8 100644 (file)
@@ -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);
 }
 
index 831cfc7bf2c0227678a887c062c72bf5a5436acb..a13cd2eefbb1fa3613ac4a2d4b72a562fdb454ac 100644 (file)
@@ -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.
index 1aefaf41245ac0fb7ebfccdabacbf52b8e7227a9..cad15c4bfc155a8268d249f9180f19d13d378644 100644 (file)
@@ -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) {
index e348da80fc91106c5ecd693ff4e49cd7f7ec3ecf..e1775b1cfd80bb79df023fa6879d4500b80e8c2e 100644 (file)
@@ -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);
index 16fc940f4f665cbcc85730c4480ebd9715698398..7ba70bb1d0a4e356cf9ba30127e372e9b86ce102 100644 (file)
@@ -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')",
index 4ee8e872a60b03b572696c087d8cc7795f5cd491..e98695450587aa9459f5b7ae6813557de9784c18 100644 (file)
@@ -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);
index 706bc5d4da232667bc6d2cddde56cbf08c2f55eb..0e7fafd4671c4ee1353d9e01995108191d2000ba 100644 (file)
@@ -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);
     }
index d4b30a6d20389c6237cd61c38d811df853ab9c30..b2bfd231ddea10ca140aaa78b9915e712dda6800 100644 (file)
@@ -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",
index 4001a81456581bbbfad71fe09c594e6332378921..cc459068223e2d0cead79cdb7b603d9f7b0475f3 100644 (file)
@@ -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 */
index 9cc30385e338b58dfc9ae25676d6e020bde95636..a1016cd9c4c90ec26762108acb93906b229b602f 100644 (file)
@@ -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;
        }
index 5bfaf73571e286e02cbd3e788ce8ae39ec93f437..341ae3d4242fb77e7c5ce32677122fa86ea129c1 100644 (file)
@@ -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));
index b30f7c300b64b3a20eb7a5b4b58da942ad28a0a3..44675cb2ef7a97dc0f86d405d8832217abba7c42 100644 (file)
@@ -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: <queue id>: reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.5 Server configuration error; from=<reject@dunno.domain> to=<reject@dunno.domain> proto=SMTP helo=<foobar>
-451 4.3.5 Server configuration error
+./smtpd_check: <queue id>: reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 <reject@dunno.domain>: Temporary lookup failure; from=<reject@dunno.domain> to=<reject@dunno.domain> proto=SMTP helo=<foobar>
+451 4.3.0 <reject@dunno.domain>: 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: <queue id>: reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 <reject@dunno.domain>: Temporary lookup failure; from=<> to=<reject@dunno.domain> proto=SMTP helo=<foobar>
 451 4.3.0 <reject@dunno.domain>: 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: <queue id>: reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 <user@example.com>: Temporary lookup failure; from=<> to=<user@example.com> proto=SMTP helo=<foobar>
 451 4.3.0 <user@example.com>: Temporary lookup failure
index 43baa74bca3207f267642b03714a1479a175fd47..c03dfbef8725d6c974aa36bce330db3106a7596e 100644 (file)
@@ -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 */
index 368a1cea89fd107d2133954215859256636a1642..cff7d8a0588e4530de600faa25e9d831c788ef4d 100644 (file)
@@ -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
index baeff9d2043074c1f1ef00e3a4fc778c2c3aa608..875c5c513ec902bd77628594684204647517b8df 100644 (file)
@@ -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;
 /*
 /*     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 */
index 90536a2267e51b4cdc1c3e1b03edf663b82d4f6a..f8e91a405272369b1be93b2b6d7ba2866e567792 100644 (file)
@@ -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
index b684210210218a23fae07402782618e1f84b8439..03321f1da35097d4b1404c2de5708fb29c4cd72f 100644 (file)
@@ -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);
 }
 
index 099ecc99d567ffa94d007ddc60a6863ddd1c2187..9de10fdf0fdeea7ebd7c34aaba83560ea005349e 100644 (file)
@@ -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
index 97f4867db3e76fccae1cc0e5623b93f8fd7abfe3..5731e58d4e425755f87c03939f41878d61e73fb7 100644 (file)
@@ -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);
 
index 898dab53a487d46d1eea55755ffc36284f1c19c6..29e4cda09610c81c2f8bbedde6dc0cac14f83421 100644 (file)
@@ -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
index bdc4c3888995ebdeacc17ce4e68cd16b928e70aa..c0627d87b3d968b4dc5f1d064344b200a3d82bdc 100644 (file)
@@ -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)
index c4f8f54d5c3246bb67269c7304ae90829a6babea..06c10f50c820532d5fdf95f4fca73da54a779d66 100644 (file)
@@ -6,9 +6,9 @@
 /* SYNOPSIS
 /*     #include <dict_fail.h>
 /*
-/*     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
index a06e5f359d14519a1b041066982b52dbadf4f3f4..357011f80cfe6e6ef47aa2d85a63678436563da1 100644 (file)
@@ -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;
index 819bf0750fc4d0ad475befcec1521054ef84d14f..1f5e1266aa446cbf924932da1be696965ca954cd 100644 (file)
@@ -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
index 067416442fbf3b6dcb5e88df4b3342e31b8829ee..d53d4b6c830737f427759c8837570bf0d4cc89c2 100644 (file)
@@ -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 */
index b7b08c62f2b19755c0632864f69d025a13433fd8..2cab3b4e0264f9501f1b0f324f6792274d05540b 100644 (file)
@@ -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 (file)
index 0000000..42dbe8f
--- /dev/null
@@ -0,0 +1,169 @@
+/*++
+/* NAME
+/*     dict_surrogate 3
+/* SUMMARY
+/*     surrogate table for graceful "open" failure
+/* SYNOPSIS
+/*     #include <dict_surrogate.h>
+/*
+/*     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 <sys_defs.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <msg.h>
+#include <dict.h>
+
+/* 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));
+}
index 1f85b5f10061bc57f068ba566856f5ee8e096a43..a20956538f9bc1f936f8fe6792119ced740971a8 100644 (file)
@@ -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
index 68165e2fb0a6b87a457ef0bbbed385dca80971a0..3d60b7a92a4c76f231abbe23d44d30c427472db8 100644 (file)
@@ -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)) {
index a03dc3f4bf89b5c9dc232deb64d59f019a95b20c..2c35db9a3566f95c99d221aa2b37647e4a42e39a 100644 (file)
@@ -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 (file)
index 0000000..67e7578
--- /dev/null
@@ -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
index cd103cb40aca8d427874f149cd6fa95118e52eec..4635344300dfc75abf7466805c9bc88165582e31 100644 (file)
@@ -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;
index f6f7fc34da68758c9c87ab72a08ae69dfb8a9d71..ca7091efb30b89a5d08eb95b108d5537f8f5e8fb 100644 (file)
@@ -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);
index d138d05799ed6ae90aba59744d40502504036e53..f978d2629b80597828b028e5eabfa576017ac97b 100644 (file)
@@ -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);
index ae7798017184850c9db6529d9d06ccde22a36c3e..70c6eab083635934af52671e78abdab9ae3bee73 100644 (file)
 /*     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! */
index 4b1c6d0bf397c92d955a823157514e41630cd5fe..599c69a57b88a4eb73e6ac9eb3205518fe766c21 100644 (file)
@@ -14,6 +14,7 @@
 /*
  * System library.
  */
+#include <stdarg.h>
 #include <time.h>
 
 /*
@@ -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);