2
0
mirror of https://github.com/vdukhovni/postfix synced 2025-08-22 09:57:34 +00:00

postfix-3.11-20250803

This commit is contained in:
Wietse Z Venema 2025-08-03 00:00:00 -05:00 committed by Viktor Dukhovni
parent e3e5fbac8c
commit 1c43ccaf44
14 changed files with 266 additions and 23 deletions

View File

@ -29530,3 +29530,21 @@ Apologies for any names omitted.
change did not have space between POSTLOG_HOSTNAME and change did not have space between POSTLOG_HOSTNAME and
XDG_RUNTIME_DIR, breaking maillog_file support and graphical XDG_RUNTIME_DIR, breaking maillog_file support and graphical
debugging. File: global/mail_params.h. 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.

View File

@ -17393,6 +17393,58 @@ Example:
</pre> </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> </DD>
<DT><b><a name="smtpd_reject_footer">smtpd_reject_footer</a> <DT><b><a name="smtpd_reject_footer">smtpd_reject_footer</a>

View File

@ -1437,6 +1437,13 @@ SMTPD(8) SMTPD(8)
Do not include SMTP client session information in the Postfix Do not include SMTP client session information in the Postfix
SMTP server's Received: message header. 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> <b><a name="see_also">SEE ALSO</a></b>
<a href="anvil.8.html">anvil(8)</a>, connection/rate limiting <a href="anvil.8.html">anvil(8)</a>, connection/rate limiting
<a href="cleanup.8.html">cleanup(8)</a>, message canonicalization <a href="cleanup.8.html">cleanup(8)</a>, message canonicalization

View File

@ -11815,6 +11815,60 @@ Example:
smtpd_recipient_restrictions = permit_mynetworks, reject_unauth_destination smtpd_recipient_restrictions = permit_mynetworks, reject_unauth_destination
.fi .fi
.ad .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) .SH smtpd_reject_footer (default: empty)
Optional information that is appended after each Postfix SMTP Optional information that is appended after each Postfix SMTP
server server

View File

@ -1237,6 +1237,12 @@ Available in Postfix 3.10 and later:
.IP "\fBsmtpd_hide_client_session (no)\fR" .IP "\fBsmtpd_hide_client_session (no)\fR"
Do not include SMTP client session information in the Postfix Do not include SMTP client session information in the Postfix
SMTP server's Received: message header. 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" .SH "SEE ALSO"
.na .na
.nf .nf

View File

@ -770,6 +770,7 @@ while (<>) {
s;\bsmtpd_use_tls\b;<a href="postconf.5.html#smtpd_use_tls">$&</a>;g; 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\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_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_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_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; s;\bsmtpd_min_data_rate\b;<a href="postconf.5.html#smtpd_min_data_rate">$&</a>;g;

View 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> RFC 5321. The form does still meet RFC 5322 requirements. </p>
<p> This feature is available in Postfix &ge; 3.10. </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>

View File

@ -194,3 +194,4 @@ proto proto COMPATIBILITY_README html
src global config_known_tcp_ports c postmulti postmulti c src global config_known_tcp_ports c postmulti postmulti c
virtual virtual c virtual virtual c
request Reported by John Doe File tlsproxy tlsproxy c request Reported by John Doe File tlsproxy tlsproxy c
smtpd smtpd c smtpd smtpd_chat c global mail_params h

View File

@ -1867,3 +1867,4 @@ ossl
deduplicates deduplicates
intmax intmax
lflag lflag
REPLYCODE

View File

@ -2568,7 +2568,8 @@ extern int var_local_rcpt_code;
" $" VAR_SMTP_BODY_CHKS \ " $" VAR_SMTP_BODY_CHKS \
" $" VAR_SMTP_HEAD_CHKS \ " $" VAR_SMTP_HEAD_CHKS \
" $" VAR_SMTP_MIME_CHKS \ " $" VAR_SMTP_MIME_CHKS \
" $" VAR_SMTP_NEST_CHKS " $" VAR_SMTP_NEST_CHKS \
" $" VAR_SMTPD_REJECT_FILTER_MAPS
extern char *var_proxy_read_maps; extern char *var_proxy_read_maps;
#define VAR_PROXY_WRITE_MAPS "proxy_write_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" #define DEF_SMTPD_HIDE_CLIENT_SESSION "no"
extern int var_smtpd_hide_client_session; 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 /* LICENSE
/* .ad /* .ad
/* .fi /* .fi

View File

@ -20,7 +20,7 @@
* Patches change both the patchlevel and the release date. Snapshots have no * Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only. * patchlevel; they change the release date only.
*/ */
#define MAIL_RELEASE_DATE "20250801" #define MAIL_RELEASE_DATE "20250803"
#define MAIL_VERSION_NUMBER "3.11" #define MAIL_VERSION_NUMBER "3.11"
#ifdef SNAPSHOT #ifdef SNAPSHOT

View File

@ -507,24 +507,6 @@ static int smtp_get_effective_tls_level(DSN_BUF *why, SMTP_STATE *state)
SMTP_ITERATOR *iter = state->iterator; SMTP_ITERATOR *iter = state->iterator;
SMTP_TLS_POLICY *tls = state->tls; 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. * 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); 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 * TODO(wietse) If the domain publishes a TLSRPT policy, they expect
* that clients use SMTP over TLS. Should we upgrade a TLS security * that clients use SMTP over TLS. Should we upgrade a TLS security
* level of "may" to "encrypt"? This would disable falling back to * level of "may" to "encrypt"? This would disable falling back to
* plaintext, and could break interoperability with receivers that * plaintext, and could break interoperability with receivers that
* crank up security up to 11. * 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 #ifdef USE_TLSRPT
if (smtp_mode && var_smtp_tlsrpt_enable if (smtp_mode && var_smtp_tlsrpt_enable
&& iter->tlsreqno == 0
&& tls_level_lookup(var_smtp_tls_level) > TLS_LEV_NONE && tls_level_lookup(var_smtp_tls_level) > TLS_LEV_NONE
&& !valid_hostaddr(domain, DONT_GRIPE)) && !valid_hostaddr(domain, DONT_GRIPE))
smtp_tlsrpt_create_wrapper(state, domain); smtp_tlsrpt_create_wrapper(state, domain);

View File

@ -1179,6 +1179,12 @@
/* .IP "\fBsmtpd_hide_client_session (no)\fR" /* .IP "\fBsmtpd_hide_client_session (no)\fR"
/* Do not include SMTP client session information in the Postfix /* Do not include SMTP client session information in the Postfix
/* SMTP server's Received: message header. /* 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 /* SEE ALSO
/* anvil(8), connection/rate limiting /* anvil(8), connection/rate limiting
/* cleanup(8), message canonicalization /* cleanup(8), message canonicalization
@ -1478,6 +1484,7 @@ bool var_smtpd_tls_auth_only;
char *var_smtpd_cmd_filter; char *var_smtpd_cmd_filter;
char *var_smtpd_rej_footer; char *var_smtpd_rej_footer;
char *var_smtpd_rej_ftr_maps; char *var_smtpd_rej_ftr_maps;
char *var_smtpd_reject_filter_maps;
char *var_smtpd_acl_perm_log; char *var_smtpd_acl_perm_log;
char *var_smtpd_dns_re_filter; 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); 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(); 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_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_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_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_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_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, VAR_SMTPD_FORBID_BARE_LF, DEF_SMTPD_FORBID_BARE_LF, &var_smtpd_forbid_bare_lf, 1, 0,

View File

@ -112,8 +112,9 @@
#include "smtpd_chat.h" #include "smtpd_chat.h"
/* /*
* Reject footer. * Reject filter and footer maps.
*/ */
static MAPS *smtpd_reject_filter_maps;
static MAPS *smtpd_rej_ftr_maps; static MAPS *smtpd_rej_ftr_maps;
#define STR vstring_str #define STR vstring_str
@ -128,6 +129,14 @@ void smtpd_chat_pre_jail_init(void)
if (init_count++ != 0) if (init_count++ != 0)
msg_panic("smtpd_chat_pre_jail_init: multiple calls"); 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. * SMTP server reject footer.
*/ */
@ -206,6 +215,7 @@ void vsmtpd_chat_reply(SMTPD_STATE *state, const char *format, va_list ap)
char *cp; char *cp;
char *next; char *next;
char *end; char *end;
const char *alt_reply;
const char *footer; 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) if (state->error_count >= var_smtpd_soft_erlim)
sleep(delay = var_smtpd_err_sleep); 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); 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') if ((*(cp = STR(state->buffer)) == '4' || *cp == '5')
&& ((smtpd_rej_ftr_maps != 0 && ((smtpd_rej_ftr_maps != 0
&& (footer = maps_find(smtpd_rej_ftr_maps, cp, 0)) != 0) && (footer = maps_find(smtpd_rej_ftr_maps, cp, 0)) != 0)