]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-3.11-20250803
authorWietse Z Venema <wietse@porcupine.org>
Sun, 3 Aug 2025 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <ietf-dane@dukhovni.org>
Thu, 7 Aug 2025 18:21:52 +0000 (04:21 +1000)
14 files changed:
postfix/HISTORY
postfix/html/postconf.5.html
postfix/html/smtpd.8.html
postfix/man/man5/postconf.5
postfix/man/man8/smtpd.8
postfix/mantools/postlink
postfix/proto/postconf.proto
postfix/proto/stop.double-history
postfix/proto/stop.spell-cc
postfix/src/global/mail_params.h
postfix/src/global/mail_version.h
postfix/src/smtp/smtp_connect.c
postfix/src/smtpd/smtpd.c
postfix/src/smtpd/smtpd_chat.c

index 1982d6e69ad15c0c61f9e6cdff677d23d4e66759..98fd5f0ab379c528210363f52bc07dadf2c72df4 100644 (file)
@@ -29530,3 +29530,21 @@ Apologies for any names omitted.
        change did not have space between POSTLOG_HOSTNAME and
        XDG_RUNTIME_DIR, breaking maillog_file support and graphical
        debugging. File: global/mail_params.h.
+
+20250801
+
+       Feature: smtpd_reject_filter_maps can selectively replace a
+       reject response from the Postfix SMTP server, or from a
+       program that replies through the Postfix SMTP server. Files:
+       smtpd/smtpd.c, smtpd/smtpd_chat.c, global/mail_params.h,
+       proto/postconf.proto, mantools/postlink.
+
+20250803
+
+       Cleanup: when "tls_required_enable = yes" and a message
+       contains a "TLS-Required: no" header", the Postfix SMTP
+       client now also ignores the recipient-side TLSRPT policy,
+       in addition to the already ignored recipient-side MTA-STS
+       and DANE policies. This prevents TLSRPT notifications for
+       all SMTP deliveries that do not require TLS. File:
+       smtp/smtp_connect.c.
index 9b03afe72b2f08805f77709a364faf8e32891ae6..b61babdf24cf6d3816389339a98abd38e0e43a28 100644 (file)
@@ -17393,6 +17393,58 @@ Example:
 </pre>
 
 
+</DD>
+
+<DT><b><a name="smtpd_reject_filter_maps">smtpd_reject_filter_maps</a>
+(default: empty)</b></DT><DD>
+
+<p> An optional filter that can replace a reject response from the
+Postfix SMTP server itself, or from a program that replies through
+the Postfix SMTP server. The filter is applied before the optional
+reject footers are appended. Typically, the filter will be a <a href="regexp_table.5.html">regexp</a>:
+or <a href="pcre_table.5.html">pcre</a>: table, where the left-hand side specifies a pattern, and
+the right-hand side specifies replacement text. </p>
+
+<p> The input is a server response that starts with a 4XX or 5XX
+reply code (see <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a>), usually followed by an enhanced status
+code (see <a href="https://tools.ietf.org/html/rfc3463">RFC 3463</a>) and text. The filter returns replacement text
+or indicates that there was no match. This feature cannot be used
+to change a reject reply into a non-reject one or vice versa. </p>
+
+<p> LIMITATION: <a href="postconf.5.html#smtpd_reject_filter_maps">smtpd_reject_filter_maps</a> will not replace text that
+was already logged before the Postfix SMTP server replies to the
+remote SMTP client. To help with logfile analysis, the Postfix SMTP
+server logs both the unmodified reply (logged below as "reject
+filter in") and the replacement reply (logged below as "reject
+filter out").
+
+<p> Example: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+    <a href="postconf.5.html#smtpd_reject_filter_maps">smtpd_reject_filter_maps</a> = <a href="regexp_table.5.html">regexp</a>:/etc/postfix/smtpd_reject_filter
+</pre>
+
+<pre>
+/etc/postfix/smtpd_reject_filter:
+    # Replace soft reject with hard reject.
+    /^451 4(\.6\.0 Alias expansion error)/ 550 5${1}
+</pre>
+
+<pre>
+    # Silly rule for demo purposes.
+    /^(4.+[^.])\.*$/ $1. See you later.
+</pre>
+
+<pre>
+/var/log/maillog:
+    NOQUEUE: reject filter in: 451 4.6.0 Alias expansion error
+    NOQUEUE: reject filter out: 550 5.6.0 Alias expansion error
+</pre>
+
+<p> This feature is available in Postfix &ge; 3.11. </p>
+
+
 </DD>
 
 <DT><b><a name="smtpd_reject_footer">smtpd_reject_footer</a>
index 47be68d078ac04398cf3bd0fcec6dd2162c58ab1..8465bbf2529157c6c7e8a9c0a7726116aa46448c 100644 (file)
@@ -1437,6 +1437,13 @@ SMTPD(8)                                                              SMTPD(8)
               Do  not  include  SMTP client session information in the Postfix
               SMTP server's Received: message header.
 
+       Available in Postfix version 3.11 and later:
+
+       <b><a href="postconf.5.html#smtpd_reject_filter_maps">smtpd_reject_filter_maps</a> (empty)</b>
+              An optional filter that can replace a reject response  from  the
+              Postfix  SMTP  server  itself,  or  from  a program that replies
+              through the Postfix SMTP server.
+
 <b><a name="see_also">SEE ALSO</a></b>
        <a href="anvil.8.html">anvil(8)</a>, connection/rate limiting
        <a href="cleanup.8.html">cleanup(8)</a>, message canonicalization
index 8c2039ecb6ec60819afe52fc406dbc83bdbc92dd..73fce25ddfb2f4b6eb2679c7ee450985f27519fe 100644 (file)
@@ -11815,6 +11815,60 @@ Example:
 smtpd_recipient_restrictions = permit_mynetworks, reject_unauth_destination
 .fi
 .ad
+.SH smtpd_reject_filter_maps (default: empty)
+An optional filter that can replace a reject response from the
+Postfix SMTP server itself, or from a program that replies through
+the Postfix SMTP server. The filter is applied before the optional
+reject footers are appended. Typically, the filter will be a regexp:
+or pcre: table, where the left\-hand side specifies a pattern, and
+the right\-hand side specifies replacement text.
+.PP
+The input is a server response that starts with a 4XX or 5XX
+reply code (see RFC 5321), usually followed by an enhanced status
+code (see RFC 3463) and text. The filter returns replacement text
+or indicates that there was no match. This feature cannot be used
+to change a reject reply into a non\-reject one or vice versa.
+.PP
+LIMITATION: smtpd_reject_filter_maps will not replace text that
+was already logged before the Postfix SMTP server replies to the
+remote SMTP client. To help with logfile analysis, the Postfix SMTP
+server logs both the unmodified reply (logged below as "reject
+filter in") and the replacement reply (logged below as "reject
+filter out").
+.PP
+Example:
+.PP
+.nf
+.na
+/etc/postfix/main.cf:
+    smtpd_reject_filter_maps = regexp:/etc/postfix/smtpd_reject_filter
+.fi
+.ad
+.PP
+.nf
+.na
+/etc/postfix/smtpd_reject_filter:
+    # Replace soft reject with hard reject.
+    /^451 4(\e.6\e.0 Alias expansion error)/ 550 5${1}
+.fi
+.ad
+.PP
+.nf
+.na
+    # Silly rule for demo purposes.
+    /^(4.+[^.])\e.*$/ $1. See you later.
+.fi
+.ad
+.PP
+.nf
+.na
+/var/log/maillog:
+    NOQUEUE: reject filter in: 451 4.6.0 Alias expansion error
+    NOQUEUE: reject filter out: 550 5.6.0 Alias expansion error
+.fi
+.ad
+.PP
+This feature is available in Postfix >= 3.11.
 .SH smtpd_reject_footer (default: empty)
 Optional information that is appended after each Postfix SMTP
 server
index a671d476bbeb97bce3fb30034a2684e37e9e2ee6..616a4a14af5209ef3eb8a12661703d40dfc69aef 100644 (file)
@@ -1237,6 +1237,12 @@ Available in Postfix 3.10 and later:
 .IP "\fBsmtpd_hide_client_session (no)\fR"
 Do not include SMTP client session information in the Postfix
 SMTP server's Received: message header.
+.PP
+Available in Postfix version 3.11 and later:
+.IP "\fBsmtpd_reject_filter_maps (empty)\fR"
+An optional filter that can replace a reject response from the
+Postfix SMTP server itself, or from a program that replies through
+the Postfix SMTP server.
 .SH "SEE ALSO"
 .na
 .nf
index d641b89953d98477aef32ef2fe5141667f481abd..061e0aef1c49be2667d1db93e5cf7e562bf46684 100755 (executable)
@@ -770,6 +770,7 @@ while (<>) {
     s;\bsmtpd_use_tls\b;<a href="postconf.5.html#smtpd_use_tls">$&</a>;g;
     s;\bsmtpd_reject_footer\b;<a href="postconf.5.html#smtpd_reject_footer">$&</a>;g;
     s;\bsmtpd_reject_footer_maps\b;<a href="postconf.5.html#smtpd_reject_footer_maps">$&</a>;g;
+    s;\bsmtpd_reject_filter_maps\b;<a href="postconf.5.html#smtpd_reject_filter_maps">$&</a>;g;
     s;\bsmtpd_per_record_deadline\b;<a href="postconf.5.html#smtpd_per_record_deadline">$&</a>;g;
     s;\bsmtpd_per_request_deadline\b;<a href="postconf.5.html#smtpd_per_request_deadline">$&</a>;g;
     s;\bsmtpd_min_data_rate\b;<a href="postconf.5.html#smtpd_min_data_rate">$&</a>;g;
index 0a4a9255e183169691216e800fa49460df17a565..666264fe239493d073acf54b087492bbf7eded3a 100644 (file)
@@ -19661,3 +19661,51 @@ and therefore it does not need to provide the information required by
 RFC 5321. The form does still meet RFC 5322 requirements. </p>
 
 <p> This feature is available in Postfix &ge; 3.10. </p>
+
+%PARAM smtpd_reject_filter_maps
+
+<p> An optional filter that can replace a reject response from the
+Postfix SMTP server itself, or from a program that replies through
+the Postfix SMTP server. The filter is applied before the optional
+reject footers are appended. Typically, the filter will be a regexp:
+or pcre: table, where the left-hand side specifies a pattern, and
+the right-hand side specifies replacement text. </p>
+
+<p> The input is a server response that starts with a 4XX or 5XX
+reply code (see RFC 5321), usually followed by an enhanced status
+code (see RFC 3463) and text. The filter returns replacement text
+or indicates that there was no match. This feature cannot be used
+to change a reject reply into a non-reject one or vice versa. </p>
+
+<p> LIMITATION: smtpd_reject_filter_maps will not replace text that
+was already logged before the Postfix SMTP server replies to the
+remote SMTP client. To help with logfile analysis, the Postfix SMTP
+server logs both the unmodified reply (logged below as "reject
+filter in") and the replacement reply (logged below as "reject
+filter out").
+
+<p> Example: </p>
+
+<pre>
+/etc/postfix/main.cf:
+    smtpd_reject_filter_maps = regexp:/etc/postfix/smtpd_reject_filter
+</pre>
+
+<pre>
+/etc/postfix/smtpd_reject_filter:
+    # Replace soft reject with hard reject.
+    /^451 4(\.6\.0 Alias expansion error)/ 550 5${1}
+</pre>
+
+<pre>
+    # Silly rule for demo purposes.
+    /^(4.+[^.])\.*$/ $1. See you later.
+</pre>
+
+<pre>
+/var/log/maillog:
+    NOQUEUE: reject filter in: 451 4.6.0 Alias expansion error
+    NOQUEUE: reject filter out: 550 5.6.0 Alias expansion error
+</pre>
+
+<p> This feature is available in Postfix &ge; 3.11. </p>
index 0bf01b7ed73bd846147344b011118b08f4c054d5..4df8659386b99ca3002a110da0fc981111b79b9d 100644 (file)
@@ -194,3 +194,4 @@ proto  proto COMPATIBILITY_README html
  src global config_known_tcp_ports c postmulti postmulti c 
  virtual virtual c 
  request Reported by John Doe File tlsproxy tlsproxy c 
+ smtpd smtpd c smtpd smtpd_chat c global mail_params h 
index 9a5925cde2d428dcc5f32bcf576beff087dae5b9..ff05172104c13a95014b5d036ab029b5bf1bab4f 100644 (file)
@@ -1867,3 +1867,4 @@ ossl
 deduplicates
 intmax
 lflag
+REPLYCODE
index 275116ff259d324223634644523d84c902990e8c..551020a8614028dbb186a8d6c72f5ddd6e0bf45a 100644 (file)
@@ -2568,7 +2568,8 @@ extern int var_local_rcpt_code;
                                " $" VAR_SMTP_BODY_CHKS \
                                " $" VAR_SMTP_HEAD_CHKS \
                                " $" VAR_SMTP_MIME_CHKS \
-                               " $" VAR_SMTP_NEST_CHKS
+                               " $" VAR_SMTP_NEST_CHKS \
+                               " $" VAR_SMTPD_REJECT_FILTER_MAPS
 extern char *var_proxy_read_maps;
 
 #define VAR_PROXY_WRITE_MAPS   "proxy_write_maps"
@@ -4534,6 +4535,13 @@ extern int var_sockmap_max_reply;
 #define DEF_SMTPD_HIDE_CLIENT_SESSION  "no"
 extern int var_smtpd_hide_client_session;
 
+ /*
+  * SMTP server reject response filter.
+  */
+#define VAR_SMTPD_REJECT_FILTER_MAPS   "smtpd_reject_filter_maps"
+#define DEF_SMTPD_REJECT_FILTER_MAPS   ""
+extern char *var_smtpd_reject_filter_maps;
+
 /* LICENSE
 /* .ad
 /* .fi
index 6be51952302ff869afafaddb08e847f17e6104a4..c03965bdd58b5561061cc0cc6308f105a451873d 100644 (file)
@@ -20,7 +20,7 @@
   * Patches change both the patchlevel and the release date. Snapshots have no
   * patchlevel; they change the release date only.
   */
-#define MAIL_RELEASE_DATE      "20250801"
+#define MAIL_RELEASE_DATE      "20250803"
 #define MAIL_VERSION_NUMBER    "3.11"
 
 #ifdef SNAPSHOT
index e4b60791a582d8d8d92a8e28aad6b162340578cf..8c26e9f648aa9e2bf221887fa0b490bdc96c1917 100644 (file)
@@ -507,24 +507,6 @@ 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.
      */
@@ -970,15 +952,40 @@ static void smtp_connect_inet(SMTP_STATE *state, const char *nexthop,
 
        SMTP_ITER_INIT(iter, dest, NO_HOST, NO_ADDR, port, state);
 
+       /*
+        * If a "TLS-Required: no" header is in effect, update the iterator
+        * to override TLS policy selection and to limit the security level
+        * to "may". Do not reset the security level after policy selection,
+        * as that would result in errors. For example, when TLSA records are
+        * looked up for security level "dane", and then the security level
+        * is reset to "may", the activation of those TLSA records will fail.
+        * 
+        * 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
+
        /*
         * TODO(wietse) If the domain publishes a TLSRPT policy, they expect
         * that clients use SMTP over TLS. Should we upgrade a TLS security
         * level of "may" to "encrypt"? This would disable falling back to
         * plaintext, and could break interoperability with receivers that
         * crank up security up to 11.
+        * 
+        * As of change 20250803, with "TLS-Required: no", the SMTP client also
+        * ignores the recipient-side policy mechanism TLSRPT, in addition to
+        * the already ignored DANE and MTA-STS mechanisms. This prevents
+        * TLSRPT notifications for all SMTP deliveries that do not require
+        * TLS.
         */
 #ifdef USE_TLSRPT
        if (smtp_mode && var_smtp_tlsrpt_enable
+           && iter->tlsreqno == 0
            && tls_level_lookup(var_smtp_tls_level) > TLS_LEV_NONE
            && !valid_hostaddr(domain, DONT_GRIPE))
            smtp_tlsrpt_create_wrapper(state, domain);
index 9c11b18ca0b7fc529d7c35005449ef7a4dfb87df..3d3bc79ace3257cc2735fd9c24d59c27cf15496c 100644 (file)
 /* .IP "\fBsmtpd_hide_client_session (no)\fR"
 /*     Do not include SMTP client session information in the Postfix
 /*     SMTP server's Received: message header.
+/* .PP
+/*     Available in Postfix version 3.11 and later:
+/* .IP "\fBsmtpd_reject_filter_maps (empty)\fR"
+/*     An optional filter that can replace a reject response from the
+/*     Postfix SMTP server itself, or from a program that replies through
+/*     the Postfix SMTP server.
 /* SEE ALSO
 /*     anvil(8), connection/rate limiting
 /*     cleanup(8), message canonicalization
@@ -1478,6 +1484,7 @@ bool    var_smtpd_tls_auth_only;
 char   *var_smtpd_cmd_filter;
 char   *var_smtpd_rej_footer;
 char   *var_smtpd_rej_ftr_maps;
+char   *var_smtpd_reject_filter_maps;
 char   *var_smtpd_acl_perm_log;
 char   *var_smtpd_dns_re_filter;
 
@@ -6635,9 +6642,9 @@ static void pre_jail_init(char *unused_name, char **unused_argv)
                              var_smtpd_dns_re_filter);
 
     /*
-     * Reject footer.
+     * Reject filter and footer.
      */
-    if (*var_smtpd_rej_ftr_maps)
+    if (*var_smtpd_rej_ftr_maps || *var_smtpd_reject_filter_maps)
        smtpd_chat_pre_jail_init();
 }
 
@@ -6911,6 +6918,7 @@ int     main(int argc, char **argv)
        VAR_SMTPD_POLICY_CONTEXT, DEF_SMTPD_POLICY_CONTEXT, &var_smtpd_policy_context, 0, 0,
        VAR_SMTPD_DNS_RE_FILTER, DEF_SMTPD_DNS_RE_FILTER, &var_smtpd_dns_re_filter, 0, 0,
        VAR_SMTPD_REJ_FTR_MAPS, DEF_SMTPD_REJ_FTR_MAPS, &var_smtpd_rej_ftr_maps, 0, 0,
+       VAR_SMTPD_REJECT_FILTER_MAPS, DEF_SMTPD_REJECT_FILTER_MAPS, &var_smtpd_reject_filter_maps, 0, 0,
        VAR_HFROM_FORMAT, DEF_HFROM_FORMAT, &var_hfrom_format, 1, 0,
        VAR_SMTPD_FORBID_BARE_LF_EXCL, DEF_SMTPD_FORBID_BARE_LF_EXCL, &var_smtpd_forbid_bare_lf_excl, 0, 0,
        VAR_SMTPD_FORBID_BARE_LF, DEF_SMTPD_FORBID_BARE_LF, &var_smtpd_forbid_bare_lf, 1, 0,
index 278e5362ac71e40c498698ff59a665aa5a069886..ab4ad83c87fa7f57e852ce1816dc026df9d76f66 100644 (file)
 #include "smtpd_chat.h"
 
  /*
-  * Reject footer.
+  * Reject filter and footer maps.
   */
+static MAPS *smtpd_reject_filter_maps;
 static MAPS *smtpd_rej_ftr_maps;
 
 #define STR    vstring_str
@@ -128,6 +129,14 @@ void    smtpd_chat_pre_jail_init(void)
     if (init_count++ != 0)
        msg_panic("smtpd_chat_pre_jail_init: multiple calls");
 
+    /*
+     * SMTP server reject filter.
+     */
+    if (*var_smtpd_reject_filter_maps)
+       smtpd_reject_filter_maps = maps_create(VAR_SMTPD_REJECT_FILTER_MAPS,
+                                        var_smtpd_reject_filter_maps,
+                                        DICT_FLAG_LOCK);
+
     /*
      * SMTP server reject footer.
      */
@@ -206,6 +215,7 @@ void    vsmtpd_chat_reply(SMTPD_STATE *state, const char *format, va_list ap)
     char   *cp;
     char   *next;
     char   *end;
+    const char *alt_reply;
     const char *footer;
 
     /*
@@ -215,8 +225,30 @@ void    vsmtpd_chat_reply(SMTPD_STATE *state, const char *format, va_list ap)
     if (state->error_count >= var_smtpd_soft_erlim)
        sleep(delay = var_smtpd_err_sleep);
 
+    /*
+     * Postfix generates single-line reject responses, but Milters may
+     * generate multi-line rejects with the SMFIR_REPLYCODE request.
+     */
     vstring_vsprintf(state->buffer, format, ap);
-
+    cp = STR(state->buffer);
+    if ((*cp == '4' || *cp == '5')
+       && smtpd_reject_filter_maps != 0
+       && (alt_reply = maps_find(smtpd_reject_filter_maps, cp, 0)) != 0) {
+       const char *queue_id = state->queue_id ? state->queue_id : "NOQUEUE";
+
+       /* XXX Enforce this for each line of a multi-line reply. */
+       if ((alt_reply[0] != '4' && alt_reply[0] != '5')
+           || !ISDIGIT(alt_reply[1]) || !ISDIGIT(alt_reply[2])
+           || (alt_reply[3] != ' ' && alt_reply[3] != '-')
+           || (ISDIGIT(alt_reply[4]) && (alt_reply[4] != alt_reply[0]))) {
+           msg_warn("%s: ignoring invalid reject filter result: %s",
+                    queue_id, alt_reply);
+       } else {
+           msg_info("%s: reply filter in: %s", queue_id, cp);
+           msg_info("%s: reply filter out: %s", queue_id, alt_reply);
+           vstring_strcpy(state->buffer, alt_reply);
+       }
+    }
     if ((*(cp = STR(state->buffer)) == '4' || *cp == '5')
        && ((smtpd_rej_ftr_maps != 0
             && (footer = maps_find(smtpd_rej_ftr_maps, cp, 0)) != 0)