]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-3.1-20150710
authorWietse Venema <wietse@porcupine.org>
Fri, 10 Jul 2015 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <postfix-users@dukhovni.org>
Sat, 11 Jul 2015 18:01:06 +0000 (14:01 -0400)
47 files changed:
postfix/HISTORY
postfix/RELEASE_NOTES
postfix/WISHLIST
postfix/html/dnsblog.8.html
postfix/html/oqmgr.8.html
postfix/html/postconf.5.html
postfix/html/postscreen.8.html
postfix/html/qmgr.8.html
postfix/makedefs
postfix/man/man5/postconf.5
postfix/man/man8/dnsblog.8
postfix/man/man8/oqmgr.8
postfix/man/man8/postscreen.8
postfix/man/man8/qmgr.8
postfix/mantools/postlink
postfix/proto/postconf.proto
postfix/src/dns/Makefile.in
postfix/src/dns/dns.h
postfix/src/dns/dns_lookup.c
postfix/src/dns/dns_strrecord.c
postfix/src/dns/dnsbl_ttl_127.0.0.1_bind_ncache.ref [new file with mode: 0644]
postfix/src/dns/dnsbl_ttl_127.0.0.1_bind_plain.ref [new file with mode: 0644]
postfix/src/dns/dnsbl_ttl_127.0.0.2_bind_plain.ref [new file with mode: 0644]
postfix/src/dns/error.ref
postfix/src/dns/no-mx.ref
postfix/src/dns/nxdomain_test.ref
postfix/src/dns/test_dns_lookup.c
postfix/src/dnsblog/dnsblog.c
postfix/src/global/mail_params.h
postfix/src/global/mail_version.h
postfix/src/oqmgr/qmgr.c
postfix/src/oqmgr/qmgr.h
postfix/src/oqmgr/qmgr_deliver.c
postfix/src/oqmgr/qmgr_transport.c
postfix/src/postconf/postconf_builtin.c
postfix/src/postconf/postconf_service.c
postfix/src/postscreen/postscreen.c
postfix/src/postscreen/postscreen.h
postfix/src/postscreen/postscreen_dnsbl.c
postfix/src/postscreen/postscreen_early.c
postfix/src/qmgr/qmgr.c
postfix/src/qmgr/qmgr.h
postfix/src/qmgr/qmgr_deliver.c
postfix/src/qmgr/qmgr_transport.c
postfix/src/smtpd/smtpd_check.c
postfix/src/smtpd/smtpd_dns_filter.ref
postfix/src/smtpd/smtpd_server.ref

index 8ac693bcd76a4c804e0d435af0695fe9ecce7095..f1d589e3c4a047860dd8e828b6c909c718f7538f 100644 (file)
@@ -21757,3 +21757,52 @@ Apologies for any names omitted.
 
        Cleanup: updated the examples in MILTER_README.  File:
        proto/MILTER_README.html
+
+20150529
+
+       Support for DNS reply TTL values in dnsblog and postscreen.
+       Files: dbsblog/dnsblog.c, postscreen/postscreen_early.c,
+       postscreen/postscreen_dnsbl.c.
+
+20150607
+
+       Support for DNS reply TTL values for "not found" responses
+       (negative reply caching).  The postscreen daemon needs this to
+       accurately whitelist an SMTP client that is not found on any
+       DNSBL. Files: dns/dns_lookup.c, dns/dns_strrecord.c, dns/dns.h,
+       dns/test_dns_lookup.c.
+
+20150615
+
+       Two new parameters to limit how long a DNSBL or DNSWL lookup
+       result remains valid: postscreen_dnsbl_max_ttl is an upper
+       limit for the TTL from a DNS query, and postscreen_dnsbl_min_ttl
+       is a lower limit. The old postscreen_dnsbl_ttl provides a
+       backwards-compatible default for postscreen_dnsbl_max_ttl.
+       Files: global/mail_params.h, postscreen/postscreen.c,
+       postscreen/postscreen_early.c, mantools/postlink,
+       proto/postconf.proto.
+
+20150616
+
+       Refinement: the postscreen daemon now computes two combined
+       DNS reply TTLs: one combined TTL for replies that the client
+       should be blocked, and one combined TTL for replies that the
+       client should be allowed. This is more conservative than
+       simply combining all reply TTLs into one number.  File:
+
+20150621
+
+       Feature: default_transport_rate_delay (and the transport-specific
+       *transport*_transport_rate_delay) to enforce a destination-
+       independent rate limit on deliveries.  Files: mantools/postlink,
+       proto/postconf.proto, *qmgr/qmgr.h, *qmgr/qmgr_transport.c,
+       *qmgr/qmgr_deliver.c, *qmgr/qmgr.c.
+
+       postscreen/postscreen_dnsbl.c.
+
+20150707
+
+       Workaround: some DNS servers reply with NXDOMAIN for type
+       NS queries with names that actually have an A record. This
+       broke check_mumble_ns_access.  File: smtpd/smtpd_check.c.
index c47c503b29da10d95b0726f6fa93821ac293b385..758df223c9b14c26c857003f84ff171f9cd7c10b 100644 (file)
@@ -15,3 +15,54 @@ specifies the release date of a stable release or snapshot release.
 
 If you upgrade from Postfix 2.11 or earlier, read RELEASE_NOTES-3.0
 before proceeding.
+
+Major changes with snaphot 20150710
+===================================
+
+postscreen support for the TTL of DNSBL and DNSWL lookup results
+----------------------------------------------------------------
+
+Historically, the default setting "postscreen_dnsbl_ttl = 1h" assumes
+that a "not found" result from a DNSBL server will be valid for one
+hour.  This may have been adequate five years ago when postscreen
+was first implemented, but nowadays, that one hour can result in
+missed opportunities to block new spambots.
+
+To address this, postscreen now respects the TTL of DNSBL "not
+found" replies, as well as the TTL of DNSWL replies (both "found"
+and "not found").  The TTL for a "not found" reply is determined
+according to RFC 2308 (the TTL of an SOA record in the reply).
+
+Support for DNSBL or DNSWL reply TTL values is controlled by two
+configuration parameters:
+
+postscreen_dnsbl_min_ttl (default: 60 seconds).
+
+    This parameter specifies a minimum for the amount of time that
+    a DNSBL or DNSWL result will be cached in the postscreen_cache_map.
+    This prevents an excessive number of postscreen cache updates
+    when a DNSBL or DNSWL server specifies a very small reply TTL.
+
+postscreen_dnsbl_max_ttl (default: $postscreen_dnsbl_ttl or 1 hour)
+
+    This parameter specifies a maximum for the amount of time that
+    a DNSBL or DNSWL result will be cached in the postscreen_cache_map.
+    This prevents cache pollution when a DNSBL or DNSWL server
+    specifies a very large reply TTL.
+
+The postscreen_dnsbl_ttl parameter is now obsolete, and has become
+a default value for the new postscreen_dnsbl_max_ttl parameter.
+
+Destination-independent delivery rate delay
+-------------------------------------------
+
+Support to enforce a destination-independent delay between meail
+deliveries.  The following example inserts 20 seconds of delay
+between deliveries with the SMTP transport, limiting the delivery
+rate to at most three messages per minute.
+
+/etc/postfix/main.cf:
+    smtp_transport_rate_delay = 20s
+
+For details, see the description of default_transport_rate_delay
+and transport_transport_rate_delay in the postconf(5) manpage.
index aa4d8524383ba1c1ad5169bf64ff688587e5fb4b..eaeb18738fe35b49809044a1b32f96a33bd71482 100644 (file)
@@ -10,6 +10,8 @@ Wish list:
 
        Things to do after the stable release:
 
+       Make the dns_res_query() workaround on/off configurable.
+
        TLS certificate provenance: indicate whether a subject
        name/issuer are verified or not (for example, change the
        attribute name to unverified_ccert_subject etc.).  This is
index c96f588cad7106efa6a1e7abafd7569b1812a504..b1ca0e66a250718e73c537d063f23e233cfab3a9 100644 (file)
@@ -22,9 +22,10 @@ DNSBLOG(8)                                                          DNSBLOG(8)
        list domain name, an IP address, and an  ID.   If  the  IP  address  is
        listed  under  the  DNS white/blacklist, the <a href="dnsblog.8.html"><b>dnsblog</b>(8)</a> server logs the
        match and replies with the query arguments plus an  address  list  with
-       the  resulting  IP  addresses  separated  by  whitespace.  Otherwise it
-       replies with the query arguments plus an empty address list.   Finally,
-       The <a href="dnsblog.8.html"><b>dnsblog</b>(8)</a> server closes the connection.
+       the resulting IP addresses, separated by whitespace, and the reply TTL.
+       Otherwise it replies with the query arguments  plus  an  empty  address
+       list  and  the  reply TTL (-1 if unavailable).  Finally, The <a href="dnsblog.8.html"><b>dnsblog</b>(8)</a>
+       server closes the connection.
 
 <b>DIAGNOSTICS</b>
        Problems and transactions are logged to <b>syslogd</b>(8).
@@ -34,15 +35,15 @@ DNSBLOG(8)                                                          DNSBLOG(8)
        run for only a limited amount of time. Use the command "<b>postfix reload</b>"
        to speed up a change.
 
-       The  text  below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+       The text below provides only a parameter summary. See  <a href="postconf.5.html"><b>postconf</b>(5)</a>  for
        more details including examples.
 
        <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
-              The default location of the Postfix <a href="postconf.5.html">main.cf</a> and  <a href="master.5.html">master.cf</a>  con-
+              The  default  location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
               figuration files.
 
        <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
-              How  much  time  a  Postfix  daemon process may take to handle a
+              How much time a Postfix daemon process  may  take  to  handle  a
               request before it is terminated by a built-in watchdog timer.
 
        <b><a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> (empty)</b>
@@ -50,7 +51,7 @@ DNSBLOG(8)                                                          DNSBLOG(8)
               factors.
 
        <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
-              The  time  limit  for  sending  or receiving information over an
+              The time limit for sending  or  receiving  information  over  an
               internal communication channel.
 
        <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
@@ -66,8 +67,8 @@ DNSBLOG(8)                                                          DNSBLOG(8)
               The syslog facility of Postfix logging.
 
        <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
-              The mail system name that is prepended to the  process  name  in
-              syslog  records,  so  that  "smtpd" becomes, for example, "post-
+              The  mail  system  name that is prepended to the process name in
+              syslog records, so that "smtpd"  becomes,  for  example,  "post-
               fix/smtpd".
 
 <b>SEE ALSO</b>
index f9a730210ecdea8c822ddc93f6549cd5ac8d80a9..3ad052dfb16cbffc13f2ab6252591b724a503bb4 100644 (file)
@@ -295,6 +295,16 @@ OQMGR(8)                                                              OQMGR(8)
        <b><a href="postconf.5.html#transport_destination_rate_delay"><i>transport</i>_destination_rate_delay</a> $<a href="postconf.5.html#default_destination_rate_delay">default_destination_rate_delay</a></b>
               Idem, for delivery via the named message <i>transport</i>.
 
+       Available in Postfix version 3.1 and later:
+
+       <b><a href="postconf.5.html#default_transport_rate_delay">default_transport_rate_delay</a> (0s)</b>
+              The default amount of delay that is inserted between  individual
+              deliveries  over the same message delivery transport, regardless
+              of destination.
+
+       <b><a href="postconf.5.html#transport_transport_rate_delay"><i>transport</i>_transport_rate_delay</a> $<a href="postconf.5.html#default_transport_rate_delay">default_transport_rate_delay</a></b>
+              Idem, for delivery via the named message <i>transport</i>.
+
 <b>SAFETY CONTROLS</b>
        <b><a href="postconf.5.html#qmgr_daemon_timeout">qmgr_daemon_timeout</a> (1000s)</b>
               How much time a Postfix queue manager process may take to handle
index c14c4ea3b70273186fbaa87f330f75992ba75226..160b0e42578ea5d35898dfbafe0cef1fde60e0bc 100644 (file)
@@ -2627,6 +2627,40 @@ Example:
 </pre>
 
 
+</DD>
+
+<DT><b><a name="default_transport_rate_delay">default_transport_rate_delay</a>
+(default: 0s)</b></DT><DD>
+
+<p> The default amount of delay that is inserted between individual
+deliveries over the same message delivery transport, regardless of
+destination. If non-zero, all deliveries over the same message
+delivery transport will happen one at a time. </p>
+
+<p>Use <a href="postconf.5.html#transport_transport_rate_delay"><i>transport</i>_transport_rate_delay</a> to specify a
+transport-specific override, where the initial <i>transport</i> is
+the <a href="master.5.html">master.cf</a> name of the message delivery transport. </p>
+
+<p> Example: throttle outbound SMTP mail to at most 3 deliveries
+per minute. </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+    smtp_transport_rate_delay = 20s
+</pre>
+
+<p> To enable the delay, specify a non-zero time value (an integral
+value plus an optional one-letter suffix that specifies the time
+unit). </p>
+
+<p> Time units: s (seconds), m (minutes), h (hours), d (days), w
+(weeks). The default time unit is s (seconds). </p>
+
+<p> NOTE: the delay is enforced by the queue manager. </p>
+
+<p> This feature is available in Postfix 3.1 and later. </p>
+
+
 </DD>
 
 <DT><b><a name="default_verp_delimiters">default_verp_delimiters</a>
@@ -7746,6 +7780,43 @@ this test the next time the client connects. </dd>
 <p> This feature is available in Postfix 2.8. </p>
 
 
+</DD>
+
+<DT><b><a name="postscreen_dnsbl_max_ttl">postscreen_dnsbl_max_ttl</a>
+(default: ${<a href="postconf.5.html#postscreen_dnsbl_ttl">postscreen_dnsbl_ttl</a>?{$<a href="postconf.5.html#postscreen_dnsbl_ttl">postscreen_dnsbl_ttl</a>}:{1}}h)</b></DT><DD>
+
+<p> The maximum amount of time that <a href="postscreen.8.html">postscreen(8)</a> will use the
+result from a successful DNS-based reputation test before a
+client IP address is required to pass that test again. If the DNS
+reply specifies a shorter TTL value, that value will be used unless
+it would be smaller than <a href="postconf.5.html#postscreen_dnsbl_min_ttl">postscreen_dnsbl_min_ttl</a>.  </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit).  Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).  </p>
+
+<p> This feature is available in Postfix 3.1. The default setting
+is backwards-compatible with older Postfix versions. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_dnsbl_min_ttl">postscreen_dnsbl_min_ttl</a>
+(default: 60s)</b></DT><DD>
+
+<p> The minimum amount of time that <a href="postscreen.8.html">postscreen(8)</a> will use the
+result from a successful DNS-based reputation test before a
+client IP address is required to pass that test again. If the DNS
+reply specifies a larger TTL value, that value will be used unless
+it would be larger than <a href="postconf.5.html#postscreen_dnsbl_max_ttl">postscreen_dnsbl_max_ttl</a>.  </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit).  Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).  </p>
+
+<p> This feature is available in Postfix 3.1. </p>
+
+
 </DD>
 
 <DT><b><a name="postscreen_dnsbl_reply_map">postscreen_dnsbl_reply_map</a>
@@ -7869,16 +7940,15 @@ resolver(3) routines. </p>
 (default: 1h)</b></DT><DD>
 
 <p> The amount of time that <a href="postscreen.8.html">postscreen(8)</a> will use the result from
-a successful DNS blocklist test. During this time, the client IP address
-is excluded from this test. The default is relatively short, because a
-good client can immediately talk to a real Postfix SMTP server.
-</p>
+a successful DNS-based reputation test before a client
+IP address is required to pass that test again.  </p>
 
 <p> Specify a non-zero time value (an integral value plus an optional
 one-letter suffix that specifies the time unit).  Time units: s
 (seconds), m (minutes), h (hours), d (days), w (weeks).  </p>
 
-<p> This feature is available in Postfix 2.8.  </p>
+<p> This feature is available in Postfix 2.8-3.0. It was
+replaced by <a href="postconf.5.html#postscreen_dnsbl_max_ttl">postscreen_dnsbl_max_ttl</a> in Postfix 3.1.  </p>
 
 
 </DD>
@@ -18563,6 +18633,16 @@ of a <a href="master.5.html">master.cf</a> service name and a built-in suffix (i
 "_time_limit"). </p>
 
 
+</DD>
+
+<DT><b><a name="transport_transport_rate_delay">transport_transport_rate_delay</a>
+(default: $<a href="postconf.5.html#default_transport_rate_delay">default_transport_rate_delay</a>)</b></DT><DD>
+
+<p> A transport-specific override for the <a href="postconf.5.html#default_transport_rate_delay">default_transport_rate_delay</a>
+parameter value, where the initial <i>transport</i> in the parameter
+name is the <a href="master.5.html">master.cf</a> name of the message delivery transport. </p>
+
+
 </DD>
 
 <DT><b><a name="trigger_timeout">trigger_timeout</a>
index 85761ea45b87041f1d7c7d1387b9040b826b7fa0..09cd0e170167e419a107749a66595a904f6e0fc2 100644 (file)
@@ -292,9 +292,16 @@ POSTSCREEN(8)                                                    POSTSCREEN(8)
               The amount of time that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will use the result from a
               successful "bare newline" SMTP protocol test.
 
-       <b><a href="postconf.5.html#postscreen_dnsbl_ttl">postscreen_dnsbl_ttl</a> (1h)</b>
-              The amount of time that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will use the result from a
-              successful DNS blocklist test.
+       <b><a href="postconf.5.html#postscreen_dnsbl_max_ttl">postscreen_dnsbl_max_ttl</a></b>
+       <b>(${<a href="postconf.5.html#postscreen_dnsbl_ttl">postscreen_dnsbl_ttl</a>?{$<a href="postconf.5.html#postscreen_dnsbl_ttl">postscreen_dnsbl_ttl</a>}:{1}}h)</b>
+              The  maximum  amount  of  time  that  <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will use the
+              result from a successful  DNS-based  reputation  test  before  a
+              client IP address is required to pass that test again.
+
+       <b><a href="postconf.5.html#postscreen_dnsbl_min_ttl">postscreen_dnsbl_min_ttl</a> (60s)</b>
+              The  minimum  amount  of  time  that  <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will use the
+              result from a successful  DNS-based  reputation  test  before  a
+              client IP address is required to pass that test again.
 
        <b><a href="postconf.5.html#postscreen_greet_ttl">postscreen_greet_ttl</a> (1d)</b>
               The amount of time that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will use the result from a
@@ -310,34 +317,34 @@ POSTSCREEN(8)                                                    POSTSCREEN(8)
 
 <b>RESOURCE CONTROLS</b>
        <b><a href="postconf.5.html#line_length_limit">line_length_limit</a> (2048)</b>
-              Upon  input,  long  lines  are chopped up into pieces of at most
+              Upon input, long lines are chopped up into  pieces  of  at  most
               this length; upon delivery, long lines are reconstructed.
 
        <b><a href="postconf.5.html#postscreen_client_connection_count_limit">postscreen_client_connection_count_limit</a>         ($<a href="postconf.5.html#smtpd_client_connection_count_limit">smtpd_client_connec</a>-</b>
        <b><a href="postconf.5.html#smtpd_client_connection_count_limit">tion_count_limit</a>)</b>
-              How many simultaneous connections  any  remote  SMTP  client  is
+              How  many  simultaneous  connections  any  remote SMTP client is
               allowed to have with the <a href="postscreen.8.html"><b>postscreen</b>(8)</a> daemon.
 
        <b><a href="postconf.5.html#postscreen_command_count_limit">postscreen_command_count_limit</a> (20)</b>
-              The  limit  on the total number of commands per SMTP session for
+              The limit on the total number of commands per SMTP  session  for
               <a href="postscreen.8.html"><b>postscreen</b>(8)</a>'s built-in SMTP protocol engine.
 
        <b><a href="postconf.5.html#postscreen_command_time_limit">postscreen_command_time_limit</a> (normal: 300s, overload: 10s)</b>
-              The  time  limit  to  read   an   entire   command   line   with
+              The   time   limit   to   read   an  entire  command  line  with
               <a href="postscreen.8.html"><b>postscreen</b>(8)</a>'s built-in SMTP protocol engine.
 
        <b><a href="postconf.5.html#postscreen_post_queue_limit">postscreen_post_queue_limit</a> ($<a href="postconf.5.html#default_process_limit">default_process_limit</a>)</b>
-              The  number  of  clients  that can be waiting for service from a
+              The number of clients that can be waiting  for  service  from  a
               real Postfix SMTP server process.
 
        <b><a href="postconf.5.html#postscreen_pre_queue_limit">postscreen_pre_queue_limit</a> ($<a href="postconf.5.html#default_process_limit">default_process_limit</a>)</b>
-              The number of non-whitelisted clients that can be waiting for  a
-              decision  whether  they will receive service from a real Postfix
+              The  number of non-whitelisted clients that can be waiting for a
+              decision whether they will receive service from a  real  Postfix
               SMTP server process.
 
        <b><a href="postconf.5.html#postscreen_watchdog_timeout">postscreen_watchdog_timeout</a> (10s)</b>
-              How much time a <a href="postscreen.8.html"><b>postscreen</b>(8)</a> process may take to respond  to  a
-              remote  SMTP  client  command  or  to  perform a cache operation
+              How  much  time a <a href="postscreen.8.html"><b>postscreen</b>(8)</a> process may take to respond to a
+              remote SMTP client command  or  to  perform  a  cache  operation
               before it is terminated by a built-in watchdog timer.
 
 <b>STARTTLS CONTROLS</b>
@@ -350,11 +357,11 @@ POSTSCREEN(8)                                                    POSTSCREEN(8)
               The name of the <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> service entry in <a href="master.5.html">master.cf</a>.
 
 <b>OBSOLETE STARTTLS SUPPORT CONTROLS</b>
-       These parameters are supported for compatibility with  <a href="smtpd.8.html"><b>smtpd</b>(8)</a>  legacy
+       These  parameters  are supported for compatibility with <a href="smtpd.8.html"><b>smtpd</b>(8)</a> legacy
        parameters.
 
        <b><a href="postconf.5.html#postscreen_use_tls">postscreen_use_tls</a> ($<a href="postconf.5.html#smtpd_use_tls">smtpd_use_tls</a>)</b>
-              Opportunistic  TLS:  announce  STARTTLS  support  to remote SMTP
+              Opportunistic TLS: announce  STARTTLS  support  to  remote  SMTP
               clients, but do not require that clients use TLS encryption.
 
        <b><a href="postconf.5.html#postscreen_enforce_tls">postscreen_enforce_tls</a> ($<a href="postconf.5.html#smtpd_enforce_tls">smtpd_enforce_tls</a>)</b>
@@ -363,18 +370,18 @@ POSTSCREEN(8)                                                    POSTSCREEN(8)
 
 <b>MISCELLANEOUS CONTROLS</b>
        <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
-              The  default  location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+              The default location of the Postfix <a href="postconf.5.html">main.cf</a> and  <a href="master.5.html">master.cf</a>  con-
               figuration files.
 
        <b><a href="postconf.5.html#delay_logging_resolution_limit">delay_logging_resolution_limit</a> (2)</b>
-              The maximal number of digits after the decimal point  when  log-
+              The  maximal  number of digits after the decimal point when log-
               ging sub-second delay values.
 
        <b><a href="postconf.5.html#command_directory">command_directory</a> (see 'postconf -d' output)</b>
               The location of all postfix administrative commands.
 
        <b><a href="postconf.5.html#max_idle">max_idle</a> (100s)</b>
-              The  maximum  amount of time that an idle Postfix daemon process
+              The maximum amount of time that an idle Postfix  daemon  process
               waits for an incoming connection before terminating voluntarily.
 
        <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
@@ -387,8 +394,8 @@ POSTSCREEN(8)                                                    POSTSCREEN(8)
               The syslog facility of Postfix logging.
 
        <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
-              The  mail  system  name that is prepended to the process name in
-              syslog records, so that "smtpd"  becomes,  for  example,  "post-
+              The mail system name that is prepended to the  process  name  in
+              syslog  records,  so  that  "smtpd" becomes, for example, "post-
               fix/smtpd".
 
 <b>SEE ALSO</b>
@@ -406,7 +413,7 @@ POSTSCREEN(8)                                                    POSTSCREEN(8)
 <b>HISTORY</b>
        This service was introduced with Postfix version 2.8.
 
-       Many ideas in <a href="postscreen.8.html"><b>postscreen</b>(8)</a> were explored in earlier  work  by  Michael
+       Many  ideas  in  <a href="postscreen.8.html"><b>postscreen</b>(8)</a> were explored in earlier work by Michael
        Tokarev, in OpenBSD spamd, and in MailChannels Traffic Control.
 
 <b>AUTHOR(S)</b>
index 6225c559d37cf4f6884c88af673ebf3b970dd535..34a8ca760d4ab14e75e6ac43b69218f6e64dadfd 100644 (file)
@@ -357,6 +357,16 @@ QMGR(8)                                                                QMGR(8)
        <b><a href="postconf.5.html#transport_destination_rate_delay"><i>transport</i>_destination_rate_delay</a> $<a href="postconf.5.html#default_destination_rate_delay">default_destination_rate_delay</a></b>
               Idem, for delivery via the named message <i>transport</i>.
 
+       Available in Postfix version 3.1 and later:
+
+       <b><a href="postconf.5.html#default_transport_rate_delay">default_transport_rate_delay</a> (0s)</b>
+              The  default amount of delay that is inserted between individual
+              deliveries over the same message delivery transport,  regardless
+              of destination.
+
+       <b><a href="postconf.5.html#transport_transport_rate_delay"><i>transport</i>_transport_rate_delay</a> $<a href="postconf.5.html#default_transport_rate_delay">default_transport_rate_delay</a></b>
+              Idem, for delivery via the named message <i>transport</i>.
+
 <b>SAFETY CONTROLS</b>
        <b><a href="postconf.5.html#qmgr_daemon_timeout">qmgr_daemon_timeout</a> (1000s)</b>
               How much time a Postfix queue manager process may take to handle
index 052cf9b98b00723072ee25917bbfb0f6f3077bcd..2ce0774db049fa0601bbd559a8e087bb9ea7cd2f 100644 (file)
@@ -817,7 +817,7 @@ CCARGS="$CCARGS -DSNAPSHOT"
 
 # Non-production: needs thorough testing, or major changes are still
 # needed before the code stabilizes.
-#CCARGS="$CCARGS -DNONPROD"
+CCARGS="$CCARGS -DNONPROD"
 
 # Workaround: prepend Postfix include files before other include files.
 CCARGS="-I. -I../../include $CCARGS"
index 4c303432a45caeacb1c16df3412e3667f2371a3c..042cbdc53b9448e7d6d3f81c7fb8388af3b8696d 100644 (file)
@@ -1676,6 +1676,38 @@ default_transport = uucp:relayhostname
 .fi
 .ad
 .ft R
+.SH default_transport_rate_delay (default: 0s)
+The default amount of delay that is inserted between individual
+deliveries over the same message delivery transport, regardless of
+destination. If non\-zero, all deliveries over the same message
+delivery transport will happen one at a time.
+.PP
+Use \fItransport\fR_transport_rate_delay to specify a
+transport\-specific override, where the initial \fItransport\fR is
+the master.cf name of the message delivery transport.
+.PP
+Example: throttle outbound SMTP mail to at most 3 deliveries
+per minute.
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+    smtp_transport_rate_delay = 20s
+.fi
+.ad
+.ft R
+.PP
+To enable the delay, specify a non\-zero time value (an integral
+value plus an optional one\-letter suffix that specifies the time
+unit).
+.PP
+Time units: s (seconds), m (minutes), h (hours), d (days), w
+(weeks). The default time unit is s (seconds).
+.PP
+NOTE: the delay is enforced by the queue manager.
+.PP
+This feature is available in Postfix 3.1 and later.
 .SH default_verp_delimiters (default: +=)
 The two default VERP delimiter characters. These are used when
 no explicit delimiters are specified with the SMTP XVERP command
@@ -4719,6 +4751,31 @@ this test the next time the client connects.
 .br
 .PP
 This feature is available in Postfix 2.8.
+.SH postscreen_dnsbl_max_ttl (default: ${postscreen_dnsbl_ttl?{$postscreen_dnsbl_ttl}:{1}}h)
+The maximum amount of time that \fBpostscreen\fR(8) will use the
+result from a successful DNS\-based reputation test before a
+client IP address is required to pass that test again. If the DNS
+reply specifies a shorter TTL value, that value will be used unless
+it would be smaller than postscreen_dnsbl_min_ttl.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit).  Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+.PP
+This feature is available in Postfix 3.1. The default setting
+is backwards\-compatible with older Postfix versions.
+.SH postscreen_dnsbl_min_ttl (default: 60s)
+The minimum amount of time that \fBpostscreen\fR(8) will use the
+result from a successful DNS\-based reputation test before a
+client IP address is required to pass that test again. If the DNS
+reply specifies a larger TTL value, that value will be used unless
+it would be larger than postscreen_dnsbl_max_ttl.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit).  Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+.PP
+This feature is available in Postfix 3.1.
 .SH postscreen_dnsbl_reply_map (default: empty)
 A mapping from actual DNSBL domain name which includes a secret
 password, to the DNSBL domain name that postscreen will reply with
@@ -4825,15 +4882,15 @@ the timeouts in the \fBdnsblog\fR(8) daemon which are defined by system
 This feature is available in Postfix 3.0.
 .SH postscreen_dnsbl_ttl (default: 1h)
 The amount of time that \fBpostscreen\fR(8) will use the result from
-a successful DNS blocklist test. During this time, the client IP address
-is excluded from this test. The default is relatively short, because a
-good client can immediately talk to a real Postfix SMTP server.
+a successful DNS\-based reputation test before a client
+IP address is required to pass that test again.
 .PP
 Specify a non\-zero time value (an integral value plus an optional
 one\-letter suffix that specifies the time unit).  Time units: s
 (seconds), m (minutes), h (hours), d (days), w (weeks).
 .PP
-This feature is available in Postfix 2.8.
+This feature is available in Postfix 2.8\-3.0. It was
+replaced by postscreen_dnsbl_max_ttl in Postfix 3.1.
 .SH postscreen_dnsbl_whitelist_threshold (default: 0)
 Allow a remote SMTP client to skip "before" and "after 220
 greeting" protocol tests, based on its combined DNSBL score as
@@ -12741,6 +12798,10 @@ in "postconf" command output before Postfix version 2.9.  This
 limitation applies to many parameters whose name is a combination
 of a master.cf service name and a built\-in suffix (in this case:
 "_time_limit").
+.SH transport_transport_rate_delay (default: $default_transport_rate_delay)
+A transport\-specific override for the default_transport_rate_delay
+parameter value, where the initial \fItransport\fR in the parameter
+name is the master.cf name of the message delivery transport.
 .SH trigger_timeout (default: 10s)
 The time limit for sending a trigger to a Postfix daemon (for
 example, the \fBpickup\fR(8) or \fBqmgr\fR(8) daemon). This time limit prevents
index 8be212d6e6831a794473a8987db11f9efe18848c..c36bfbcb84e3255140df9b9a30288bb60e179acd 100644 (file)
@@ -26,8 +26,9 @@ a DNS white/blacklist domain name, an IP address, and an ID.
 If the IP address is listed under the DNS white/blacklist, the
 \fBdnsblog\fR(8) server logs the match and replies with the
 query arguments plus an address list with the resulting IP
-addresses separated by whitespace.  Otherwise it replies
-with the query arguments plus an empty address list.  Finally,
+addresses, separated by whitespace, and the reply TTL.
+Otherwise it replies with the query arguments plus an empty
+address list and the reply TTL (\-1 if unavailable).  Finally,
 The \fBdnsblog\fR(8) server closes the connection.
 .SH DIAGNOSTICS
 .ad
index 6bd15fe40da63a19fe0c097a755da768b7dacdce..f9f07554ee5f240573a620c415928bf58a054c5c 100644 (file)
@@ -289,6 +289,14 @@ deliveries to the same destination; the resulting behavior depends
 on the value of the corresponding per\-destination recipient limit.
 .IP "\fItransport\fB_destination_rate_delay $default_destination_rate_delay
 Idem, for delivery via the named message \fItransport\fR.
+.PP
+Available in Postfix version 3.1 and later:
+.IP "\fBdefault_transport_rate_delay (0s)\fR"
+The default amount of delay that is inserted between individual
+deliveries over the same message delivery transport, regardless of
+destination.
+.IP "\fItransport\fB_transport_rate_delay $default_transport_rate_delay
+Idem, for delivery via the named message \fItransport\fR.
 .SH "SAFETY CONTROLS"
 .na
 .nf
index bf92fbe00be9c76c4da6b6971a1b3416061ef501..faeda727448ccd5fc1918f683b73308660e07cd2 100644 (file)
@@ -307,9 +307,14 @@ temporary whitelist entry before it is removed.
 .IP "\fBpostscreen_bare_newline_ttl (30d)\fR"
 The amount of time that \fBpostscreen\fR(8) will use the result from
 a successful "bare newline" SMTP protocol test.
-.IP "\fBpostscreen_dnsbl_ttl (1h)\fR"
-The amount of time that \fBpostscreen\fR(8) will use the result from
-a successful DNS blocklist test.
+.IP "\fBpostscreen_dnsbl_max_ttl (${postscreen_dnsbl_ttl?{$postscreen_dnsbl_ttl}:{1}}h)\fR"
+The maximum amount of time that \fBpostscreen\fR(8) will use the
+result from a successful DNS\-based reputation test before a
+client IP address is required to pass that test again.
+.IP "\fBpostscreen_dnsbl_min_ttl (60s)\fR"
+The minimum amount of time that \fBpostscreen\fR(8) will use the
+result from a successful DNS\-based reputation test before a
+client IP address is required to pass that test again.
 .IP "\fBpostscreen_greet_ttl (1d)\fR"
 The amount of time that \fBpostscreen\fR(8) will use the result from
 a successful PREGREET test.
index 6ecfed563b4257a51505e95acc230f37c4b109bf..0e395c362971a1fbfd3caca40ba7520142400f13 100644 (file)
@@ -337,6 +337,14 @@ deliveries to the same destination; the resulting behavior depends
 on the value of the corresponding per\-destination recipient limit.
 .IP "\fItransport\fB_destination_rate_delay $default_destination_rate_delay
 Idem, for delivery via the named message \fItransport\fR.
+.PP
+Available in Postfix version 3.1 and later:
+.IP "\fBdefault_transport_rate_delay (0s)\fR"
+The default amount of delay that is inserted between individual
+deliveries over the same message delivery transport, regardless of
+destination.
+.IP "\fItransport\fB_transport_rate_delay $default_transport_rate_delay
+Idem, for delivery via the named message \fItransport\fR.
 .SH "SAFETY CONTROLS"
 .na
 .nf
index fd55bb7a0bed3aa20bc915b682399acd8ddbbe20..2557555e115ff15aceb813e212aa321cf916597c 100755 (executable)
@@ -394,6 +394,7 @@ while (<>) {
     s;\bdefault_desti[-</Bb>]*\n* *[<Bb>]*na[-</Bb>]*\n* *[<Bb>]*tion_con[-</Bb>]*\n* *[<Bb>]*currency_failed_cohort_limit\b;<a href="postconf.5.html#default_destination_concurrency_failed_cohort_limit">$&</a>;g;
     s;\bdestination_concurrency_feedback_debug\b;<a href="postconf.5.html#destination_concurrency_feedback_debug">$&</a>;g;
     s;\bdefault_destina[-</Bb>]*\n* *[<Bb>]*tion_rate_delay\b;<a href="postconf.5.html#default_destination_rate_delay">$&</a>;g;
+    s;\bdefault_trans[-<\/bB>]*\n*[ <bB>]*port_rate_delay\b;<a href="postconf.5.html#default_transport_rate_delay">$&</a>;g;
     s;\bmeta_directory\b;<a href="postconf.5.html#meta_directory">$&</a>;g;
 
     s;\bqmqpd_client_port_logging\b;<a href="postconf.5.html#qmqpd_client_port_logging">$&</a>;g;
@@ -764,6 +765,7 @@ while (<>) {
     s;(<i>transport</i>)(<b>)?(_recipient_refill_limit)\b;$2<a href="postconf.5.html#transport_recipient_refill_limit">$1$3</a>;g;
     s;(<i>transport</i>)(<b>)?(_time_limit)\b;$2<a href="postconf.5.html#transport_time_limit">$1$3</a>;g;
     s;(<i>transport</i>)(<b>)?(_destination_rate_delay)\b;$2<a href="postconf.5.html#transport_destination_rate_delay">$1$3</a>;g;
+    s;(<i>transport</i>)(<b>)?(_transport_rate_delay)\b;$2<a href="postconf.5.html#transport_transport_rate_delay">$1$3</a>;g;
 
     # Undo hyperlinks of manual pages with the same name as parameters.
 
@@ -1004,6 +1006,8 @@ while (<>) {
     s;\bpostscreen_dnsbl_thresh[-</bB>]*\n* *[<bB>]*old\b;<a href="postconf.5.html#postscreen_dnsbl_threshold">$&</a>;g;
     s;\bpostscreen_dnsbl_whitelist_thresh[-</bB>]*\n* *[<bB>]*old\b;<a href="postconf.5.html#postscreen_dnsbl_whitelist_threshold">$&</a>;g;
     s;\bpostscreen_dnsbl_action\b;<a href="postconf.5.html#postscreen_dnsbl_action">$&</a>;g;
+    s;\bpostscreen_dnsbl_max_ttl\b;<a href="postconf.5.html#postscreen_dnsbl_max_ttl">$&</a>;g;
+    s;\bpostscreen_dnsbl_min_ttl\b;<a href="postconf.5.html#postscreen_dnsbl_min_ttl">$&</a>;g;
     s;\bpostscreen_dnsbl_ttl\b;<a href="postconf.5.html#postscreen_dnsbl_ttl">$&</a>;g;
     s;\bpostscreen_dnsbl_timeout\b;<a href="postconf.5.html#postscreen_dnsbl_timeout">$&</a>;g;
     s;\bpostscreen_for[-</bB>]*\n*[ <bB>]*bid[-</bB>]*\n* *[<bB>]*den_commands\b;<a href="postconf.5.html#postscreen_forbidden_commands">$&</a>;g;
index d710b6d9a96c85cf04346a237de775a3f77f6529..08d3a95b57e83324a53994b0282059181d91eb5c 100644 (file)
@@ -13153,6 +13153,42 @@ this case: "_recipient_refill_delay").  </p>
 
 <p> This feature is available in Postfix 2.4 and later. </p>
 
+%PARAM default_transport_rate_delay 0s
+
+<p> The default amount of delay that is inserted between individual
+deliveries over the same message delivery transport, regardless of
+destination. If non-zero, all deliveries over the same message
+delivery transport will happen one at a time. </p>
+
+<p>Use <i>transport</i>_transport_rate_delay to specify a
+transport-specific override, where the initial <i>transport</i> is
+the master.cf name of the message delivery transport. </p>
+
+<p> Example: throttle outbound SMTP mail to at most 3 deliveries
+per minute. </p>
+
+<pre>
+/etc/postfix/main.cf:
+    smtp_transport_rate_delay = 20s
+</pre>
+
+<p> To enable the delay, specify a non-zero time value (an integral
+value plus an optional one-letter suffix that specifies the time
+unit). </p>
+<p> Time units: s (seconds), m (minutes), h (hours), d (days), w
+(weeks). The default time unit is s (seconds). </p>
+
+<p> NOTE: the delay is enforced by the queue manager. </p>
+
+<p> This feature is available in Postfix 3.1 and later. </p>
+
+%PARAM transport_transport_rate_delay $default_transport_rate_delay
+
+<p> A transport-specific override for the default_transport_rate_delay
+parameter value, where the initial <i>transport</i> in the parameter
+name is the master.cf name of the message delivery transport. </p>
+
 %PARAM default_destination_rate_delay 0s
 
 <p> The default amount of delay that is inserted between individual
@@ -14305,16 +14341,44 @@ built-in SMTP protocol engine. </p>
 %PARAM postscreen_dnsbl_ttl 1h
 
 <p> The amount of time that postscreen(8) will use the result from
-a successful DNS blocklist test. During this time, the client IP address
-is excluded from this test. The default is relatively short, because a
-good client can immediately talk to a real Postfix SMTP server.
-</p>
+a successful DNS-based reputation test before a client
+IP address is required to pass that test again.  </p>
 
 <p> Specify a non-zero time value (an integral value plus an optional
 one-letter suffix that specifies the time unit).  Time units: s
 (seconds), m (minutes), h (hours), d (days), w (weeks).  </p>
 
-<p> This feature is available in Postfix 2.8.  </p>
+<p> This feature is available in Postfix 2.8-3.0. It was 
+replaced by postscreen_dnsbl_max_ttl in Postfix 3.1.  </p>
+
+%PARAM postscreen_dnsbl_min_ttl 60s
+
+<p> The minimum amount of time that postscreen(8) will use the
+result from a successful DNS-based reputation test before a
+client IP address is required to pass that test again. If the DNS
+reply specifies a larger TTL value, that value will be used unless
+it would be larger than postscreen_dnsbl_max_ttl.  </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit).  Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).  </p>
+
+<p> This feature is available in Postfix 3.1. </p>
+
+%PARAM postscreen_dnsbl_max_ttl ${postscreen_dnsbl_ttl?{$postscreen_dnsbl_ttl}:{1}}h
+
+<p> The maximum amount of time that postscreen(8) will use the
+result from a successful DNS-based reputation test before a
+client IP address is required to pass that test again. If the DNS
+reply specifies a shorter TTL value, that value will be used unless
+it would be smaller than postscreen_dnsbl_min_ttl.  </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit).  Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).  </p>
+
+<p> This feature is available in Postfix 3.1. The default setting
+is backwards-compatible with older Postfix versions. </p>
 
 %PARAM postscreen_pipelining_action enforce
 
index d8551b0f87c5209ca2e089887fcb2f3a01435510..878fd692c09d8fc4066afe125ee8eb3354b0465f 100644 (file)
@@ -30,7 +30,33 @@ test:        $(TESTPROG)
 
 tests: test dns_rr_to_pa_test dns_rr_to_sa_test dns_sa_to_rr_test \
        dns_rr_eq_sa_test no-a-test no-aaaa-test no-mx-test \
-       error-filter-test nullmx_test nxdomain_test mxonly_test
+       error-filter-test nullmx_test nxdomain_test mxonly_test \
+       dnsbl_tests
+
+dnsbl_tests: \
+       dnsbl_ttl_127.0.0.2_bind_plain_test \
+       dnsbl_ttl_127.0.0.2_bind_ncache_test \
+       dnsbl_ttl_127.0.0.2_priv_plain_test \
+       dnsbl_ttl_127.0.0.2_priv_ncache_test \
+       dnsbl_ttl_127.0.0.1_bind_plain_test \
+       dnsbl_ttl_127.0.0.1_bind_ncache_test \
+       dnsbl_ttl_127.0.0.1_priv_plain_test \
+       dnsbl_ttl_127.0.0.1_priv_ncache_test
+
+DNSBL_NEXIST_REPLY_FIX = \
+       sed -e 's/ [0-9][0-9]* IN SOA / TTL IN SOA /' \
+               -e 's/len=[0-9][0-9]* /len=LEN /' \
+               -e 's/nscount=[1-9][0-9]*/nscount=N/' \
+               -e 's/ [0-9]* [0-9]* [0-9]* [0-9]* [0-9]*/ D D D D D/'
+
+DNSBL_EXIST_REPLY_FIX = \
+       sed -e 's/ [0-9][0-9]* IN A / TTL IN A /' \
+           -e 's/len=[0-9][0-9]* /len=LEN /' \
+           -e 's/ancount=[1-9][0-9]*/ancount=N/' \
+           -e 's/nscount=[1-9][0-9]*/nscount=N/' \
+           -e 's/ [0-9]* [0-9]* [0-9]* [0-9]* [0-9]*/ D D D D D/' \
+           -e 's/127.0.0.[0-9]*$$/127.0.0.D/' \
+           | uniq 
 
 root_tests:
 
@@ -134,6 +160,94 @@ mxonly_test: test_dns_lookup mxonly_test.ref
        diff mxonly_test.ref mxonly_test.tmp
        rm -f mxonly_test.tmp
 
+# Non-existent record, libbind API, RFC 2308 disabled.
+
+dnsbl_ttl_127.0.0.1_bind_plain_test: test_dns_lookup dnsbl_ttl_127.0.0.1_bind_plain.ref
+       (set -e; \
+        $(SHLIB_ENV) ./test_dns_lookup a 1.0.0.127.zen.spamhaus.org; \
+        $(SHLIB_ENV) ./test_dns_lookup a 1.0.0.127.b.barracudacentral.org; \
+        $(SHLIB_ENV) ./test_dns_lookup a 1.0.0.127.bl.spamcop.net; \
+       ) 2>&1 | $(DNSBL_NEXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.1_bind_plain.tmp
+       diff dnsbl_ttl_127.0.0.1_bind_plain.ref dnsbl_ttl_127.0.0.1_bind_plain.tmp
+       rm -f dnsbl_ttl_127.0.0.1_bind_plain.tmp
+
+# Non-existent record, private API, RFC 2308 disabled.
+
+dnsbl_ttl_127.0.0.1_priv_plain_test: test_dns_lookup dnsbl_ttl_127.0.0.1_bind_plain.ref
+       (set -e; \
+        $(SHLIB_ENV) ./test_dns_lookup -p a 1.0.0.127.zen.spamhaus.org; \
+        $(SHLIB_ENV) ./test_dns_lookup -p a 1.0.0.127.b.barracudacentral.org; \
+        $(SHLIB_ENV) ./test_dns_lookup -p a 1.0.0.127.bl.spamcop.net; \
+       ) 2>&1 | $(DNSBL_NEXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.1_priv_plain.tmp
+       diff dnsbl_ttl_127.0.0.1_bind_plain.ref dnsbl_ttl_127.0.0.1_priv_plain.tmp
+       rm -f dnsbl_ttl_127.0.0.1_priv_plain.tmp
+
+# Non-existent record, libbind API, RFC 2308 enabled.
+
+dnsbl_ttl_127.0.0.1_bind_ncache_test: test_dns_lookup dnsbl_ttl_127.0.0.1_bind_ncache.ref
+       (set -e; \
+        $(SHLIB_ENV) ./test_dns_lookup -n a 1.0.0.127.zen.spamhaus.org; \
+        $(SHLIB_ENV) ./test_dns_lookup -n a 1.0.0.127.b.barracudacentral.org; \
+        $(SHLIB_ENV) ./test_dns_lookup -n a 1.0.0.127.bl.spamcop.net; \
+       ) 2>&1 | $(DNSBL_NEXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.1_bind_ncache.tmp
+       diff dnsbl_ttl_127.0.0.1_bind_ncache.ref dnsbl_ttl_127.0.0.1_bind_ncache.tmp
+       rm -f dnsbl_ttl_127.0.0.1_bind_ncache.tmp
+
+# Non-existent record, private API, RFC 2308 enabled.
+
+dnsbl_ttl_127.0.0.1_priv_ncache_test: test_dns_lookup dnsbl_ttl_127.0.0.1_bind_ncache.ref
+       (set -e; \
+        $(SHLIB_ENV) ./test_dns_lookup -n -p a 1.0.0.127.zen.spamhaus.org; \
+        $(SHLIB_ENV) ./test_dns_lookup -n -p a 1.0.0.127.b.barracudacentral.org; \
+        $(SHLIB_ENV) ./test_dns_lookup -n -p a 1.0.0.127.bl.spamcop.net; \
+       ) 2>&1 | $(DNSBL_NEXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.1_priv_ncache.tmp
+       diff dnsbl_ttl_127.0.0.1_bind_ncache.ref dnsbl_ttl_127.0.0.1_priv_ncache.tmp
+       rm -f dnsbl_ttl_127.0.0.1_priv_ncache.tmp
+
+# Existing record, libbind API, RFC 2308 disabled.
+
+dnsbl_ttl_127.0.0.2_bind_plain_test: test_dns_lookup dnsbl_ttl_127.0.0.2_bind_plain.ref
+       (set -e; \
+        $(SHLIB_ENV) ./test_dns_lookup a 2.0.0.127.zen.spamhaus.org; \
+        $(SHLIB_ENV) ./test_dns_lookup a 2.0.0.127.b.barracudacentral.org; \
+        $(SHLIB_ENV) ./test_dns_lookup a 2.0.0.127.bl.spamcop.net; \
+       ) 2>&1 | $(DNSBL_EXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.2_bind_plain.tmp
+       diff dnsbl_ttl_127.0.0.2_bind_plain.ref dnsbl_ttl_127.0.0.2_bind_plain.tmp
+       rm -f dnsbl_ttl_127.0.0.2_bind_plain.tmp
+
+# Existing record, private API, RFC 2308 disabled.
+
+dnsbl_ttl_127.0.0.2_priv_plain_test: test_dns_lookup dnsbl_ttl_127.0.0.2_bind_plain.ref
+       (set -e; \
+        $(SHLIB_ENV) ./test_dns_lookup -p a 2.0.0.127.zen.spamhaus.org; \
+        $(SHLIB_ENV) ./test_dns_lookup -p a 2.0.0.127.b.barracudacentral.org; \
+        $(SHLIB_ENV) ./test_dns_lookup -p a 2.0.0.127.bl.spamcop.net; \
+       ) 2>&1 | $(DNSBL_EXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.2_priv_plain.tmp
+       diff dnsbl_ttl_127.0.0.2_bind_plain.ref dnsbl_ttl_127.0.0.2_priv_plain.tmp
+       rm -f dnsbl_ttl_127.0.0.2_priv_plain.tmp
+
+# Existing record, libbind API, RFC 2308 enabled.
+
+dnsbl_ttl_127.0.0.2_bind_ncache_test: test_dns_lookup dnsbl_ttl_127.0.0.2_bind_plain.ref
+       (set -e; \
+        $(SHLIB_ENV) ./test_dns_lookup -n a 2.0.0.127.zen.spamhaus.org; \
+        $(SHLIB_ENV) ./test_dns_lookup -n a 2.0.0.127.b.barracudacentral.org; \
+        $(SHLIB_ENV) ./test_dns_lookup -n a 2.0.0.127.bl.spamcop.net; \
+       ) 2>&1 | $(DNSBL_EXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.2_bind_ncache.tmp
+       diff dnsbl_ttl_127.0.0.2_bind_plain.ref dnsbl_ttl_127.0.0.2_bind_ncache.tmp
+       rm -f dnsbl_ttl_127.0.0.2_bind_ncache.tmp
+
+# Existing record, private API, RFC 2308 enabled.
+
+dnsbl_ttl_127.0.0.2_priv_ncache_test: test_dns_lookup dnsbl_ttl_127.0.0.2_bind_plain.ref
+       (set -e; \
+        $(SHLIB_ENV) ./test_dns_lookup -n -p a 2.0.0.127.zen.spamhaus.org; \
+        $(SHLIB_ENV) ./test_dns_lookup -n -p a 2.0.0.127.b.barracudacentral.org; \
+        $(SHLIB_ENV) ./test_dns_lookup -n -p a 2.0.0.127.bl.spamcop.net; \
+       ) 2>&1 | $(DNSBL_EXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.2_priv_ncache.tmp
+       diff dnsbl_ttl_127.0.0.2_bind_plain.ref dnsbl_ttl_127.0.0.2_priv_ncache.tmp
+       rm -f dnsbl_ttl_127.0.0.2_priv_ncache.tmp
+
 printfck: $(OBJS) $(PROG)
        rm -rf printfck
        mkdir printfck
index 7cfc581080c6e74651f18764dcd3395d3cf1ec0b..2938ac3d6584865782d7f09c61aa7fd3c080ea0b 100644 (file)
@@ -212,15 +212,20 @@ extern int dns_rr_eq_sa(DNS_RR *, struct sockaddr *);
  /*
   * dns_lookup.c
   */
-extern int dns_lookup_r(const char *, unsigned, unsigned, DNS_RR **,
-                               VSTRING *, VSTRING *, int *);
+extern int dns_lookup_x(const char *, unsigned, unsigned, DNS_RR **,
+                               VSTRING *, VSTRING *, int *, unsigned);
 extern int dns_lookup_rl(const char *, unsigned, DNS_RR **, VSTRING *,
                                 VSTRING *, int *, int,...);
 extern int dns_lookup_rv(const char *, unsigned, DNS_RR **, VSTRING *,
                                 VSTRING *, int *, int, unsigned *);
+extern int dns_ncache_ttl_fix_enable;
 
 #define dns_lookup(name, type, rflags, list, fqdn, why) \
-    dns_lookup_r((name), (type), (rflags), (list), (fqdn), (why), (int *) 0)
+    dns_lookup_x((name), (type), (rflags), (list), (fqdn), (why), (int *) 0, \
+       (unsigned) 0)
+#define dns_lookup_r(name, type, rflags, list, fqdn, why, rcode) \
+    dns_lookup_x((name), (type), (rflags), (list), (fqdn), (why), (rcode), \
+       (unsigned) 0)
 #define dns_lookup_l(name, rflags, list, fqdn, why, lflags, ...) \
     dns_lookup_rl((name), (rflags), (list), (fqdn), (why), (int *) 0, \
        (lflags), __VA_ARGS__)
@@ -235,6 +240,7 @@ extern int dns_lookup_rv(const char *, unsigned, DNS_RR **, VSTRING *,
 #define DNS_REQ_FLAG_STOP_INVAL        (1<<1)
 #define DNS_REQ_FLAG_STOP_NULLMX (1<<2)
 #define DNS_REQ_FLAG_STOP_MX_POLICY (1<<3)
+#define DNS_REQ_FLAG_NCACHE_TTL        (1<<4)
 #define DNS_REQ_FLAG_NONE      (0)
 
  /*
index 3838ea6b14599d33ceb35f40e5d667c7213fc968..a89d2a468fb56483dc666112dcbe33fc1a6c7052 100644 (file)
@@ -32,6 +32,8 @@
 /*     int     lflags;
 /*     unsigned *ltype;
 /* AUXILIARY FUNCTIONS
+/*     int     dns_ncache_ttl_fix_enable;
+/*
 /*     int     dns_lookup_r(name, type, rflags, list, fqdn, why, rcode)
 /*     const char *name;
 /*     unsigned type;
 /*     int     *rcode;
 /*     int     lflags;
 /*     unsigned *ltype;
+/*
+/*     int     dns_lookup_x(name, type, rflags, list, fqdn, why, rcode, lflags)
+/*     const char *name;
+/*     unsigned type;
+/*     unsigned rflags;
+/*     DNS_RR  **list;
+/*     VSTRING *fqdn;
+/*     VSTRING *why;
+/*     int     *rcode;
+/*     unsigned lflags;
 /* DESCRIPTION
 /*     dns_lookup() looks up DNS resource records. When requested to
 /*     look up data other than type CNAME, it will follow a limited
 /*     dns_lookup_l() and dns_lookup_v() allow the user to specify
 /*     a list of resource types.
 /*
-/*     dns_lookup_r(), dns_lookup_rl() and dns_lookup_rv() provide
-/*     additional information.
+/*     dns_lookup_x, dns_lookup_r(), dns_lookup_rl() and dns_lookup_rv()
+/*     accept or return additional information.
+/*
+/*     The dns_ncache_ttl_fix_enable variable controls a workaround
+/*     for res_search(3) implementations that break the
+/*     DNS_REQ_FLAG_NCACHE_TTL feature. The workaround does not
+/*     support EDNS0 or DNSSEC, but it should be sufficient for
+/*     DNSBL/DNSWL lookups.
 /* INPUTS
 /* .ad
 /* .fi
 /*     implement DNSSEC.
 /* .RE
 /* .IP lflags
-/*     Multi-type request control for dns_lookup_l() and dns_lookup_v().
-/*     For convenience, DNS_REQ_FLAG_NONE requests no special
-/*     processing. Invoke dns_lookup() for all specified resource
-/*     record types in the specified order, and merge their results.
+/*     Flags that control the operation of the dns_lookup*()
+/*     functions.  DNS_REQ_FLAG_NONE requests no special processing.
 /*     Otherwise, specify one or more of the following:
 /* .RS
 /* .IP DNS_REQ_FLAG_STOP_INVAL
+/*     This flag is used by dns_lookup_l() and dns_lookup_v().
 /*     Invoke dns_lookup() for the resource types in the order as
 /*     specified, and return when dns_lookup() returns DNS_INVAL.
 /* .IP DNS_REQ_FLAG_STOP_NULLMX
+/*     This flag is used by dns_lookup_l() and dns_lookup_v().
 /*     Invoke dns_lookup() for the resource types in the order as
 /*     specified, and return when dns_lookup() returns DNS_NULLMX.
 /* .IP DNS_REQ_FLAG_STOP_MX_POLICY
+/*     This flag is used by dns_lookup_l() and dns_lookup_v().
 /*     Invoke dns_lookup() for the resource types in the order as
 /*     specified, and return when dns_lookup() returns DNS_POLICY
 /*     for an MX query.
 /* .IP DNS_REQ_FLAG_STOP_OK
+/*     This flag is used by dns_lookup_l() and dns_lookup_v().
 /*     Invoke dns_lookup() for the resource types in the order as
 /*     specified, and return when dns_lookup() returns DNS_OK.
+/* .IP DNS_REQ_FLAG_NCACHE_TTL
+/*     When the lookup result status is DNS_NOTFOUND, return the
+/*     SOA record(s) from the authority section in the reply, if
+/*     available. The per-record reply TTL specifies how long the
+/*     DNS_NOTFOUND answer is valid. The caller should pass the
+/*     record(s) to dns_rr_free().
 /* .RE
 /* .IP ltype
 /*     The resource record types to be looked up. In the case of
   */
 #define DEF_DNS_REPLY_SIZE     4096    /* in case we're using TCP */
 #define MAX_DNS_REPLY_SIZE     65536   /* in case we're using TCP */
+#define MAX_DNS_QUERY_SIZE     2048    /* XXX */
 
 typedef struct DNS_REPLY {
     unsigned char *buf;                        /* raw reply data */
@@ -223,22 +250,163 @@ typedef struct DNS_REPLY {
     int     dnssec_ad;                 /* DNSSEC AD bit */
     int     query_count;               /* number of queries */
     int     answer_count;              /* number of answers */
+    int     auth_count;                        /* number of authority records */
     unsigned char *query_start;                /* start of query data */
     unsigned char *answer_start;       /* start of answer data */
     unsigned char *end;                        /* first byte past reply */
 } DNS_REPLY;
 
+ /*
+  * Test/set primitives to determine if the reply buffer contains a server
+  * response. We use this when the caller requests DNS_REQ_FLAG_NCACHE_TTL,
+  * and the DNS server replies that the requested record does not exist.
+  */
+#define TEST_HAVE_DNS_REPLY_PACKET(r)  ((r)->end > (r)->buf)
+#define SET_HAVE_DNS_REPLY_PACKET(r, l)        ((r)->end = (r)->buf + (l))
+#define SET_NO_DNS_REPLY_PACKET(r)     ((r)->end = (r)->buf)
+
 #define INET_ADDR_LEN  4               /* XXX */
 #define INET6_ADDR_LEN 16              /* XXX */
 
+ /*
+  * To improve postscreen's whitelisting support, we need to know how long a
+  * DNSBL "not found" answer is valid. The 2010 implementation assumed it was
+  * valid for 3600 seconds. That is too long by 2015 standards.
+  * 
+  * Instead of guessing, Postfix 3.1 and later implement RFC 2308 (DNS NCACHE),
+  * where a DNS server provides the TTL of a "not found" response as the TTL
+  * of an SOA record in the authority section.
+  * 
+  * Unfortunately, the res_search() and res_query() API gets in the way. These
+  * functions overload their result value, the server reply length, and
+  * return -1 when the requested record does not exist. With libbind-based
+  * res_search() implementations, the server response is still available in a
+  * caller-supplied buffer, thanks to a promise made by res_send() and the
+  * functions that depend on it. With some creativity we can still use the
+  * server response.
+  * 
+  * If this should stop working (for example, res_search() does not call
+  * res_send(), but some non-libbind implementation that updates the
+  * caller-supplied buffer only when the requested record exists), then we
+  * have a way out by setting the dns_ncache_ttl_fix_enable variable. This
+  * enables a limited res_query() clone that should be sufficient for DNSBL /
+  * DNSWL lookups.
+  * 
+  * The libunbound API does not comingle the reply length and reply status
+  * information, but that will have to wait until it is safe to make
+  * libunbound a mandatory dependency for Postfix.
+  */
+int     dns_ncache_ttl_fix_enable = 0;
+
+/* dns_res_query - a res_query() clone that can return negative replies */
+
+static int dns_res_query(const char *name, int class, int type,
+                                unsigned char *answer, int anslen)
+{
+    unsigned char msg_buf[MAX_DNS_QUERY_SIZE];
+    HEADER *reply_header = (HEADER *) answer;
+    int     len;
+
+    /*
+     * Differences with res_query() from libbind:
+     * 
+     * - This function returns a positive server reply length not only in case
+     * of success, but in all cases where a server reply is available that
+     * passes the preliminary checks in res_send().
+     * 
+     * - This function clears h_errno in case of success. The caller must use
+     * h_errno instead of the return value to decide if the lookup was
+     * successful.
+     * 
+     * - No support for EDNS0 and DNSSEC (including turning off EDNS0 after
+     * error). That should be sufficient for DNS reputation lookups where the
+     * reply contains a small number of IP addresses.  TXT records are out of
+     * scope for this workaround.
+     */
+    reply_header->rcode = NOERROR;
+
+#define NO_MKQUERY_DATA_BUF     ((unsigned char *) 0)
+#define NO_MKQUERY_DATA_LEN     ((int) 0)
+#define NO_MKQUERY_NEWRR        ((unsigned char *) 0)
+
+    if ((len = res_mkquery(QUERY, name, class, type, NO_MKQUERY_DATA_BUF,
+                          NO_MKQUERY_DATA_LEN, NO_MKQUERY_NEWRR,
+                          msg_buf, sizeof(msg_buf))) < 0) {
+       SET_H_ERRNO(NO_RECOVERY);
+       if (msg_verbose)
+           msg_info("res_mkquery() failed");
+       return (len);
+    } else if ((len = res_send(msg_buf, len, answer, anslen)) < 0) {
+       SET_H_ERRNO(TRY_AGAIN);
+       if (msg_verbose)
+           msg_info("res_send() failed");
+       return (len);
+    } else {
+       switch (reply_header->rcode) {
+       case NXDOMAIN:
+           SET_H_ERRNO(HOST_NOT_FOUND);
+           break;
+       case NOERROR:
+           if (reply_header->ancount != 0)
+               SET_H_ERRNO(0);
+           else
+               SET_H_ERRNO(NO_DATA);
+           break;
+       case SERVFAIL:
+           SET_H_ERRNO(TRY_AGAIN);
+           break;
+       default:
+           SET_H_ERRNO(NO_RECOVERY);
+           break;
+       }
+       return (len);
+    }
+}
+
+/* dns_res_search - res_search() that can return negative replies */
+
+static int dns_res_search(const char *name, int class, int type,
+                      unsigned char *answer, int anslen, int keep_notfound)
+{
+    int     len;
+
+    /*
+     * Differences with res_search() from libbind:
+     * 
+     * - With a non-zero keep_notfound argument, this function returns a
+     * positive server reply length not only in case of success, but also in
+     * case of a "notfound" reply status. The keep_notfound argument is
+     * usually zero, which allows us to avoid an unnecessary memset() call in
+     * the most common use case.
+     * 
+     * - This function clears h_errno in case of success. The caller must use
+     * h_errno instead of the return value to decide if a lookup was
+     * successful.
+     */
+#define NOT_FOUND_H_ERRNO(he) ((he) == HOST_NOT_FOUND || (he) == NO_DATA)
+
+    if (keep_notfound)
+       /* Prepare for returning a null-padded server reply. */
+       memset(answer, 0, anslen);
+    len = res_query(name, class, type, answer, anslen);
+    if (len > 0) {
+       SET_H_ERRNO(0);
+    } else if (keep_notfound && NOT_FOUND_H_ERRNO(h_errno)) {
+       /* Expect to return a null-padded server reply. */
+       len = anslen;
+    }
+    return (len);
+}
+
 /* dns_query - query name server and pre-parse the reply */
 
-static int dns_query(const char *name, int type, int flags,
-                            DNS_REPLY *reply, VSTRING *why)
+static int dns_query(const char *name, int type, unsigned flags,
+                            DNS_REPLY *reply, VSTRING *why, unsigned lflags)
 {
     HEADER *reply_header;
     int     len;
     unsigned long saved_options;
+    int     keep_notfound = (lflags & DNS_REQ_FLAG_NCACHE_TTL);
 
     /*
      * Initialize the reply buffer.
@@ -289,12 +457,18 @@ static int dns_query(const char *name, int type, int flags,
     for (;;) {
        _res.options &= ~saved_options;
        _res.options |= flags;
-       len = res_search((char *) name, C_IN, type, reply->buf, reply->buf_len);
+       if (keep_notfound && dns_ncache_ttl_fix_enable) {
+           len = dns_res_query((char *) name, C_IN, type, reply->buf,
+                               reply->buf_len);
+       } else {
+           len = dns_res_search((char *) name, C_IN, type, reply->buf,
+                                reply->buf_len, keep_notfound);
+       }
        _res.options &= ~flags;
        _res.options |= saved_options;
        reply_header = (HEADER *) reply->buf;
        reply->rcode = reply_header->rcode;
-       if (len < 0) {
+       if (h_errno != 0) {
            if (why)
                vstring_sprintf(why, "Host or domain name not found. "
                                "Name service error for name=%s type=%s: %s",
@@ -307,13 +481,17 @@ static int dns_query(const char *name, int type, int flags,
                return (DNS_FAIL);
            case HOST_NOT_FOUND:
            case NO_DATA:
+               if (keep_notfound)
+                   break;
+               SET_NO_DNS_REPLY_PACKET(reply);
                return (DNS_NOTFOUND);
            default:
                return (DNS_RETRY);
            }
+       } else {
+           if (msg_verbose)
+               msg_info("dns_query: %s (%s): OK", name, dns_strtype(type));
        }
-       if (msg_verbose)
-           msg_info("dns_query: %s (%s): OK", name, dns_strtype(type));
 
        if (reply_header->tc == 0 || reply->buf_len >= MAX_DNS_REPLY_SIZE)
            break;
@@ -322,6 +500,14 @@ static int dns_query(const char *name, int type, int flags,
        reply->buf_len *= 2;
     }
 
+    /*
+     * Future proofing. If this reaches the panic call, then some code change
+     * introduced a bug.
+     */
+    if (len < 0)
+       msg_panic("dns_query: bad length %d (h_errno=%s)",
+                 len, dns_strerror(h_errno));
+
     /*
      * Paranoia.
      */
@@ -340,12 +526,28 @@ static int dns_query(const char *name, int type, int flags,
 #else
     reply->dnssec_ad = 0;
 #endif
-    reply->end = reply->buf + len;
+    SET_HAVE_DNS_REPLY_PACKET(reply, len);
     reply->query_start = reply->buf + sizeof(HEADER);
     reply->answer_start = 0;
     reply->query_count = ntohs(reply_header->qdcount);
     reply->answer_count = ntohs(reply_header->ancount);
-    return (DNS_OK);
+    reply->auth_count = ntohs(reply_header->nscount);
+    if (msg_verbose > 1)
+       msg_info("dns_query: reply len=%d ancount=%d nscount=%d",
+                len, reply->answer_count, reply->auth_count);
+
+    /*
+     * Future proofing. If this reaches the panic call, then some code change
+     * introduced a bug.
+     */
+    if (h_errno == 0) {
+       return (DNS_OK);
+    } else if (keep_notfound) {
+       return (DNS_NOTFOUND);
+    } else {
+       msg_panic("dns_query: unexpected reply status: %s",
+                 dns_strerror(h_errno));
+    }
 }
 
 /* dns_skip_query - skip query data in name server reply */
@@ -354,7 +556,6 @@ static int dns_skip_query(DNS_REPLY *reply)
 {
     int     query_count = reply->query_count;
     unsigned char *pos = reply->query_start;
-    char    temp[DNS_NAME_LEN];
     int     len;
 
     /*
@@ -364,7 +565,7 @@ static int dns_skip_query(DNS_REPLY *reply)
     while (query_count-- > 0) {
        if (pos >= reply->end)
            return DNS_RETRY;
-       len = dn_expand(reply->buf, reply->end, pos, temp, DNS_NAME_LEN);
+       len = dn_skipname(pos, reply->end);
        if (len < 0)
            return (DNS_RETRY);
        pos += len + QFIXEDSZ;
@@ -441,6 +642,8 @@ static int dns_get_rr(DNS_RR **list, const char *orig_name, DNS_REPLY *reply,
 {
     char    temp[DNS_NAME_LEN];
     char   *tempbuf = temp;
+    UINT32_TYPE soa_buf[5];
+    int     comp_len;
     ssize_t data_len;
     unsigned pref = 0;
     unsigned char *src;
@@ -532,6 +735,35 @@ static int dns_get_rr(DNS_RR **list, const char *orig_name, DNS_REPLY *reply,
        data_len = fixed->length;
        tempbuf = (char *) pos;
        break;
+
+       /*
+        * We use the SOA record TTL to determine the negative reply TTL. We
+        * save the time fields in the SOA record for debugging, but for now
+        * we don't bother saving the source host and mailbox information, as
+        * that would require changes to the DNS_RR structure and APIs. See
+        * also code in dns_strrecord().
+        */
+    case T_SOA:
+       comp_len = dn_skipname(pos, reply->end);
+       if (comp_len < 0)
+           return (DNS_RETRY);
+       pos += comp_len;
+       comp_len = dn_skipname(pos, reply->end);
+       if (comp_len < 0)
+           return (DNS_RETRY);
+       pos += comp_len;
+       if (reply->end - pos < sizeof(soa_buf)) {
+           msg_warn("extract_answer: bad SOA length: %d", fixed->length);
+           return (DNS_RETRY);
+       }
+       GETLONG(soa_buf[0], pos);               /* Serial */
+       GETLONG(soa_buf[1], pos);               /* Refresh */
+       GETLONG(soa_buf[2], pos);               /* Retry */
+       GETLONG(soa_buf[3], pos);               /* Expire */
+       GETLONG(soa_buf[4], pos);               /* Ncache TTL */
+       tempbuf = (char *) soa_buf;
+       data_len = sizeof(soa_buf);
+       break;
     }
     *list = dns_rr_create(orig_name, rr_name, fixed->type, fixed->class,
                          fixed->ttl, pref, tempbuf, data_len);
@@ -576,8 +808,6 @@ static int dns_get_answer(const char *orig_name, DNS_REPLY *reply, int type,
        if ((status = dns_skip_query(reply)) < 0)
            return (status);
     pos = reply->answer_start;
-    if (rrlist)
-       *rrlist = 0;
 
     /*
      * Either this, or use a GOTO for emergency exits. The purpose is to
@@ -663,11 +893,11 @@ static int dns_get_answer(const char *orig_name, DNS_REPLY *reply, int type,
     return (not_found_status);
 }
 
-/* dns_lookup_r - DNS lookup user interface */
+/* dns_lookup_x - DNS lookup user interface */
 
-int     dns_lookup_r(const char *name, unsigned type, unsigned flags,
+int     dns_lookup_x(const char *name, unsigned type, unsigned flags,
                             DNS_RR **rrlist, VSTRING *fqdn, VSTRING *why,
-                            int *rcode)
+                            int *rcode, unsigned lflags)
 {
     char    cname[DNS_NAME_LEN];
     int     c_len = sizeof(cname);
@@ -677,6 +907,13 @@ int     dns_lookup_r(const char *name, unsigned type, unsigned flags,
     int     maybe_secure = 1;          /* Query name presumed secure */
     const char *orig_name = name;
 
+    /*
+     * Reset results early. DNS_OK is not the only status that returns
+     * resource records; DNS_NOTFOUND will do that too, if requested.
+     */
+    if (rrlist)
+       *rrlist = 0;
+
     /*
      * DJBDNS produces a bogus A record when given a numerical hostname.
      */
@@ -685,6 +922,8 @@ int     dns_lookup_r(const char *name, unsigned type, unsigned flags,
            vstring_sprintf(why,
                   "Name service error for %s: invalid host or domain name",
                            name);
+       if (rcode)
+           *rcode = NXDOMAIN;
        SET_H_ERRNO(HOST_NOT_FOUND);
        return (DNS_NOTFOUND);
     }
@@ -697,6 +936,8 @@ int     dns_lookup_r(const char *name, unsigned type, unsigned flags,
            vstring_sprintf(why,
                   "Name service error for %s: invalid host or domain name",
                            name);
+       if (rcode)
+           *rcode = NXDOMAIN;
        SET_H_ERRNO(HOST_NOT_FOUND);
        return (DNS_NOTFOUND);
     }
@@ -710,11 +951,25 @@ int     dns_lookup_r(const char *name, unsigned type, unsigned flags,
        /*
         * Perform the DNS lookup, and pre-parse the name server reply.
         */
-       status = dns_query(name, type, flags, &reply, why);
+       status = dns_query(name, type, flags, &reply, why, lflags);
        if (rcode)
            *rcode = reply.rcode;
-       if (status != DNS_OK)
+       if (status != DNS_OK) {
+
+           /*
+            * If the record does not exist, and we have a copy of the server
+            * response, try to extract the negative caching TTL for the SOA
+            * record in the authority section. DO NOT return an error if an
+            * SOA record is malformed.
+            */
+           if (status == DNS_NOTFOUND && TEST_HAVE_DNS_REPLY_PACKET(&reply)
+               && reply.auth_count > 0) {
+               reply.answer_count = reply.auth_count;  /* XXX TODO: Fix API */
+               (void) dns_get_answer(orig_name, &reply, T_SOA, rrlist, fqdn,
+                                     cname, c_len, &maybe_secure);
+           }
            return (status);
+       }
 
        /*
         * Extract resource records of the requested type. Pick up CNAME
@@ -733,10 +988,10 @@ int     dns_lookup_r(const char *name, unsigned type, unsigned flags,
            if (why)
                vstring_sprintf(why, "Domain %s does not accept mail (nullMX)",
                                name);
-           h_errno = NO_DATA;
+           SET_H_ERRNO(NO_DATA);
            return (status);
        case DNS_OK:
-           if (dns_rr_filter_maps) {
+           if (rrlist && dns_rr_filter_maps) {
                if (dns_rr_filter_execute(rrlist) < 0) {
                    if (why)
                        vstring_sprintf(why,
@@ -790,6 +1045,7 @@ int     dns_lookup_rl(const char *name, unsigned flags, DNS_RR **rrlist,
     int     hpref_status = INT_MIN;
     VSTRING *hpref_rtext = 0;
     int     hpref_rcode;
+    int     hpref_h_errno;
     DNS_RR *rr;
 
     /* Save intermediate highest-priority result. */
@@ -801,6 +1057,7 @@ int     dns_lookup_rl(const char *name, unsigned flags, DNS_RR **rrlist,
            vstring_strcpy(hpref_rtext ? hpref_rtext : \
                           (hpref_rtext = vstring_alloc(VSTRING_LEN(why))), \
                           vstring_str(why)); \
+       hpref_h_errno = h_errno; \
     } while (0)
 
     /* Restore intermediate highest-priority result. */
@@ -810,6 +1067,7 @@ int     dns_lookup_rl(const char *name, unsigned flags, DNS_RR **rrlist,
            *rcode = hpref_rcode; \
        if (why && status != DNS_OK) \
            vstring_strcpy(why, vstring_str(hpref_rtext)); \
+       SET_H_ERRNO(hpref_h_errno); \
     } while (0)
 
     if (rrlist)
@@ -820,11 +1078,11 @@ int     dns_lookup_rl(const char *name, unsigned flags, DNS_RR **rrlist,
        if (msg_verbose)
            msg_info("lookup %s type %s flags %d",
                     name, dns_strtype(type), flags);
-       status = dns_lookup_r(name, type, flags, rrlist ? &rr : (DNS_RR **) 0,
-                             fqdn, why, rcode);
+       status = dns_lookup_x(name, type, flags, rrlist ? &rr : (DNS_RR **) 0,
+                             fqdn, why, rcode, lflags);
+       if (rrlist && rr)
+           *rrlist = dns_rr_append(*rrlist, rr);
        if (status == DNS_OK) {
-           if (rrlist)
-               *rrlist = dns_rr_append(*rrlist, rr);
            if (lflags & DNS_REQ_FLAG_STOP_OK)
                break;
        } else if (status == DNS_INVAL) {
@@ -862,6 +1120,7 @@ int     dns_lookup_rv(const char *name, unsigned flags, DNS_RR **rrlist,
     int     hpref_status = INT_MIN;
     VSTRING *hpref_rtext = 0;
     int     hpref_rcode;
+    int     hpref_h_errno;
     DNS_RR *rr;
 
     if (rrlist)
@@ -871,11 +1130,11 @@ int     dns_lookup_rv(const char *name, unsigned flags, DNS_RR **rrlist,
        if (msg_verbose)
            msg_info("lookup %s type %s flags %d",
                     name, dns_strtype(type), flags);
-       status = dns_lookup_r(name, type, flags, rrlist ? &rr : (DNS_RR **) 0,
-                             fqdn, why, rcode);
+       status = dns_lookup_x(name, type, flags, rrlist ? &rr : (DNS_RR **) 0,
+                             fqdn, why, rcode, lflags);
+       if (rrlist && rr)
+           *rrlist = dns_rr_append(*rrlist, rr);
        if (status == DNS_OK) {
-           if (rrlist)
-               *rrlist = dns_rr_append(*rrlist, rr);
            if (lflags & DNS_REQ_FLAG_STOP_OK)
                break;
        } else if (status == DNS_INVAL) {
index 318cdb90b3fca741f0d0e44c5f0136ad6542fc12..370850917fe2e8c1fbd7248ae65dcba0abae1426 100644 (file)
@@ -29,6 +29,7 @@
 /* System library. */
 
 #include <sys_defs.h>
+#include <string.h>                    /* memcpy */
 
 /* Utility library. */
 
@@ -45,6 +46,7 @@ char   *dns_strrecord(VSTRING *buf, DNS_RR *rr)
 {
     const char myname[] = "dns_strrecord";
     MAI_HOSTADDR_STR host;
+    UINT32_TYPE soa_buf[5];
 
     vstring_sprintf(buf, "%s. %u IN %s ",
                    rr->rname, rr->ttl, dns_strtype(rr->type));
@@ -88,6 +90,19 @@ char   *dns_strrecord(VSTRING *buf, DNS_RR *rr)
        } else {
            vstring_sprintf_append(buf, "[truncated record]");
        }
+
+       /*
+        * We use the SOA record TTL to determine the negative reply TTL. We
+        * save the time fields in the SOA record for debugging, but for now
+        * we don't bother saving the source host and mailbox information, as
+        * that would require changes to the DNS_RR structure. See also code
+        * in dns_get_rr().
+        */
+    case T_SOA:
+       memcpy(soa_buf, rr->data, sizeof(soa_buf));
+       vstring_sprintf_append(buf, "- - %u %u %u %u %u",
+                              soa_buf[0], soa_buf[1], soa_buf[2],
+                              soa_buf[3], soa_buf[4]);
        break;
     default:
        msg_fatal("%s: don't know how to print type %s",
diff --git a/postfix/src/dns/dnsbl_ttl_127.0.0.1_bind_ncache.ref b/postfix/src/dns/dnsbl_ttl_127.0.0.1_bind_ncache.ref
new file mode 100644 (file)
index 0000000..134309f
--- /dev/null
@@ -0,0 +1,15 @@
+./test_dns_lookup: lookup 1.0.0.127.zen.spamhaus.org type A flags 2097152
+./test_dns_lookup: dns_query: 1.0.0.127.zen.spamhaus.org (A): Host not found
+./test_dns_lookup: dns_get_answer: type SOA for zen.spamhaus.org
+./test_dns_lookup: warning: Host or domain name not found. Name service error for name=1.0.0.127.zen.spamhaus.org type=A: Host not found (rcode=3)
+1.0.0.127.zen.spamhaus.org: fqdn: zen.spamhaus.org
+ad: 0, rr: zen.spamhaus.org. TTL IN SOA - - D D D D D
+./test_dns_lookup: lookup 1.0.0.127.b.barracudacentral.org type A flags 2097152
+./test_dns_lookup: dns_query: 1.0.0.127.b.barracudacentral.org (A): Host not found
+./test_dns_lookup: warning: Host or domain name not found. Name service error for name=1.0.0.127.b.barracudacentral.org type=A: Host not found (rcode=3)
+./test_dns_lookup: lookup 1.0.0.127.bl.spamcop.net type A flags 2097152
+./test_dns_lookup: dns_query: 1.0.0.127.bl.spamcop.net (A): Host not found
+./test_dns_lookup: dns_get_answer: type SOA for bl.spamcop.net
+./test_dns_lookup: warning: Host or domain name not found. Name service error for name=1.0.0.127.bl.spamcop.net type=A: Host not found (rcode=3)
+1.0.0.127.bl.spamcop.net: fqdn: bl.spamcop.net
+ad: 0, rr: bl.spamcop.net. TTL IN SOA - - D D D D D
diff --git a/postfix/src/dns/dnsbl_ttl_127.0.0.1_bind_plain.ref b/postfix/src/dns/dnsbl_ttl_127.0.0.1_bind_plain.ref
new file mode 100644 (file)
index 0000000..e5741d3
--- /dev/null
@@ -0,0 +1,9 @@
+./test_dns_lookup: lookup 1.0.0.127.zen.spamhaus.org type A flags 2097152
+./test_dns_lookup: dns_query: 1.0.0.127.zen.spamhaus.org (A): Host not found
+./test_dns_lookup: warning: Host or domain name not found. Name service error for name=1.0.0.127.zen.spamhaus.org type=A: Host not found (rcode=3)
+./test_dns_lookup: lookup 1.0.0.127.b.barracudacentral.org type A flags 2097152
+./test_dns_lookup: dns_query: 1.0.0.127.b.barracudacentral.org (A): Host not found
+./test_dns_lookup: warning: Host or domain name not found. Name service error for name=1.0.0.127.b.barracudacentral.org type=A: Host not found (rcode=3)
+./test_dns_lookup: lookup 1.0.0.127.bl.spamcop.net type A flags 2097152
+./test_dns_lookup: dns_query: 1.0.0.127.bl.spamcop.net (A): Host not found
+./test_dns_lookup: warning: Host or domain name not found. Name service error for name=1.0.0.127.bl.spamcop.net type=A: Host not found (rcode=3)
diff --git a/postfix/src/dns/dnsbl_ttl_127.0.0.2_bind_plain.ref b/postfix/src/dns/dnsbl_ttl_127.0.0.2_bind_plain.ref
new file mode 100644 (file)
index 0000000..06bc7d8
--- /dev/null
@@ -0,0 +1,15 @@
+./test_dns_lookup: lookup 2.0.0.127.zen.spamhaus.org type A flags 2097152
+./test_dns_lookup: dns_query: 2.0.0.127.zen.spamhaus.org (A): OK
+./test_dns_lookup: dns_get_answer: type A for 2.0.0.127.zen.spamhaus.org
+2.0.0.127.zen.spamhaus.org: fqdn: 2.0.0.127.zen.spamhaus.org
+ad: 0, rr: 2.0.0.127.zen.spamhaus.org. TTL IN A 127.0.0.D
+./test_dns_lookup: lookup 2.0.0.127.b.barracudacentral.org type A flags 2097152
+./test_dns_lookup: dns_query: 2.0.0.127.b.barracudacentral.org (A): OK
+./test_dns_lookup: dns_get_answer: type A for 2.0.0.127.b.barracudacentral.org
+2.0.0.127.b.barracudacentral.org: fqdn: 2.0.0.127.b.barracudacentral.org
+ad: 0, rr: 2.0.0.127.b.barracudacentral.org. TTL IN A 127.0.0.D
+./test_dns_lookup: lookup 2.0.0.127.bl.spamcop.net type A flags 2097152
+./test_dns_lookup: dns_query: 2.0.0.127.bl.spamcop.net (A): OK
+./test_dns_lookup: dns_get_answer: type A for 2.0.0.127.bl.spamcop.net
+2.0.0.127.bl.spamcop.net: fqdn: 2.0.0.127.bl.spamcop.net
+ad: 0, rr: 2.0.0.127.bl.spamcop.net. TTL IN A 127.0.0.D
index 2e0f9e67a9a998efa4e20e8a1a2de70686385ff3..4b19ee49910c658b50ef445b36c8e02a0ad27435 100644 (file)
@@ -10,4 +10,4 @@
 ./test_dns_lookup: dict_regexp_lookup: error.reg: spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2
 ./test_dns_lookup: maps_find: DNS reply filter: regexp:error.reg(0,lock|fold_fix): spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2 = oops
 ./test_dns_lookup: warning: DNS reply filter: unknown DNS filter action: "oops"
-./test_dns_lookup: fatal: Error looking up name=spike.porcupine.org type=AAAA: Invalid DNS reply filter syntax (rcode=0)
+./test_dns_lookup: warning: Error looking up name=spike.porcupine.org type=AAAA: Invalid DNS reply filter syntax (rcode=0)
index 6b0085d6f626a324b93060a1226bc919ba78feae..314541b6a03b224f8cabbf3304270cce7d0f245b 100644 (file)
@@ -5,7 +5,6 @@
 ./test_dns_lookup: dns_get_answer: type MX for porcupine.org
 ./test_dns_lookup: dns_get_answer: type MX for porcupine.org
 ./test_dns_lookup: dns_query: porcupine.org (MX): OK
-./test_dns_lookup: fatal: Error looking up name=porcupine.org type=MX: DNS reply filter drops all results (rcode=0)
 ./test_dns_lookup: ignoring DNS RR: porcupine.org. 3600 IN MX 10 spike.porcupine.org.
 ./test_dns_lookup: ignoring DNS RR: porcupine.org. 3600 IN MX 20 fist.porcupine.org.
 ./test_dns_lookup: ignoring DNS RR: porcupine.org. 3600 IN MX 30 m1.porcupine.org.
@@ -13,3 +12,4 @@
 ./test_dns_lookup: maps_find: DNS reply filter: regexp:no-mx.reg(0,lock|fold_fix): porcupine.org. 3600 IN MX 10 spike.porcupine.org. = ignore
 ./test_dns_lookup: maps_find: DNS reply filter: regexp:no-mx.reg(0,lock|fold_fix): porcupine.org. 3600 IN MX 20 fist.porcupine.org. = ignore
 ./test_dns_lookup: maps_find: DNS reply filter: regexp:no-mx.reg(0,lock|fold_fix): porcupine.org. 3600 IN MX 30 m1.porcupine.org. = ignore
+./test_dns_lookup: warning: Error looking up name=porcupine.org type=MX: DNS reply filter drops all results (rcode=0)
index 3bf8aa1669380ab95a493f8a28260b7ec9b5c322..aecbeb454ae40b2de5c81ede314b8eed53822471 100644 (file)
@@ -2,4 +2,4 @@
 ./test_dns_lookup: dns_query: nxdomain.porcupine.org (MX): Host not found
 ./test_dns_lookup: lookup nxdomain.porcupine.org type A flags 2097152
 ./test_dns_lookup: dns_query: nxdomain.porcupine.org (A): Host not found
-./test_dns_lookup: fatal: Host or domain name not found. Name service error for name=nxdomain.porcupine.org type=A: Host not found (rcode=3)
+./test_dns_lookup: warning: Host or domain name not found. Name service error for name=nxdomain.porcupine.org type=A: Host not found (rcode=3)
index e27250b6354c43ae2d2d65ec86cc44ba8d3ea0a4..e927eda246495abacd66935a90fdfc7633795110 100644 (file)
@@ -51,7 +51,7 @@ static void print_rr(VSTRING *buf, DNS_RR *rr)
 
 static NORETURN usage(char **argv)
 {
-    msg_fatal("usage: %s [-v] [-f filter] types name", argv[0]);
+    msg_fatal("usage: %s [-npv] [-f filter] types name", argv[0]);
 }
 
 int     main(int argc, char **argv)
@@ -66,15 +66,23 @@ int     main(int argc, char **argv)
     DNS_RR *rr;
     int     i;
     int     ch;
+    int     lflags = DNS_REQ_FLAG_NONE;
 
     msg_vstream_init(argv[0], VSTREAM_ERR);
-    while ((ch = GETOPT(argc, argv, "vf:")) > 0) {
+    while ((ch = GETOPT(argc, argv, "f:npv")) > 0) {
        switch (ch) {
+       case 'v':
            msg_verbose++;
            break;
        case 'f':
            dns_rr_filter_compile("DNS reply filter", optarg);
            break;
+       case 'n':
+           lflags |= DNS_REQ_FLAG_NCACHE_TTL;
+           break;
+       case 'p':
+           dns_ncache_ttl_fix_enable = 1;
+           break;
        default:
            usage(argv);
        }
@@ -91,16 +99,18 @@ int     main(int argc, char **argv)
     name = argv[optind + 1];
     msg_verbose = 1;
     switch (dns_lookup_rv(name, RES_USE_DNSSEC, &rr, fqdn, why,
-                         &rcode, DNS_REQ_FLAG_NONE, types)) {
+                         &rcode, lflags, types)) {
     default:
-       msg_fatal("%s (rcode=%d)", vstring_str(why), rcode);
+       msg_warn("%s (rcode=%d)", vstring_str(why), rcode);
     case DNS_OK:
-       vstream_printf("%s: fqdn: %s\n", name, vstring_str(fqdn));
-       buf = vstring_alloc(100);
-       print_rr(buf, rr);
-       dns_rr_free(rr);
-       vstring_free(buf);
-       vstream_fflush(VSTREAM_OUT);
+       if (rr) {
+           vstream_printf("%s: fqdn: %s\n", name, vstring_str(fqdn));
+           buf = vstring_alloc(100);
+           print_rr(buf, rr);
+           dns_rr_free(rr);
+           vstring_free(buf);
+           vstream_fflush(VSTREAM_OUT);
+       }
     }
     myfree((void *) types);
     exit(0);
index 6b5c46a7d55e5e4aa3e61a961099c052990750f6..a9de032d72fec1b2a1d84e64bed9c79b89e4a060 100644 (file)
@@ -18,8 +18,9 @@
 /*     If the IP address is listed under the DNS white/blacklist, the
 /*     \fBdnsblog\fR(8) server logs the match and replies with the
 /*     query arguments plus an address list with the resulting IP
-/*     addresses separated by whitespace.  Otherwise it replies
-/*     with the query arguments plus an empty address list.  Finally,
+/*     addresses, separated by whitespace, and the reply TTL.
+/*     Otherwise it replies with the query arguments plus an empty
+/*     address list and the reply TTL (-1 if unavailable).  Finally,
 /*     The \fBdnsblog\fR(8) server closes the connection.
 /* DIAGNOSTICS
 /*     Problems and transactions are logged to \fBsyslogd\fR(8).
@@ -78,6 +79,7 @@
 /* System library. */
 
 #include <sys_defs.h>
+#include <limits.h>
 
 /* Utility library. */
 
@@ -128,7 +130,8 @@ static VSTRING *result;
 
 /* static void dnsblog_query - query DNSBL for client address */
 
-static VSTRING *dnsblog_query(VSTRING *result, const char *dnsbl_domain,
+static VSTRING *dnsblog_query(VSTRING *result, int *result_ttl,
+                                     const char *dnsbl_domain,
                                      const char *addr)
 {
     const char *myname = "dnsblog_query";
@@ -183,8 +186,16 @@ static VSTRING *dnsblog_query(VSTRING *result, const char *dnsbl_domain,
      * Tack on the RBL domain name and query the DNS for an A record.
      */
     vstring_strcat(query, dnsbl_domain);
-    dns_status = dns_lookup(STR(query), T_A, 0, &addr_list, (VSTRING *) 0, why);
+    dns_status = dns_lookup_x(STR(query), T_A, 0, &addr_list, (VSTRING *) 0,
+                             why, (int *) 0, DNS_REQ_FLAG_NCACHE_TTL);
+
+    /*
+     * We return the lowest TTL in the response from the A record(s) if
+     * found, or from the SOA record(s) if available. If the reply specifies
+     * no TTL, or if the query fails, we return a TTL of -1.
+     */
     VSTRING_RESET(result);
+    *result_ttl = -1;
     if (dns_status == DNS_OK) {
        for (rr = addr_list; rr != 0; rr = rr->next) {
            if (dns_rr_to_pa(rr, &hostaddr) == 0) {
@@ -196,6 +207,9 @@ static VSTRING *dnsblog_query(VSTRING *result, const char *dnsbl_domain,
                if (LEN(result) > 0)
                    vstring_strcat(result, " ");
                vstring_strcat(result, hostaddr.buf);
+               /* Grab the positive reply TTL. */
+               if (*result_ttl < 0 || *result_ttl > rr->ttl)
+                   *result_ttl = rr->ttl;
            }
        }
        dns_rr_free(addr_list);
@@ -203,6 +217,12 @@ static VSTRING *dnsblog_query(VSTRING *result, const char *dnsbl_domain,
        if (msg_verbose)
            msg_info("%s: addr %s not listed by domain %s",
                     myname, addr, dnsbl_domain);
+       /* Grab the negative reply TTL. */
+       for (rr = addr_list; rr != 0; rr = rr->next) {
+           if (rr->type == T_SOA && (*result_ttl < 0 || *result_ttl > rr->ttl))
+               *result_ttl = rr->ttl;
+       }
+       dns_rr_free(addr_list);
     } else {
        msg_warn("%s: lookup error for DNS query %s: %s",
                 myname, STR(query), STR(why));
@@ -217,6 +237,7 @@ static void dnsblog_service(VSTREAM *client_stream, char *unused_service,
                                    char **argv)
 {
     int     request_id;
+    int     result_ttl;
 
     /*
      * Sanity check. This service takes no command-line arguments.
@@ -235,7 +256,7 @@ static void dnsblog_service(VSTREAM *client_stream, char *unused_service,
                  RECV_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, addr),
                  RECV_ATTR_INT(MAIL_ATTR_LABEL, &request_id),
                  ATTR_TYPE_END) == 3) {
-       (void) dnsblog_query(result, STR(rbl_domain), STR(addr));
+       (void) dnsblog_query(result, &result_ttl, STR(rbl_domain), STR(addr));
        if (var_dnsblog_delay > 0)
            sleep(var_dnsblog_delay);
        attr_print(client_stream, ATTR_FLAG_NONE,
@@ -243,6 +264,7 @@ static void dnsblog_service(VSTREAM *client_stream, char *unused_service,
                   SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, STR(addr)),
                   SEND_ATTR_INT(MAIL_ATTR_LABEL, request_id),
                   SEND_ATTR_STR(MAIL_ATTR_RBL_ADDR, STR(result)),
+                  SEND_ATTR_INT(MAIL_ATTR_TTL, result_ttl),
                   ATTR_TYPE_END);
        vstream_fflush(client_stream);
     }
index 823fc0c85d572473ce245cf67a9a7b703ffba011..d1d339b4d61473f13e16d331701a1808b1024a55 100644 (file)
@@ -3396,6 +3396,11 @@ extern bool var_conc_feedback_debug;
 #define DEF_DEST_RATE_DELAY    "0s"
 extern int var_dest_rate_delay;
 
+#define VAR_XPORT_RATE_DELAY   "default_transport_rate_delay"
+#define _XPORT_RATE_DELAY      "_transport_rate_delay"
+#define DEF_XPORT_RATE_DELAY   "0s"
+extern int var_xport_rate_delay;
+
  /*
   * Stress handling.
   */
@@ -3523,9 +3528,13 @@ extern char *var_psc_dnsbl_enable;
 #define DEF_PSC_DNSBL_ACTION   "ignore"
 extern char *var_psc_dnsbl_action;
 
-#define VAR_PSC_DNSBL_TTL      "postscreen_dnsbl_ttl"
-#define DEF_PSC_DNSBL_TTL      "1h"
-extern int var_psc_dnsbl_ttl;
+#define VAR_PSC_DNSBL_MIN_TTL  "postscreen_dnsbl_min_ttl"
+#define DEF_PSC_DNSBL_MIN_TTL  "60s"
+extern int var_psc_dnsbl_min_ttl;
+
+#define VAR_PSC_DNSBL_MAX_TTL  "postscreen_dnsbl_max_ttl"
+#define DEF_PSC_DNSBL_MAX_TTL  "${postscreen_dnsbl_ttl?{$postscreen_dnsbl_ttl}:{1}}h"
+extern int var_psc_dnsbl_max_ttl;
 
 #define        VAR_PSC_DNSBL_REPLY     "postscreen_dnsbl_reply_map"
 #define        DEF_PSC_DNSBL_REPLY     ""
index b663dfcdae437d7d3b2dfa7ad98c74eaee9731da..2d6b52a0376b36943dd5a90f867e9eecc6a06a28 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      "20150523"
+#define MAIL_RELEASE_DATE      "20150710"
 #define MAIL_VERSION_NUMBER    "3.1"
 
 #ifdef SNAPSHOT
index b806df3dc73ab6f1fbfd561f7d1d46392da4896c..b291b8690beaedfb5f0a1f4e68c2fd44bfdc57b1 100644 (file)
 /*     on the value of the corresponding per-destination recipient limit.
 /* .IP "\fItransport\fB_destination_rate_delay $default_destination_rate_delay
 /*     Idem, for delivery via the named message \fItransport\fR.
+/* .PP
+/*     Available in Postfix version 3.1 and later:
+/* .IP "\fBdefault_transport_rate_delay (0s)\fR"
+/*     The default amount of delay that is inserted between individual
+/*     deliveries over the same message delivery transport, regardless of
+/*     destination.
+/* .IP "\fItransport\fB_transport_rate_delay $default_transport_rate_delay
+/*     Idem, for delivery via the named message \fItransport\fR.
 /* SAFETY CONTROLS
 /* .ad
 /* .fi
@@ -386,6 +394,7 @@ char   *var_conc_pos_feedback;
 char   *var_conc_neg_feedback;
 int     var_conc_cohort_limit;
 int     var_conc_feedback_debug;
+int     var_xport_rate_delay;
 int     var_dest_rate_delay;
 char   *var_def_filter_nexthop;
 int     var_qmgr_daemon_timeout;
@@ -634,6 +643,7 @@ int     main(int argc, char **argv)
        VAR_DSN_QUEUE_TIME, DEF_DSN_QUEUE_TIME, &var_dsn_queue_time, 0, 8640000,
        VAR_XPORT_RETRY_TIME, DEF_XPORT_RETRY_TIME, &var_transport_retry_time, 1, 0,
        VAR_QMGR_CLOG_WARN_TIME, DEF_QMGR_CLOG_WARN_TIME, &var_qmgr_clog_warn_time, 0, 0,
+       VAR_XPORT_RATE_DELAY, DEF_XPORT_RATE_DELAY, &var_xport_rate_delay, 0, 0,
        VAR_DEST_RATE_DELAY, DEF_DEST_RATE_DELAY, &var_dest_rate_delay, 0, 0,
        VAR_QMGR_DAEMON_TIMEOUT, DEF_QMGR_DAEMON_TIMEOUT, &var_qmgr_daemon_timeout, 1, 0,
        VAR_QMGR_IPC_TIMEOUT, DEF_QMGR_IPC_TIMEOUT, &var_qmgr_ipc_timeout, 1, 0,
index 6d61eb45bc3f93d9dfaead88484019f1d9fc731a..b8c8d0e48bc53e11943845c17859972ce852477a 100644 (file)
@@ -163,10 +163,12 @@ struct QMGR_TRANSPORT {
     QMGR_FEEDBACK pos_feedback;                /* positive feedback control */
     QMGR_FEEDBACK neg_feedback;                /* negative feedback control */
     int     fail_cohort_limit;         /* flow shutdown control */
+    int     xport_rate_delay;          /* suspend per delivery */
     int     rate_delay;                        /* suspend per delivery */
 };
 
 #define QMGR_TRANSPORT_STAT_DEAD       (1<<1)
+#define QMGR_TRANSPORT_STAT_RATE_LOCK  (1<<2)
 
 typedef void (*QMGR_TRANSPORT_ALLOC_NOTIFY) (QMGR_TRANSPORT *, VSTREAM *);
 extern QMGR_TRANSPORT *qmgr_transport_select(void);
index cd6f35a1d8b34337a264d88c5b4994c5519f0bb0..02db6b93aefc88b494c21529137e862fd8ba1a75 100644 (file)
 
 #include "qmgr.h"
 
+ /*
+  * Important note on the _transport_rate_delay implementation: after
+  * qmgr_transport_alloc() sets the QMGR_TRANSPORT_STAT_RATE_LOCK flag, all
+  * code paths must directly or indirectly invoke qmgr_transport_unthrottle()
+  * or qmgr_transport_throttle(). Otherwise, transports with non-zero
+  * _transport_rate_delay will become stuck.
+  */
+
 int     qmgr_deliver_concurrency;
 
  /*
@@ -341,9 +349,10 @@ static void qmgr_deliver_update(int unused_event, void *context)
      * No problems detected. Mark the transport and queue as alive. The queue
      * itself won't go away before we dispose of the current queue entry.
      */
-    if (status != DELIVER_STAT_CRASH && VSTRING_LEN(dsb->reason) == 0) {
+    if (status != DELIVER_STAT_CRASH) {
        qmgr_transport_unthrottle(transport);
-       qmgr_queue_unthrottle(queue);
+       if (VSTRING_LEN(dsb->reason) == 0)
+           qmgr_queue_unthrottle(queue);
     }
 
     /*
index 58a644c03cf49b0fbccd10a8adf2657a25a0e724..cb26453f05061431e9b911c29914d66f6a8fd144 100644 (file)
@@ -158,6 +158,14 @@ struct QMGR_TRANSPORT_ALLOC {
 #define QMGR_TRANSPORT_MAX_PEND        2
 #endif
 
+ /*
+  * Important note on the _transport_rate_delay implementation: after
+  * qmgr_transport_alloc() sets the QMGR_TRANSPORT_STAT_RATE_LOCK flag, all
+  * code paths must directly or indirectly invoke qmgr_transport_unthrottle()
+  * or qmgr_transport_throttle(). Otherwise, transports with non-zero
+  * _transport_rate_delay will become stuck.
+  */
+
 /* qmgr_transport_unthrottle_wrapper - in case (char *) != (struct *) */
 
 static void qmgr_transport_unthrottle_wrapper(int unused_event, void *context)
@@ -175,7 +183,7 @@ void    qmgr_transport_unthrottle(QMGR_TRANSPORT *transport)
      * This routine runs after expiration of the timer set by
      * qmgr_transport_throttle(), or whenever a delivery transport has been
      * used without malfunction. In either case, we enable delivery again if
-     * the transport was blocked, otherwise the request is ignored.
+     * the transport was throttled. We always reset the transport rate lock.
      */
     if ((transport->flags & QMGR_TRANSPORT_STAT_DEAD) != 0) {
        if (msg_verbose)
@@ -189,6 +197,8 @@ void    qmgr_transport_unthrottle(QMGR_TRANSPORT *transport)
        event_cancel_timer(qmgr_transport_unthrottle_wrapper,
                           (void *) transport);
     }
+    if (transport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK)
+       transport->flags &= ~QMGR_TRANSPORT_STAT_RATE_LOCK;
 }
 
 /* qmgr_transport_throttle - disable delivery process allocation */
@@ -225,6 +235,16 @@ static void qmgr_transport_abort(int unused_event, void *context)
     msg_fatal("timeout connecting to transport: %s", alloc->transport->name);
 }
 
+/* qmgr_transport_rate_event - delivery process availability notice */
+
+static void qmgr_transport_rate_event(int unused_event, void *context)
+{
+    QMGR_TRANSPORT_ALLOC *alloc = (QMGR_TRANSPORT_ALLOC *) context;
+
+    alloc->notify(alloc->transport, alloc->stream);
+    myfree((void *) alloc);
+}
+
 /* qmgr_transport_event - delivery process availability notice */
 
 static void qmgr_transport_event(int unused_event, void *context)
@@ -256,8 +276,16 @@ static void qmgr_transport_event(int unused_event, void *context)
     /*
      * Notify the requestor.
      */
-    alloc->notify(alloc->transport, alloc->stream);
-    myfree((void *) alloc);
+    if (alloc->transport->xport_rate_delay > 0) {
+       if ((alloc->transport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK) == 0)
+           msg_panic("transport_event: missing rate lock for transport %s",
+                     alloc->transport->name);
+       event_request_timer(qmgr_transport_rate_event, (void *) alloc,
+                           alloc->transport->xport_rate_delay);
+    } else {
+       alloc->notify(alloc->transport, alloc->stream);
+       myfree((void *) alloc);
+    }
 }
 
 /* qmgr_transport_select - select transport for allocation */
@@ -282,6 +310,7 @@ QMGR_TRANSPORT *qmgr_transport_select(void)
 
     for (xport = qmgr_transport_list.next; xport; xport = xport->peers.next) {
        if ((xport->flags & QMGR_TRANSPORT_STAT_DEAD) != 0
+           || (xport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK) != 0
            || xport->pending >= QMGR_TRANSPORT_MAX_PEND)
            continue;
        need = xport->pending + 1;
@@ -311,9 +340,18 @@ void    qmgr_transport_alloc(QMGR_TRANSPORT *transport, QMGR_TRANSPORT_ALLOC_NOT
      */
     if (transport->flags & QMGR_TRANSPORT_STAT_DEAD)
        msg_panic("qmgr_transport: dead transport: %s", transport->name);
+    if (transport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK)
+       msg_panic("qmgr_transport: rate-locked transport: %s", transport->name);
     if (transport->pending >= QMGR_TRANSPORT_MAX_PEND)
        msg_panic("qmgr_transport: excess allocation: %s", transport->name);
 
+    /*
+     * When this message delivery transport is rate-limited, do not select it
+     * again before the end of a message delivery transaction.
+     */
+    if (transport->xport_rate_delay > 0)
+       transport->flags |= QMGR_TRANSPORT_STAT_RATE_LOCK;
+
     /*
      * Connect to the well-known port for this delivery service, and wake up
      * when a process announces its availability. Allow only a limited number
@@ -387,6 +425,9 @@ QMGR_TRANSPORT *qmgr_transport_create(const char *name)
     transport->init_dest_concurrency =
        get_mail_conf_int2(name, _INIT_DEST_CON,
                           var_init_dest_concurrency, 1, 0);
+    transport->xport_rate_delay = get_mail_conf_time2(name, _XPORT_RATE_DELAY,
+                                                     var_xport_rate_delay,
+                                                     's', 0, 0);
     transport->rate_delay = get_mail_conf_time2(name, _DEST_RATE_DELAY,
                                                var_dest_rate_delay,
                                                's', 0, 0);
index a966ebaec8d19f07b1deef905da210d327fddb8f..9b06c25f03315c9ff7686b591c3284500ae8cba8 100644 (file)
@@ -136,6 +136,7 @@ static const CONFIG_STR_TABLE pcf_legacy_str_table[] = {
     {"fallback_relay", ""},
     {"authorized_verp_clients", ""},
     {"smtpd_client_connection_limit_exceptions", ""},
+    {"postscreen_dnsbl_ttl", ""},
     0,
 };
 
index bebf6de6b8372614e32fef2d7d176786a3dec978..9322c093ab1d9f59f28d626b0e7b98a243006b8a 100644 (file)
@@ -134,6 +134,7 @@ void    pcf_register_service_parameters(void)
        _CONC_NEG_FDBACK, VAR_CONC_NEG_FDBACK,
        _CONC_COHORT_LIM, VAR_CONC_COHORT_LIM,
        _DEST_RATE_DELAY, VAR_DEST_RATE_DELAY,
+       _XPORT_RATE_DELAY, VAR_XPORT_RATE_DELAY,
        0,
     };
     static const PCF_STRING_NV spawn_params[] = {
index 95c70b44e538b5e41c59767dfeb10167829d63b4..923cbff047b96f089a89533bf2aee39e85246333 100644 (file)
 /* .IP "\fBpostscreen_bare_newline_ttl (30d)\fR"
 /*     The amount of time that \fBpostscreen\fR(8) will use the result from
 /*     a successful "bare newline" SMTP protocol test.
-/* .IP "\fBpostscreen_dnsbl_ttl (1h)\fR"
-/*     The amount of time that \fBpostscreen\fR(8) will use the result from
-/*     a successful DNS blocklist test.
+/* .IP "\fBpostscreen_dnsbl_max_ttl (${postscreen_dnsbl_ttl?{$postscreen_dnsbl_ttl}:{1}}h)\fR"
+/*     The maximum amount of time that \fBpostscreen\fR(8) will use the
+/*     result from a successful DNS-based reputation test before a
+/*     client IP address is required to pass that test again.
+/* .IP "\fBpostscreen_dnsbl_min_ttl (60s)\fR"
+/*     The minimum amount of time that \fBpostscreen\fR(8) will use the
+/*     result from a successful DNS-based reputation test before a
+/*     client IP address is required to pass that test again.
 /* .IP "\fBpostscreen_greet_ttl (1d)\fR"
 /*     The amount of time that \fBpostscreen\fR(8) will use the result from
 /*     a successful PREGREET test.
@@ -476,7 +481,8 @@ char   *var_psc_dnsbl_reply;
 int     var_psc_dnsbl_thresh;
 int     var_psc_dnsbl_wthresh;
 char   *var_psc_dnsbl_action;
-int     var_psc_dnsbl_ttl;
+int     var_psc_dnsbl_min_ttl;
+int     var_psc_dnsbl_max_ttl;
 int     var_psc_dnsbl_tmout;
 
 bool    var_psc_pipel_enable;
@@ -524,7 +530,6 @@ int     psc_pipel_action;           /* PSC_ACT_DROP/ENFORCE/etc */
 int     psc_nsmtp_action;              /* PSC_ACT_DROP/ENFORCE/etc */
 int     psc_barlf_action;              /* PSC_ACT_DROP/ENFORCE/etc */
 int     psc_min_ttl;                   /* Update with new tests! */
-int     psc_max_ttl;                   /* Update with new tests! */
 STRING_LIST *psc_forbid_cmds;          /* CONNECT GET POST */
 int     psc_stress_greet_wait;         /* stressed greet wait */
 int     psc_normal_greet_wait;         /* stressed greet wait */
@@ -1022,13 +1027,9 @@ static void post_jail_init(char *unused_name, char **unused_argv)
      * Pre-compute the minimal and maximal TTL.
      */
     psc_min_ttl =
-       PSC_MIN(PSC_MIN(var_psc_pregr_ttl, var_psc_dnsbl_ttl),
+       PSC_MIN(PSC_MIN(var_psc_pregr_ttl, var_psc_dnsbl_min_ttl),
                PSC_MIN(PSC_MIN(var_psc_pipel_ttl, var_psc_nsmtp_ttl),
                        var_psc_barlf_ttl));
-    psc_max_ttl =
-       PSC_MAX(PSC_MAX(var_psc_pregr_ttl, var_psc_dnsbl_ttl),
-               PSC_MAX(PSC_MAX(var_psc_pipel_ttl, var_psc_nsmtp_ttl),
-                       var_psc_barlf_ttl));
 
     /*
      * Pre-compute the stress and normal command time limits.
@@ -1122,7 +1123,8 @@ int     main(int argc, char **argv)
     static const CONFIG_TIME_TABLE time_table[] = {
        VAR_PSC_GREET_WAIT, DEF_PSC_GREET_WAIT, &var_psc_greet_wait, 1, 0,
        VAR_PSC_PREGR_TTL, DEF_PSC_PREGR_TTL, &var_psc_pregr_ttl, 1, 0,
-       VAR_PSC_DNSBL_TTL, DEF_PSC_DNSBL_TTL, &var_psc_dnsbl_ttl, 1, 0,
+       VAR_PSC_DNSBL_MIN_TTL, DEF_PSC_DNSBL_MIN_TTL, &var_psc_dnsbl_min_ttl, 1, 0,
+       VAR_PSC_DNSBL_MAX_TTL, DEF_PSC_DNSBL_MAX_TTL, &var_psc_dnsbl_max_ttl, 1, 0,
        VAR_PSC_PIPEL_TTL, DEF_PSC_PIPEL_TTL, &var_psc_pipel_ttl, 1, 0,
        VAR_PSC_NSMTP_TTL, DEF_PSC_NSMTP_TTL, &var_psc_nsmtp_ttl, 1, 0,
        VAR_PSC_BARLF_TTL, DEF_PSC_BARLF_TTL, &var_psc_barlf_ttl, 1, 0,
index 130b52770e51e61f8dc041cf757257bb726f0498..ceb2a2571c3bc53181a95f37d467a0baa2990f8d 100644 (file)
@@ -75,6 +75,7 @@ typedef struct {
     time_t  expire_time[PSC_TINDX_COUNT];      /* per-test expiration */
     VSTRING *dnsbl_reply;              /* dnsbl reject text */
     int     dnsbl_score;               /* saved DNSBL score */
+    int     dnsbl_ttl;                 /* saved DNSBL TTL */
     const char *dnsbl_name;            /* DNSBL name with largest weight */
     int     dnsbl_index;               /* dnsbl request index */
     const char *rcpt_reply;            /* how to reject recipients */
@@ -372,7 +373,6 @@ extern int psc_pipel_action;                /* PSC_ACT_DROP etc. */
 extern int psc_nsmtp_action;           /* PSC_ACT_DROP etc. */
 extern int psc_barlf_action;           /* PSC_ACT_DROP etc. */
 extern int psc_min_ttl;                        /* Update with new tests! */
-extern int psc_max_ttl;                        /* Update with new tests! */
 extern STRING_LIST *psc_forbid_cmds;   /* CONNECT GET POST */
 extern int psc_stress_greet_wait;      /* stressed greet wait */
 extern int psc_normal_greet_wait;      /* stressed greet wait */
@@ -480,7 +480,7 @@ const char *psc_maps_find(MAPS *, const char *, int);
   * postscreen_dnsbl.c
   */
 extern void psc_dnsbl_init(void);
-extern int psc_dnsbl_retrieve(const char *, const char **, int);
+extern int psc_dnsbl_retrieve(const char *, const char **, int, int *);
 extern int psc_dnsbl_request(const char *, void (*) (int, void *), void *);
 
  /*
index 76c21ed0d4f3a1185fc77194c8110f1c877d3241..34d586202b4288862fa5afec35f95afff8d21d03 100644 (file)
 /*     void    (*callback)(int, char *);
 /*     char    *context;
 /*
-/*     int     psc_dnsbl_retrieve(client_addr, dnsbl_name, dnsbl_index)
+/*     int     psc_dnsbl_retrieve(client_addr, dnsbl_name, dnsbl_index,
+/*                                     dnsbl_ttl)
 /*     char    *client_addr;
 /*     const char **dnsbl_name;
 /*     int     dnsbl_index;
+/*     int     *dnsbl_ttl;
 /* DESCRIPTION
 /*     This module implements preliminary support for DNSBL lookups.
 /*     Multiple requests for the same information are handled with
 /*     The result value is the index for the psc_dnsbl_retrieve()
 /*     call.
 /*
-/*     psc_dnsbl_retrieve() retrieves the result score requested with
-/*     psc_dnsbl_request() and decrements the reference count. It
+/*     psc_dnsbl_retrieve() retrieves the result score and reply
+/*     TTL requested with psc_dnsbl_request(), and decrements the
+/*     reference count. The reply TTL value is clamped to
+/*     postscreen_dnsbl_min_ttl and postscreen_dnsbl_max_ttl.  It
 /*     is an error to retrieve a score without requesting it first.
 /* LICENSE
 /* .ad
@@ -58,6 +62,7 @@
 #include <netinet/in.h>                        /* inet_pton() */
 #include <arpa/inet.h>                 /* inet_pton() */
 #include <stdio.h>                     /* sscanf */
+#include <limits.h>
 
 /* Utility library. */
 
@@ -140,7 +145,9 @@ typedef struct {
 typedef struct {
     const char *dnsbl_name;            /* DNSBL with largest contribution */
     int     dnsbl_weight;              /* weight of largest contribution */
-    int     total;                     /* combined blocklist score */
+    int     total;                     /* combined white+blocklist score */
+    int     fail_ttl;                  /* combined reply TTL */
+    int     pass_ttl;                  /* combined reply TTL */
     int     refcount;                  /* score reference count */
     int     pending_lookups;           /* nr of DNS requests in flight */
     int     request_id;                        /* duplicate suppression */
@@ -306,11 +313,12 @@ static int psc_dnsbl_match(const char *filter, ARGV *reply)
 /* psc_dnsbl_retrieve - retrieve blocklist score, decrement reference count */
 
 int     psc_dnsbl_retrieve(const char *client_addr, const char **dnsbl_name,
-                                  int dnsbl_index)
+                                  int dnsbl_index, int *dnsbl_ttl)
 {
     const char *myname = "psc_dnsbl_retrieve";
     PSC_DNSBL_SCORE *score;
     int     result_score;
+    int     result_ttl;
 
     /*
      * Sanity check.
@@ -329,6 +337,16 @@ int     psc_dnsbl_retrieve(const char *client_addr, const char **dnsbl_name,
      */
     result_score = score->total;
     *dnsbl_name = score->dnsbl_name;
+    result_ttl = (result_score > 0) ? score->fail_ttl : score->pass_ttl;
+    /* As with dnsblog(8), a value < 0 means no reply TTL. */
+    if (result_ttl < var_psc_dnsbl_min_ttl)
+       result_ttl = var_psc_dnsbl_min_ttl;
+    if (result_ttl > var_psc_dnsbl_max_ttl)
+       result_ttl = var_psc_dnsbl_max_ttl;
+    *dnsbl_ttl = result_ttl;
+    if (msg_verbose)
+       msg_info("%s: addr=%s score=%d ttl=%d",
+                myname, client_addr, result_score, result_ttl);
     score->refcount -= 1;
     if (score->refcount < 1) {
        if (msg_verbose > 1)
@@ -349,6 +367,7 @@ static void psc_dnsbl_receive(int event, void *context)
     PSC_DNSBL_SITE *site;
     ARGV   *reply_argv;
     int     request_id;
+    int     dnsbl_ttl;
 
     PSC_CLEAR_EVENT_REQUEST(vstream_fileno(stream), psc_dnsbl_receive, context);
 
@@ -374,7 +393,8 @@ static void psc_dnsbl_receive(int event, void *context)
                     RECV_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, reply_client),
                     RECV_ATTR_INT(MAIL_ATTR_LABEL, &request_id),
                     RECV_ATTR_STR(MAIL_ATTR_RBL_ADDR, reply_addr),
-                    ATTR_TYPE_END) == 4
+                    RECV_ATTR_INT(MAIL_ATTR_TTL, &dnsbl_ttl),
+                    ATTR_TYPE_END) == 5
        && (score = (PSC_DNSBL_SCORE *)
            htable_find(dnsbl_score_cache, STR(reply_client))) != 0
        && score->request_id == request_id) {
@@ -387,14 +407,17 @@ static void psc_dnsbl_receive(int event, void *context)
         * server may be messed up.
         */
        if (msg_verbose > 1)
-           msg_info("%s: client=\"%s\" score=%d domain=\"%s\" reply=\"%s\"",
+           msg_info("%s: client=\"%s\" score=%d domain=\"%s\" reply=\"%d %s\"",
                     myname, STR(reply_client), score->total,
-                    STR(reply_dnsbl), STR(reply_addr));
-       if (*STR(reply_addr) != 0) {
-           head = (PSC_DNSBL_HEAD *)
-               htable_find(dnsbl_site_cache, STR(reply_dnsbl));
-           site = (head ? head->first : (PSC_DNSBL_SITE *) 0);
-           for (reply_argv = 0; site != 0; site = site->next) {
+                    STR(reply_dnsbl), dnsbl_ttl, STR(reply_addr));
+       head = (PSC_DNSBL_HEAD *)
+           htable_find(dnsbl_site_cache, STR(reply_dnsbl));
+       if (head == 0) {
+           /* Bogus domain. Do nothing. */
+       } else if (*STR(reply_addr) != 0) {
+           /* DNS reputation record(s) found. */
+           reply_argv = 0;
+           for (site = head->first; site != 0; site = site->next) {
                if (site->byte_codes == 0
                    || psc_dnsbl_match(site->byte_codes, reply_argv ? reply_argv :
                         (reply_argv = argv_split(STR(reply_addr), " ")))) {
@@ -409,9 +432,29 @@ static void psc_dnsbl_receive(int event, void *context)
                               myname, site->filter ? site->filter : "null",
                                 site->weight, score->total);
                }
+               /* As with dnsblog(8), a value < 0 means no reply TTL. */
+               if (site->weight > 0) {
+                   if (score->fail_ttl < 0 || score->fail_ttl > dnsbl_ttl)
+                       score->fail_ttl = dnsbl_ttl;
+               } else {
+                   if (score->pass_ttl < 0 || score->pass_ttl > dnsbl_ttl)
+                       score->pass_ttl = dnsbl_ttl;
+               }
            }
            if (reply_argv != 0)
                argv_free(reply_argv);
+       } else {
+           /* No DNS reputation record found. */
+           for (site = head->first; site != 0; site = site->next) {
+               /* As with dnsblog(8), a value < 0 means no reply TTL. */
+               if (site->weight > 0) {
+                   if (score->pass_ttl < 0 || score->pass_ttl > dnsbl_ttl)
+                       score->pass_ttl = dnsbl_ttl;
+               } else {
+                   if (score->fail_ttl < 0 || score->fail_ttl > dnsbl_ttl)
+                       score->fail_ttl = dnsbl_ttl;
+               }
+           }
        }
 
        /*
@@ -485,6 +528,9 @@ int     psc_dnsbl_request(const char *client_addr,
     score->request_id = request_count++;
     score->dnsbl_name = 0;
     score->dnsbl_weight = 0;
+    /* As with dnsblog(8), a value < 0 means no reply TTL. */
+    score->pass_ttl = -1;
+    score->fail_ttl = -1;
     score->total = 0;
     score->refcount = 1;
     score->pending_lookups = 0;
index e46487bcbcfd8f0758de14fd2830a3cbe2ce052c..c4e1a80d799b2e5110ba8fcf8fd56bc504d6ebcb 100644 (file)
@@ -87,8 +87,8 @@ static void psc_whitelist_non_dnsbl(PSC_STATE *state)
                state->flags |= PSC_STATE_FLAG_BYTINDX_PASS(tindx);
            }
            /* Update expiration even if the test was completed or disabled. */
-           if (state->expire_time[tindx] < now + var_psc_dnsbl_ttl)
-               state->expire_time[tindx] = now + var_psc_dnsbl_ttl;
+           if (state->expire_time[tindx] < now + state->dnsbl_ttl)
+               state->expire_time[tindx] = now + state->dnsbl_ttl;
        }
     }
 }
@@ -164,12 +164,13 @@ static void psc_early_event(int event, void *context)
                state->dnsbl_score =
                    psc_dnsbl_retrieve(state->smtp_client_addr,
                                       &state->dnsbl_name,
-                                      state->dnsbl_index);
+                                      state->dnsbl_index,
+                                      &state->dnsbl_ttl);
                if (var_psc_dnsbl_wthresh < 0)
                    psc_whitelist_non_dnsbl(state);
            }
            if (state->dnsbl_score < var_psc_dnsbl_thresh) {
-               state->dnsbl_stamp = event_time() + var_psc_dnsbl_ttl;
+               state->dnsbl_stamp = event_time() + state->dnsbl_ttl;
                PSC_PASS_SESSION_STATE(state, "dnsbl test",
                                       PSC_STATE_FLAG_DNSBL_PASS);
            } else {
@@ -228,7 +229,8 @@ static void psc_early_event(int event, void *context)
                && (state->flags & PSC_STATE_FLAG_DNSBL_TODO))
                (void) psc_dnsbl_retrieve(state->smtp_client_addr,
                                          &state->dnsbl_name,
-                                         state->dnsbl_index);
+                                         state->dnsbl_index,
+                                         &state->dnsbl_ttl);
            /* XXX Wait for DNS replies to come in. */
            psc_hangup_event(state);
            return;
@@ -246,7 +248,8 @@ static void psc_early_event(int event, void *context)
                && (state->flags & PSC_STATE_FLAG_DNSBL_TODO))
                (void) psc_dnsbl_retrieve(state->smtp_client_addr,
                                          &state->dnsbl_name,
-                                         state->dnsbl_index);
+                                         state->dnsbl_index,
+                                         &state->dnsbl_ttl);
            PSC_DROP_SESSION_STATE(state, "521 5.5.1 Protocol error\r\n");
            return;
        case PSC_ACT_ENFORCE:
@@ -298,7 +301,7 @@ static void psc_early_dnsbl_event(int unused_event, void *context)
      */
     state->dnsbl_score =
        psc_dnsbl_retrieve(state->smtp_client_addr, &state->dnsbl_name,
-                          state->dnsbl_index);
+                          state->dnsbl_index, &state->dnsbl_ttl);
     if (var_psc_dnsbl_wthresh < 0)
        psc_whitelist_non_dnsbl(state);
 
index 1b44ef1b14e986887c940fb045624adbdadac1d4..25359df9d4d0949d36c9b86e8ed5a7ed34b27f1c 100644 (file)
 /*     on the value of the corresponding per-destination recipient limit.
 /* .IP "\fItransport\fB_destination_rate_delay $default_destination_rate_delay
 /*     Idem, for delivery via the named message \fItransport\fR.
+/* .PP
+/*     Available in Postfix version 3.1 and later:
+/* .IP "\fBdefault_transport_rate_delay (0s)\fR"
+/*     The default amount of delay that is inserted between individual
+/*     deliveries over the same message delivery transport, regardless of
+/*     destination.
+/* .IP "\fItransport\fB_transport_rate_delay $default_transport_rate_delay
+/*     Idem, for delivery via the named message \fItransport\fR.
 /* SAFETY CONTROLS
 /* .ad
 /* .fi
@@ -446,6 +454,7 @@ char   *var_conc_pos_feedback;
 char   *var_conc_neg_feedback;
 int     var_conc_cohort_limit;
 int     var_conc_feedback_debug;
+int     var_xport_rate_delay;
 int     var_dest_rate_delay;
 char   *var_def_filter_nexthop;
 int     var_qmgr_daemon_timeout;
@@ -702,6 +711,7 @@ int     main(int argc, char **argv)
        VAR_XPORT_RETRY_TIME, DEF_XPORT_RETRY_TIME, &var_transport_retry_time, 1, 0,
        VAR_QMGR_CLOG_WARN_TIME, DEF_QMGR_CLOG_WARN_TIME, &var_qmgr_clog_warn_time, 0, 0,
        VAR_XPORT_REFILL_DELAY, DEF_XPORT_REFILL_DELAY, &var_xport_refill_delay, 1, 0,
+       VAR_XPORT_RATE_DELAY, DEF_XPORT_RATE_DELAY, &var_xport_rate_delay, 0, 0,
        VAR_DEST_RATE_DELAY, DEF_DEST_RATE_DELAY, &var_dest_rate_delay, 0, 0,
        VAR_QMGR_DAEMON_TIMEOUT, DEF_QMGR_DAEMON_TIMEOUT, &var_qmgr_daemon_timeout, 1, 0,
        VAR_QMGR_IPC_TIMEOUT, DEF_QMGR_IPC_TIMEOUT, &var_qmgr_ipc_timeout, 1, 0,
index d5f48453d180d262dd0d976fac9de2dc7d9c9701..88ce7920ebeb3293ca8a0fd32b8077cd8b57293a 100644 (file)
@@ -204,10 +204,12 @@ struct QMGR_TRANSPORT {
     QMGR_FEEDBACK pos_feedback;                /* positive feedback control */
     QMGR_FEEDBACK neg_feedback;                /* negative feedback control */
     int     fail_cohort_limit;         /* flow shutdown control */
+    int     xport_rate_delay;          /* suspend per delivery */
     int     rate_delay;                        /* suspend per delivery */
 };
 
 #define QMGR_TRANSPORT_STAT_DEAD       (1<<1)
+#define QMGR_TRANSPORT_STAT_RATE_LOCK  (1<<2)
 
 typedef void (*QMGR_TRANSPORT_ALLOC_NOTIFY) (QMGR_TRANSPORT *, VSTREAM *);
 extern QMGR_TRANSPORT *qmgr_transport_select(void);
index 2c7ca7f8d3ad81cb07adcedce40888f2f4c2c3f5..575f7591240e5b8e061558dc8e1ae99c0eb10642 100644 (file)
 
 #include "qmgr.h"
 
+ /*
+  * Important note on the _transport_rate_delay implementation: after
+  * qmgr_transport_alloc() sets the QMGR_TRANSPORT_STAT_RATE_LOCK flag, all
+  * code paths must directly or indirectly invoke qmgr_transport_unthrottle()
+  * or qmgr_transport_throttle(). Otherwise, transports with non-zero
+  * _transport_rate_delay will become stuck.
+  */
+
 int     qmgr_deliver_concurrency;
 
  /*
@@ -346,9 +354,10 @@ static void qmgr_deliver_update(int unused_event, void *context)
      * No problems detected. Mark the transport and queue as alive. The queue
      * itself won't go away before we dispose of the current queue entry.
      */
-    if (status != DELIVER_STAT_CRASH && VSTRING_LEN(dsb->reason) == 0) {
+    if (status != DELIVER_STAT_CRASH) {
        qmgr_transport_unthrottle(transport);
-       qmgr_queue_unthrottle(queue);
+       if (VSTRING_LEN(dsb->reason) == 0)
+           qmgr_queue_unthrottle(queue);
     }
 
     /*
index 0611f3b22c05558de05420ef9c61aeb5d7e87473..a5089f493731d3c8955769c01e0f1b85f62d08d9 100644 (file)
@@ -163,6 +163,14 @@ struct QMGR_TRANSPORT_ALLOC {
 #define QMGR_TRANSPORT_MAX_PEND        2
 #endif
 
+ /*
+  * Important note on the _transport_rate_delay implementation: after
+  * qmgr_transport_alloc() sets the QMGR_TRANSPORT_STAT_RATE_LOCK flag, all
+  * code paths must directly or indirectly invoke qmgr_transport_unthrottle()
+  * or qmgr_transport_throttle(). Otherwise, transports with non-zero
+  * _transport_rate_delay will become stuck.
+  */
+
 /* qmgr_transport_unthrottle_wrapper - in case (char *) != (struct *) */
 
 static void qmgr_transport_unthrottle_wrapper(int unused_event, void *context)
@@ -180,7 +188,7 @@ void    qmgr_transport_unthrottle(QMGR_TRANSPORT *transport)
      * This routine runs after expiration of the timer set by
      * qmgr_transport_throttle(), or whenever a delivery transport has been
      * used without malfunction. In either case, we enable delivery again if
-     * the transport was blocked, otherwise the request is ignored.
+     * the transport was throttled. We always reset the transport rate lock.
      */
     if ((transport->flags & QMGR_TRANSPORT_STAT_DEAD) != 0) {
        if (msg_verbose)
@@ -194,6 +202,8 @@ void    qmgr_transport_unthrottle(QMGR_TRANSPORT *transport)
        event_cancel_timer(qmgr_transport_unthrottle_wrapper,
                           (void *) transport);
     }
+    if (transport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK)
+       transport->flags &= ~QMGR_TRANSPORT_STAT_RATE_LOCK;
 }
 
 /* qmgr_transport_throttle - disable delivery process allocation */
@@ -230,6 +240,16 @@ static void qmgr_transport_abort(int unused_event, void *context)
     msg_fatal("timeout connecting to transport: %s", alloc->transport->name);
 }
 
+/* qmgr_transport_rate_event - delivery process availability notice */
+
+static void qmgr_transport_rate_event(int unused_event, void *context)
+{
+    QMGR_TRANSPORT_ALLOC *alloc = (QMGR_TRANSPORT_ALLOC *) context;
+
+    alloc->notify(alloc->transport, alloc->stream);
+    myfree((void *) alloc);
+}
+
 /* qmgr_transport_event - delivery process availability notice */
 
 static void qmgr_transport_event(int unused_event, void *context)
@@ -261,8 +281,16 @@ static void qmgr_transport_event(int unused_event, void *context)
     /*
      * Notify the requestor.
      */
-    alloc->notify(alloc->transport, alloc->stream);
-    myfree((void *) alloc);
+    if (alloc->transport->xport_rate_delay > 0) {
+       if ((alloc->transport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK) == 0)
+           msg_panic("transport_event: missing rate lock for transport %s",
+                     alloc->transport->name);
+       event_request_timer(qmgr_transport_rate_event, (void *) alloc,
+                           alloc->transport->xport_rate_delay);
+    } else {
+       alloc->notify(alloc->transport, alloc->stream);
+       myfree((void *) alloc);
+    }
 }
 
 /* qmgr_transport_select - select transport for allocation */
@@ -287,6 +315,7 @@ QMGR_TRANSPORT *qmgr_transport_select(void)
 
     for (xport = qmgr_transport_list.next; xport; xport = xport->peers.next) {
        if ((xport->flags & QMGR_TRANSPORT_STAT_DEAD) != 0
+           || (xport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK) != 0
            || xport->pending >= QMGR_TRANSPORT_MAX_PEND)
            continue;
        need = xport->pending + 1;
@@ -316,9 +345,18 @@ void    qmgr_transport_alloc(QMGR_TRANSPORT *transport, QMGR_TRANSPORT_ALLOC_NOT
      */
     if (transport->flags & QMGR_TRANSPORT_STAT_DEAD)
        msg_panic("qmgr_transport: dead transport: %s", transport->name);
+    if (transport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK)
+       msg_panic("qmgr_transport: rate-locked transport: %s", transport->name);
     if (transport->pending >= QMGR_TRANSPORT_MAX_PEND)
        msg_panic("qmgr_transport: excess allocation: %s", transport->name);
 
+    /*
+     * When this message delivery transport is rate-limited, do not select it
+     * again before the end of a message delivery transaction.
+     */
+    if (transport->xport_rate_delay > 0)
+       transport->flags |= QMGR_TRANSPORT_STAT_RATE_LOCK;
+
     /*
      * Connect to the well-known port for this delivery service, and wake up
      * when a process announces its availability. Allow only a limited number
@@ -392,6 +430,9 @@ QMGR_TRANSPORT *qmgr_transport_create(const char *name)
     transport->init_dest_concurrency =
        get_mail_conf_int2(name, _INIT_DEST_CON,
                           var_init_dest_concurrency, 1, 0);
+    transport->xport_rate_delay = get_mail_conf_time2(name, _XPORT_RATE_DELAY,
+                                                     var_xport_rate_delay,
+                                                     's', 0, 0);
     transport->rate_delay = get_mail_conf_time2(name, _DEST_RATE_DELAY,
                                                var_dest_rate_delay,
                                                's', 0, 0);
index cafc69d1e6503d5a394b2ddb2eddf09f451ffdd4..83d9466e3ad51696aa7556d30f6ab2fbff0471e4 100644 (file)
@@ -2944,6 +2944,9 @@ static int check_server_access(SMTPD_STATE *state, const char *table,
      * 
      * If the domain name exists but no NS record exists, look up parent domain
      * NS records.
+     * 
+     * XXX 20150707 Work around broken DNS servers that reply with NXDOMAIN
+     * instead of "no data".
      */
     if (type == T_A
 #ifdef HAS_IPV6
@@ -2962,7 +2965,7 @@ static int check_server_access(SMTPD_STATE *state, const char *table,
                server_list = dns_rr_create(domain, domain, type, C_IN, 0, 0,
                                            domain, strlen(domain) + 1);
                dns_status = DNS_OK;
-           } else if (type == T_NS && h_errno == NO_DATA) {
+           } else if (type == T_NS /* && h_errno == NO_DATA */ ) {
                while ((domain = strchr(domain, '.')) != 0 && domain[1]) {
                    domain += 1;
                    dns_status = dns_lookup(domain, type, 0, &server_list,
index edef24650beaba10723e05f35f92618c28fb34bd..92c9102036413e8be91c004193d13017f586f1da 100644 (file)
@@ -148,7 +148,7 @@ OK
 OK
 >>> # EXPECT reject + A record, "all TXT results dropped" warning.
 >>> client localhost 127.0.0.2
-./smtpd_check: ignoring DNS RR: 2.0.0.127.dnsbltest.porcupine.org. TTL IN TXT DNS blocklist test.
+./smtpd_check: ignoring DNS RR: 2.0.0.127.dnsbltest.porcupine.org. TTL IN TXT DNS blocklist test
 ./smtpd_check: warning: 2.0.0.127.dnsbltest.porcupine.org: TXT lookup error: DNS reply filter drops all results
 ./smtpd_check: <queue id>: reject: CONNECT from localhost[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; from=<user@nxdomain.porcupine.org> proto=SMTP helo=<localhost>
 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org
index a35a15f8f6560a718711c85aba398aa761d8aa1d..7ac0c8d5259e7202ba64a3dcb85d1fb573612d3f 100644 (file)
@@ -51,7 +51,7 @@ OK
 ./smtpd_check: <queue id>: reject: HELO from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <www.porcupine.org>: Helo command rejected: Access denied; from=<foo@postfix.org> proto=SMTP helo=<www.porcupine.org>
 554 5.7.1 <www.porcupine.org>: Helo command rejected: Access denied
 >>> helo example.tld
-./smtpd_check: warning: Unable to look up NS host for example.tld: Host not found
+./smtpd_check: warning: Unable to look up NS host for tld: Host not found
 OK
 >>> helo foo@postfix.org
 OK
@@ -61,7 +61,7 @@ OK
 ./smtpd_check: <queue id>: reject: MAIL from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <foo@www.porcupine.org>: Sender address rejected: Access denied; from=<foo@www.porcupine.org> proto=SMTP helo=<foo@postfix.org>
 554 5.7.1 <foo@www.porcupine.org>: Sender address rejected: Access denied
 >>> mail example.tld
-./smtpd_check: warning: Unable to look up NS host for example.tld: Host not found
+./smtpd_check: warning: Unable to look up NS host for tld: Host not found
 OK
 >>> mail foo@postfix.org
 OK
@@ -71,7 +71,7 @@ OK
 ./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 5.7.1 <foo@www.porcupine.org>: Recipient address rejected: Access denied; from=<foo@postfix.org> to=<foo@www.porcupine.org> proto=SMTP helo=<foo@postfix.org>
 554 5.7.1 <foo@www.porcupine.org>: Recipient address rejected: Access denied
 >>> rcpt foo@example.tld
-./smtpd_check: warning: Unable to look up NS host for example.tld: Host not found
+./smtpd_check: warning: Unable to look up NS host for tld: Host not found
 OK
 >>> rcpt foo@postfix.org
 OK