]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-3.11-20250621
authorWietse Z Venema <wietse@porcupine.org>
Sat, 21 Jun 2025 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <ietf-dane@dukhovni.org>
Sun, 22 Jun 2025 05:33:48 +0000 (15:33 +1000)
42 files changed:
postfix/.indent.pro
postfix/HISTORY
postfix/README_FILES/DEPRECATION_README
postfix/conf/relocated
postfix/html/DEPRECATION_README.html
postfix/html/postconf.5.html
postfix/html/relocated.5.html
postfix/man/man5/postconf.5
postfix/man/man5/relocated.5
postfix/mantools/postlink
postfix/proto/DEPRECATION_README.html
postfix/proto/postconf.proto
postfix/proto/relocated
postfix/proto/stop.double-install-proto-text
postfix/proto/stop.double-proto-html
postfix/src/global/mail_params.h
postfix/src/global/mail_version.h
postfix/src/global/maps.c
postfix/src/postconf/Makefile.in
postfix/src/postconf/postconf_unused.c
postfix/src/postconf/test78.ref [new file with mode: 0644]
postfix/src/smtp/smtp.h
postfix/src/smtp/smtp_connect.c
postfix/src/smtp/smtp_proto.c
postfix/src/smtp/smtp_tls_policy.c
postfix/src/testing/Makefile.in
postfix/src/testing/dict_test_helper.c [new file with mode: 0644]
postfix/src/testing/dict_test_helper.h [new file with mode: 0644]
postfix/src/trivial-rewrite/Makefile.in
postfix/src/trivial-rewrite/resolve.c
postfix/src/trivial-rewrite/trivial-rewrite.c
postfix/src/util/Makefile.in
postfix/src/util/dict.c
postfix/src/util/dict.h
postfix/src/util/dict_pipe.c
postfix/src/util/dict_pipe_test.c [new file with mode: 0644]
postfix/src/util/dict_pipe_test.in [deleted file]
postfix/src/util/dict_pipe_test.ref [deleted file]
postfix/src/util/dict_union.c
postfix/src/util/dict_union_test.c [new file with mode: 0644]
postfix/src/util/dict_union_test.in [deleted file]
postfix/src/util/dict_union_test.ref [deleted file]

index 86522dfde2f0d400e8aa9eeec61e8edd3d90ca55..9fa65a8f98523a03973139a85d187c7608d4395d 100644 (file)
 -Td2i_X509_t
 -Tdane_digest
 -Tdane_mtype
+-Tdict_lookup_verify_data
 -Tfilter_ctx
 -Tgeneral_name_stack_t
 -Tiana_digest
index 13b6b27d6b4368c40a1a215a39e3016d4ad3985e..20d3fcec9b50cf445c0f88d51634e8436d87b25d 100644 (file)
@@ -29194,3 +29194,70 @@ Apologies for any names omitted.
        compatibility level >= 3.11. Files: smtp/smtp_tlsrpt.c,
        global/mail_params.[hc], proto/COMPATIBILITY_README.html.
        proto/memcache_table, global/dict_memcache.c, util/hex_code.[hc].
+
+20250608
+
+       Feature: specify "relocated_prefix_enable = no" to disable
+       the hard-coded prefix "5.1.6 User has moved to " that is
+       by default prepended to all relocated_maps lookup results.
+       This setting requires that the table contains responses
+       with both custom enhanced status code (X.Y.Z) and text.
+       Files: proto/postconf.proto, mantools/postlink,
+       global/mail_params.h, trivial-rewrite/trivial-rewrite.c,
+       trivial-rewrite/resolve.c.
+
+       This was tested against an unprivileged Postfix instance.
+       Support to spin up a disposable unprivileged Postfix instance
+       for testing will be made available later.
+
+20250609
+
+       Deprecation: smtp_tls_enforce_peername and
+       lmtp_tls_enforce_peername are now officially deprecated.
+       Files: proto/DEPRECATION_README.html, postconf/postconf_unused.c,
+       postconf/Makefile.in, postconf/test78.ref.
+
+20250614
+
+       Bugfix (defect introduced: Postfix 3.0, date 20140923):
+       Lookup tables specified inside the {} were left open after
+       unionmap:{} or pipemap:{} syntax error. Richard Hansen.
+       Files: util/dict_pipe.c, util/dict_union.c.
+
+20250617
+
+       Proper unit tests for the pipemap and unionmap implementations.
+       Files: testing/dict_test_helper.[hc], util/dict.[hc],
+       util/dict_pipe_test.c, util/dict_union_test.c. This obsoletes
+       the ad-hoc test files dict_pipe_test.{in,ref} and
+       dict_union_test.{in,ref} and corresponding Makefile targets.
+
+       Proper sharing of identical dictionary instances opened
+       with dict_pipe_open() and dict_union_open(), reusing the
+       sharing policy that was already implemented with maps_create().
+       Files: util/dict.c, util/dict.h, util/dict_pipe.c,
+       util/dict_union.c, global/maps.c.
+
+20250619
+
+       Bugfix (defect introduced: Postfix-3.10, date 20250117): the
+       Postfix SMTP client attempted to look up TLSA records even
+       with "TLS-Required: no". This could result in unnecessary
+       failures. Fix by Viktor Dukhovni & Wietse. Files: smtp/smtp.h,
+       smtp/smtp_policy.c, smtp/smtp_connect.c.
+
+       Bugfix (defect introduced: Postfix-3.10, date 20250117):
+       include the current TLS security level in the SMTP connection
+       cache lookup key for lookups by next-hop destination. The
+       idea is that to deliver a message without "TLS-Required:
+       no" header, the Postfix SMTP client must not reuse a
+       connection that was created during a burst of deliveries
+       of messages with "TLS-Required: no" to the same destination.
+       Including the TLS security level in the SMTP connection
+       cache lookup key will also prevent false connection reuse
+       when any future feature is added that overrides the TLS
+       security level based on message content or envelope metadata.
+       Files: smtp/smtp.h.
+
+       Likewise, include the current TLS security level in the TLS
+       client serverid field. File: smtp/smtp_proto.c.
index 87cf2474f39f18ce8c95b2603c5759fc55ff85ae..ee797e50d848413b925cbd603c048dfff6de51be 100644 (file)
@@ -58,6 +58,8 @@ the "obsolete feature" name for a more detailed description.
     |                           |o\bof\bf     |i\bin\bn v\bve\ber\brs\bsi\bio\bon\bn|                         |
     |                           |v\bve\ber\brs\bsi\bio\bon\bn|          |                         |
     |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+    |xxx_tls_enforce_peername   |  3.11 |      -   |xxx_tls_security_level   |
+    |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
     |disable_dns_lookups        |   3.9 |      -   |smtp_dns_support_level   |
     |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
     |xxx_use_tls                |   3.9 |      -   |xxx_tls_security_level   |
@@ -83,6 +85,33 @@ the "obsolete feature" name for a more detailed description.
     |                           |       |          |permit_sasl_authenticated|
     |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
 
+O\bOb\bbs\bso\bol\ble\bet\bte\be T\bTL\bLS\bS p\bpe\bee\ber\br n\bna\bam\bme\be m\bma\bat\btc\bch\bh c\bco\bon\bnf\bfi\big\bgu\bur\bra\bat\bti\bio\bon\bn
+
+The postconf(1) command logs one of the following:
+
+  * support for parameter "lmtp_tls_enforce_peername" will be removed; instead,
+    specify "lmtp_tls_security_level"
+  * support for parameter "smtp_tls_enforce_peername" will be removed; instead,
+    specify "smtp_tls_security_level"
+
+There are similarly-named parameters and warnings for postscreen(8) and
+tlsproxy(8), but those parameters should rarely be specified by hand.
+
+Replace obsolete configuration with its replacement:
+
+     _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b 
+    |G\bGo\boa\bal\bl                   |O\bOb\bbs\bso\bol\ble\bet\bte\be c\bco\bon\bnf\bfi\big\bgu\bur\bra\bat\bti\bio\bon\bn|R\bRe\bep\bpl\bla\bac\bce\bem\bme\ben\bnt\bt c\bco\bon\bnf\bfi\big\bgu\bur\bra\bat\bti\bio\bon\bn|
+    |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+    |                       |                      |xxx_security_level =     |
+    |Enforce peer name match|xxx_enforce_peername =|verify                   |
+    |with server certificate|yes                   |xxx_security_level =     |
+    |                       |                      |secure                   |
+    |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+    |Disable peer name match|xxx_enforce_peername =|xxx_security_level = may |
+    |with server certificate|no                    |xxx_security_level =     |
+    |                       |                      |encrypt                  |
+    |_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b|_\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b _\b |
+
 O\bOb\bbs\bso\bol\ble\bet\bte\be D\bDN\bNS\bS o\bon\bn/\b/o\bof\bff\bf c\bco\bon\bnf\bfi\big\bgu\bur\bra\bat\bti\bio\bon\bn
 
 The postconf(1) command logs the following:
index 5c432667243285fb507ef3110a1729bbf4c5106c..f3d9f8bfd7ad70838a97e9f6382e8a5719896060 100644 (file)
 #        lookup fields can match both upper and lower case.
 # 
 # TABLE FORMAT
-#        The input format for the postmap(1) command is as follows:
-# 
-#        o      An entry has one of the following form:
+#        o      By default, Postfix will prepend a hard-coded  pre-
+#               fix  "5.1.6  User  has moved to " to a table lookup
+#               result, and the format for a table entry is as fol-
+#               lows:
 # 
 #                    pattern      new_location
 # 
 #               such as an  email  address,  or  perhaps  a  street
 #               address or telephone number.
 # 
+#        o      Postfix  3.11  and later can optionally disable the
+#               hard-coded prefix. Specify "relocated_prefix_enable
+#               =   no"  in  main.cf,  and  specify  relocated_maps
+#               entries with your own RFC  3463-compliant  enhanced
+#               status code and text, for example:
+# 
+#                    pattern      5.2.0 Mailbox is unavailable
+#                    pattern      5.2.1 Mailbox is disabled
+# 
 #        o      Empty  lines and whitespace-only lines are ignored,
 #               as are lines whose first  non-whitespace  character
 #               is a `#'.
 #               Optional lookup tables with new contact information
 #               for users or domains that no longer exist.
 # 
+#        Available with Postfix version 3.11 and later:
+# 
+#        relocated_prefix_enable (yes)
+#               Prepend  the  prefix  "5.1.6 User has moved to " to
+#               all relocated_maps lookup results.
+# 
 #        Other parameters of interest:
 # 
 #        inet_interfaces (all)
-#               The  local  network  interface  addresses that this
+#               The local network  interface  addresses  that  this
 #               mail system receives mail on.
 # 
 #        mydestination  ($myhostname,  localhost.$mydomain,  local-
 #        host)
-#               The list of domains  that  are  delivered  via  the
+#               The  list  of  domains  that  are delivered via the
 #               $local_transport mail delivery transport.
 # 
 #        myorigin ($myhostname)
 #               The domain name that locally-posted mail appears to
-#               come from, and that locally posted mail  is  deliv-
+#               come  from,  and that locally posted mail is deliv-
 #               ered to.
 # 
 #        proxy_interfaces (empty)
-#               The  remote  network  interface addresses that this
-#               mail system receives mail on by way of a  proxy  or
+#               The remote network interface  addresses  that  this
+#               mail  system  receives mail on by way of a proxy or
 #               network address translation unit.
 # 
 # SEE ALSO
 #        postconf(5), configuration parameters
 # 
 # README FILES
-#        Use  "postconf  readme_directory" or "postconf html_direc-
+#        Use "postconf readme_directory" or  "postconf  html_direc-
 #        tory" to locate this information.
 #        DATABASE_README, Postfix lookup table overview
 #        ADDRESS_REWRITING_README, address rewriting guide
 # 
 # LICENSE
-#        The Secure Mailer license must be  distributed  with  this
+#        The  Secure  Mailer  license must be distributed with this
 #        software.
 # 
 # AUTHOR(S)
index ace103015b1d35ed8ff94a8ee6aaa07cf4baf184..60402835055c6a5aaa84e51c6cafa20cfc23a08a 100644 (file)
@@ -104,11 +104,16 @@ detailed description. </p>
 </th> <th> Removed <br> in version </th> <th> Replacement </th>
 </tr>
 
+<tr> <td> <a href="#xxx_tls_enforce_peername">
+<i>xxx</i>_tls_enforce_peername </a> </td> <td align="center"> 3.11
+</td> <td align="center"> - </td> <td> <i>xxx</i>_tls_security_level
+</td> </tr>
+
 <tr> <td> <a href="#disable_dns_lookups"> disable_dns_lookups </a>
 </td> <td align="center"> 3.9 </td> <td align="center"> - </td>
 <td> <a href="postconf.5.html#smtp_dns_support_level">smtp_dns_support_level</a> </td> </tr>
 
-<tr> <td> <a href="#xxx_enforce_tls"> <i>xxx</i>_use_tls </a> </td>
+<tr> <td> <a href="#xxx_use_tls"> <i>xxx</i>_use_tls </a> </td>
 <td align="center"> 3.9 </td> <td align="center"> - </td> <td>
 <i>xxx</i>_tls_security_level </td> </tr>
 
@@ -149,6 +154,45 @@ smtpd_tls_dh1024_param_file </a> </td> <td align="center"> 3.9 </td>
 
 </blockquote>
 
+<h3> <a name="xxx_tls_enforce_peername"> Obsolete TLS peer name
+match configuration </a> </h3>
+
+<p> The <a href="postconf.1.html">postconf(1)</a> command logs one of the following: </p>
+
+<ul>
+
+<li> support for parameter "<a href="postconf.5.html#lmtp_tls_enforce_peername">lmtp_tls_enforce_peername</a>" will be
+removed; instead, specify "<a href="postconf.5.html#lmtp_tls_security_level">lmtp_tls_security_level</a>"
+
+<li> support for parameter "<a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a>" will be
+removed; instead, specify "<a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a>"
+
+</ul>
+
+<p> There are similarly-named parameters and warnings for <a href="postscreen.8.html">postscreen(8)</a>
+and <a href="tlsproxy.8.html">tlsproxy(8)</a>, but those parameters should rarely be specified
+by hand. </p>
+
+<p> Replace obsolete configuration with its replacement: </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th width="33%"> Goal </th> <th width="33%"> Obsolete configuration </th> <th> Replacement configuration </th> </tr>
+
+<tr> <td> Enforce peer name match with server certificate </td>
+<td> <i>xxx</i>_enforce_peername = yes </td> <td> <i>xxx</i>_security_level
+= verify <br> <i>xxx</i>_security_level = secure </td> </tr>
+
+<tr> <td> Disable peer name match with server certificate </td>
+<td> <i>xxx</i>_enforce_peername = no </td> <td> <i>xxx</i>_security_level
+= may <br> <i>xxx</i>_security_level = encrypt </td> </tr>
+
+</table>
+
+</blockquote>
+
 <h3> <a name="disable_dns_lookups"> Obsolete DNS on/off configuration
 </a> </h3>
 
index 0d63f28ee5f9f9795eb4b3d54e08fde388d3352e..520bd062d87123376d1c857b77e74b4a530b62a5 100644 (file)
@@ -3482,6 +3482,30 @@ Postfix versions before 2.0 have no support for the original recipient
 address. </p>
 
 
+</DD>
+
+<DT><b><a name="relocated_prefix_enable">relocated_prefix_enable</a>
+(default: yes)</b></DT><DD>
+
+<p> Prepend the prefix "<b>5.1.6 User has moved to </b>" to all
+<a href="postconf.5.html#relocated_maps">relocated_maps</a> lookup results. With "<a href="postconf.5.html#relocated_prefix_enable">relocated_prefix_enable</a> =
+no", all lookup results must contain a valid <a href="https://tools.ietf.org/html/rfc3463">RFC 3463</a> compliant
+enhanced status code and text (format: "[45].number.number text...").
+
+<p>
+Example:
+</p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+    <a href="postconf.5.html#relocated_maps">relocated_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/relocated
+    <a href="postconf.5.html#relocated_prefix_enable">relocated_prefix_enable</a> = no
+<br>
+<a href="DATABASE_README.html#types">hash</a>:/etc/postfix/relocated:
+    user@example.com 5.2.1 User account is disabled
+</pre>
+
+
 </DD>
 
 <DT><b><a name="enable_threaded_bounces">enable_threaded_bounces</a>
index 3ce1074193a8bbbe6fc703d41814df132e6ead00..a6397fe62b8c867e026cc11b678c3928c2e8072f 100644 (file)
@@ -41,71 +41,79 @@ RELOCATED(5)                                                      RELOCATED(5)
        lower case.
 
 <b><a name="table_format">TABLE FORMAT</a></b>
-       The input format for the <a href="postmap.1.html"><b>postmap</b>(1)</a> command is as follows:
-
-       <b>o</b>      An entry has one of the following form:
+       <b>o</b>      By default, Postfix will prepend a hard-coded prefix "5.1.6 User
+              has  moved  to  " to a table lookup result, and the format for a
+              table entry is as follows:
 
                    <i>pattern      new</i><b>_</b><i>location</i>
 
               Where <i>new</i><b>_</b><i>location</i> specifies  contact  information  such  as  an
               email  address, or perhaps a street address or telephone number.
 
-       <b>o</b>      Empty lines and whitespace-only lines are ignored, as are  lines
+       <b>o</b>      Postfix 3.11 and later can  optionally  disable  the  hard-coded
+              prefix.  Specify  "<a href="postconf.5.html#relocated_prefix_enable">relocated_prefix_enable</a> = no" in <a href="postconf.5.html">main.cf</a>, and
+              specify <a href="postconf.5.html#relocated_maps">relocated_maps</a> entries with your own <a href="https://tools.ietf.org/html/rfc3463">RFC  3463</a>-compliant
+              enhanced status code and text, for example:
+
+                   <i>pattern</i>      5.2.0 Mailbox is unavailable
+                   <i>pattern</i>      5.2.1 Mailbox is disabled
+
+       <b>o</b>      Empty  lines and whitespace-only lines are ignored, as are lines
               whose first non-whitespace character is a `#'.
 
-       <b>o</b>      A  logical  line  starts  with  non-whitespace text. A line that
+       <b>o</b>      A logical line starts with  non-whitespace  text.  A  line  that
               starts with whitespace continues a logical line.
 
 <b><a name="table_search_order">TABLE SEARCH ORDER</a></b>
-       With lookups from indexed files such as DB or DBM,  or  from  networked
-       tables  such  as  NIS,  LDAP or SQL, patterns are tried in the order as
+       With  lookups  from  indexed files such as DB or DBM, or from networked
+       tables such as NIS, LDAP or SQL, patterns are tried  in  the  order  as
        listed below:
 
        <i>user</i>@<i>domain</i>
-              Matches <i>user</i>@<i>domain</i>. This form has  precedence  over  all  other
+              Matches  <i>user</i>@<i>domain</i>.  This  form  has precedence over all other
               forms.
 
        <i>user</i>   Matches <i>user</i>@<i>site</i> when <i>site</i> is $<b><a href="postconf.5.html#myorigin">myorigin</a></b>, when <i>site</i> is listed in
-              $<b><a href="postconf.5.html#mydestination">mydestination</a></b>, or when <i>site</i> is listed  in  $<b><a href="postconf.5.html#inet_interfaces">inet_interfaces</a></b>  or
+              $<b><a href="postconf.5.html#mydestination">mydestination</a></b>,  or  when  <i>site</i> is listed in $<b><a href="postconf.5.html#inet_interfaces">inet_interfaces</a></b> or
               $<b><a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a></b>.
 
        @<i>domain</i>
-              Matches  other  addresses  in  <i>domain</i>.  This form has the lowest
+              Matches other addresses in <i>domain</i>.  This  form  has  the  lowest
               precedence.
 
 <b><a name="address_extension">ADDRESS EXTENSION</a></b>
        When a mail address localpart contains the optional recipient delimiter
-       (e.g.,  <i>user+foo</i>@<i>domain</i>),  the  lookup  order becomes: <i>user+foo</i>@<i>domain</i>,
+       (e.g., <i>user+foo</i>@<i>domain</i>), the  lookup  order  becomes:  <i>user+foo</i>@<i>domain</i>,
        <i>user</i>@<i>domain</i>, <i>user+foo</i>, <i>user</i>, and @<i>domain</i>.
 
 <b><a name="regular_expression_tables">REGULAR EXPRESSION TABLES</a></b>
-       This section describes how the table lookups change when the  table  is
-       given  in  the form of regular expressions or when lookups are directed
-       to a TCP-based server. For a description of regular  expression  lookup
-       table  syntax,  see <a href="regexp_table.5.html"><b>regexp_table</b>(5)</a> or <a href="pcre_table.5.html"><b>pcre_table</b>(5)</a>. For a description
+       This  section  describes how the table lookups change when the table is
+       given in the form of regular expressions or when lookups  are  directed
+       to  a  TCP-based server. For a description of regular expression lookup
+       table syntax, see <a href="regexp_table.5.html"><b>regexp_table</b>(5)</a> or <a href="pcre_table.5.html"><b>pcre_table</b>(5)</a>. For  a  description
        of the TCP client/server table lookup protocol, see <a href="tcp_table.5.html"><b>tcp_table</b>(5)</a>.  This
        feature is available in Postfix 2.5 and later.
 
-       Each  pattern  is  a  regular  expression that is applied to the entire
-       address being looked up. Thus, <i>user@domain</i> mail addresses are not  bro-
-       ken  up  into their <i>user</i> and <i>@domain</i> constituent parts, nor is <i>user+foo</i>
+       Each pattern is a regular expression that  is  applied  to  the  entire
+       address  being looked up. Thus, <i>user@domain</i> mail addresses are not bro-
+       ken up into their <i>user</i> and <i>@domain</i> constituent parts, nor  is  <i>user+foo</i>
        broken up into <i>user</i> and <i>foo</i>.
 
-       Patterns are applied in the order as specified in the  table,  until  a
+       Patterns  are  applied  in the order as specified in the table, until a
        pattern is found that matches the search string.
 
-       Results  are the same as with indexed file lookups, with the additional
-       feature that parenthesized substrings from the pattern can be  interpo-
+       Results are the same as with indexed file lookups, with the  additional
+       feature  that parenthesized substrings from the pattern can be interpo-
        lated as <b>$1</b>, <b>$2</b> and so on.
 
 <b><a name="tcp-based_tables">TCP-BASED TABLES</a></b>
-       This  section  describes  how the table lookups change when lookups are
-       directed  to  a  TCP-based  server.  For  a  description  of  the   TCP
-       client/server  lookup  protocol,  see  <a href="tcp_table.5.html"><b>tcp_table</b>(5)</a>.   This  feature is
+       This section describes how the table lookups change  when  lookups  are
+       directed   to  a  TCP-based  server.  For  a  description  of  the  TCP
+       client/server lookup  protocol,  see  <a href="tcp_table.5.html"><b>tcp_table</b>(5)</a>.   This  feature  is
        available in Postfix 2.5 and later.
 
-       Each lookup operation uses the entire address once.  Thus,  <i>user@domain</i>
-       mail  addresses  are  not  broken  up  into their <i>user</i> and <i>@domain</i> con-
+       Each  lookup operation uses the entire address once.  Thus, <i>user@domain</i>
+       mail addresses are not broken up  into  their  <i>user</i>  and  <i>@domain</i>  con-
        stituent parts, nor is <i>user+foo</i> broken up into <i>user</i> and <i>foo</i>.
 
        Results are the same as with indexed file lookups.
@@ -114,14 +122,20 @@ RELOCATED(5)                                                      RELOCATED(5)
        The table format does not understand quoting conventions.
 
 <b><a name="configuration_parameters">CONFIGURATION PARAMETERS</a></b>
-       The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are  especially  relevant.   The  text
-       below  provides  only  a  parameter  summary.  See <a href="postconf.5.html"><b>postconf</b>(5)</a> for more
+       The  following  <a href="postconf.5.html"><b>main.cf</b></a>  parameters  are especially relevant.  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#relocated_maps">relocated_maps</a> (empty)</b>
               Optional lookup tables with new contact information for users or
               domains that no longer exist.
 
+       Available with Postfix version 3.11 and later:
+
+       <b><a href="postconf.5.html#relocated_prefix_enable">relocated_prefix_enable</a> (yes)</b>
+              Prepend the prefix "<b>5.1.6 User has  moved  to</b>  "  to  all  relo-
+              cated_maps lookup results.
+
        Other parameters of interest:
 
        <b><a href="postconf.5.html#inet_interfaces">inet_interfaces</a> (all)</b>
index 24351183c30fa4a5261a14200b8f90f0817303c6..a9adc8eef9c906d443d9a31a8ba19698c52c1bc7 100644 (file)
@@ -2190,6 +2190,24 @@ This feature is available in Postfix 2.1 and later. Postfix
 version 2.0 behaves as if this parameter is always set to \fByes\fR.
 Postfix versions before 2.0 have no support for the original recipient
 address.
+.SH relocated_prefix_enable (default: yes)
+Prepend the prefix "\fB5.1.6 User has moved to \fR" to all
+relocated_maps lookup results. With "relocated_prefix_enable =
+no", all lookup results must contain a valid RFC 3463 compliant
+enhanced status code and text (format: "[45].number.number text...").
+.PP
+Example:
+.PP
+.nf
+.na
+/etc/postfix/main.cf:
+    relocated_maps = hash:/etc/postfix/relocated
+    relocated_prefix_enable = no
+.br
+hash:/etc/postfix/relocated:
+    user@example.com 5.2.1 User account is disabled
+.fi
+.ad
 .SH enable_threaded_bounces (default: no)
 Enable non\-delivery, success, and delay notifications that link
 to the original message by including a References: and In\-Reply\-To:
index e1d7863b478c9497654b09a5bffc31317527c52d..768b592b139ea7eb56b55fab61f2cd1cf821ffd6 100644 (file)
@@ -46,9 +46,10 @@ lookup fields can match both upper and lower case.
 .nf
 .ad
 .fi
-The input format for the \fBpostmap\fR(1) command is as follows:
 .IP \(bu
-An entry has one of the following form:
+By default, Postfix will prepend a hard\-coded prefix "5.1.6 User
+has moved to " to a table lookup result, and the format for a
+table entry is as follows:
 
 .nf
      \fIpattern      new_location\fR
@@ -57,6 +58,16 @@ An entry has one of the following form:
 Where \fInew_location\fR specifies contact information such as
 an email address, or perhaps a street address or telephone number.
 .IP \(bu
+Postfix 3.11 and later can optionally disable the hard\-coded
+prefix. Specify "relocated_prefix_enable = no" in main.cf, and
+specify relocated_maps entries with your own RFC 3463\-compliant
+enhanced status code and text, for example:
+
+.nf
+     \fIpattern\fR      5.2.0 Mailbox is unavailable
+     \fIpattern\fR      5.2.1 Mailbox is disabled
+.fi
+.IP \(bu
 Empty lines and whitespace\-only lines are ignored, as
 are lines whose first non\-whitespace character is a `#'.
 .IP \(bu
@@ -145,6 +156,11 @@ The text below provides only a parameter summary. See
 Optional lookup tables with new contact information for users or
 domains that no longer exist.
 .PP
+Available with Postfix version 3.11 and later:
+.IP "\fBrelocated_prefix_enable (yes)\fR"
+Prepend the prefix "\fB5.1.6 User has moved to \fR" to all
+relocated_maps lookup results.
+.PP
 Other parameters of interest:
 .IP "\fBinet_interfaces (all)\fR"
 The local network interface addresses that this mail system
index b06c2d00fc4b17eb2eed6baceee9d794fa4d1f3e..d641b89953d98477aef32ef2fe5141667f481abd 100755 (executable)
@@ -443,6 +443,7 @@ while (<>) {
     s;\brelay_transport\b;<a href="postconf.5.html#relay_transport">$&</a>;g;
     s;\brelay[-</bB>]*\n*[ <bB>]*host\b;<a href="postconf.5.html#relayhost">$&</a>;g;
     s;\brelocated_maps\b;<a href="postconf.5.html#relocated_maps">$&</a>;g;
+    s;\brelocated_prefix_enable\b;<a href="postconf.5.html#relocated_prefix_enable">$&</a>;g;
     s;\brequire_home_directory\b;<a href="postconf.5.html#require_home_directory">$&</a>;g;
     s;\bresolve_dequoted_address\b;<a href="postconf.5.html#resolve_dequoted_address">$&</a>;g;
     s;\brewrite_service_name\b;<a href="postconf.5.html#rewrite_service_name">$&</a>;g;
index 356d2323026a345c15529b44b9f876e6c0506f2d..6f9f247bfddec4c0b5db8044d59ed4c504d58fdb 100644 (file)
@@ -104,11 +104,16 @@ detailed description. </p>
 </th> <th> Removed <br> in version </th> <th> Replacement </th>
 </tr>
 
+<tr> <td> <a href="#xxx_tls_enforce_peername">
+<i>xxx</i>_tls_enforce_peername </a> </td> <td align="center"> 3.11
+</td> <td align="center"> - </td> <td> <i>xxx</i>_tls_security_level
+</td> </tr>
+
 <tr> <td> <a href="#disable_dns_lookups"> disable_dns_lookups </a>
 </td> <td align="center"> 3.9 </td> <td align="center"> - </td>
 <td> smtp_dns_support_level </td> </tr>
 
-<tr> <td> <a href="#xxx_enforce_tls"> <i>xxx</i>_use_tls </a> </td>
+<tr> <td> <a href="#xxx_use_tls"> <i>xxx</i>_use_tls </a> </td>
 <td align="center"> 3.9 </td> <td align="center"> - </td> <td>
 <i>xxx</i>_tls_security_level </td> </tr>
 
@@ -149,6 +154,45 @@ reject_rbl_client </td> </tr>
 
 </blockquote>
 
+<h3> <a name="xxx_tls_enforce_peername"> Obsolete TLS peer name
+match configuration </a> </h3>
+
+<p> The postconf(1) command logs one of the following: </p>
+
+<ul>
+
+<li> support for parameter "lmtp_tls_enforce_peername" will be
+removed; instead, specify "lmtp_tls_security_level"
+
+<li> support for parameter "smtp_tls_enforce_peername" will be
+removed; instead, specify "smtp_tls_security_level"
+
+</ul>
+
+<p> There are similarly-named parameters and warnings for postscreen(8)
+and tlsproxy(8), but those parameters should rarely be specified
+by hand. </p>
+
+<p> Replace obsolete configuration with its replacement: </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th width="33%"> Goal </th> <th width="33%"> Obsolete configuration </th> <th> Replacement configuration </th> </tr>
+
+<tr> <td> Enforce peer name match with server certificate </td>
+<td> <i>xxx</i>_enforce_peername = yes </td> <td> <i>xxx</i>_security_level
+= verify <br> <i>xxx</i>_security_level = secure </td> </tr>
+
+<tr> <td> Disable peer name match with server certificate </td>
+<td> <i>xxx</i>_enforce_peername = no </td> <td> <i>xxx</i>_security_level
+= may <br> <i>xxx</i>_security_level = encrypt </td> </tr>
+
+</table>
+
+</blockquote>
+
 <h3> <a name="disable_dns_lookups"> Obsolete DNS on/off configuration
 </a> </h3>
 
index b1130e134d846772662099e70990df3cf26d13a2..860f1aec9ab0174d124ee9bf211803c1e16047d7 100644 (file)
@@ -4012,6 +4012,26 @@ relocated_maps = dbm:/etc/postfix/relocated
 relocated_maps = hash:/etc/postfix/relocated
 </pre>
 
+%PARAM relocated_prefix_enable  yes
+
+<p> Prepend the prefix "<b>5.1.6 User has moved to </b>" to all
+relocated_maps lookup results. With "relocated_prefix_enable =
+no", all lookup results must contain a valid RFC 3463 compliant
+enhanced status code and text (format: "[45].number.number text...").
+
+<p>
+Example:
+</p>
+
+<pre>
+/etc/postfix/main.cf:
+    relocated_maps = hash:/etc/postfix/relocated
+    relocated_prefix_enable = no
+<br>
+hash:/etc/postfix/relocated:
+    user@example.com 5.2.1 User account is disabled
+</pre>
+
 %PARAM require_home_directory no
 
 <p>
index b517b354fdf8a3377cfb920c239b5ae7136d66fe..f48b53093dce5d1d42bcaa6259d921062e5b402b 100644 (file)
 # TABLE FORMAT
 # .ad
 # .fi
-#      The input format for the \fBpostmap\fR(1) command is as follows:
 # .IP \(bu
-#      An entry has one of the following form:
+#      By default, Postfix will prepend a hard-coded prefix "5.1.6 User
+#      has moved to " to a table lookup result, and the format for a
+#      table entry is as follows:
 #
 # .nf
 #           \fIpattern      new_location\fR
 #      Where \fInew_location\fR specifies contact information such as
 #      an email address, or perhaps a street address or telephone number.
 # .IP \(bu
+#      Postfix 3.11 and later can optionally disable the hard-coded
+#      prefix. Specify "relocated_prefix_enable = no" in main.cf, and
+#      specify relocated_maps entries with your own RFC 3463-compliant
+#      enhanced status code and text, for example:
+#
+# .nf
+#           \fIpattern\fR      5.2.0 Mailbox is unavailable
+#           \fIpattern\fR      5.2.1 Mailbox is disabled
+# .fi
+# .IP \(bu
 #      Empty lines and whitespace-only lines are ignored, as
 #      are lines whose first non-whitespace character is a `#'.
 # .IP \(bu
 #      Optional lookup tables with new contact information for users or
 #      domains that no longer exist.
 # .PP
+#      Available with Postfix version 3.11 and later:
+# .IP "\fBrelocated_prefix_enable (yes)\fR"
+#      Prepend the prefix "\fB5.1.6 User has moved to \fR" to all
+#      relocated_maps lookup results.
+# .PP
 #      Other parameters of interest:
 # .IP "\fBinet_interfaces (all)\fR"
 #      The local network interface addresses that this mail system
index 4e3f43dca29dfdecfac61b125faef735c6e2a233..7721d87d7dde47509f85c1fc7c6b87f915db4b51 100644 (file)
@@ -43,3 +43,6 @@ virtual  virtual alias domain anything right hand content does not matter
 Inbound SMTP smuggling strip extra CR in CR LF CR CR LF 
 Inbound SMTP smuggling don t strip extra CR in CR LF CR CR LF 
  CR LF CR CR LF to silence false alarms from test tools
+ Prepend the prefix 5 1 6 User has moved to to all
+ pattern number number number text 
+to  to the lookup result With Postfix 3 11 and later specify
index 911ed5145d1e7355f1491b8ef4154bb39b9e5102..bed72b65e968902d6064b0397b054993237382f8 100644 (file)
@@ -364,3 +364,4 @@ Postfix  Postfix legacy TLS Support
  with cipher ECDHE RSA AES256 GCM SHA384 256 256 bits 
  TLSv1 2 with cipher ECDHE RSA AES256 GCM SHA384 256 256 bits 
  The recommended socket location is still to be determined A good socket location would be under the Postfix queue directory for example smtp_tlsrpt_socket_name run tlsrpt tlsrpt sock The advantage of using a relative name is that it
+enhanced status code and text format 45 number number text 
index 40aa5c2ba88b083115ede52426f9215293d7fb14..15cc2469ed856397c835652b55703ee63a020216 100644 (file)
@@ -763,6 +763,10 @@ extern int var_tls_prng_upd_period;
 #define DEF_RELOCATED_MAPS             ""
 extern char *var_relocated_maps;
 
+#define VAR_ENB_RELOCATED_PFX          "relocated_prefix_enable"
+#define DEF_ENB_RELOCATED_PFX          "yes"
+extern bool var_enb_relocated_pfx;
+
  /*
   * Queue manager: after each failed attempt the backoff time (how long we
   * won't try this host in seconds) is doubled until it reaches the maximum.
index 102c9cd24f05109a41d74c1f2c49f37766921d8a..0c6018de1fa23b6606722e396e659260e6f868c0 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      "20250606"
+#define MAIL_RELEASE_DATE      "20250621"
 #define MAIL_VERSION_NUMBER    "3.11"
 
 #ifdef SNAPSHOT
index d2370029d08b86f7ff7f59e27b965cb477c66d0a..592ec91d93a1e05c01ee8742f7a34886c3775acd 100644 (file)
@@ -154,9 +154,8 @@ MAPS   *maps_create(const char *title, const char *map_names, int dict_flags)
 #define OPEN_FLAGS     O_RDONLY
 
        while ((map_type_name = mystrtokq(&bufp, sep, parens)) != 0) {
-           vstring_sprintf(map_type_name_flags, "%s(%o,%s)",
-                           map_type_name, OPEN_FLAGS,
-                           dict_flags_str(dict_flags));
+           dict_make_registered_name(map_type_name_flags, map_type_name,
+                                     OPEN_FLAGS, dict_flags);
            if ((dict = dict_handle(vstring_str(map_type_name_flags))) == 0)
                dict = dict_open(map_type_name, OPEN_FLAGS, dict_flags);
            if ((dict->flags & dict_flags) != dict_flags)
index 9e5e3b96619f90e61768944e8fc1a77f1b449976..163900bb7e4a8c2e4a39d280a2aa48fd137198a9 100644 (file)
@@ -56,7 +56,7 @@ tests: test1 test2 test3 test4 test5 test6 test7 test8 test9 test10 test11 \
        test42 test43 test44 test45 test46 test47 test48 test49 test50 test51 \
        test52 test53 test54 test55 test56 test57 test58 test59 test60 test61 \
        test62 test63 test64 test65 test66 test67 test68 test69 test70 test71 \
-       test72 test73 test74 test75 test76
+       test72 test73 test74 test75 test76 test78
 
 root_tests:
 
@@ -1065,6 +1065,26 @@ test76:  $(PROG) test76.ref
        diff /dev/null test76.tmp
        rm -f main.cf master.cf test76.tmp
 
+# Warn about unused, deprecated, or deleted parameters.
+test78:        $(PROG) test78.ref
+       rm -f main.cf master.cf
+       touch main.cf master.cf
+       $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c. \
+           config_directory=. \
+           smtp_tls_enforce_peername=yes \
+           lmtp_tls_enforce_peername=yes \
+           >test78.tmp 2>&1
+       touch -t 197601010000 main.cf
+       echo foo unix - n n - 0 other >> master.cf
+       echo ' -o lmtp_tls_enforce_peername=no' >> master.cf
+       echo ' -o smtp_tls_enforce_peername=no' >> master.cf
+       touch -t 197601010000 master.cf
+       $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc. >>test78.tmp 2>&1
+       diff test78.ref test78.tmp
+       $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -qnc. >/dev/null 2>test78.tmp
+       diff /dev/null test78.tmp
+       rm -f main.cf master.cf test78.tmp
+
 clean:
        rm -f *.o *core $(PROG) $(TESTPROG) junk $(MAKES) $(AUTOS) $(DUMMIES) \
        $(TEST_TMP) $(DB_MAKES)
index 717d1a53a118c332c28c8b6991378f80f7378d01..0b8d1464670f41dcd9d28820b94178d62fc49130 100644 (file)
@@ -94,8 +94,15 @@ static const PCF_DEPR_PARAM_INFO pcf_depr_param_info[] = {
     "smtpd_tls_dh1024_param_file", "do not specify (leave at default)",
     "smtpd_tls_eecdh_grade", "do not specify (leave at default)",
     "deleted-test-only", "do not specify",     /* For testing */
+
+    /*
+     * Deprecated as of Postfix 3.11.
+     */
+    "lmtp_tls_enforce_peername", "specify \"lmtp_tls_security_level\"",
+    "smtp_tls_enforce_peername", "specify \"smtp_tls_security_level\"",
     0,
 };
+
 static HTABLE *pcf_depr_param_table;
 
 /* pcf_init_depr_params - initialize lookup table */
diff --git a/postfix/src/postconf/test78.ref b/postfix/src/postconf/test78.ref
new file mode 100644 (file)
index 0000000..59d171b
--- /dev/null
@@ -0,0 +1,7 @@
+config_directory = .
+lmtp_tls_enforce_peername = yes
+smtp_tls_enforce_peername = yes
+./postconf: warning: ./main.cf: support for parameter "smtp_tls_enforce_peername" will be removed; instead, specify "smtp_tls_security_level"
+./postconf: warning: ./main.cf: support for parameter "lmtp_tls_enforce_peername" will be removed; instead, specify "lmtp_tls_security_level"
+./postconf: warning: ./master.cf: support for parameter "smtp_tls_enforce_peername" will be removed; instead, specify "smtp_tls_security_level"
+./postconf: warning: ./master.cf: support for parameter "lmtp_tls_enforce_peername" will be removed; instead, specify "lmtp_tls_security_level"
index 8c5ee0a0f944f39abc14567bfb83997901850b1a..7214661a64ad12d6748be484b4936a16a219e8ca 100644 (file)
@@ -59,6 +59,9 @@ typedef struct SMTP_ITERATOR {
     VSTRING *host;                     /* hostname or empty */
     VSTRING *addr;                     /* printable address or empty */
     unsigned port;                     /* network byte order or null */
+#ifdef USE_TLS
+    int     tlsreqno;                  /* "TLS-Required: no" */
+#endif
     struct DNS_RR *rr;                 /* DNS resource record or null */
     struct DNS_RR *mx;                 /* DNS resource record or null */
     /* Private members. */
@@ -66,11 +69,18 @@ typedef struct SMTP_ITERATOR {
     struct SMTP_STATE *parent;         /* parent linkage */
 } SMTP_ITERATOR;
 
+#ifdef USE_TLS
+#define IF_USE_TLS(...) (__VA_ARGS__)
+#else
+#define IF_USE_TLS(...)
+#endif
+
 #define SMTP_ITER_INIT(iter, _dest, _host, _addr, _port, state) do { \
        vstring_strcpy((iter)->dest, (_dest)); \
        vstring_strcpy((iter)->host, (_host)); \
        vstring_strcpy((iter)->addr, (_addr)); \
        (iter)->port = (_port); \
+       IF_USE_TLS((iter)->tlsreqno = 0); \
        (iter)->mx = (iter)->rr = 0; \
        vstring_strcpy((iter)->saved_dest, ""); \
        (iter)->parent = (state); \
@@ -728,7 +738,7 @@ char   *smtp_key_prefix(VSTRING *, const char *, SMTP_ITERATOR *, int);
   */
 #define SMTP_KEY_MASK_SCACHE_DEST_LABEL \
        (SMTP_KEY_FLAG_SERVICE | COND_SASL_SMTP_KEY_FLAG_SENDER \
-       | SMTP_KEY_FLAG_REQ_NEXTHOP)
+       | SMTP_KEY_FLAG_REQ_NEXTHOP | SMTP_KEY_FLAG_TLS_LEVEL)
 
  /*
   * Connection-cache endpoint lookup key. The SENDER, CUR_NEXTHOP, HOSTNAME,
index 2bfff1c93d0c675b14b795b6cee0c5f4c49eede9..e4b60791a582d8d8d92a8e28aad6b162340578cf 100644 (file)
@@ -507,6 +507,24 @@ static int smtp_get_effective_tls_level(DSN_BUF *why, SMTP_STATE *state)
     SMTP_ITERATOR *iter = state->iterator;
     SMTP_TLS_POLICY *tls = state->tls;
 
+    /*
+     * If the message contains a "TLS-Required: no" header, update the
+     * iterator to limit the policy at TLS_LEV_MAY.
+     * 
+     * We must do this early to avoid possible failure if TLSA record lookups
+     * fail, or if TLSA records are found, but can't be activated because the
+     * security level has been reset to "may".
+     * 
+     * Note that the REQUIRETLS verb in ESMTP overrides the "TLS-Required: no"
+     * header.
+     */
+#ifdef USE_TLS
+    if (var_tls_required_enable
+       && (state->request->sendopts & SOPT_REQUIRETLS_HEADER)) {
+       iter->tlsreqno = 1;
+    }
+#endif
+
     /*
      * Determine the TLS level for this destination.
      */
@@ -529,16 +547,6 @@ static int smtp_get_effective_tls_level(DSN_BUF *why, SMTP_STATE *state)
     }
 #endif
 
-    /*
-     * Otherwise, if the TLS level is not TLS_LEV_NONE or some non-level, and
-     * the message contains a "TLS-Required: no" header, limit the level to
-     * TLS_LEV_MAY.
-     */
-    else if (var_tls_required_enable && tls->level > TLS_LEV_NONE
-            && (state->request->sendopts & SOPT_REQUIRETLS_HEADER)) {
-       tls->level = TLS_LEV_MAY;
-    }
-
     /*
      * Success.
      */
index df2f7439168744bef388d88801bf52493c3a8cec..b8968637819ed93ea397d7a8e5a3dd7985b75a9e 100644 (file)
@@ -926,13 +926,16 @@ static int smtp_start_tls(SMTP_STATE *state)
      * XXX: The TLS library will salt the serverid with further details of the
      * protocol and cipher requirements including the server ehlo response.
      * Deferring the helo to the digested suffix results in more predictable
-     * SSL session lookup key lengths.
+     * SSL session lookup key lengths. Add the current TLS security level to
+     * account for TLS level overrides based on message content or envelope
+     * metadata.
      */
     serverid = vstring_alloc(10);
     smtp_key_prefix(serverid, "&", state->iterator, SMTP_KEY_FLAG_SERVICE
                    | SMTP_KEY_FLAG_CUR_NEXTHOP /* With port */
                    | SMTP_KEY_FLAG_HOSTNAME
-                   | SMTP_KEY_FLAG_ADDR);
+                   | SMTP_KEY_FLAG_ADDR
+                   | SMTP_KEY_FLAG_TLS_LEVEL);
 
     if (state->tls->conn_reuse) {
        TLS_CLIENT_PARAMS tls_params;
index 6122c2d7823a28b8e1faadbf563c5d16545a8b93..6b9ee66e6fdbf51910e345eb2b5c301e302592ba 100644 (file)
@@ -651,7 +651,12 @@ static void *policy_create(const char *unused_key, void *context)
     tls->level = global_tls_level();
     site_level = TLS_LEV_NOTFOUND;
 
-    if (tls_policy) {
+    if (iter->tlsreqno) {
+       if (msg_verbose)
+           msg_info("%s: no tls policy lookup", __func__);
+       if (tls->level > TLS_LEV_MAY)
+           tls->level = TLS_LEV_MAY;
+    } else if (tls_policy) {
        tls_policy_lookup(tls, &site_level, dest, "next-hop destination");
     } else if (tls_per_site) {
        tls_site_lookup(tls, &site_level, dest, "next-hop destination");
index ad624de78a6d91280564818c644557006db705a3..16375e2d065391cb1e06d48f42e8c96fd9c69e9d 100644 (file)
@@ -1,22 +1,63 @@
+SHELL  = /bin/sh
+SRCS   = nosleep.c dict_test_helper.c
+LIB_OBJ        = dict_test_helper.o
+MOCK_OBJ=
+TEST_OBJ=
+HDRS   = dict_test_helper.h
+TESTSRC        =
+DEFS   = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+INCL   =
+LIB    = libtesting.a
 LIB_SO = nosleep.so
+TESTPROG=
+
+LIBS   = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
+       ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
 LIB_DIR        = ../../lib
 INC_DIR        = ../../include
+MAKES  =
+
+.c.o:;  $(CC) $(SHLIB_CFLAGS) $(CFLAGS) -c $*.c
+
+all: $(LIB_SO) $(LIB) $(MOCK_OBJ)
 
-all: $(LIB_SO)
+$(LIB_OBJ) $(MOCK_OBJ) $(TEST_OBJ): ../../conf/makedefs.out
 
 Makefile: Makefile.in
        cat ../../conf/makedefs.out $? >$@
 
+test:  $(TESTPROG)
+
+$(LIB):        $(LIB_OBJ)
+       $(_AR) $(ARFL) $(LIB) $?
+       $(_RANLIB) $(LIB)
+
+$(LIB_DIR)/$(LIB): $(LIB)
+       cp $(LIB) $(LIB_DIR)
+       $(_RANLIB) $(LIB_DIR)/$(LIB)
+
 nosleep.so: nosleep.c
        $(CC) $(CFLAGS) -fPIC -shared -o $@ $?
 
-update: lib_so_update
+update: lib_so_update lib_update
 
 lib_so_update: $(LIB_SO)
        for i in $(LIB_SO); do \
            cmp -s $$i $(LIB_DIR)/$$i 2>/dev/null || cp $$i $(LIB_DIR); \
        done
 
+lib_update: $(LIB_DIR)/$(LIB) $(HDRS) $(MOCK_OBJ)
+       -for i in $(HDRS); \
+       do \
+         cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \
+       done
+       (cd $(INC_DIR); chmod 644 $(HDRS))
+       -for i in $(MOCK_OBJ); \
+       do \
+         cmp -s $$i $(LIB_DIR)/$$i 2>/dev/null || cp $$i $(LIB_DIR); \
+       done
+
 clean:
        rm -f $(LIB_SO) *.o
 
@@ -32,4 +73,16 @@ depend: $(MAKES)
        @$(EXPORT) make -f Makefile.in Makefile 1>&2
 
 # do not edit below this line - it is generated by 'make depend'
+dict_test_helper.o: ../../include/argv.h
+dict_test_helper.o: ../../include/check_arg.h
+dict_test_helper.o: ../../include/dict.h
+dict_test_helper.o: ../../include/msg.h
+dict_test_helper.o: ../../include/myflock.h
+dict_test_helper.o: ../../include/stringops.h
+dict_test_helper.o: ../../include/sys_defs.h
+dict_test_helper.o: ../../include/vbuf.h
+dict_test_helper.o: ../../include/vstream.h
+dict_test_helper.o: ../../include/vstring.h
+dict_test_helper.o: dict_test_helper.c
+dict_test_helper.o: dict_test_helper.h
 nosleep.o: nosleep.c
diff --git a/postfix/src/testing/dict_test_helper.c b/postfix/src/testing/dict_test_helper.c
new file mode 100644 (file)
index 0000000..22474a2
--- /dev/null
@@ -0,0 +1,266 @@
+/*++
+/* NAME
+/*     dict_test_helper 3
+/* SUMMARY
+/*     dictionary test helpers
+/* SYNOPSIS
+/*     #include <dict_test_helper.h>
+/*
+/*     DICT    *dict_open_and_capture_msg(
+/*     const char *type_name,
+/*     int     open_flags,
+/*     int     dict_flags,
+/*     VSTRING *out_msg_buf)
+/*
+/*     char    *dict_compose_spec(
+/*     const char *dict_type,
+/*     const char **component_specs,
+/*     int     open_flags,
+/*     int     dict_flags,
+/*     VSTRING *out_composite_spec,
+/*     ARGV    *out_reg_component_specs)
+/*
+/*     chat char *dict_get_and_capture_msg(
+/*     DICT    *dict,
+/*     const char *key,
+/*     VSTRING *out_msg_buf)
+/*
+/*     struct dict_get_verify_data {
+/* .in +4
+/*     const char *key;
+/*     const char *want_value;
+/*     int     want_error;
+/*     const char *want_msg;
+/* .in -4
+/*     }
+/*
+/*     int dict_get_and_verify(
+/*     DICT    *dict,
+/*     const char *key,
+/*     const char *want_value,
+/*     int     want_error,
+/*     const char *want_msg)
+/*
+/*     int     dict_get_and_verify_bulk(
+/*     DICT    *dict,
+/*     const struct dict_get_verify_data *data)
+/* DESCRIPTION
+/*     This module contains common code for dictionary tests.
+/*
+/*     All functions that capture msg(3) output clear the output
+/*     VSTRING buffer first.
+/*
+/*     dict_open_and_capture_msg() invokes dict_open() while
+/*     capturing msg(3) output to a VSTRING buffer.
+/*
+/*     dict_compose_spec() constructs a composite dictionary spec with
+/*     the form "\fIdict_type:{component_specs[0],...}\fR". It records in
+/*     out_reg_component_specs the names under which the component_specs
+/*     dictionaries will be registered with dict_register(), with each
+/*     name having the form "\fItype:name(open_flags,dict_flags). The
+/*     result value is the out_composite_spec string value.
+/*
+/*     dict_get_and_capture_msg() invokes dict_get() while capturing
+/*     msg(3) output to a VSTRING buffer.
+/*
+/*     dict_get_and_verify() invokes dict_get() and verifies that
+/*     expectations are met. The want_value argument requires an exact
+/*     match; specify null if the expected lookup result is "not found".
+/*     The want_error argument requires an exact match; specify zero
+/*     (DICT_ERR_NONE) or one of the other expected DICT_ERR_*
+/*     values. The want_msg argument requires a substring match;
+/*     specify null if no msg(3) output is expected. The result value
+/*     is PASS or FAIL.
+/*
+/*     dict_get_and_verify_bulk() provides a convenient interface for
+/*     multiple lookup tests.
+/* LICENSE
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     porcupine.org
+/*--*/
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <string.h>
+
+ /*
+  * Utility library.
+  */
+#include <argv.h>
+#include <dict.h>
+#include <dict_test_helper.h>
+#include <msg.h>
+#include <stringops.h>
+#include <vstream.h>
+#include <vstring.h>
+
+#define LEN(x) VSTRING_LEN(x)
+#define STR(x) vstring_str(x)
+
+ /*
+  * TODO(wietse) factor this out to common testing header file.
+  */
+#define PASS   1
+#define FAIL   0
+
+ /*
+  * TODO(wietse) make this a proper VSTREAM interface or test helper API.
+  */
+
+/* vstream_swap - capture msg(3) output output for testing */
+
+static void vstream_swap(VSTREAM *one, VSTREAM *two)
+{
+    VSTREAM save;
+
+    save = *one;
+    *one = *two;
+    *two = save;
+}
+
+/* dict_open_and_capture_msg - open dictionary and capture msg(3) output */
+
+DICT   *dict_open_and_capture_msg(const char *type_name, int open_flags,
+                                      int dict_flags, VSTRING *out_msg_buf)
+{
+    VSTREAM *memory_stream;
+    DICT   *dict;
+
+    VSTRING_RESET(out_msg_buf);
+    VSTRING_TERMINATE(out_msg_buf);
+    if ((memory_stream = vstream_memopen(out_msg_buf, O_WRONLY)) == 0)
+       msg_fatal("open memory stream: %m");
+    vstream_swap(VSTREAM_ERR, memory_stream);
+    dict = dict_open(type_name, open_flags, dict_flags);
+    vstream_swap(memory_stream, VSTREAM_ERR);
+    (void) vstream_fclose(memory_stream);
+    return (dict);
+}
+
+/* dict_compose_spec - compose aggregate spec and component registered specs */
+
+char   *dict_compose_spec(const char *dict_type,
+                                 const char **component_specs,
+                                 int open_flags, int dict_flags,
+                                 VSTRING *out_composite_spec,
+                                 ARGV *out_reg_component_specs)
+{
+    VSTRING *reg_spec = vstring_alloc(100);
+    const char **cpp;
+
+    /*
+     * A dictionary spec is formatted as "type:name", and a dictionary is
+     * registered with dict_register() as "type:name(open_flags,dict_flags)".
+     * The latter form is used to share dictionary instances that have the
+     * exact same same properties.
+     * 
+     * Build the composite dictionary spec from the dict_type and component
+     * dictionary specs, and build the list of component specs decorated with
+     * open_flags and initial dict_flags such as locking.
+     * 
+     * Normally, these decorated specs are used for registering tables with
+     * dict_register() and for looking them up with dict_handle(). For
+     * testing, we need those names to determine whether a component
+     * dictionary is registered.
+     * 
+     * The dict_flags in a registered component spec may differ from actual
+     * dictionary flags: when a dictionary is opened, it may add dict_flags
+     * that describe its own properties such as whether the table's left-hand
+     * side is a fixed string or a pattern.
+     */
+    argv_truncate(out_reg_component_specs, 0);
+    vstring_strcpy(out_composite_spec, dict_type);
+    vstring_strcat(out_composite_spec, ":{");
+    for (cpp = component_specs; *cpp; cpp++) {
+       vstring_strcat(out_composite_spec, *cpp);
+       if (cpp[1])
+           vstring_strcat(out_composite_spec, ",");
+       dict_make_registered_name(reg_spec, *cpp, open_flags, dict_flags);
+       argv_add(out_reg_component_specs, STR(reg_spec), (char *) 0);
+    }
+    vstring_strcat(out_composite_spec, "}");
+    vstring_free(reg_spec);
+    return (STR(out_composite_spec));
+}
+
+/* dict_get_and_capture_msg - deploy dict_get() and capture msg(3) output */
+
+const char *dict_get_and_capture_msg(DICT *dict, const char *key,
+                                            VSTRING *out_msg_buf)
+{
+    VSTREAM *memory_stream;
+    const char *value;
+
+    VSTRING_RESET(out_msg_buf);
+    VSTRING_TERMINATE(out_msg_buf);
+    if ((memory_stream = vstream_memopen(out_msg_buf, O_WRONLY)) == 0)
+       msg_fatal("open memory stream: %m");
+    vstream_swap(VSTREAM_ERR, memory_stream);
+    value = dict_get(dict, key);
+    vstream_swap(memory_stream, VSTREAM_ERR);
+    (void) vstream_fclose(memory_stream);
+    return (value);
+}
+
+/* dict_get_and_verify - deploy dict_get() and verify results */
+
+int     dict_get_and_verify(DICT *dict, const char *key, const char *want_value,
+                                   int want_error, const char *want_msg)
+{
+    VSTRING *msg_buf = vstring_alloc(100);
+    int     ret = PASS;
+    const char *got;
+
+    got = dict_get_and_capture_msg(dict, key, msg_buf);
+    if (LEN(msg_buf) > 0 && want_msg == 0) {
+       msg_warn("unexpected error message: '%s'", STR(msg_buf));
+       ret = FAIL;
+    } else if (want_msg != 0 && strstr(STR(msg_buf), want_msg) == 0) {
+       msg_warn("unexpected error message: got '%s', want '%s'",
+                STR(msg_buf), want_msg);
+       ret = FAIL;
+    } else if (dict->error != want_error) {
+       msg_warn("unexpected lookup error for '%s': got '%d', want '%d",
+                key, dict->error, want_error);
+       ret = FAIL;
+    } else if (got == 0) {
+       if (want_value != 0) {
+           msg_warn("unexpected lookup result for '%s': got '%s', want '%s'",
+                    key, "NOTFOUND", want_value);
+           ret = FAIL;
+       }
+    } else {
+       if (want_value == 0) {
+           msg_warn("unexpected lookup result for '%s': got '%s', want '%s'",
+                    key, got, "NOTFOUND");
+           ret = FAIL;
+       } else if (strcmp(got, want_value) != 0) {
+           msg_warn("unexpected lookup result for '%s': got '%s', want '%s'",
+                    key, got, want_value);
+           ret = FAIL;
+       }
+    }
+    vstring_free(msg_buf);
+    return (ret);
+}
+
+/* dict_get_and_verify_bulk - dict_get_and_verify() wrapper for bulk usage */
+
+int     dict_get_and_verify_bulk(DICT *dict,
+                                  const struct dict_get_verify_data * data)
+{
+    int     ret = PASS;
+    const struct dict_get_verify_data *dp;
+
+    for (dp = data; dp->key; dp++) {
+       if (dict_get_and_verify(dict, dp->key, dp->want_value,
+                               dp->want_error, dp->want_msg) == FAIL)
+           ret = FAIL;
+    }
+    return (ret);
+}
diff --git a/postfix/src/testing/dict_test_helper.h b/postfix/src/testing/dict_test_helper.h
new file mode 100644 (file)
index 0000000..044dec0
--- /dev/null
@@ -0,0 +1,45 @@
+#ifndef _DICT_TEST_HELPER_H_INCLUDED_
+#define _DICT_TEST_HELPER_H_INCLUDED_
+
+/*++
+/* NAME
+/*     dict_test_helper 3h
+/* SUMMARY
+/*     dictionary test helpers
+/* SYNOPSIS
+/*     #include <dict_test_helper.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+  * Utility library.
+  */
+#include <argv.h>
+#include <dict.h>
+#include <vstring.h>
+
+ /*
+  * External interface.
+  */
+extern DICT *dict_open_and_capture_msg(const char *, int, int, VSTRING *);
+extern char *dict_compose_spec(const char *, const char **, int, int, VSTRING *, ARGV *);
+extern const char *dict_get_and_capture_msg(DICT *, const char *, VSTRING *);
+
+struct dict_get_verify_data {
+    const char *key;
+    const char *want_value;
+    int     want_error;
+    const char *want_msg;
+};
+
+extern int dict_get_and_verify(DICT *, const char *, const char *, int, const char *);
+extern int dict_get_and_verify_bulk(DICT *, const struct dict_get_verify_data *);
+
+/* LICENSE
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     porcupine.org
+/*--*/
+
+#endif
index ffe9ee71b79a0d9837350cb4ffad1332157a2bac..72ed8363648715e1d9605e90ab1cdedfe746e917 100644 (file)
@@ -68,6 +68,7 @@ resolve.o: ../../include/attr.h
 resolve.o: ../../include/check_arg.h
 resolve.o: ../../include/dict.h
 resolve.o: ../../include/domain_list.h
+resolve.o: ../../include/dsn_util.h
 resolve.o: ../../include/htable.h
 resolve.o: ../../include/iostuff.h
 resolve.o: ../../include/mail_addr_find.h
index df761e76ceac85b18c5a75983695ddf8803f3f08..5ca1f791ae3eb387699d477e4e0a094f4c3cff72 100644 (file)
@@ -87,6 +87,7 @@
 #include <maps.h>
 #include <mail_addr_find.h>
 #include <valid_mailhost_addr.h>
+#include <dsn_util.h>
 
 /* Application-specific. */
 
@@ -715,13 +716,23 @@ static void resolve_addr(RES_CONTEXT *rp, char *sender, char *addr,
 #define IGNORE_ADDR_EXTENSION   ((char **) 0)
 
     if (relocated_maps != 0) {
-       const char *newloc;
+       const char *reply;
+       DSN_SPLIT dp;
 
-       if ((newloc = mail_addr_find(relocated_maps, STR(nextrcpt),
-                                    IGNORE_ADDR_EXTENSION)) != 0) {
+       if ((reply = mail_addr_find(relocated_maps, STR(nextrcpt),
+                                   IGNORE_ADDR_EXTENSION)) != 0) {
            vstring_strcpy(channel, MAIL_SERVICE_ERROR);
-           /* 5.1.6 is the closest match, but not perfect. */
-           vstring_sprintf(nexthop, "5.1.6 User has moved to %s", newloc);
+           if (var_enb_relocated_pfx) {
+               /* 5.1.6 is the closest match, but not perfect. */
+               vstring_sprintf(nexthop, "5.1.6 User has moved to %s", reply);
+           } else if (!dsn_valid(reply)
+                      || dsn_split(&dp, "5.2.0", reply)->text[0] == 0) {
+               msg_warn("%s result must contain RFC 3463 status and text: '%.100s'",
+                        VAR_RELOCATED_MAPS, reply);
+               vstring_sprintf(nexthop, "5.2.0 Mailbox is unavailable");
+           } else {
+               vstring_sprintf(nexthop, "%s %s", DSN_STATUS(dp.dsn), dp.text);
+           }
        } else if (relocated_maps->error != 0) {
            msg_warn("%s lookup failure", VAR_RELOCATED_MAPS);
            *flags |= RESOLVE_FLAG_FAIL;
index bb8da091d7f9756e6873207fa6cce5bbc7e12c5a..8c896749c71f0e7c5596c141326baa7b365c23ef 100644 (file)
@@ -352,6 +352,7 @@ char   *var_virt_mailbox_maps;              /* XXX virtual_mailbox_domains */
 char   *var_virt_alias_doms;
 char   *var_virt_mailbox_doms;
 char   *var_relocated_maps;
+bool    var_enb_relocated_pfx;
 char   *var_def_transport;
 char   *var_snd_def_xport_maps;
 char   *var_empty_addr;
@@ -648,6 +649,7 @@ int     main(int argc, char **argv)
     };
     static const CONFIG_NBOOL_TABLE nbool_table[] = {
        VAR_APP_DOT_MYDOMAIN, DEF_APP_DOT_MYDOMAIN, &var_append_dot_mydomain,
+       VAR_ENB_RELOCATED_PFX, DEF_ENB_RELOCATED_PFX, &var_enb_relocated_pfx,
        0,
     };
 
index 7c99ae23a67ed084d79b174abafbe73005dde1f4..c6573320f6e4b3ade93079243f6ebf65529101b2 100644 (file)
@@ -153,12 +153,14 @@ TESTPROG= dict_open dup2_pass_on_exec events exec_command fifo_open \
        known_tcp_ports dict_stream find_inet binhash hash_fnv argv \
        clean_env inet_prefix_top printable readlline quote_for_json \
        normalize_ws valid_uri_scheme clean_ascii_cntrl_space \
-       normalize_v4mapped_addr_test ossl_digest_test
+       normalize_v4mapped_addr_test ossl_digest_test dict_pipe_test \
+       dict_union_test
 PLUGIN_MAP_SO = $(LIB_PREFIX)pcre$(LIB_SUFFIX) $(LIB_PREFIX)lmdb$(LIB_SUFFIX) \
        $(LIB_PREFIX)cdb$(LIB_SUFFIX) $(LIB_PREFIX)sdbm$(LIB_SUFFIX)
 HTABLE_FIX = NORANDOMIZE=1
 LIB_DIR        = ../../lib
 INC_DIR        = ../../include
+TESTLIB        = $(LIB_DIR)/libtesting.a
 
 .c.o:; $(CC) $(SHLIB_CFLAGS) $(CFLAGS) -c $*.c
 
@@ -638,6 +640,12 @@ normalize_v4mapped_addr_test: normalize_v4mapped_addr_test.c $(LIB)
 ossl_digest_test: ossl_digest_test.c $(LIB)
        $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(SYSLIBS)
 
+dict_pipe_test: dict_pipe_test.c $(TESTLIB) $(LIB)
+       $(CC) $(CFLAGS) -o $@ $@.c $(TESTLIB) $(LIB) $(SYSLIBS)
+
+dict_union_test: dict_union_test.c $(TESTLIB) $(LIB)
+       $(CC) $(CFLAGS) -o $@ $@.c $(TESTLIB) $(LIB) $(SYSLIBS)
+
 tests: all valid_hostname_test mac_expand_test dict_test unescape_test \
        hex_quote_test ctable_test inet_addr_list_test base64_code_test \
        attr_scan64_test attr_scan0_test host_port_test dict_tests \
@@ -650,12 +658,13 @@ tests: all valid_hostname_test mac_expand_test dict_test unescape_test \
        binhash_test argv_test inet_prefix_top_test printable_test \
        valid_utf8_string_test readlline_test quote_for_json_test \
        normalize_ws_test valid_uri_scheme_test clean_ascii_cntrl_space_test \
-       test_normalize_v4mapped_addr test_ossl_digest
+       test_normalize_v4mapped_addr test_ossl_digest test_dict_pipe
+       test_dict_union
  
 dict_tests: all dict_test \
        dict_pcre_tests dict_cidr_test dict_thash_test dict_static_test \
-       dict_inline_test dict_utf8_test dict_regexp_test dict_union_test \
-       dict_pipe_test dict_regexp_file_test dict_cidr_file_test \
+       dict_inline_test dict_utf8_test dict_regexp_test \
+       dict_regexp_file_test dict_cidr_file_test \
        dict_static_file_test dict_random_test dict_random_file_test \
        dict_inline_file_test dict_stream_test dict_inline_regexp_test \
        dict_inline_cidr_test
@@ -1056,16 +1065,6 @@ vbuf_print_test: vbuf_print vbuf_print_test.in vbuf_print_test.ref
        diff vbuf_print_test.ref vbuf_print_test.tmp
        rm -f vbuf_print_test.tmp
 
-dict_union_test: dict_open dict_union_test.in dict_union_test.ref
-       $(SHLIB_ENV) ${VALGRIND} sh -x dict_union_test.in >dict_union_test.tmp 2>&1
-       diff dict_union_test.ref dict_union_test.tmp
-       rm -f dict_union_test.tmp
-
-dict_pipe_test: dict_open dict_pipe_test.in dict_pipe_test.ref
-       $(SHLIB_ENV) ${VALGRIND} sh -x dict_pipe_test.in >dict_pipe_test.tmp 2>&1
-       diff dict_pipe_test.ref dict_pipe_test.tmp
-       rm -f dict_pipe_test.tmp
-
 vstring_test: dict_open vstring vstring_test.ref
        $(SHLIB_ENV) ${VALGRIND} ./vstring one two three >vstring_test.tmp 2>&1
        diff vstring_test.ref vstring_test.tmp
@@ -1142,6 +1141,12 @@ test_normalize_v4mapped_addr: update normalize_v4mapped_addr_test
 test_ossl_digest: update ossl_digest_test
        $(SHLIB_ENV) ${VALGRIND} ./ossl_digest_test
 
+test_dict_pipe: update dict_pipe_test
+       $(SHLIB_ENV) ${VALGRIND} ./dict_pipe_test
+
+test_dict_union: update dict_union_test
+       $(SHLIB_ENV) ${VALGRIND} ./dict_union_test
+
 depend: $(MAKES)
        (sed '1,/^# do not edit/!d' Makefile.in; \
        set -e; for i in [a-z][a-z0-9]*.c; do \
@@ -1735,6 +1740,20 @@ dict_pipe.o: sys_defs.h
 dict_pipe.o: vbuf.h
 dict_pipe.o: vstream.h
 dict_pipe.o: vstring.h
+dict_pipe_test.o: ../../include/dict_test_helper.h
+dict_pipe_test.o: argv.h
+dict_pipe_test.o: check_arg.h
+dict_pipe_test.o: dict.h
+dict_pipe_test.o: dict_pipe.h
+dict_pipe_test.o: dict_pipe_test.c
+dict_pipe_test.o: msg.h
+dict_pipe_test.o: msg_vstream.h
+dict_pipe_test.o: myflock.h
+dict_pipe_test.o: stringops.h
+dict_pipe_test.o: sys_defs.h
+dict_pipe_test.o: vbuf.h
+dict_pipe_test.o: vstream.h
+dict_pipe_test.o: vstring.h
 dict_random.o: argv.h
 dict_random.o: check_arg.h
 dict_random.o: dict.h
@@ -1902,6 +1921,20 @@ dict_union.o: sys_defs.h
 dict_union.o: vbuf.h
 dict_union.o: vstream.h
 dict_union.o: vstring.h
+dict_union_test.o: ../../include/dict_test_helper.h
+dict_union_test.o: argv.h
+dict_union_test.o: check_arg.h
+dict_union_test.o: dict.h
+dict_union_test.o: dict_union.h
+dict_union_test.o: dict_union_test.c
+dict_union_test.o: msg.h
+dict_union_test.o: msg_vstream.h
+dict_union_test.o: myflock.h
+dict_union_test.o: stringops.h
+dict_union_test.o: sys_defs.h
+dict_union_test.o: vbuf.h
+dict_union_test.o: vstream.h
+dict_union_test.o: vstring.h
 dict_unix.o: argv.h
 dict_unix.o: check_arg.h
 dict_unix.o: dict.h
@@ -2602,6 +2635,26 @@ open_lock.o: sys_defs.h
 open_lock.o: vbuf.h
 open_lock.o: vstream.h
 open_lock.o: vstring.h
+ossl_digest.o: argv.h
+ossl_digest.o: check_arg.h
+ossl_digest.o: msg.h
+ossl_digest.o: mymalloc.h
+ossl_digest.o: ossl_digest.c
+ossl_digest.o: ossl_digest.h
+ossl_digest.o: sys_defs.h
+ossl_digest.o: vbuf.h
+ossl_digest.o: vstring.h
+ossl_digest_test.o: argv.h
+ossl_digest_test.o: check_arg.h
+ossl_digest_test.o: hex_code.h
+ossl_digest_test.o: msg.h
+ossl_digest_test.o: mymalloc.h
+ossl_digest_test.o: ossl_digest.h
+ossl_digest_test.o: ossl_digest_test.c
+ossl_digest_test.o: stringops.h
+ossl_digest_test.o: sys_defs.h
+ossl_digest_test.o: vbuf.h
+ossl_digest_test.o: vstring.h
 pass_accept.o: attr.h
 pass_accept.o: check_arg.h
 pass_accept.o: htable.h
index 35c02db050195afa4bc8aa53d4bbef36aa998a67..1fe169d2dd050def24b4fa6c05431291597314d1 100644 (file)
 /*
 /*     int     dict_flags_mask(names)
 /*     const char *names;
+/*
+/*     char    *dict_make_registered_name(
+/*     VSTRING *out,
+/*     const char *type_name,
+/*     int     open_flags,
+/*     int     dict_flags)
 /* DESCRIPTION
 /*     This module maintains a collection of name-value dictionaries.
 /*     Each dictionary has its own name and has its own methods to read
 /*
 /*     dict_flags_mask() returns the bitmask for the specified
 /*     comma/space-separated dictionary flag names.
+/*
+/*     dict_make_registered_name() formats a dictionary type:name and
+/*     (initial) flag values for use in dict_register() calls.
+/*     This encourages consistent sharing of dictionary instances that
+/*     have the exact same type:name and (initial) flags. The result
+/*     value is the string value of the \fIout\fR VSTRING buffer.
 /* TRUST AND PROVENANCE
 /* .ad
 /* .fi
@@ -668,3 +680,13 @@ int     dict_flags_mask(const char *names)
 {
     return (name_mask("dictionary flags", dict_mask, names));
 }
+
+/* dict_make_registered_name - format registry name for consistent sharing */
+
+char   *dict_make_registered_name(VSTRING *out, const char *type_name,
+                                         int open_flags, int dict_flags)
+{
+    return (STR(vstring_sprintf(out, "%s(%o,%s)",
+                               type_name, open_flags,
+                               dict_flags_str(dict_flags))));
+}
index ef0e83343ed3aacc2d74f38a1b8698da89c10d52..a6badac26dc9e15692b079ce5e5233a5f9f89cea 100644 (file)
@@ -280,6 +280,13 @@ void    dict_test(int, char **);
 extern int dict_allow_surrogate;
 extern DICT *PRINTFLIKE(5, 6) dict_surrogate(const char *, const char *, int, int, const char *,...);
 
+ /*
+  * Support for consistent sharing and collision avoidance when tables are
+  * registered with dict_register(). Only share instances that have the same
+  * type, name, open_flags, and (initial) dict_flags.
+  */
+extern char *dict_make_registered_name(VSTRING *, const char *, int, int);
+
  /*
   * This name is reserved for matchlist error handling.
   */
index 8ce0faad728d5f973fc2718bbe2917b6670997a5..9002bd369e9391c08e61866684645b038a0a5f2f 100644 (file)
@@ -115,6 +115,8 @@ DICT   *dict_pipe_open(const char *name, int open_flags, int dict_flags)
     int     match_flags = 0;
     struct DICT_OWNER aggr_owner;
     size_t  len;
+    VSTRING *reg_name_buf = vstring_alloc(100);
+    char   *reg_name;
 
     /*
      * Clarity first. Let the optimizer worry about redundant code.
@@ -124,6 +126,8 @@ DICT   *dict_pipe_open(const char *name, int open_flags, int dict_flags)
                myfree(saved_name); \
            if (argv != 0) \
                argv_free(argv); \
+           if (reg_name_buf != 0) \
+               vstring_free(reg_name_buf); \
            return (x); \
        } while (0)
 
@@ -151,13 +155,10 @@ DICT   *dict_pipe_open(const char *name, int open_flags, int dict_flags)
                                        DICT_TYPE_PIPE));
 
     /*
-     * The least-trusted table in the pipeline determines the over-all trust
-     * level. The first table determines the pattern-matching flags.
+     * Check all underlying table specs before registering any of them to
+     * avoid leaking refcounts if one of them is bad.
      */
-    DICT_OWNER_AGGREGATE_INIT(aggr_owner);
     for (cpp = argv->argv; (dict_type_name = *cpp) != 0; cpp++) {
-       if (msg_verbose)
-           msg_info("%s: %s", myname, dict_type_name);
        if (strchr(dict_type_name, ':') == 0)
            DICT_PIPE_RETURN(dict_surrogate(DICT_TYPE_PIPE, name,
                                            open_flags, dict_flags,
@@ -165,9 +166,22 @@ DICT   *dict_pipe_open(const char *name, int open_flags, int dict_flags)
                                            "need \"%s:{type:name...}\"",
                                            DICT_TYPE_PIPE, name,
                                            DICT_TYPE_PIPE));
-       if ((dict = dict_handle(dict_type_name)) == 0)
+    }
+
+    /*
+     * The least-trusted table in the pipeline determines the over-all trust
+     * level. The first table determines the pattern-matching flags.
+     */
+    DICT_OWNER_AGGREGATE_INIT(aggr_owner);
+    for (cpp = argv->argv; (dict_type_name = *cpp) != 0; cpp++) {
+       if (msg_verbose)
+           msg_info("%s: %s", myname, dict_type_name);
+       reg_name = dict_make_registered_name(reg_name_buf, dict_type_name,
+                                            open_flags, dict_flags);
+       if ((dict = dict_handle(reg_name)) == 0)
            dict = dict_open(dict_type_name, open_flags, dict_flags);
-       dict_register(dict_type_name, dict);
+       dict_register(reg_name, dict);
+       argv_replace_one(argv, cpp - argv->argv, reg_name);
        DICT_OWNER_AGGREGATE_UPDATE(aggr_owner, dict->owner);
        if (cpp == argv->argv)
            match_flags = dict->flags & (DICT_FLAG_FIXED | DICT_FLAG_PATTERN);
@@ -182,7 +196,8 @@ DICT   *dict_pipe_open(const char *name, int open_flags, int dict_flags)
     dict_pipe->dict.close = dict_pipe_close;
     dict_pipe->dict.flags = dict_flags | match_flags;
     dict_pipe->dict.owner = aggr_owner;
-    dict_pipe->qr_buf = vstring_alloc(100);
+    dict_pipe->qr_buf = reg_name_buf;
+    reg_name_buf = 0;
     dict_pipe->map_pipe = argv;
     argv = 0;
     DICT_PIPE_RETURN(DICT_DEBUG (&dict_pipe->dict));
diff --git a/postfix/src/util/dict_pipe_test.c b/postfix/src/util/dict_pipe_test.c
new file mode 100644 (file)
index 0000000..79bf3fa
--- /dev/null
@@ -0,0 +1,226 @@
+/*++
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     porcupine.org
+/*--*/
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <string.h>
+
+ /*
+  * Utility library.
+  */
+#include <argv.h>
+#include <dict_pipe.h>
+#include <msg.h>
+#include <msg_vstream.h>
+#include <stringops.h>
+#include <vstring.h>
+
+ /*
+  * Testing library.
+  */
+#include <dict_test_helper.h>
+
+#define LEN(x) VSTRING_LEN(x)
+#define STR(x) vstring_str(x)
+
+ /*
+  * TODO(wietse) move these to common testing header file.
+  */
+#define PASS   1
+#define FAIL   0
+
+static VSTRING *msg_buf;
+
+static int valid_refcounts_for_good_composite_syntax(void)
+{
+    DICT   *dict;
+    int     open_flags = O_RDONLY;
+    int     dict_flags = DICT_FLAG_LOCK;
+    VSTRING *composite_spec = vstring_alloc(100);
+    ARGV   *reg_component_specs = argv_alloc(3);
+    const char *component_specs[] = {
+       "static:foo",
+       "inline:{xx=yy}",
+       "unix:passwd.byname",
+       0,
+    };
+    char  **cpp;
+
+    dict_compose_spec(DICT_TYPE_PIPE, component_specs, open_flags, dict_flags,
+                     composite_spec, reg_component_specs);
+    dict = dict_open_and_capture_msg(STR(composite_spec), open_flags,
+                                    dict_flags, msg_buf);
+
+#undef RETURN
+#define RETURN(x) do { \
+       if (dict) dict_close(dict); \
+       vstring_free(composite_spec); \
+       argv_free(reg_component_specs); \
+       return (x); \
+    } while (0);
+
+    if (LEN(msg_buf) > 0) {
+       msg_warn("unexpected dict_open() warning: got %s", STR(msg_buf));
+       RETURN(FAIL);
+    }
+    for (cpp = reg_component_specs->argv; *cpp; cpp++) {
+       if (dict_handle(*cpp) == 0) {
+           msg_warn("table '%s' is not registered after dict_open()", *cpp);
+           RETURN(FAIL);
+       }
+    }
+    dict_close(dict);
+    dict = 0;
+    for (cpp = reg_component_specs->argv; *cpp; cpp++) {
+       if (dict_handle(*cpp) != 0) {
+           msg_warn("table '%s' is still registered after dict_close()", *cpp);
+           RETURN(FAIL);
+       }
+    }
+    RETURN(PASS);
+}
+
+static int valid_refcounts_for_bad_composite_syntax(void)
+{
+    DICT   *dict;
+    int     open_flags = O_RDONLY;
+    int     dict_flags = DICT_FLAG_LOCK;
+    VSTRING *composite_spec = vstring_alloc(100);
+    ARGV   *reg_component_specs = argv_alloc(3);
+    const char *component_specs[] = {
+       "static:foo",
+       "inline{xx=yy}",
+       "unix:passwd.byname",
+       0,
+    };
+    char  **cpp;
+    const char *want_msg = "bad syntax:";
+
+    dict_compose_spec(DICT_TYPE_PIPE, component_specs, open_flags, dict_flags,
+                     composite_spec, reg_component_specs);
+    dict = dict_open_and_capture_msg(STR(composite_spec), open_flags, dict_flags,
+                                    msg_buf);
+
+#undef RETURN
+#define RETURN(x) do { \
+       dict_close(dict); \
+       vstring_free(composite_spec); \
+       argv_free(reg_component_specs); \
+       return (x); \
+    } while (0);
+
+    if (LEN(msg_buf) == 0) {
+       msg_warn("missing dict_open() warning: want '%s'", want_msg);
+       RETURN(FAIL);
+    }
+    if (strstr(STR(msg_buf), want_msg) == 0) {
+       msg_warn("unexpected warning message: got '%s', want '%s'",
+                STR(msg_buf), want_msg);
+       RETURN(FAIL);
+    }
+    for (cpp = reg_component_specs->argv; *cpp; cpp++) {
+       if (dict_handle(*cpp) != 0) {
+           msg_warn("table '%s' is registered after failed dict_open()",
+                    *cpp);
+           RETURN(FAIL);
+       }
+    }
+    RETURN(PASS);
+}
+
+static int propagates_notfound_and_found(void)
+{
+    DICT   *dict;
+    int     open_flags = O_RDONLY;
+    int     dict_flags = DICT_FLAG_LOCK;
+    const char *dict_spec = "pipemap:{inline:{k1=v1,k2=v2},inline:{v2=v3}}";
+    static struct dict_get_verify_data expectations[] = {
+       {.key = "k0",.want_value = 0},
+       {.key = "k1",.want_value = 0},
+       {.key = "k2",.want_value = "v3"},
+       {0},
+    };
+    int     ret;
+
+    dict = dict_open_and_capture_msg(dict_spec, open_flags, dict_flags,
+                                    msg_buf);
+    if (LEN(msg_buf) > 0) {
+       msg_warn("unexpected dict_open() warning: got '%s'", STR(msg_buf));
+       ret = FAIL;
+    } else {
+       ret = dict_get_and_verify_bulk(dict, expectations);
+    }
+    dict_close(dict);
+    return (ret);
+}
+
+static int propagates_notfound_and_error(void)
+{
+    DICT   *dict;
+    int     open_flags = O_RDONLY;
+    int     dict_flags = DICT_FLAG_LOCK;
+    const char *dict_spec = "pipemap:{inline:{k1=v1},fail:fail}";
+    static struct dict_get_verify_data expectations[] = {
+       {.key = "k0",.want_value = 0,.want_error = DICT_ERR_NONE},
+       {.key = "k1",.want_value = 0,.want_error = DICT_ERR_RETRY},
+       {0},
+    };
+    int     ret;
+
+    dict = dict_open_and_capture_msg(dict_spec, open_flags, dict_flags,
+                                    msg_buf);
+    if (LEN(msg_buf) > 0) {
+       msg_warn("unexpected dict_open() warning: got '%s'", STR(msg_buf));
+       ret = FAIL;
+    } else {
+       ret = dict_get_and_verify_bulk(dict, expectations);
+    }
+    dict_close(dict);
+    return (ret);
+}
+
+struct TEST_CASE {
+    const char *label;
+    int     (*action) (void);
+};
+
+static const struct TEST_CASE test_cases[] = {
+    {"valid_refcounts_for_good_composite_syntax", valid_refcounts_for_good_composite_syntax,},
+    {"valid_refcounts_for_bad_composite_syntax", valid_refcounts_for_bad_composite_syntax,},
+    {"propagates_notfound_and_found", propagates_notfound_and_found,},
+    {"propagates_notfound_and_error", propagates_notfound_and_error,},
+    {0},
+};
+
+int     main(int argc, char **argv)
+{
+    static int tests_passed = 0;
+    static int tests_failed = 0;
+    const struct TEST_CASE *tp;
+
+    msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR);
+
+    msg_buf = vstring_alloc(100);
+    dict_allow_surrogate = 1;
+
+    for (tp = test_cases; tp->label; tp++) {
+       msg_info("RUN  %s", tp->label);
+       if (tp->action() == PASS) {
+           msg_info("PASS %s", tp->label);
+           tests_passed += 1;
+       } else {
+           msg_info("FAIL %s", tp->label);
+           tests_failed += 1;
+       }
+    }
+    vstring_free(msg_buf);
+
+    msg_info("PASS=%d FAIL=%d", tests_passed, tests_failed);
+    exit(tests_failed != 0);
+}
diff --git a/postfix/src/util/dict_pipe_test.in b/postfix/src/util/dict_pipe_test.in
deleted file mode 100644 (file)
index 9626dcd..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-${VALGRIND} ./dict_open 'pipemap:{inline:{k1=v1,k2=v2},inline:{v2=v3}}' read <<EOF
-get k0
-get k1
-get k2
-EOF
-${VALGRIND} ./dict_open 'pipemap:{inline:{k1=v1},fail:fail}' read <<EOF
-get k0
-get k1
-EOF
diff --git a/postfix/src/util/dict_pipe_test.ref b/postfix/src/util/dict_pipe_test.ref
deleted file mode 100644 (file)
index ecb865a..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-+ ./dict_open pipemap:{inline:{k1=v1,k2=v2},inline:{v2=v3}} read
-owner=trusted (uid=2147483647)
-> get k0
-k0: not found
-> get k1
-k1: not found
-> get k2
-k2=v3
-+ ./dict_open pipemap:{inline:{k1=v1},fail:fail} read
-owner=trusted (uid=2147483647)
-> get k0
-k0: not found
-> get k1
-k1: error
index 80df03b61717d7ea943ef5768de361789d63e7c7..cda3fe1773166ea3147f973cd03b6972b43136ae 100644 (file)
@@ -128,15 +128,19 @@ DICT   *dict_union_open(const char *name, int open_flags, int dict_flags)
     int     match_flags = 0;
     struct DICT_OWNER aggr_owner;
     size_t  len;
+    VSTRING *reg_name_buf = vstring_alloc(100);
+    char   *reg_name;
 
     /*
      * Clarity first. Let the optimizer worry about redundant code.
      */
 #define DICT_UNION_RETURN(x) do { \
              if (saved_name != 0) \
-                 myfree(saved_name); \
+                 myfree(saved_name); \
              if (argv != 0) \
-                 argv_free(argv); \
+                 argv_free(argv); \
+             if (reg_name_buf != 0) \
+                 vstring_free(reg_name_buf); \
              return (x); \
          } while (0)
 
@@ -164,13 +168,10 @@ DICT   *dict_union_open(const char *name, int open_flags, int dict_flags)
                                         DICT_TYPE_UNION));
 
     /*
-     * The least-trusted table in the set determines the over-all trust
-     * level. The first table determines the pattern-matching flags.
+     * Check all underlying table specs before registering any of them to
+     * avoid leaking refcounts if one of them is bad.
      */
-    DICT_OWNER_AGGREGATE_INIT(aggr_owner);
     for (cpp = argv->argv; (dict_type_name = *cpp) != 0; cpp++) {
-       if (msg_verbose)
-           msg_info("%s: %s", myname, dict_type_name);
        if (strchr(dict_type_name, ':') == 0)
            DICT_UNION_RETURN(dict_surrogate(DICT_TYPE_UNION, name,
                                             open_flags, dict_flags,
@@ -178,9 +179,22 @@ DICT   *dict_union_open(const char *name, int open_flags, int dict_flags)
                                             "need \"%s:{type:name...}\"",
                                             DICT_TYPE_UNION, name,
                                             DICT_TYPE_UNION));
-       if ((dict = dict_handle(dict_type_name)) == 0)
+    }
+
+    /*
+     * The least-trusted table in the set determines the over-all trust
+     * level. The first table determines the pattern-matching flags.
+     */
+    DICT_OWNER_AGGREGATE_INIT(aggr_owner);
+    for (cpp = argv->argv; (dict_type_name = *cpp) != 0; cpp++) {
+       if (msg_verbose)
+           msg_info("%s: %s", myname, dict_type_name);
+       reg_name = dict_make_registered_name(reg_name_buf, dict_type_name,
+                                            open_flags, dict_flags);
+       if ((dict = dict_handle(reg_name)) == 0)
            dict = dict_open(dict_type_name, open_flags, dict_flags);
-       dict_register(dict_type_name, dict);
+       dict_register(reg_name, dict);
+       argv_replace_one(argv, cpp - argv->argv, reg_name);
        DICT_OWNER_AGGREGATE_UPDATE(aggr_owner, dict->owner);
        if (cpp == argv->argv)
            match_flags = dict->flags & (DICT_FLAG_FIXED | DICT_FLAG_PATTERN);
@@ -195,7 +209,8 @@ DICT   *dict_union_open(const char *name, int open_flags, int dict_flags)
     dict_union->dict.close = dict_union_close;
     dict_union->dict.flags = dict_flags | match_flags;
     dict_union->dict.owner = aggr_owner;
-    dict_union->re_buf = vstring_alloc(100);
+    dict_union->re_buf = reg_name_buf;
+    reg_name_buf = 0;
     dict_union->map_union = argv;
     argv = 0;
     DICT_UNION_RETURN(DICT_DEBUG (&dict_union->dict));
diff --git a/postfix/src/util/dict_union_test.c b/postfix/src/util/dict_union_test.c
new file mode 100644 (file)
index 0000000..0ed87b8
--- /dev/null
@@ -0,0 +1,253 @@
+/*++
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     porcupine.org
+/*--*/
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <string.h>
+
+ /*
+  * Utility library.
+  */
+#include <argv.h>
+#include <dict_union.h>
+#include <msg.h>
+#include <msg_vstream.h>
+#include <stringops.h>
+#include <vstring.h>
+
+ /*
+  * Testing library.
+  */
+#include <dict_test_helper.h>
+
+#define LEN(x) VSTRING_LEN(x)
+#define STR(x) vstring_str(x)
+
+ /*
+  * TODO(wietse) move these to common testing header file.
+  */
+#define PASS   1
+#define FAIL   0
+
+static VSTRING *msg_buf;
+
+static int valid_refcounts_for_good_composite_syntax(void)
+{
+    DICT   *dict;
+    int     open_flags = O_RDONLY;
+    int     dict_flags = DICT_FLAG_LOCK;
+    VSTRING *composite_spec = vstring_alloc(100);
+    ARGV   *reg_component_specs = argv_alloc(3);
+    const char *component_specs[] = {
+       "static:one",
+       "static:two",
+       "inline:{foo=three}",
+       0,
+    };
+    char  **cpp;
+
+    dict_compose_spec(DICT_TYPE_UNION, component_specs, open_flags, dict_flags,
+                     composite_spec, reg_component_specs);
+    dict = dict_open_and_capture_msg(STR(composite_spec), open_flags, dict_flags,
+                                    msg_buf);
+
+#undef RETURN
+#define RETURN(x) do { \
+       if (dict) dict_close(dict); \
+       vstring_free(composite_spec); \
+       argv_free(reg_component_specs); \
+       return (x); \
+    } while (0);
+
+    if (LEN(msg_buf) > 0) {
+       msg_warn("unexpected dict_open() warning: got '%s'", STR(msg_buf));
+       RETURN(FAIL);
+    }
+    for (cpp = reg_component_specs->argv; *cpp; cpp++) {
+       if (dict_handle(*cpp) == 0) {
+           msg_warn("table '%s' is not registered after dict_open()", *cpp);
+           RETURN(FAIL);
+       }
+    }
+    dict_close(dict);
+    dict = 0;
+    for (cpp = reg_component_specs->argv; *cpp; cpp++) {
+       if (dict_handle(*cpp) != 0) {
+           msg_warn("table '%s' is still registered after dict_close()", *cpp);
+           RETURN(FAIL);
+       }
+    }
+    RETURN(PASS);
+}
+
+static int valid_refcounts_for_bad_composite_syntax(void)
+{
+    DICT   *dict;
+    int     open_flags = O_RDONLY;
+    int     dict_flags = DICT_FLAG_LOCK;
+    VSTRING *composite_spec = vstring_alloc(100);
+    ARGV   *reg_component_specs = argv_alloc(3);
+    const char *component_specs[] = {
+       "static:one",
+       "static:two",
+       "inline{foo=three}",
+       0,
+    };
+    char  **cpp;
+    const char *want_msg = "bad syntax:";
+
+    dict_compose_spec(DICT_TYPE_UNION, component_specs, open_flags, dict_flags,
+                     composite_spec, reg_component_specs);
+    dict = dict_open_and_capture_msg(STR(composite_spec), open_flags,
+                                    dict_flags, msg_buf);
+
+#undef RETURN
+#define RETURN(x) do { \
+       dict_close(dict); \
+       vstring_free(composite_spec); \
+       argv_free(reg_component_specs); \
+       return (x); \
+    } while (0);
+
+    if (LEN(msg_buf) == 0) {
+       msg_warn("missing dict_open() warning: want '%s'", want_msg);
+       RETURN(FAIL);
+    }
+    if (strstr(STR(msg_buf), want_msg) == 0) {
+       msg_warn("unexpected warning message: got '%s', want '%s'",
+                STR(msg_buf), want_msg);
+       RETURN(FAIL);
+    }
+    for (cpp = reg_component_specs->argv; *cpp; cpp++) {
+       if (dict_handle(*cpp) != 0) {
+           msg_warn("table '%s' is registered after failed dict_open()",
+                    *cpp);
+           RETURN(FAIL);
+       }
+    }
+    RETURN(PASS);
+}
+
+static int propagates_notfound_and_found(void)
+{
+    DICT   *dict;
+    int     open_flags = O_RDONLY;
+    int     dict_flags = DICT_FLAG_LOCK;
+    const char *dict_spec = ("unionmap:{static:one,static:two,"
+                            "inline:{foo=three}}");
+    static struct dict_get_verify_data expectations[] = {
+       {.key = "foo",.want_value = "one,two,three"},
+       {.key = "bar",.want_value = "one,two"},
+       {0},
+    };
+    int     ret;
+
+    dict = dict_open_and_capture_msg(dict_spec, open_flags, dict_flags,
+                                    msg_buf);
+    if (LEN(msg_buf) > 0) {
+       msg_warn("unexpected dict_open() warning: got '%s'", STR(msg_buf));
+       ret = FAIL;
+    } else {
+       ret = dict_get_and_verify_bulk(dict, expectations);
+    }
+    dict_close(dict);
+    return (ret);
+}
+
+static int propagates_error(void)
+{
+    DICT   *dict;
+    int     open_flags = O_RDONLY;
+    int     dict_flags = DICT_FLAG_LOCK;
+    const char *dict_spec = "unionmap:{static:one,fail:fail}";
+    static struct dict_get_verify_data expectations[] = {
+       {.key = "foo",.want_value = 0,.want_error = DICT_ERR_RETRY},
+       {0},
+    };
+    int     ret;
+
+    dict = dict_open_and_capture_msg(dict_spec, open_flags, dict_flags,
+                                    msg_buf);
+    if (LEN(msg_buf) > 0) {
+       msg_warn("unexpected dict_open() warning: got '%s'", STR(msg_buf));
+       ret = FAIL;
+    } else {
+       ret = dict_get_and_verify_bulk(dict, expectations);
+    }
+    dict_close(dict);
+    return (ret);
+}
+
+static int no_comma_for_not_found(void)
+{
+    DICT   *dict;
+    int     open_flags = O_RDONLY;
+    int     dict_flags = DICT_FLAG_LOCK;
+    const char *dict_spec = "unionmap:{regexp:{{/a|c/ 1}},regexp:{{/b|c/ 2}}}";
+    static struct dict_get_verify_data expectations[] = {
+       {.key = "x",.want_value = 0},
+       {.key = "a",.want_value = "1"},
+       {.key = "b",.want_value = "2"},
+       {.key = "c",.want_value = "1,2"},
+       {0},
+    };
+    int     ret;
+
+    dict = dict_open_and_capture_msg(dict_spec, open_flags, dict_flags,
+                                    msg_buf);
+    if (LEN(msg_buf) > 0) {
+       msg_warn("unexpected dict_open() warning: got '%s'", STR(msg_buf));
+       ret = FAIL;
+    } else {
+       ret = dict_get_and_verify_bulk(dict, expectations);
+    }
+    dict_close(dict);
+    return (ret);
+}
+
+struct TEST_CASE {
+    const char *label;
+    int     (*action) (void);
+};
+
+static const struct TEST_CASE test_cases[] = {
+    {"valid_refcounts_for_good_composite_syntax", valid_refcounts_for_good_composite_syntax,},
+    {"valid_refcounts_for_bad_composite_syntax", valid_refcounts_for_bad_composite_syntax,},
+    {"propagates_notfound_and_found", propagates_notfound_and_found,},
+    {"propagates_error", propagates_error,},
+    {"no_comma_for_not_found", no_comma_for_not_found,},
+    {0},
+};
+
+int     main(int argc, char **argv)
+{
+    static int tests_passed = 0;
+    static int tests_failed = 0;
+    const struct TEST_CASE *tp;
+
+    msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR);
+
+    msg_buf = vstring_alloc(100);
+    dict_allow_surrogate = 1;
+
+    for (tp = test_cases; tp->label; tp++) {
+       msg_info("RUN  %s", tp->label);
+       if (tp->action() == PASS) {
+           msg_info("PASS %s", tp->label);
+           tests_passed += 1;
+       } else {
+           msg_info("FAIL %s", tp->label);
+           tests_failed += 1;
+       }
+    }
+    vstring_free(msg_buf);
+
+    msg_info("PASS=%d FAIL=%d", tests_passed, tests_failed);
+    exit(tests_failed != 0);
+}
diff --git a/postfix/src/util/dict_union_test.in b/postfix/src/util/dict_union_test.in
deleted file mode 100644 (file)
index 9d111d4..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-${VALGRIND} ./dict_open 'unionmap:{static:one,static:two,inline:{foo=three}}' read <<EOF
-get foo
-get bar
-EOF
-${VALGRIND} ./dict_open 'unionmap:{static:one,fail:fail}' read <<EOF
-get foo
-EOF
diff --git a/postfix/src/util/dict_union_test.ref b/postfix/src/util/dict_union_test.ref
deleted file mode 100644 (file)
index b609410..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-+ ./dict_open unionmap:{static:one,static:two,inline:{foo=three}} read
-owner=trusted (uid=2147483647)
-> get foo
-foo=one,two,three
-> get bar
-bar=one,two
-+ ./dict_open unionmap:{static:one,fail:fail} read
-owner=trusted (uid=2147483647)
-> get foo
-foo: error