diff --git a/postfix/HISTORY b/postfix/HISTORY index 0f2e25b15..8a2ba1794 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -29345,3 +29345,53 @@ Apologies for any names omitted. Cleanup: simplified the rule parser in global/server_acl.c. Unbroke dict_debug Valgrind checks. File: util/dict_debug_test.sh. + +20250710 + + Bugfix (defect introduced: postfix-2.2, date 20050203): + after detecting a lookup table change, and after starting + a new postscreen process, the old postscreen process logged + an ENOTSOCK error while attempting to accept a connection + on a socket that it was no longer listening on. This error + was introduced first in the multi_server skeleton code, and + was five years later duplicated in the event_server skeleton + that was created for postscreen. Problem reported by Florian + Piekert. Files: master/multi_server.c, master/event_server.c. + +20250713 + + Cleanup: allow "postmap -s" and "postalias -s" with proxied + tables. The proxymap protocol already supported this. Files: + postmap/postmap.c, postalias/postalias.c. + + Cleanup: simplified the proxymap protocol and the proxymap + table sharing strategy. Share only table instances that + have identical client-side dictionary flags when opening a + table (instead of sharing tables that have a common subset + of flags). With each client request, propagate all client-side + dictionary flags to the server, and upon request completion, + propagate all resulting server-side dictionary flags to the + client. Files: dict.h, dict_proxy.c, proxymap/proxymap.c, + global/mail_proto.h. + + Cleanup; stop hard-coding "dict->flags = DICT_FLAG_FIXED" + in dict_alloc.c. All tables already overwrote that information. + + Debugging: the default import_environment now also imports + XDG_RUNTIME_DIR to support GUI debugging a Postfix daemon + process on some platforms (it already imported XAUTHORITY + and DISPLAY for X-based debuggers). These environment + variables are set only when Postfix is started 'by hand'. + File: global/mail_params.h. + + Graceful degradation: when a proxymap or proxywrite server + denies access to a table, do not terminate the program. + Instead, return a surrogate object that fails all requests + with an informative message. File: global/dict_proxy.c. + + Workaround: added an example to the smtp_reply_filter + documentation that works around Microsoft SASL server + implementations that send a non-empty initial GSSAPI + challenge. File: proto/postconf.proto. + + Typo in COMPATIBILITY_README.html. Emmanuel Fusté. diff --git a/postfix/README_FILES/COMPATIBILITY_README b/postfix/README_FILES/COMPATIBILITY_README index e69de29bb..8c3a0b71b 100644 --- a/postfix/README_FILES/COMPATIBILITY_README +++ b/postfix/README_FILES/COMPATIBILITY_README @@ -0,0 +1,459 @@ +PPoossttffiixx BBaacckkwwaarrddss--CCoommppaattiibbiilliittyy SSaaffeettyy NNeett + +------------------------------------------------------------------------------- + +PPuurrppoossee ooff tthhiiss ddooccuummeenntt + +Postfix 3.0 introduces a safety net that runs Postfix programs with backwards- +compatible default settings after an upgrade. The safety net will log a warning +whenever a "new" default setting could have an negative effect on your mail +flow. + +This document provides information on the following topics: + + * Detailed descriptions of Postfix backwards-compatibility warnings. + + * What backwards-compatible settings you may have to make permanent in + main.cf or master.cf. + + * How to turn off Postfix backwards-compatibility warnings. + +OOvveerrvviieeww + +With backwards compatibility turned on, Postfix logs a message whenever a +backwards-compatible default setting may be required for continuity of service. +Based on this logging the system administrator can decide if any backwards- +compatible settings need to be made permanent in main.cf or master.cf, before +turning off the backwards-compatibility safety net as described at the end of +this document. + +Logged with compatibility_level < 1: + + * Using backwards-compatible default setting append_dot_mydomain=yes + + * Using backwards-compatible default setting chroot=y + + * Using backwards-compatible default setting "smtpd_relay_restrictions = + (empty)" + + * Using backwards-compatible default setting smtputf8_enable=no + +Logged with compatibility_level < 2: + + * Using backwards-compatible default setting mynetworks_style=subnet + + * Using backwards-compatible default setting relay_domains=$mydestination + +Logged with compatibility_level < 3.6: + + * Using backwards-compatible default setting smtpd_tls_fingerprint_digest=md5 + + * Using backwards-compatible default setting smtp_tls_fingerprint_digest=md5 + + * Using backwards-compatible default setting lmtp_tls_fingerprint_digest=md5 + + * Using backwards-compatible default setting + smtpd_relay_before_recipient_restrictions=no + + * Using backwards-compatible default setting respectful_logging=no + +Logged with compatibility_level < 3.11: + + * using backwards-compatible default setting + smtp_tlsrpt_skip_reused_handshakes=yes + + * using backwards-compatible default setting xxx_security_level=(empty) + +If such a message is logged in the context of a legitimate request, the system +administrator should make the backwards-compatible setting permanent in main.cf +or master.cf, as detailed in the sections that follow. + +When no more backwards-compatible settings need to be made permanent, the +system administrator should turn off the backwards-compatibility safety net as +described at the end of this document. + +UUssiinngg bbaacckkwwaarrddss--ccoommppaattiibbllee ddeeffaauulltt sseettttiinngg aappppeenndd__ddoott__mmyyddoommaaiinn==yyeess + +The append_dot_mydomain default value has changed from "yes" to "no". This +could result in unexpected non-delivery of email after Postfix is updated from +an older version. The backwards-compatibility safety net is designed to prevent +such surprises. + +As long as the append_dot_mydomain parameter is left unspecified at its +implicit default value, and the compatibility_level setting is less than 1, +Postfix may log one of the following messages: + + * Messages about missing "localhost" in mydestination or other address class: + + postfix/trivial-rewrite[14777]: using backwards-compatible + default setting append_dot_mydomain=yes to rewrite + "localhost" to "localhost.example.com"; please add + "localhost" to mydestination or other address class + + If Postfix logs the above message, add "localhost" to mydestination (or + virtual_alias_domains, virtual_mailbox_domains, or relay_domains) and + execute the command "ppoossttffiixx rreellooaadd". + + * Messages about incomplete domains in email addresses: + + postfix/trivial-rewrite[25835]: using backwards-compatible + default setting append_dot_mydomain=yes to rewrite "foo" to + "foo.example.com" + + If Postfix logs the above message for domains different from "localhost", + and the sender cannot be changed to use complete domain names in email + addresses, then the system administrator should make the backwards- + compatible setting "append_dot_mydomain = yes" permanent in main.cf: + + # ppoossttccoonnff aappppeenndd__ddoott__mmyyddoommaaiinn==yyeess + # ppoossttffiixx rreellooaadd + +UUssiinngg bbaacckkwwaarrddss--ccoommppaattiibbllee ddeeffaauulltt sseettttiinngg cchhrroooott==yy + +The master.cf chroot default value has changed from "y" (yes) to "n" (no). The +new default avoids the need for copies of system files under the Postfix queue +directory. However, sites with strict security requirements may want to keep +the chroot feature enabled after updating Postfix from an older version. The +backwards-compatibility safety net is designed allow the administrator to +choose if they want to keep the old behavior. + +As long as a master.cf chroot field is left unspecified at its implicit default +value, and the compatibility_level setting is less than 1, Postfix may log the +following message while it reads the master.cf file: + + postfix/master[27664]: /etc/postfix/master.cf: line 72: using + backwards-compatible default setting chroot=y + +If this service should remain chrooted, then the system administrator should +make the backwards-compatible setting "chroot = y" permanent in master.cf. For +example, to update the chroot setting for the "smtp inet" service: + + # ppoossttccoonnff --FF ssmmttpp//iinneett//cchhrroooott==yy + # ppoossttffiixx rreellooaadd + +UUssiinngg bbaacckkwwaarrddss--ccoommppaattiibbllee ddeeffaauulltt sseettttiinngg ssmmttppdd__rreellaayy__rreessttrriiccttiioonnss == ((eemmppttyy)) + +The smtpd_relay_restrictions feature was introduced with Postfix version 2.10, +as a safety mechanism for configuration errors in smtpd_recipient_restrictions +that could make Postfix an open relay. + +The smtpd_relay_restrictions implicit default setting forbids mail to remote +destinations from clients that don't match permit_mynetworks or +permit_sasl_authenticated. This could result in unexpected 'Relay access +denied' errors after Postfix is updated from an older Postfix version. The +backwards-compatibility safety net is designed to prevent such surprises. + +When the compatibility_level less than 1, and the smtpd_relay_restrictions +parameter is left unspecified at its implicit default setting, Postfix may log +the following message: + + postfix/smtpd[38463]: using backwards-compatible default setting + "smtpd_relay_restrictions = (empty)" to avoid "Relay access + denied" error for recipient "user@example.com" from client + "host.example.net[10.0.0.2]" + +If this request should not be blocked, then the system administrator should +make the backwards-compatible setting "smtpd_relay_restrictions=" (i.e. empty) +permanent in main.cf: + + # ppoossttccoonnff ssmmttppdd__rreellaayy__rreessttrriiccttiioonnss== + # ppoossttffiixx rreellooaadd + +UUssiinngg bbaacckkwwaarrddss--ccoommppaattiibbllee ddeeffaauulltt sseettttiinngg ssmmttppuuttff88__eennaabbllee==nnoo + +The smtputf8_enable default value has changed from "no" to "yes". With the new +"yes" setting, the Postfix SMTP server rejects non-ASCII addresses from clients +that don't request SMTPUTF8 support, after Postfix is updated from an older +version. The backwards-compatibility safety net is designed to prevent such +surprises. + +As long as the smtputf8_enable parameter is left unspecified at its implicit +default value, and the compatibility_level setting is less than 1, Postfix logs +a warning each time an SMTP command uses a non-ASCII address localpart without +requesting SMTPUTF8 support: + + postfix/smtpd[27560]: using backwards-compatible default setting + smtputf8_enable=no to accept non-ASCII sender address + "??@example.org" from localhost[127.0.0.1] + + postfix/smtpd[27560]: using backwards-compatible default setting + smtputf8_enable=no to accept non-ASCII recipient address + "??@example.com" from localhost[127.0.0.1] + +If the address should not be rejected, and the client cannot be updated to use +SMTPUTF8, then the system administrator should make the backwards-compatible +setting "smtputf8_enable = no" permanent in main.cf: + + # ppoossttccoonnff ssmmttppuuttff88__eennaabbllee==nnoo + # ppoossttffiixx rreellooaadd + +UUssiinngg bbaacckkwwaarrddss--ccoommppaattiibbllee ddeeffaauulltt sseettttiinngg mmyynneettwwoorrkkss__ssttyyllee==ssuubbnneett + +The mynetworks_style default value has changed from "subnet" to "host". This +parameter is used to implement the "permit_mynetworks" feature. The change +could cause unexpected 'access denied' errors after Postfix is updated from an +older version. The backwards-compatibility safety net is designed to prevent +such surprises. + +As long as the mynetworks and mynetworks_style parameters are left unspecified +at their implicit default values, and the compatibility_level setting is less +than 2, the Postfix SMTP server may log one of the following messages: + + postfix/smtpd[17375]: using backwards-compatible default setting + mynetworks_style=subnet to permit request from client + "foo.example.com[10.1.1.1]" + + postfix/postscreen[24982]: using backwards-compatible default + setting mynetworks_style=subnet to permit request from client + "10.1.1.1" + +If the client request should not be rejected, then the system administrator +should make the backwards-compatible setting "mynetworks_style = subnet" +permanent in main.cf: + + # ppoossttccoonnff mmyynneettwwoorrkkss__ssttyyllee==ssuubbnneett + # ppoossttffiixx rreellooaadd + +UUssiinngg bbaacckkwwaarrddss--ccoommppaattiibbllee ddeeffaauulltt sseettttiinngg rreellaayy__ddoommaaiinnss==$$mmyyddeessttiinnaattiioonn + +The relay_domains default value has changed from "$mydestination" to the empty +value. This could result in unexpected 'Relay access denied' errors or ETRN +errors after Postfix is updated from an older version. The backwards- +compatibility safety net is designed to prevent such surprises. + +As long as the relay_domains parameter is left unspecified at its implicit +default value, and the compatibility_level setting is less than 2, Postfix may +log one of the following messages. + + * Messages about accepting mail for a remote domain: + + postfix/smtpd[19052]: using backwards-compatible default setting + relay_domains=$mydestination to accept mail for domain + "foo.example.com" + + postfix/smtpd[19052]: using backwards-compatible default setting + relay_domains=$mydestination to accept mail for address + "user@foo.example.com" + + * Messages about providing ETRN service for a remote domain: + + postfix/smtpd[19138]: using backwards-compatible default setting + relay_domains=$mydestination to flush mail for domain + "bar.example.com" + + postfix/smtp[13945]: using backwards-compatible default setting + relay_domains=$mydestination to update fast-flush logfile for + domain "bar.example.com" + +If Postfix should continue to accept mail for that domain or continue to +provide ETRN service for that domain, then the system administrator should make +the backwards-compatible setting "relay_domains = $mydestination" permanent in +main.cf: + + # ppoossttccoonnff ''rreellaayy__ddoommaaiinnss==$$mmyyddeessttiinnaattiioonn'' + # ppoossttffiixx rreellooaadd + +Note: quotes are required as indicated above. + +Instead of $mydestination, it may be better to specify an explicit list of +domain names. + +UUssiinngg bbaacckkwwaarrddss--ccoommppaattiibbllee ddeeffaauulltt sseettttiinngg ssmmttppdd__ttllss__ffiinnggeerrpprriinntt__ddiiggeesstt==mmdd55 + +The smtpd_tls_fingerprint_digest default value has changed from "md5" to +"sha256". With the new "sha256" setting, the Postfix SMTP server avoids using +the deprecated "md5" algorithm and computes a more secure digest of the client +certificate. + +If you're using the default "md5" setting, or even an explicit "sha1" (also +deprecated) setting, you should consider switching to "sha256". This will +require updating any associated lookup table keys with the "sha256" digests of +the expected client certificate or public key. + +As long as the smtpd_tls_fingerprint_digest parameter is left unspecified at +its implicit default value, and the compatibility_level setting is less than +3.6, Postfix logs a warning each time a client certificate or public key +fingerprint is (potentially) used for access control: + + postfix/smtpd[27560]: using backwards-compatible default setting + smtpd_tls_fingerprint_digest=md5 to compute certificate fingerprints + +Since any client certificate fingerprints are passed in policy service lookups, +and Postfix doesn't know whether the fingerprint will be used, the warning may +also be logged when policy lookups are performed for connections that used a +client certificate, even if the policy service does not in fact examine the +client certificate. To reduce the noise somewhat, such warnings are issued at +most once per smtpd(8) process instance. + +If you prefer to stick with "md5", you can suppress the warnings by making that +setting explicit. After addressing any other compatibility warnings, you can +update your compatibility level. + + # ppoossttccoonnff ssmmttppdd__ttllss__ffiinnggeerrpprriinntt__ddiiggeesstt==mmdd55 + # ppoossttffiixx rreellooaadd + +UUssiinngg bbaacckkwwaarrddss--ccoommppaattiibbllee ddeeffaauulltt sseettttiinngg ssmmttpp__ttllss__ffiinnggeerrpprriinntt__ddiiggeesstt==mmdd55 + +The smtp_tls_fingerprint_digest and lmtp_tls_fingerprint_digest default values +have changed from "md5" to "sha256". With the new "sha256" setting, the Postfix +SMTP and LMTP client avoids using the deprecated "md5" algorithm and computes a +more secure digest of the server certificate. + +If you're using the default "md5" setting, or even an explicit "sha1" (also +deprecated) setting, you should consider switching to "sha256". This will +require updating any "fingerprint" security level policies in the TLS policy +table to specify matching "sha256" digests of the expected server certificates +or public keys. + +As long as the smtp_tls_fingerprint_digest (or LMTP equivalent) parameter is +left unspecified at its implicit default value, and the compatibility_level +setting is less than 3.6, Postfix logs a warning each time the "fingerprint" +security level is used to specify matching "md5" digests of trusted server +certificates or public keys: + + postfix/smtp[27560]: using backwards-compatible default setting + smtp_tls_fingerprint_digest=md5 to compute certificate fingerprints + +If you prefer to stick with "md5", you can suppress the warnings by making that +setting explicit. After addressing any other compatibility warnings, you can +update your compatibility level. + + # ppoossttccoonnff ''ssmmttpp__ttllss__ffiinnggeerrpprriinntt__ddiiggeesstt == mmdd55'' \\ + ''llmmttpp__ttllss__ffiinnggeerrpprriinntt__ddiiggeesstt == mmdd55'' + # ppoossttffiixx rreellooaadd + +UUssiinngg bbaacckkwwaarrddss--ccoommppaattiibbllee ddeeffaauulltt sseettttiinngg +ssmmttppdd__rreellaayy__bbeeffoorree__rreecciippiieenntt__rreessttrriiccttiioonnss==nnoo + +The smtpd_relay_before_recipient_restrictions feature was introduced in Postfix +version 3.6, to evaluate smtpd_relay_restrictions before +smtpd_recipient_restrictions. Historically, smtpd_relay_restrictions was +evaluated after smtpd_recipient_restrictions, contradicting documented +behavior. + + Background: smtpd_relay_restrictions is primarily designed to enforce a + mail relaying policy, while smtpd_recipient_restrictions is primarily + designed to enforce spam blocking policy. Both are evaluated while replying + to the RCPT TO command, and both support the same features. + +To maintain compatibility with earlier versions, Postfix will keep evaluating +smtpd_recipient_restrictions before smtpd_relay_restrictions, as long as the +compatibility_level is less than 3.6, and the +smtpd_relay_before_recipient_restrictions parameter is left unspecified at its +implicit default setting. As a reminder, Postfix may log the following message: + + postfix/smtpd[54696]: using backwards-compatible default setting + smtpd_relay_before_recipient_restrictions=no to reject recipient + "user@example.com" from client "host.example.net[10.0.0.2]" + +If Postfix should keep evaluating smtpd_recipient_restrictions before +smtpd_relay_restrictions, then the system administrator should make the +backwards-compatible setting "smtpd_relay_before_recipient_restrictions=no" +permanent in main.cf: + + # ppoossttccoonnff ssmmttppdd__rreellaayy__bbeeffoorree__rreecciippiieenntt__rreessttrriiccttiioonnss==nnoo + # ppoossttffiixx rreellooaadd + +UUssiinngg bbaacckkwwaarrddss--ccoommppaattiibbllee ddeeffaauulltt sseettttiinngg rreessppeeccttffuull__llooggggiinngg==nnoo + +Postfix version 3.6 deprecates configuration parameter names and logging that +suggest white is better than black. Instead it prefers 'allowlist, 'denylist', +and variations of those words. While the renamed configuration parameters have +backwards-compatible default values, the changes in logging could affect +logfile analysis tools. + +To avoid breaking existing logfile analysis tools, Postfix will keep logging +the deprecated form, as long as the respectful_logging parameter is left +unspecified at its implicit default value, and the compatibility_level setting +is less than 3.6. As a reminder, Postfix may log the following when a remote +SMTP client is allowlisted or denylisted: + + postfix/postscreen[22642]: Using backwards-compatible default setting + respectful_logging=no for client [address]:port + +If Postfix should keep logging the deprecated form, then the system +administrator should make the backwards-compatible setting "respectful_logging += no" permanent in main.cf. + + # ppoossttccoonnff ""rreessppeeccttffuull__llooggggiinngg == nnoo"" + # ppoossttffiixx rreellooaadd + +UUssiinngg bbaacckkwwaarrddss--ccoommppaattiibbllee ddeeffaauulltt sseettttiinngg +ssmmttpp__ttllssrrpptt__sskkiipp__rreeuusseedd__hhaannddsshhaakkeess==yyeess + +Postfix version 3.11 changes the default value for +smtp_tlsrpt_skip_reused_handshakes from "yes" to "no". The backwards- +compatibility safety net is designed to prevent an unexpected change in +reporting behavior when Postfix is updated from an older version. + +As long as the smtp_tlsrpt_skip_reused_handshakes parameter is left unspecified +at its implicit default value, and the compatibility_level setting is less than +3.11, Postfix will log a reminder that it is using the backwards-compatible +default: + + postfix/smtp[388157] using backwards-compatible default setting + smtp_tlsrpt_skip_reused_handshakes=yes + +To keep the old default setting, the system administrator should make the +backwards-compatible setting "smtp_tlsrpt_skip_reused_handshakes = yes" +permanent in main.cf: + + # ppoossttccoonnff ssmmttpp__ttllssrrpptt__sskkiipp__rreeuusseedd__hhaannddsshhaakkeess==yyeess + # ppoossttffiixx rreellooaadd + +UUssiinngg bbaacckkwwaarrddss--ccoommppaattiibbllee ddeeffaauulltt sseettttiinngg xxxxxx__sseeccuurriittyy__lleevveell==((eemmppttyy)) + +Postfix version 3.11 changes the default value for client TLS security levels +from "empty" to "may". The backwards-compatibility safety net is designed to +prevent an unexpected change in mail sending behavior when Postfix is updated +from an older version. + +There is no equivalent change for Postfix server TLS security levels, because +changing the level alone is not sufficient. Server-side TLS requires that at +least one private key and one public-key certificate chain are configured. + +As long as a TLS security level parameter is left unspecified at its implicit +default value, and the compatibility_level setting is less than 3.11, Postfix +will log one of the following reminders that it is using the backwards- +compatible default: + + postfix/smtp[...] using backwards-compatible default setting + smtp_tls_security_level=(empty) + + postfix/tlsproxy[...] using backwards-compatible default setting + tlsproxy_client_security_level=(empty) + +To keep the old default setting, the system administrator should make the +backwards-compatible empty setting permanent in main.cf: + + # ppoossttccoonnff xxxxxx__sseeccuurriittyy__lleevveell== + # ppoossttffiixx rreellooaadd + +where xxx is taken from the above compatibility message. + +TTuurrnniinngg ooffff tthhee bbaacckkwwaarrddss--ccoommppaattiibbiilliittyy ssaaffeettyy nneett + +Backwards compatibility is turned off by updating the compatibility_level +setting in main.cf. + + # ppoossttccoonnff ccoommppaattiibbiilliittyy__lleevveell==NN + # ppoossttffiixx rreellooaadd + +For N specify the number that is logged in your postfix(1) warning message: + + warning: To disable backwards compatibility use "postconf + compatibility_level=N" and "postfix reload" + +Sites that don't care about backwards compatibility may set +"compatibility_level = 9999" at their own risk. + +Starting with Postfix version 3.6, the compatibility level in the above warning +message is the Postfix version that introduced the last incompatible change. +The level is formatted as major.minor.patch, where patch is usually omitted and +defaults to zero. Earlier compatibility levels are 0, 1 and 2. + +NOTE: Postfix 3.6 also introduces support for the " diff --git a/postfix/html/postconf.5.html b/postfix/html/postconf.5.html index 8332ff686..9b03afe72 100644 --- a/postfix/html/postconf.5.html +++ b/postfix/html/postconf.5.html @@ -4236,15 +4236,20 @@ environment. Examples of relevant environment variables:

Needed to make "postfix -c" work.
+
POSTLOG_HOSTNAME
+ +
Needed to make "maillog_file" work during daemon +process initialization.
+
POSTLOG_SERVICE
Needed to make "maillog_file" work during daemon process initialization.
-
POSTLOG_HOSTNAME
+
XDG_RUNTIME_DIR
-
Needed to make "maillog_file" work during daemon -process initialization.
+
Needed for debugging Postfix daemons with an XDG-style debugger. +
@@ -12497,6 +12502,12 @@ line.

 /etc/postfix/reply_filter:
+    # Some Microsoft servers violate RFC 2554 section 4, causing Postfix
+    # to complain with "non-empty initial GSSAPI challenge from server"
+    /^334\s+GSSAPI\s+supported/ 334
+
+ +
     # Transform garbage into "250-filler..." so that it looks like
     # one line from a multi-line reply. It does not matter what we
     # substitute here as long it has the right syntax.  The Postfix
diff --git a/postfix/man/man5/postconf.5 b/postfix/man/man5/postconf.5
index 8baada969..8c2039ecb 100644
--- a/postfix/man/man5/postconf.5
+++ b/postfix/man/man5/postconf.5
@@ -2633,13 +2633,16 @@ Needed for debugging Postfix daemons with an X\-windows debugger.
 .IP "\fBMAIL_CONFIG\fR"
 Needed to make "\fBpostfix \-c\fR" work.
 .br
+.IP "\fBPOSTLOG_HOSTNAME\fR"
+Needed to make "\fBmaillog_file\fR" work during daemon
+process initialization.
+.br
 .IP "\fBPOSTLOG_SERVICE\fR"
 Needed to make "\fBmaillog_file\fR" work during daemon
 process initialization.
 .br
-.IP "\fBPOSTLOG_HOSTNAME\fR"
-Needed to make "\fBmaillog_file\fR" work during daemon
-process initialization.
+.IP "\fBXDG_RUNTIME_DIR\fR"
+Needed for debugging Postfix daemons with an XDG\-style debugger.
 .br
 .br
 .PP
@@ -7892,6 +7895,14 @@ Examples:
 .nf
 .na
 /etc/postfix/reply_filter:
+    # Some Microsoft servers violate RFC 2554 section 4, causing Postfix
+    # to complain with "non\-empty initial GSSAPI challenge from server"
+    /^334\es+GSSAPI\es+supported/ 334
+.fi
+.ad
+.PP
+.nf
+.na
     # Transform garbage into "250\-filler..." so that it looks like
     # one line from a multi\-line reply. It does not matter what we
     # substitute here as long it has the right syntax.  The Postfix
diff --git a/postfix/mantools/check-spell-proto-html b/postfix/mantools/check-spell-proto-html
index b81ce1f97..ffd427f96 100755
--- a/postfix/mantools/check-spell-proto-html
+++ b/postfix/mantools/check-spell-proto-html
@@ -4,4 +4,4 @@
 
 LANG=C; export LANG
 
-mantools/dehtml proto/*html proto/*.proto | spell | grep -F -vxf proto/stop | grep -F -vxf proto/stop.spell-proto-html
+mantools/dehtml proto/*html proto/*.proto | tr '+' ' ' | spell | grep -F -vxf proto/stop | grep -F -vxf proto/stop.spell-proto-html
diff --git a/postfix/proto/COMPATIBILITY_README.html b/postfix/proto/COMPATIBILITY_README.html
index 000f06269..20d72b423 100644
--- a/postfix/proto/COMPATIBILITY_README.html
+++ b/postfix/proto/COMPATIBILITY_README.html
@@ -614,7 +614,7 @@ make the backwards-compatible setting "smtp_tlsrpt_skip_reused_handshakes
 default setting xxx_security_level=(empty) 
 
 

Postfix version 3.11 changes the default value for client TLS -security levels from "empty" to "yes". The backwards-compatibility +security levels from "empty" to "may". The backwards-compatibility safety net is designed to prevent an unexpected change in mail sending behavior when Postfix is updated from an older version.

diff --git a/postfix/proto/postconf.proto b/postfix/proto/postconf.proto index e6aa3680b..0a4a9255e 100644 --- a/postfix/proto/postconf.proto +++ b/postfix/proto/postconf.proto @@ -1984,15 +1984,20 @@ environment. Examples of relevant environment variables:

Needed to make "postfix -c" work.
+
POSTLOG_HOSTNAME
+ +
Needed to make "maillog_file" work during daemon +process initialization.
+
POSTLOG_SERVICE
Needed to make "maillog_file" work during daemon process initialization.
-
POSTLOG_HOSTNAME
+
XDG_RUNTIME_DIR
-
Needed to make "maillog_file" work during daemon -process initialization.
+
Needed for debugging Postfix daemons with an XDG-style debugger. +
@@ -15098,6 +15103,12 @@ line.

 /etc/postfix/reply_filter:
+    # Some Microsoft servers violate RFC 2554 section 4, causing Postfix
+    # to complain with "non-empty initial GSSAPI challenge from server"
+    /^334\s+GSSAPI\s+supported/ 334
+
+ +
     # Transform garbage into "250-filler..." so that it looks like
     # one line from a multi-line reply. It does not matter what we
     # substitute here as long it has the right syntax.  The Postfix
diff --git a/postfix/proto/stop b/postfix/proto/stop
index c09b1c34e..5a7a3b12a 100644
--- a/postfix/proto/stop
+++ b/postfix/proto/stop
@@ -1683,3 +1683,4 @@ typofix
 LD
 PRELOAD
 rhansen
+XDG
diff --git a/postfix/proto/stop.double-history b/postfix/proto/stop.double-history
index cf5da6405..6fb0842b7 100644
--- a/postfix/proto/stop.double-history
+++ b/postfix/proto/stop.double-history
@@ -179,3 +179,5 @@ proto  proto COMPATIBILITY_README html
  postconf Makefile in postconf postconf c 
  dict_open Files util dict hc proxymap proxymap c 
  proxymap proxymap c 
+ postmap postmap c postalias postalias c 
+ client Files dict h dict_proxy c proxymap proxymap c 
diff --git a/postfix/proto/stop.spell-history b/postfix/proto/stop.spell-history
index da067ab97..42b996fab 100644
--- a/postfix/proto/stop.spell-history
+++ b/postfix/proto/stop.spell-history
@@ -106,3 +106,7 @@ Kozmenko
 Oleksandr
 Bataille
 balancers
+Unbroke
+XDG
+ENOTSOCK
+FustÃ
diff --git a/postfix/src/global/dict_proxy.c b/postfix/src/global/dict_proxy.c
index 0157d01a2..a0ce1b2e0 100644
--- a/postfix/src/global/dict_proxy.c
+++ b/postfix/src/global/dict_proxy.c
@@ -113,6 +113,7 @@ static int dict_proxy_sequence(DICT *dict, int function,
     VSTREAM *stream;
     int     status;
     int     count = 0;
+    int     inst_flags;
     int     request_flags;
 
     /*
@@ -126,8 +127,8 @@ static int dict_proxy_sequence(DICT *dict, int function,
     VSTRING_TERMINATE(dict_proxy->reskey);
     VSTRING_RESET(dict_proxy->result);
     VSTRING_TERMINATE(dict_proxy->result);
-    request_flags = dict_proxy->inst_flags
-	| (dict->flags & DICT_FLAG_RQST_MASK);
+    inst_flags = dict_proxy->inst_flags;
+    request_flags = dict->flags;
     for (;;) {
 	stream = clnt_stream_access(dict_proxy->clnt);
 	errno = 0;
@@ -136,15 +137,17 @@ static int dict_proxy_sequence(DICT *dict, int function,
 	    || attr_print(stream, ATTR_FLAG_NONE,
 			  SEND_ATTR_STR(MAIL_ATTR_REQ, PROXY_REQ_SEQUENCE),
 			  SEND_ATTR_STR(MAIL_ATTR_TABLE, dict->name),
+			  SEND_ATTR_INT(MAIL_ATTR_INST_FLAGS, inst_flags),
 			  SEND_ATTR_INT(MAIL_ATTR_FLAGS, request_flags),
 			  SEND_ATTR_INT(MAIL_ATTR_FUNC, function),
 			  ATTR_TYPE_END) != 0
 	    || vstream_fflush(stream)
 	    || attr_scan(stream, ATTR_FLAG_STRICT,
 			 RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+			 RECV_ATTR_INT(MAIL_ATTR_FLAGS, &dict->flags),
 			 RECV_ATTR_STR(MAIL_ATTR_KEY, dict_proxy->reskey),
 			 RECV_ATTR_STR(MAIL_ATTR_VALUE, dict_proxy->result),
-			 ATTR_TYPE_END) != 3) {
+			 ATTR_TYPE_END) != 4) {
 	    if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != ENOENT))
 		msg_warn("%s: service %s: %m", myname, dict_proxy->service);
 	} else {
@@ -194,6 +197,7 @@ static const char *dict_proxy_lookup(DICT *dict, const char *key)
     VSTREAM *stream;
     int     status;
     int     count = 0;
+    int     inst_flags;
     int     request_flags;
 
     /*
@@ -205,8 +209,8 @@ static const char *dict_proxy_lookup(DICT *dict, const char *key)
      */
     VSTRING_RESET(dict_proxy->result);
     VSTRING_TERMINATE(dict_proxy->result);
-    request_flags = dict_proxy->inst_flags
-	| (dict->flags & DICT_FLAG_RQST_MASK);
+    inst_flags = dict_proxy->inst_flags;
+    request_flags = dict->flags;
     for (;;) {
 	stream = clnt_stream_access(dict_proxy->clnt);
 	errno = 0;
@@ -215,14 +219,16 @@ static const char *dict_proxy_lookup(DICT *dict, const char *key)
 	    || attr_print(stream, ATTR_FLAG_NONE,
 			  SEND_ATTR_STR(MAIL_ATTR_REQ, PROXY_REQ_LOOKUP),
 			  SEND_ATTR_STR(MAIL_ATTR_TABLE, dict->name),
+			  SEND_ATTR_INT(MAIL_ATTR_INST_FLAGS, inst_flags),
 			  SEND_ATTR_INT(MAIL_ATTR_FLAGS, request_flags),
 			  SEND_ATTR_STR(MAIL_ATTR_KEY, key),
 			  ATTR_TYPE_END) != 0
 	    || vstream_fflush(stream)
 	    || attr_scan(stream, ATTR_FLAG_STRICT,
 			 RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
+			 RECV_ATTR_INT(MAIL_ATTR_FLAGS, &dict->flags),
 			 RECV_ATTR_STR(MAIL_ATTR_VALUE, dict_proxy->result),
-			 ATTR_TYPE_END) != 2) {
+			 ATTR_TYPE_END) != 3) {
 	    if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != ENOENT))
 		msg_warn("%s: service %s: %m", myname, dict_proxy->service);
 	} else {
@@ -267,6 +273,7 @@ static int dict_proxy_update(DICT *dict, const char *key, const char *value)
     VSTREAM *stream;
     int     status;
     int     count = 0;
+    int     inst_flags;
     int     request_flags;
 
     /*
@@ -276,8 +283,8 @@ static int dict_proxy_update(DICT *dict, const char *key, const char *value)
      * associated with a specific connection. Each lookup needs to specify
      * the table and the flags that were specified to dict_proxy_open().
      */
-    request_flags = dict_proxy->inst_flags
-	| (dict->flags & DICT_FLAG_RQST_MASK);
+    inst_flags = dict_proxy->inst_flags;
+    request_flags = dict->flags;
     for (;;) {
 	stream = clnt_stream_access(dict_proxy->clnt);
 	errno = 0;
@@ -286,6 +293,7 @@ static int dict_proxy_update(DICT *dict, const char *key, const char *value)
 	    || attr_print(stream, ATTR_FLAG_NONE,
 			  SEND_ATTR_STR(MAIL_ATTR_REQ, PROXY_REQ_UPDATE),
 			  SEND_ATTR_STR(MAIL_ATTR_TABLE, dict->name),
+			  SEND_ATTR_INT(MAIL_ATTR_INST_FLAGS, inst_flags),
 			  SEND_ATTR_INT(MAIL_ATTR_FLAGS, request_flags),
 			  SEND_ATTR_STR(MAIL_ATTR_KEY, key),
 			  SEND_ATTR_STR(MAIL_ATTR_VALUE, value),
@@ -293,7 +301,8 @@ static int dict_proxy_update(DICT *dict, const char *key, const char *value)
 	    || vstream_fflush(stream)
 	    || attr_scan(stream, ATTR_FLAG_STRICT,
 			 RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
-			 ATTR_TYPE_END) != 1) {
+			 RECV_ATTR_INT(MAIL_ATTR_FLAGS, &dict->flags),
+			 ATTR_TYPE_END) != 2) {
 	    if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != ENOENT))
 		msg_warn("%s: service %s: %m", myname, dict_proxy->service);
 	} else {
@@ -337,6 +346,7 @@ static int dict_proxy_delete(DICT *dict, const char *key)
     VSTREAM *stream;
     int     status;
     int     count = 0;
+    int     inst_flags;
     int     request_flags;
 
     /*
@@ -346,8 +356,8 @@ static int dict_proxy_delete(DICT *dict, const char *key)
      * associated with a specific connection. Each lookup needs to specify
      * the table and the flags that were specified to dict_proxy_open().
      */
-    request_flags = dict_proxy->inst_flags
-	| (dict->flags & DICT_FLAG_RQST_MASK);
+    inst_flags = dict_proxy->inst_flags;
+    request_flags = dict->flags;
     for (;;) {
 	stream = clnt_stream_access(dict_proxy->clnt);
 	errno = 0;
@@ -356,13 +366,15 @@ static int dict_proxy_delete(DICT *dict, const char *key)
 	    || attr_print(stream, ATTR_FLAG_NONE,
 			  SEND_ATTR_STR(MAIL_ATTR_REQ, PROXY_REQ_DELETE),
 			  SEND_ATTR_STR(MAIL_ATTR_TABLE, dict->name),
+			  SEND_ATTR_INT(MAIL_ATTR_INST_FLAGS, inst_flags),
 			  SEND_ATTR_INT(MAIL_ATTR_FLAGS, request_flags),
 			  SEND_ATTR_STR(MAIL_ATTR_KEY, key),
 			  ATTR_TYPE_END) != 0
 	    || vstream_fflush(stream)
 	    || attr_scan(stream, ATTR_FLAG_STRICT,
 			 RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
-			 ATTR_TYPE_END) != 1) {
+			 RECV_ATTR_INT(MAIL_ATTR_FLAGS, &dict->flags),
+			 ATTR_TYPE_END) != 2) {
 	    if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno !=
 					     ENOENT))
 		msg_warn("%s: service %s: %m", myname, dict_proxy->service);
@@ -478,16 +490,20 @@ DICT   *dict_proxy_open(const char *map, int open_flags, int dict_flags)
     dict_proxy->dict.delete = dict_proxy_delete;
     dict_proxy->dict.sequence = dict_proxy_sequence;
     dict_proxy->dict.close = dict_proxy_close;
-    dict_proxy->inst_flags = (dict_flags & DICT_FLAG_INST_MASK);
+    dict_proxy->inst_flags = dict_flags;
     dict_proxy->reskey = vstring_alloc(10);
     dict_proxy->result = vstring_alloc(10);
     dict_proxy->clnt = *pstream;
     dict_proxy->service = service;
 
+#define DICT_PROXY_ERR_RETURN(d) do { \
+	DICT *_d = (d); \
+	dict_proxy_close(&dict_proxy->dict); \
+	return (_d); \
+    } while (0)
+
     /*
      * Establish initial contact and get the map type specific flags.
-     * 
-     * XXX Should retrieve flags from local instance.
      */
     for (;;) {
 	stream = clnt_stream_access(dict_proxy->clnt);
@@ -496,7 +512,7 @@ DICT   *dict_proxy_open(const char *map, int open_flags, int dict_flags)
 	    || attr_print(stream, ATTR_FLAG_NONE,
 			  SEND_ATTR_STR(MAIL_ATTR_REQ, PROXY_REQ_OPEN),
 		      SEND_ATTR_STR(MAIL_ATTR_TABLE, dict_proxy->dict.name),
-		     SEND_ATTR_INT(MAIL_ATTR_FLAGS, dict_proxy->inst_flags),
+		 SEND_ATTR_INT(MAIL_ATTR_INST_FLAGS, dict_proxy->inst_flags),
 			  ATTR_TYPE_END) != 0
 	    || vstream_fflush(stream)
 	    || attr_scan(stream, ATTR_FLAG_STRICT,
@@ -512,14 +528,17 @@ DICT   *dict_proxy_open(const char *map, int open_flags, int dict_flags)
 			 dict_flags_str(server_flags));
 	    switch (status) {
 	    case PROXY_STAT_BAD:
-		msg_fatal("%s open failed for table \"%s\": invalid request",
-			  dict_proxy->service, dict_proxy->dict.name);
+		DICT_PROXY_ERR_RETURN(dict_surrogate(DICT_TYPE_PROXY,
+			      dict_proxy->dict.name, open_flags, dict_flags,
+			 "%s open failed for table \"%s\": invalid request",
+			       dict_proxy->service, dict_proxy->dict.name));
 	    case PROXY_STAT_DENY:
-		msg_fatal("%s service is not configured for table \"%s\"",
-			  dict_proxy->service, dict_proxy->dict.name);
+		DICT_PROXY_ERR_RETURN(dict_surrogate(DICT_TYPE_PROXY,
+			      dict_proxy->dict.name, open_flags, dict_flags,
+			    "%s service is not configured for table \"%s\"",
+			       dict_proxy->service, dict_proxy->dict.name));
 	    case PROXY_STAT_OK:
-		dict_proxy->dict.flags = (dict_flags & ~DICT_FLAG_IMPL_MASK)
-		    | (server_flags & DICT_FLAG_IMPL_MASK);
+		dict_proxy->dict.flags = server_flags;
 		return (&dict_proxy->dict);
 	    default:
 		msg_warn("%s open failed for table \"%s\": unexpected status %d",
diff --git a/postfix/src/global/mail_params.h b/postfix/src/global/mail_params.h
index 690256a60..cf7d791ea 100644
--- a/postfix/src/global/mail_params.h
+++ b/postfix/src/global/mail_params.h
@@ -2663,7 +2663,8 @@ extern int var_fflush_refresh;
 #define VAR_IMPORT_ENVIRON		"import_environment"
 #define DEF_IMPORT_ENVIRON		"MAIL_CONFIG MAIL_DEBUG MAIL_LOGTAG " \
 					"TZ XAUTHORITY DISPLAY LANG=C " \
-					"POSTLOG_SERVICE POSTLOG_HOSTNAME"
+					"POSTLOG_SERVICE POSTLOG_HOSTNAME" \
+					"XDG_RUNTIME_DIR"
 extern char *var_import_environ;
 
 #define VAR_EXPORT_ENVIRON		"export_environment"
diff --git a/postfix/src/global/mail_proto.h b/postfix/src/global/mail_proto.h
index 798de6694..d784ce42d 100644
--- a/postfix/src/global/mail_proto.h
+++ b/postfix/src/global/mail_proto.h
@@ -210,6 +210,8 @@ extern char *mail_pathname(const char *, const char *);
 #define MAIL_ATTR_COMPAT_LEVEL	"compatibility_level"
 #define MAIL_ATTR_MAIL_VERSION	"mail_version"
 
+#define MAIL_ATTR_INST_FLAGS	"instance_flags"
+
  /*
   * Suffixes for sender_name, sender_domain etc.
   */
diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h
index f19314a40..a1d0ccefd 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	"20250709"
+#define MAIL_RELEASE_DATE	"20250713"
 #define MAIL_VERSION_NUMBER	"3.11"
 
 #ifdef SNAPSHOT
diff --git a/postfix/src/master/event_server.c b/postfix/src/master/event_server.c
index 9802bdf2f..30e80ba5e 100644
--- a/postfix/src/master/event_server.c
+++ b/postfix/src/master/event_server.c
@@ -273,6 +273,7 @@ static unsigned event_server_generation;
 static void (*event_server_pre_disconn) (VSTREAM *, char *, char **);
 static void (*event_server_slow_exit) (char *, char **);
 static int event_server_watchdog = 1000;
+static int event_server_drain_was_called = 0;
 
 /* event_server_exit - normal termination */
 
@@ -327,6 +328,9 @@ int     event_server_drain(void)
     const char *myname = "event_server_drain";
     int     fd;
 
+    if (event_server_drain_was_called)
+	return;
+
     switch (fork()) {
 	/* Try again later. */
     case -1:
@@ -343,6 +347,7 @@ int     event_server_drain(void)
 		msg_warn("%s: dup2(%d, %d): %m", myname, STDIN_FILENO, fd);
 	}
 	var_use_limit = 1;
+	event_server_drain_was_called = 1;
 	return (0);
 	/* Let the master start a new process. */
     default:
@@ -445,6 +450,9 @@ static void event_server_accept_local(int unused_event, void *context)
     int     time_left = -1;
     int     fd;
 
+    if (event_server_drain_was_called)
+	return;
+
     /*
      * Be prepared for accept() to fail because some other process already
      * got the connection (the number of processes competing for clients is
@@ -457,6 +465,8 @@ static void event_server_accept_local(int unused_event, void *context)
 
     if (event_server_pre_accept)
 	event_server_pre_accept(event_server_name, event_server_argv);
+    if (event_server_drain_was_called)
+	return;
     fd = LOCAL_ACCEPT(listen_fd);
     if (event_server_lock != 0
 	&& myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK,
@@ -483,6 +493,9 @@ static void event_server_accept_pass(int unused_event, void *context)
     int     fd;
     HTABLE *attr = 0;
 
+    if (event_server_drain_was_called)
+	return;
+
     /*
      * Be prepared for accept() to fail because some other process already
      * got the connection (the number of processes competing for clients is
@@ -495,6 +508,8 @@ static void event_server_accept_pass(int unused_event, void *context)
 
     if (event_server_pre_accept)
 	event_server_pre_accept(event_server_name, event_server_argv);
+    if (event_server_drain_was_called)
+	return;
     fd = pass_accept_attr(listen_fd, &attr);
     if (event_server_lock != 0
 	&& myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK,
@@ -520,6 +535,9 @@ static void event_server_accept_inet(int unused_event, void *context)
     int     time_left = -1;
     int     fd;
 
+    if (event_server_drain_was_called)
+	return;
+
     /*
      * Be prepared for accept() to fail because some other process already
      * got the connection (the number of processes competing for clients is
@@ -532,6 +550,8 @@ static void event_server_accept_inet(int unused_event, void *context)
 
     if (event_server_pre_accept)
 	event_server_pre_accept(event_server_name, event_server_argv);
+    if (event_server_drain_was_called)
+	return;
     fd = inet_accept(listen_fd);
     if (event_server_lock != 0
 	&& myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK,
diff --git a/postfix/src/master/multi_server.c b/postfix/src/master/multi_server.c
index 6150f229a..51004d6d0 100644
--- a/postfix/src/master/multi_server.c
+++ b/postfix/src/master/multi_server.c
@@ -260,6 +260,7 @@ static VSTREAM *multi_server_lock;
 static int multi_server_in_flow_delay;
 static unsigned multi_server_generation;
 static void (*multi_server_pre_disconn) (VSTREAM *, char *, char **);
+static int multi_server_drain_was_called = 0;
 
 /* multi_server_exit - normal termination */
 
@@ -295,6 +296,9 @@ int     multi_server_drain(void)
     const char *myname = "multi_server_drain";
     int     fd;
 
+    if (multi_server_drain_was_called)
+	return;
+
     switch (fork()) {
 	/* Try again later. */
     case -1:
@@ -311,6 +315,7 @@ int     multi_server_drain(void)
 		msg_warn("%s: dup2(%d, %d): %m", myname, STDIN_FILENO, fd);
 	}
 	var_use_limit = 1;
+	multi_server_drain_was_called = 1;
 	return (0);
 	/* Let the master start a new process. */
     default:
@@ -429,6 +434,9 @@ static void multi_server_accept_local(int unused_event, void *context)
     int     time_left = -1;
     int     fd;
 
+    if (multi_server_drain_was_called)
+	return;
+
     /*
      * Be prepared for accept() to fail because some other process already
      * got the connection (the number of processes competing for clients is
@@ -441,6 +449,8 @@ static void multi_server_accept_local(int unused_event, void *context)
 
     if (multi_server_pre_accept)
 	multi_server_pre_accept(multi_server_name, multi_server_argv);
+    if (multi_server_drain_was_called)
+	return;
     fd = LOCAL_ACCEPT(listen_fd);
     if (multi_server_lock != 0
 	&& myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK,
@@ -467,6 +477,9 @@ static void multi_server_accept_pass(int unused_event, void *context)
     int     fd;
     HTABLE *attr = 0;
 
+    if (multi_server_drain_was_called)
+	return;
+
     /*
      * Be prepared for accept() to fail because some other process already
      * got the connection (the number of processes competing for clients is
@@ -479,6 +492,8 @@ static void multi_server_accept_pass(int unused_event, void *context)
 
     if (multi_server_pre_accept)
 	multi_server_pre_accept(multi_server_name, multi_server_argv);
+    if (multi_server_drain_was_called)
+	return;
     fd = pass_accept_attr(listen_fd, &attr);
     if (multi_server_lock != 0
 	&& myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK,
@@ -504,6 +519,9 @@ static void multi_server_accept_inet(int unused_event, void *context)
     int     time_left = -1;
     int     fd;
 
+    if (multi_server_drain_was_called)
+	return;
+
     /*
      * Be prepared for accept() to fail because some other process already
      * got the connection (the number of processes competing for clients is
@@ -516,6 +534,8 @@ static void multi_server_accept_inet(int unused_event, void *context)
 
     if (multi_server_pre_accept)
 	multi_server_pre_accept(multi_server_name, multi_server_argv);
+    if (multi_server_drain_was_called)
+	return;
     fd = inet_accept(listen_fd);
     if (multi_server_lock != 0
 	&& myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK,
diff --git a/postfix/src/postalias/postalias.c b/postfix/src/postalias/postalias.c
index c17b6a1d6..9e77a6b20 100644
--- a/postfix/src/postalias/postalias.c
+++ b/postfix/src/postalias/postalias.c
@@ -690,8 +690,6 @@ static void postalias_seq(const char *map_type, const char *map_name,
     const char *value;
     int     func;
 
-    if (strcmp(map_type, DICT_TYPE_PROXY) == 0)
-	msg_fatal("can't sequence maps via the proxy service");
     dict = dict_open3(map_type, map_name, O_RDONLY, dict_flags);
     for (func = DICT_SEQ_FUN_FIRST; /* void */ ; func = DICT_SEQ_FUN_NEXT) {
 	if (dict_seq(dict, func, &key, &value) != 0)
diff --git a/postfix/src/postmap/postmap.c b/postfix/src/postmap/postmap.c
index 9e8c78381..8f4a83c37 100644
--- a/postfix/src/postmap/postmap.c
+++ b/postfix/src/postmap/postmap.c
@@ -899,8 +899,6 @@ static void postmap_seq(const char *map_type, const char *map_name,
     const char *value;
     int     func;
 
-    if (strcmp(map_type, DICT_TYPE_PROXY) == 0)
-	msg_fatal("can't sequence maps via the proxy service");
     dict = dict_open3(map_type, map_name, O_RDONLY, dict_flags);
     for (func = DICT_SEQ_FUN_FIRST; /* void */ ; func = DICT_SEQ_FUN_NEXT) {
 	if (dict_seq(dict, func, &key, &value) != 0)
diff --git a/postfix/src/proxymap/proxymap.c b/postfix/src/proxymap/proxymap.c
index 3a00c33c1..270e491a0 100644
--- a/postfix/src/proxymap/proxymap.c
+++ b/postfix/src/proxymap/proxymap.c
@@ -320,7 +320,7 @@ static char *get_nested_dict_name(char *type_name)
 
 /* proxy_map_find - look up or open table */
 
-static DICT *proxy_map_find(const char *map_type_name, int request_flags,
+static DICT *proxy_map_find(const char *map_type_name, int inst_flags,
 			            int *statp)
 {
     DICT   *dict;
@@ -354,26 +354,12 @@ static DICT *proxy_map_find(const char *map_type_name, int request_flags,
 
     /*
      * Open one instance of a map for each combination of name+flags.
-     * 
-     * Assume that a map instance can be shared among clients with different
-     * paranoia flag settings and with different map lookup flag settings.
-     * 
-     * XXX The open() flags are passed implicitly, via the selection of the
-     * service name. For a more sophisticated interface, appropriate subsets
-     * of open() flags should be received directly from the client.
      */
-    vstring_sprintf(map_type_name_flags, "%s:%s", map_type_name,
-		    dict_flags_str(request_flags & DICT_FLAG_INST_MASK));
-    if (msg_verbose)
-	msg_info("proxy_map_find: %s", STR(map_type_name_flags));
-    if ((dict = dict_handle(STR(map_type_name_flags))) == 0) {
-	dict = dict_open(map_type_name, proxy_writer ?
-			 WRITE_OPEN_FLAGS : READ_OPEN_FLAGS,
-			 request_flags);
-	if (dict == 0)
-	    msg_panic("proxy_map_find: dict_open null result");
-	dict_register(STR(map_type_name_flags), dict);
-    }
+    dict = dict_open(map_type_name, proxy_writer ?
+		     WRITE_OPEN_FLAGS : READ_OPEN_FLAGS,
+		     inst_flags);
+    if (dict == 0)
+	msg_panic("proxy_map_find: dict_open null result");
     dict->error = 0;
     return (dict);
 }
@@ -382,6 +368,7 @@ static DICT *proxy_map_find(const char *map_type_name, int request_flags,
 
 static void proxymap_sequence_service(VSTREAM *client_stream)
 {
+    int     inst_flags;
     int     request_flags;
     DICT   *dict;
     int     request_func;
@@ -395,19 +382,19 @@ static void proxymap_sequence_service(VSTREAM *client_stream)
      */
     if (attr_scan(client_stream, ATTR_FLAG_STRICT,
 		  RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map),
+		  RECV_ATTR_INT(MAIL_ATTR_INST_FLAGS, &inst_flags),
 		  RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags),
 		  RECV_ATTR_INT(MAIL_ATTR_FUNC, &request_func),
-		  ATTR_TYPE_END) != 3
+		  ATTR_TYPE_END) != 4
 	|| (request_func != DICT_SEQ_FUN_FIRST
 	    && request_func != DICT_SEQ_FUN_NEXT)) {
 	reply_status = PROXY_STAT_BAD;
 	reply_key = reply_value = "";
-    } else if ((dict = proxy_map_find(STR(request_map), request_flags,
+    } else if ((dict = proxy_map_find(STR(request_map), inst_flags,
 				      &reply_status)) == 0) {
 	reply_key = reply_value = "";
     } else {
-	dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK)
-		       | (request_flags & DICT_FLAG_RQST_MASK));
+	dict->flags = request_flags;
 	dict_status = dict_seq(dict, request_func, &reply_key, &reply_value);
 	if (dict_status == 0) {
 	    reply_status = PROXY_STAT_OK;
@@ -426,6 +413,7 @@ static void proxymap_sequence_service(VSTREAM *client_stream)
      */
     attr_print(client_stream, ATTR_FLAG_NONE,
 	       SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status),
+	       SEND_ATTR_INT(MAIL_ATTR_FLAGS, dict->flags),
 	       SEND_ATTR_STR(MAIL_ATTR_KEY, reply_key),
 	       SEND_ATTR_STR(MAIL_ATTR_VALUE, reply_value),
 	       ATTR_TYPE_END);
@@ -435,6 +423,7 @@ static void proxymap_sequence_service(VSTREAM *client_stream)
 
 static void proxymap_lookup_service(VSTREAM *client_stream)
 {
+    int     inst_flags;
     int     request_flags;
     DICT   *dict;
     const char *reply_value;
@@ -445,16 +434,16 @@ static void proxymap_lookup_service(VSTREAM *client_stream)
      */
     if (attr_scan(client_stream, ATTR_FLAG_STRICT,
 		  RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map),
+		  RECV_ATTR_INT(MAIL_ATTR_INST_FLAGS, &inst_flags),
 		  RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags),
 		  RECV_ATTR_STR(MAIL_ATTR_KEY, request_key),
-		  ATTR_TYPE_END) != 3) {
+		  ATTR_TYPE_END) != 4) {
 	reply_status = PROXY_STAT_BAD;
 	reply_value = "";
-    } else if ((dict = proxy_map_find(STR(request_map), request_flags,
+    } else if ((dict = proxy_map_find(STR(request_map), inst_flags,
 				      &reply_status)) == 0) {
 	reply_value = "";
-    } else if (dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK)
-			      | (request_flags & DICT_FLAG_RQST_MASK)),
+    } else if (dict->flags = request_flags,
 	       (reply_value = dict_get(dict, STR(request_key))) != 0) {
 	reply_status = PROXY_STAT_OK;
     } else if (dict->error == 0) {
@@ -471,6 +460,7 @@ static void proxymap_lookup_service(VSTREAM *client_stream)
      */
     attr_print(client_stream, ATTR_FLAG_NONE,
 	       SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status),
+	       SEND_ATTR_INT(MAIL_ATTR_FLAGS, dict->flags),
 	       SEND_ATTR_STR(MAIL_ATTR_VALUE, reply_value),
 	       ATTR_TYPE_END);
 }
@@ -479,6 +469,7 @@ static void proxymap_lookup_service(VSTREAM *client_stream)
 
 static void proxymap_update_service(VSTREAM *client_stream)
 {
+    int     inst_flags;
     int     request_flags;
     DICT   *dict;
     int     dict_status;
@@ -495,21 +486,22 @@ static void proxymap_update_service(VSTREAM *client_stream)
      */
     if (attr_scan(client_stream, ATTR_FLAG_STRICT,
 		  RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map),
+		  RECV_ATTR_INT(MAIL_ATTR_INST_FLAGS, &inst_flags),
 		  RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags),
 		  RECV_ATTR_STR(MAIL_ATTR_KEY, request_key),
 		  RECV_ATTR_STR(MAIL_ATTR_VALUE, request_value),
-		  ATTR_TYPE_END) != 4) {
+		  ATTR_TYPE_END) != 5) {
 	reply_status = PROXY_STAT_BAD;
     } else if (proxy_writer == 0) {
 	msg_warn("refusing %s update request on non-%s service",
 		 STR(request_map), MAIL_SERVICE_PROXYWRITE);
 	reply_status = PROXY_STAT_DENY;
-    } else if ((dict = proxy_map_find(STR(request_map), request_flags,
+    } else if ((dict = proxy_map_find(STR(request_map), inst_flags,
 				      &reply_status)) == 0) {
 	 /* void */ ;
     } else {
-	dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK)
-		       | (request_flags & DICT_FLAG_RQST_MASK)
+	/* Sync the table now. Don't abort on duplicate update. */
+	dict->flags = (request_flags
 		       | DICT_FLAG_SYNC_UPDATE | DICT_FLAG_DUP_REPLACE);
 	dict_status = dict_put(dict, STR(request_key), STR(request_value));
 	if (dict_status == 0) {
@@ -527,6 +519,7 @@ static void proxymap_update_service(VSTREAM *client_stream)
      */
     attr_print(client_stream, ATTR_FLAG_NONE,
 	       SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status),
+	       SEND_ATTR_INT(MAIL_ATTR_FLAGS, dict->flags),
 	       ATTR_TYPE_END);
 }
 
@@ -534,6 +527,7 @@ static void proxymap_update_service(VSTREAM *client_stream)
 
 static void proxymap_delete_service(VSTREAM *client_stream)
 {
+    int     inst_flags;
     int     request_flags;
     DICT   *dict;
     int     dict_status;
@@ -547,20 +541,21 @@ static void proxymap_delete_service(VSTREAM *client_stream)
      */
     if (attr_scan(client_stream, ATTR_FLAG_STRICT,
 		  RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map),
+		  RECV_ATTR_INT(MAIL_ATTR_INST_FLAGS, &inst_flags),
 		  RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags),
 		  RECV_ATTR_STR(MAIL_ATTR_KEY, request_key),
-		  ATTR_TYPE_END) != 3) {
+		  ATTR_TYPE_END) != 4) {
 	reply_status = PROXY_STAT_BAD;
     } else if (proxy_writer == 0) {
 	msg_warn("refusing %s delete request on non-%s service",
 		 STR(request_map), MAIL_SERVICE_PROXYWRITE);
 	reply_status = PROXY_STAT_DENY;
-    } else if ((dict = proxy_map_find(STR(request_map), request_flags,
+    } else if ((dict = proxy_map_find(STR(request_map), inst_flags,
 				      &reply_status)) == 0) {
 	 /* void */ ;
     } else {
-	dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK)
-		       | (request_flags & DICT_FLAG_RQST_MASK)
+	/* Sync the table now. There is no close() request. */
+	dict->flags = (request_flags
 		       | DICT_FLAG_SYNC_UPDATE);
 	dict_status = dict_del(dict, STR(request_key));
 	if (dict_status == 0) {
@@ -578,6 +573,7 @@ static void proxymap_delete_service(VSTREAM *client_stream)
      */
     attr_print(client_stream, ATTR_FLAG_NONE,
 	       SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status),
+	       SEND_ATTR_INT(MAIL_ATTR_FLAGS, dict->flags),
 	       ATTR_TYPE_END);
 }
 
@@ -585,7 +581,7 @@ static void proxymap_delete_service(VSTREAM *client_stream)
 
 static void proxymap_open_service(VSTREAM *client_stream)
 {
-    int     request_flags;
+    int     inst_flags;
     DICT   *dict;
     int     reply_status;
     int     reply_flags;
@@ -595,11 +591,11 @@ static void proxymap_open_service(VSTREAM *client_stream)
      */
     if (attr_scan(client_stream, ATTR_FLAG_STRICT,
 		  RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map),
-		  RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags),
+		  RECV_ATTR_INT(MAIL_ATTR_INST_FLAGS, &inst_flags),
 		  ATTR_TYPE_END) != 2) {
 	reply_status = PROXY_STAT_BAD;
 	reply_flags = 0;
-    } else if ((dict = proxy_map_find(STR(request_map), request_flags,
+    } else if ((dict = proxy_map_find(STR(request_map), inst_flags,
 				      &reply_status)) == 0) {
 	reply_flags = 0;
     } else {
@@ -843,14 +839,6 @@ int     main(int argc, char **argv)
      */
     MAIL_VERSION_STAMP_ALLOCATE;
 
-    /*
-     * Workaround for programs that make explicit dict_register() calls with
-     * a table that is already registered under a different name. This is
-     * safe only in programs that do not unregister or close a table that is
-     * registered with multiple names.
-     */
-    dict_allow_multiple_dict_register_names = 1;
-
     /*
      * XXX When invoked with the master.cf service name "proxywrite", the
      * proxymap daemon will allow update requests. To update a table that is
diff --git a/postfix/src/util/dict.c b/postfix/src/util/dict.c
index 87dee602f..943772325 100644
--- a/postfix/src/util/dict.c
+++ b/postfix/src/util/dict.c
@@ -82,8 +82,6 @@
 /*	const char *name,
 /*	int	open_flags,
 /*	int	dict_flags)
-/*
-/*	int	dict_allow_multiple_dict_register_names;
 /* DESCRIPTION
 /*	This module maintains a collection of name-value dictionaries.
 /*	Each dictionary has its own name and has its own methods to read
@@ -187,14 +185,6 @@
 /*	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.
-/*
-/*	dict_allow_multiple_dict_register_names enables a temporary
-/*	workaround for programs that make explicit dict_register()
-/*	calls with a table that is already registered under a different
-/*	name. Setting this to non-zero allows a dictionary to be
-/*	registered under multiple names. This workaround is safe only
-/*	in programs that do not unregister or close a table that is
-/*	registered with multiple names.
 /* TRUST AND PROVENANCE
 /* .ad
 /* .fi
@@ -339,14 +329,6 @@ typedef struct {
 	dict = node->dict; \
 } while (0)
 
- /*
-  * Workaround for programs that make explicit dict_register() calls with
-  * tables that are already registered under a different name. This is safe
-  * only in programs that do not unregister or close a table that is
-  * registered with multiple names.
-  */
-int     dict_allow_multiple_dict_register_names = 0;
-
 #define STR(x)	vstring_str(x)
 
 /* dict_register_close - trigger dictionary cleanup */
@@ -367,8 +349,7 @@ void    dict_register(const char *dict_name, DICT *dict_info)
     /*
      * Enforce referential integrity.
      */
-    if (dict_allow_multiple_dict_register_names == 0
-	&& dict_info->reg_name && strcmp(dict_name, dict_info->reg_name) != 0)
+    if (dict_info->reg_name && strcmp(dict_name, dict_info->reg_name) != 0)
 	msg_panic("%s: '%s:%s' is already registered under '%s' and cannot "
 		  "also be registered under '%s'", myname, dict_info->type,
 		  dict_info->name, dict_info->reg_name, dict_name);
diff --git a/postfix/src/util/dict.h b/postfix/src/util/dict.h
index 595f18ed0..36def9b3c 100644
--- a/postfix/src/util/dict.h
+++ b/postfix/src/util/dict.h
@@ -141,32 +141,12 @@ extern void dict_free(DICT *);
   * The subsets of flags that control how a map is used. These are relevant
   * mainly for proxymap support. Note: some categories overlap.
   * 
-  * DICT_FLAG_IMPL_MASK - flags that are set by the map implementation itself.
-  * 
   * DICT_FLAG_PARANOID - requestor flags that forbid the use of insecure map
   * types for security-sensitive operations. These flags are checked by the
   * map implementation itself upon open, lookup etc. requests.
-  * 
-  * DICT_FLAG_RQST_MASK - all requestor flags, including paranoid flags, that
-  * the requestor may change between open, lookup etc. requests. These
-  * specify requestor properties, not map properties.
-  * 
-  * DICT_FLAG_INST_MASK - none of the above flags. The requestor may not change
-  * these flags between open, lookup, etc. requests (although a map may make
-  * changes to its copy of some of these flags). The proxymap server opens
-  * only one map instance for all client requests with the same values of
-  * these flags, and the proxymap client uses its own saved copy of these
-  * flags. DICT_FLAG_SRC_RHS_IS_FILE is an example of such a flag.
   */
 #define DICT_FLAG_PARANOID \
 	(DICT_FLAG_NO_REGSUB | DICT_FLAG_NO_PROXY | DICT_FLAG_NO_UNAUTH)
-#define DICT_FLAG_IMPL_MASK	(DICT_FLAG_FIXED | DICT_FLAG_PATTERN | \
-				DICT_FLAG_MULTI_WRITER)
-#define DICT_FLAG_RQST_MASK	(DICT_FLAG_FOLD_ANY | DICT_FLAG_LOCK | \
-				DICT_FLAG_DUP_REPLACE | DICT_FLAG_DUP_WARN | \
-				DICT_FLAG_DUP_IGNORE | DICT_FLAG_SYNC_UPDATE | \
-				DICT_FLAG_PARANOID | DICT_FLAG_UTF8_MASK)
-#define DICT_FLAG_INST_MASK	~(DICT_FLAG_IMPL_MASK | DICT_FLAG_RQST_MASK)
 
  /*
   * Feature tests.
@@ -286,14 +266,6 @@ extern DICT *PRINTFLIKE(5, 6) dict_surrogate(const char *, const char *, int, in
 extern char *dict_make_registered_name(VSTRING *, const char *, int, int);
 extern char *dict_make_registered_name4(VSTRING *, const char *, const char *, int, int);
 
- /*
-  * Workaround for programs that make explicit dict_register() calls with a
-  * table that is already registered under a different name. This is safe
-  * only in programs that do not unregister or close a table that is
-  * registered with multiple names.
-  */
-extern int dict_allow_multiple_dict_register_names;
-
  /*
   * This name is reserved for matchlist error handling.
   */
diff --git a/postfix/src/util/dict_alloc.c b/postfix/src/util/dict_alloc.c
index d780ecf97..bea8e7d7e 100644
--- a/postfix/src/util/dict_alloc.c
+++ b/postfix/src/util/dict_alloc.c
@@ -147,7 +147,7 @@ DICT   *dict_alloc(const char *dict_type, const char *dict_name, ssize_t size)
 
     dict->type = mystrdup(dict_type);
     dict->name = mystrdup(dict_name);
-    dict->flags = DICT_FLAG_FIXED;
+    dict->flags = 0;
     dict->lookup = dict_default_lookup;
     dict->update = dict_default_update;
     dict->delete = dict_default_delete;
diff --git a/postfix/src/util/dict_test.c b/postfix/src/util/dict_test.c
index bd2cadd4d..26fcd0975 100644
--- a/postfix/src/util/dict_test.c
+++ b/postfix/src/util/dict_test.c
@@ -146,14 +146,8 @@ void    dict_test(int argc, char **argv)
 	    vstream_printf("dict flags %s\n",
 			   dict_flags_str(dict->flags));
 	} else if (strcmp(cmd, "masks") == 0 && !key && !value) {
-	    vstream_printf("DICT_FLAG_IMPL_MASK %s\n",
-			   dict_flags_str(DICT_FLAG_IMPL_MASK));
 	    vstream_printf("DICT_FLAG_PARANOID %s\n",
 			   dict_flags_str(DICT_FLAG_PARANOID));
-	    vstream_printf("DICT_FLAG_RQST_MASK %s\n",
-			   dict_flags_str(DICT_FLAG_RQST_MASK));
-	    vstream_printf("DICT_FLAG_INST_MASK %s\n",
-			   dict_flags_str(DICT_FLAG_INST_MASK));
 	} else {
 	    vstream_printf("usage: %s\n", USAGE);
 	}