-Td2i_X509_t
-Tdane_digest
-Tdane_mtype
+-Tdict_lookup_verify_data
-Tfilter_ctx
-Tgeneral_name_stack_t
-Tiana_digest
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.
| |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 |
| | | |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:
# 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)
</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>
</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>
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>
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.
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>
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:
.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
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
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;
</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>
</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>
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>
# 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
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
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
#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.
* 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
#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)
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:
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)
"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 */
--- /dev/null
+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"
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. */
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); \
*/
#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,
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.
*/
}
#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.
*/
* 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;
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");
+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
@$(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
--- /dev/null
+/*++
+/* 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);
+}
--- /dev/null
+#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
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
#include <maps.h>
#include <mail_addr_find.h>
#include <valid_mailhost_addr.h>
+#include <dsn_util.h>
/* Application-specific. */
#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;
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;
};
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,
};
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
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 \
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
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
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 \
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
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
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
/*
/* 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
{
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))));
+}
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.
*/
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.
myfree(saved_name); \
if (argv != 0) \
argv_free(argv); \
+ if (reg_name_buf != 0) \
+ vstring_free(reg_name_buf); \
return (x); \
} while (0)
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,
"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);
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));
--- /dev/null
+/*++
+/* 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);
+}
+++ /dev/null
-${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
+++ /dev/null
-+ ./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
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)
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,
"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);
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));
--- /dev/null
+/*++
+/* 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);
+}
+++ /dev/null
-${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
+++ /dev/null
-+ ./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