From: Wietse Z Venema Date: Sat, 21 Jun 2025 05:00:00 +0000 (-0500) Subject: postfix-3.11-20250621 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2632a88318c1c36c263a6a385bbe72135c763bba;p=thirdparty%2Fpostfix.git postfix-3.11-20250621 --- diff --git a/postfix/.indent.pro b/postfix/.indent.pro index 86522dfde..9fa65a8f9 100644 --- a/postfix/.indent.pro +++ b/postfix/.indent.pro @@ -426,6 +426,7 @@ -Td2i_X509_t -Tdane_digest -Tdane_mtype +-Tdict_lookup_verify_data -Tfilter_ctx -Tgeneral_name_stack_t -Tiana_digest diff --git a/postfix/HISTORY b/postfix/HISTORY index 13b6b27d6..20d3fcec9 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -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. diff --git a/postfix/README_FILES/DEPRECATION_README b/postfix/README_FILES/DEPRECATION_README index 87cf2474f..ee797e50d 100644 --- a/postfix/README_FILES/DEPRECATION_README +++ b/postfix/README_FILES/DEPRECATION_README @@ -58,6 +58,8 @@ the "obsolete feature" name for a more detailed description. | |ooff |iinn vveerrssiioonn| | | |vveerrssiioonn| | | |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | + |xxx_tls_enforce_peername | 3.11 | - |xxx_tls_security_level | + |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | |disable_dns_lookups | 3.9 | - |smtp_dns_support_level | |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | |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| |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | +OObbssoolleettee TTLLSS ppeeeerr nnaammee mmaattcchh ccoonnffiigguurraattiioonn + +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: + + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + |GGooaall |OObbssoolleettee ccoonnffiigguurraattiioonn|RReeppllaacceemmeenntt ccoonnffiigguurraattiioonn| + |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | + | | |xxx_security_level = | + |Enforce peer name match|xxx_enforce_peername =|verify | + |with server certificate|yes |xxx_security_level = | + | | |secure | + |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | + |Disable peer name match|xxx_enforce_peername =|xxx_security_level = may | + |with server certificate|no |xxx_security_level = | + | | |encrypt | + |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | + OObbssoolleettee DDNNSS oonn//ooffff ccoonnffiigguurraattiioonn The postconf(1) command logs the following: diff --git a/postfix/conf/relocated b/postfix/conf/relocated index 5c4326672..f3d9f8bfd 100644 --- a/postfix/conf/relocated +++ b/postfix/conf/relocated @@ -38,9 +38,10 @@ # 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 # @@ -48,6 +49,15 @@ # 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 `#'. @@ -128,25 +138,31 @@ # 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 @@ -155,13 +171,13 @@ # 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) diff --git a/postfix/html/DEPRECATION_README.html b/postfix/html/DEPRECATION_README.html index ace103015..604028350 100644 --- a/postfix/html/DEPRECATION_README.html +++ b/postfix/html/DEPRECATION_README.html @@ -104,11 +104,16 @@ detailed description.

Removed
in version Replacement + +xxx_tls_enforce_peername 3.11 + - xxx_tls_security_level + + disable_dns_lookups 3.9 - smtp_dns_support_level - xxx_use_tls + xxx_use_tls 3.9 - xxx_tls_security_level @@ -149,6 +154,45 @@ smtpd_tls_dh1024_param_file 3.9 +

Obsolete TLS peer name +match configuration

+ +

The postconf(1) command logs one of the following:

+ + + +

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:

+ +
+ + + + + + + + + + + +
Goal Obsolete configuration Replacement configuration
Enforce peer name match with server certificate xxx_enforce_peername = yes xxx_security_level += verify
xxx_security_level = secure
Disable peer name match with server certificate xxx_enforce_peername = no xxx_security_level += may
xxx_security_level = encrypt
+ +
+

Obsolete DNS on/off configuration

diff --git a/postfix/html/postconf.5.html b/postfix/html/postconf.5.html index 0d63f28ee..520bd062d 100644 --- a/postfix/html/postconf.5.html +++ b/postfix/html/postconf.5.html @@ -3482,6 +3482,30 @@ Postfix versions before 2.0 have no support for the original recipient address.

+ + +
relocated_prefix_enable +(default: yes)
+ +

Prepend the prefix "5.1.6 User has moved to " 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..."). + +

+Example: +

+ +
+/etc/postfix/main.cf:
+    relocated_maps = hash:/etc/postfix/relocated
+    relocated_prefix_enable = no
+
+hash:/etc/postfix/relocated: + user@example.com 5.2.1 User account is disabled +
+ +
enable_threaded_bounces diff --git a/postfix/html/relocated.5.html b/postfix/html/relocated.5.html index 3ce107419..a6397fe62 100644 --- a/postfix/html/relocated.5.html +++ b/postfix/html/relocated.5.html @@ -41,71 +41,79 @@ RELOCATED(5) RELOCATED(5) 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 prefix "5.1.6 User + has moved to " to a table lookup result, and the format for a + table entry is as follows: pattern new_location Where new_location specifies contact information such as an email address, or perhaps a street address or telephone number. - o Empty lines and whitespace-only lines are ignored, as are lines + 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 `#'. - o A logical line starts with non-whitespace text. A line that + o A logical line starts with non-whitespace text. A line that starts with whitespace continues a logical line. TABLE SEARCH ORDER - 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: user@domain - Matches user@domain. This form has precedence over all other + Matches user@domain. This form has precedence over all other forms. user Matches user@site when site is $myorigin, when site is listed in - $mydestination, or when site is listed in $inet_interfaces or + $mydestination, or when site is listed in $inet_interfaces or $proxy_interfaces. @domain - Matches other addresses in domain. This form has the lowest + Matches other addresses in domain. This form has the lowest precedence. ADDRESS EXTENSION When a mail address localpart contains the optional recipient delimiter - (e.g., user+foo@domain), the lookup order becomes: user+foo@domain, + (e.g., user+foo@domain), the lookup order becomes: user+foo@domain, user@domain, user+foo, user, and @domain. REGULAR EXPRESSION TABLES - 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 regexp_table(5) or pcre_table(5). 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 regexp_table(5) or pcre_table(5). For a description of the TCP client/server table lookup protocol, see tcp_table(5). 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, user@domain mail addresses are not bro- - ken up into their user and @domain constituent parts, nor is user+foo + Each pattern is a regular expression that is applied to the entire + address being looked up. Thus, user@domain mail addresses are not bro- + ken up into their user and @domain constituent parts, nor is user+foo broken up into user and foo. - 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 $1, $2 and so on. TCP-BASED TABLES - 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 tcp_table(5). 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 tcp_table(5). This feature is available in Postfix 2.5 and later. - Each lookup operation uses the entire address once. Thus, user@domain - mail addresses are not broken up into their user and @domain con- + Each lookup operation uses the entire address once. Thus, user@domain + mail addresses are not broken up into their user and @domain con- stituent parts, nor is user+foo broken up into user and foo. 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. CONFIGURATION PARAMETERS - The following main.cf parameters are especially relevant. The text - below provides only a parameter summary. See postconf(5) for more + The following main.cf parameters are especially relevant. The text + below provides only a parameter summary. See postconf(5) for more details including examples. relocated_maps (empty) 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 relo- + cated_maps lookup results. + Other parameters of interest: inet_interfaces (all) diff --git a/postfix/man/man5/postconf.5 b/postfix/man/man5/postconf.5 index 24351183c..a9adc8eef 100644 --- a/postfix/man/man5/postconf.5 +++ b/postfix/man/man5/postconf.5 @@ -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: diff --git a/postfix/man/man5/relocated.5 b/postfix/man/man5/relocated.5 index e1d7863b4..768b592b1 100644 --- a/postfix/man/man5/relocated.5 +++ b/postfix/man/man5/relocated.5 @@ -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 diff --git a/postfix/mantools/postlink b/postfix/mantools/postlink index b06c2d00f..d641b8995 100755 --- a/postfix/mantools/postlink +++ b/postfix/mantools/postlink @@ -443,6 +443,7 @@ while (<>) { s;\brelay_transport\b;$&;g; s;\brelay[-]*\n*[ ]*host\b;$&;g; s;\brelocated_maps\b;$&;g; + s;\brelocated_prefix_enable\b;$&;g; s;\brequire_home_directory\b;$&;g; s;\bresolve_dequoted_address\b;$&;g; s;\brewrite_service_name\b;$&;g; diff --git a/postfix/proto/DEPRECATION_README.html b/postfix/proto/DEPRECATION_README.html index 356d23230..6f9f247bf 100644 --- a/postfix/proto/DEPRECATION_README.html +++ b/postfix/proto/DEPRECATION_README.html @@ -104,11 +104,16 @@ detailed description.

Removed
in version Replacement + +xxx_tls_enforce_peername 3.11 + - xxx_tls_security_level + + disable_dns_lookups 3.9 - smtp_dns_support_level - xxx_use_tls + xxx_use_tls 3.9 - xxx_tls_security_level @@ -149,6 +154,45 @@ reject_rbl_client +

Obsolete TLS peer name +match configuration

+ +

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:

+ +
+ + + + + + + + + + + +
Goal Obsolete configuration Replacement configuration
Enforce peer name match with server certificate xxx_enforce_peername = yes xxx_security_level += verify
xxx_security_level = secure
Disable peer name match with server certificate xxx_enforce_peername = no xxx_security_level += may
xxx_security_level = encrypt
+ +
+

Obsolete DNS on/off configuration

diff --git a/postfix/proto/postconf.proto b/postfix/proto/postconf.proto index b1130e134..860f1aec9 100644 --- a/postfix/proto/postconf.proto +++ b/postfix/proto/postconf.proto @@ -4012,6 +4012,26 @@ relocated_maps = dbm:/etc/postfix/relocated relocated_maps = hash:/etc/postfix/relocated +%PARAM relocated_prefix_enable yes + +

Prepend the prefix "5.1.6 User has moved to " 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..."). + +

+Example: +

+ +
+/etc/postfix/main.cf:
+    relocated_maps = hash:/etc/postfix/relocated
+    relocated_prefix_enable = no
+
+hash:/etc/postfix/relocated: + user@example.com 5.2.1 User account is disabled +
+ %PARAM require_home_directory no

diff --git a/postfix/proto/relocated b/postfix/proto/relocated index b517b354f..f48b53093 100644 --- a/postfix/proto/relocated +++ b/postfix/proto/relocated @@ -36,9 +36,10 @@ # 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 @@ -47,6 +48,16 @@ # 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 @@ -123,6 +134,11 @@ # 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 diff --git a/postfix/proto/stop.double-install-proto-text b/postfix/proto/stop.double-install-proto-text index 4e3f43dca..7721d87d7 100644 --- a/postfix/proto/stop.double-install-proto-text +++ b/postfix/proto/stop.double-install-proto-text @@ -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 diff --git a/postfix/proto/stop.double-proto-html b/postfix/proto/stop.double-proto-html index 911ed5145..bed72b65e 100644 --- a/postfix/proto/stop.double-proto-html +++ b/postfix/proto/stop.double-proto-html @@ -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 diff --git a/postfix/src/global/mail_params.h b/postfix/src/global/mail_params.h index 40aa5c2ba..15cc2469e 100644 --- a/postfix/src/global/mail_params.h +++ b/postfix/src/global/mail_params.h @@ -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. diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 102c9cd24..0c6018de1 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -20,7 +20,7 @@ * Patches change both the patchlevel and the release date. Snapshots have no * patchlevel; they change the release date only. */ -#define MAIL_RELEASE_DATE "20250606" +#define MAIL_RELEASE_DATE "20250621" #define MAIL_VERSION_NUMBER "3.11" #ifdef SNAPSHOT diff --git a/postfix/src/global/maps.c b/postfix/src/global/maps.c index d2370029d..592ec91d9 100644 --- a/postfix/src/global/maps.c +++ b/postfix/src/global/maps.c @@ -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) diff --git a/postfix/src/postconf/Makefile.in b/postfix/src/postconf/Makefile.in index 9e5e3b966..163900bb7 100644 --- a/postfix/src/postconf/Makefile.in +++ b/postfix/src/postconf/Makefile.in @@ -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) diff --git a/postfix/src/postconf/postconf_unused.c b/postfix/src/postconf/postconf_unused.c index 717d1a53a..0b8d14646 100644 --- a/postfix/src/postconf/postconf_unused.c +++ b/postfix/src/postconf/postconf_unused.c @@ -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 index 000000000..59d171b4a --- /dev/null +++ b/postfix/src/postconf/test78.ref @@ -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" diff --git a/postfix/src/smtp/smtp.h b/postfix/src/smtp/smtp.h index 8c5ee0a0f..7214661a6 100644 --- a/postfix/src/smtp/smtp.h +++ b/postfix/src/smtp/smtp.h @@ -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, diff --git a/postfix/src/smtp/smtp_connect.c b/postfix/src/smtp/smtp_connect.c index 2bfff1c93..e4b60791a 100644 --- a/postfix/src/smtp/smtp_connect.c +++ b/postfix/src/smtp/smtp_connect.c @@ -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. */ diff --git a/postfix/src/smtp/smtp_proto.c b/postfix/src/smtp/smtp_proto.c index df2f74391..b89686378 100644 --- a/postfix/src/smtp/smtp_proto.c +++ b/postfix/src/smtp/smtp_proto.c @@ -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; diff --git a/postfix/src/smtp/smtp_tls_policy.c b/postfix/src/smtp/smtp_tls_policy.c index 6122c2d78..6b9ee66e6 100644 --- a/postfix/src/smtp/smtp_tls_policy.c +++ b/postfix/src/smtp/smtp_tls_policy.c @@ -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"); diff --git a/postfix/src/testing/Makefile.in b/postfix/src/testing/Makefile.in index ad624de78..16375e2d0 100644 --- a/postfix/src/testing/Makefile.in +++ b/postfix/src/testing/Makefile.in @@ -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 index 000000000..22474a25e --- /dev/null +++ b/postfix/src/testing/dict_test_helper.c @@ -0,0 +1,266 @@ +/*++ +/* NAME +/* dict_test_helper 3 +/* SUMMARY +/* dictionary test helpers +/* SYNOPSIS +/* #include +/* +/* 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 +#include +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include +#include +#include + +#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 index 000000000..044dec05a --- /dev/null +++ b/postfix/src/testing/dict_test_helper.h @@ -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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include +#include + + /* + * 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 diff --git a/postfix/src/trivial-rewrite/Makefile.in b/postfix/src/trivial-rewrite/Makefile.in index ffe9ee71b..72ed83636 100644 --- a/postfix/src/trivial-rewrite/Makefile.in +++ b/postfix/src/trivial-rewrite/Makefile.in @@ -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 diff --git a/postfix/src/trivial-rewrite/resolve.c b/postfix/src/trivial-rewrite/resolve.c index df761e76c..5ca1f791a 100644 --- a/postfix/src/trivial-rewrite/resolve.c +++ b/postfix/src/trivial-rewrite/resolve.c @@ -87,6 +87,7 @@ #include #include #include +#include /* 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; diff --git a/postfix/src/trivial-rewrite/trivial-rewrite.c b/postfix/src/trivial-rewrite/trivial-rewrite.c index bb8da091d..8c896749c 100644 --- a/postfix/src/trivial-rewrite/trivial-rewrite.c +++ b/postfix/src/trivial-rewrite/trivial-rewrite.c @@ -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, }; diff --git a/postfix/src/util/Makefile.in b/postfix/src/util/Makefile.in index 7c99ae23a..c6573320f 100644 --- a/postfix/src/util/Makefile.in +++ b/postfix/src/util/Makefile.in @@ -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 diff --git a/postfix/src/util/dict.c b/postfix/src/util/dict.c index 35c02db05..1fe169d2d 100644 --- a/postfix/src/util/dict.c +++ b/postfix/src/util/dict.c @@ -69,6 +69,12 @@ /* /* 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 @@ -166,6 +172,12 @@ /* /* 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)))); +} diff --git a/postfix/src/util/dict.h b/postfix/src/util/dict.h index ef0e83343..a6badac26 100644 --- a/postfix/src/util/dict.h +++ b/postfix/src/util/dict.h @@ -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. */ diff --git a/postfix/src/util/dict_pipe.c b/postfix/src/util/dict_pipe.c index 8ce0faad7..9002bd369 100644 --- a/postfix/src/util/dict_pipe.c +++ b/postfix/src/util/dict_pipe.c @@ -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 index 000000000..79bf3fa55 --- /dev/null +++ b/postfix/src/util/dict_pipe_test.c @@ -0,0 +1,226 @@ +/*++ +/* AUTHOR(S) +/* Wietse Venema +/* porcupine.org +/*--*/ + + /* + * System library. + */ +#include +#include +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include +#include + + /* + * Testing library. + */ +#include + +#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 index 9626dcd3f..000000000 --- a/postfix/src/util/dict_pipe_test.in +++ /dev/null @@ -1,9 +0,0 @@ -${VALGRIND} ./dict_open 'pipemap:{inline:{k1=v1,k2=v2},inline:{v2=v3}}' read < 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 diff --git a/postfix/src/util/dict_union.c b/postfix/src/util/dict_union.c index 80df03b61..cda3fe177 100644 --- a/postfix/src/util/dict_union.c +++ b/postfix/src/util/dict_union.c @@ -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 index 000000000..0ed87b892 --- /dev/null +++ b/postfix/src/util/dict_union_test.c @@ -0,0 +1,253 @@ +/*++ +/* AUTHOR(S) +/* Wietse Venema +/* porcupine.org +/*--*/ + + /* + * System library. + */ +#include +#include +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include +#include + + /* + * Testing library. + */ +#include + +#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 index 9d111d43e..000000000 --- a/postfix/src/util/dict_union_test.in +++ /dev/null @@ -1,7 +0,0 @@ -${VALGRIND} ./dict_open 'unionmap:{static:one,static:two,inline:{foo=three}}' read < 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