From 8eec6728d45b2b145f7e91ffc9633ab237cc9bed Mon Sep 17 00:00:00 2001 From: Mix Date: Sun, 18 Oct 2026 01:01:41 +0300 Subject: [PATCH] postfix: ETRN discarded from EHLO silently After the 2.14.4 rollout the test server's mail.log held "discarding EHLO keywords: ETRN" for every connection on every port: smtpd logs a discard unless the mask has EHLO_MASK_SILENT (Postfix 3.10.13, src/smtpd/smtpd.c:2016; src/global/ehlo_mask.c:88 "SILENT-DISCARD"; postconf(5) smtpd_discard_ehlo_keywords, "Specify the silent-discard pseudo keyword to prevent this action from being logged"). smtpd_discard_ehlo_keywords is now silent-discard,etrn; ETRN is still refused (smtpd_etrn_restrictions=reject) and still not offered. make e2e (64 passed; checkPostfixHardening reads the value back, testInboundRelay still sees no ETRN in EHLO and 554) and go vet + go test (26 packages) on the test server for this tree. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 7 +++++++ build/postfix-config.sh | 7 +++++-- docs/architecture.md | 2 +- test/e2e/process_check.go | 2 +- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc4b501..ae59502 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version ## [Unreleased] +### Fixed + +- **ETRN is discarded from the EHLO reply silently**: since `2.14.4` Postfix + logged "discarding EHLO keywords: ETRN" for every connection on every port + (seen on the test server after the rollout); `silent-discard` keeps the + discard and drops the line. + ## [2.14.4] - 2026-10-17 The follow-ups of the security audit of 2026-10-17 diff --git a/build/postfix-config.sh b/build/postfix-config.sh index 27c95d0..a48a4e3 100644 --- a/build/postfix-config.sh +++ b/build/postfix-config.sh @@ -327,14 +327,17 @@ postconf -e \ # client flush the queue toward an inbound domain's upstream # (fast_flush_domains = $relay_domains), and nothing here needs it (security # audit 2026-10-17, S6; Postfix 3.10.13, src/smtpd/smtpd.c, etrn_cmd() asks -# smtpd_check_etrn(), whose "reject" answers reject_code, 554). +# smtpd_check_etrn(), whose "reject" answers reject_code, 554). The discard is +# silent: without the pseudo keyword smtpd logs "discarding EHLO keywords: +# ETRN" for every connection (src/smtpd/smtpd.c:2016, EHLO_MASK_SILENT; +# src/global/ehlo_mask.c:88; seen on the test server after 2.14.4). postconf -e \ "smtpd_tls_protocols=>=TLSv1.2" \ "smtpd_tls_mandatory_protocols=>=TLSv1.2" \ "smtp_tls_mandatory_protocols=>=TLSv1.2" \ "disable_vrfy_command=yes" \ "smtpd_etrn_restrictions=reject" \ - "smtpd_discard_ehlo_keywords=etrn" \ + "smtpd_discard_ehlo_keywords=silent-discard,etrn" \ 'smtpd_banner=$myhostname ESMTP' # SASL: Cyrus with the local sasldb2 the panel maintains. The realm is left diff --git a/docs/architecture.md b/docs/architecture.md index 0979c29..f3cf08b 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -104,7 +104,7 @@ smtp/inet` removes Debian's stock smtpd). Outbound delivery still uses the submission chain would otherwise let a signing outage defer relayed mail and would file inbound mail into the outbound send log under the sender's domain. `ETRN` is refused on every listener and not offered in the EHLO - reply (`smtpd_etrn_restrictions=reject`, `smtpd_discard_ehlo_keywords=etrn`): + reply (`smtpd_etrn_restrictions=reject`, `smtpd_discard_ehlo_keywords=silent-discard,etrn`): Postfix's default lets any client flush the queue toward an inbound domain's upstream. With the inbound relay on the chain is the optional antispam milter, then the panel's **inbound journal milter** (unix socket diff --git a/test/e2e/process_check.go b/test/e2e/process_check.go index e25db64..1734e48 100644 --- a/test/e2e/process_check.go +++ b/test/e2e/process_check.go @@ -168,7 +168,7 @@ func checkPostfixHardening(s *stack) error { return fmt.Errorf("postconf: %v\n%s", err, out) } got := strings.Split(strings.TrimSpace(out), "\n") - want := []string{">=TLSv1.2", ">=TLSv1.2", "yes", "$myhostname ESMTP", "reject", "etrn"} + want := []string{">=TLSv1.2", ">=TLSv1.2", "yes", "$myhostname ESMTP", "reject", "silent-discard,etrn"} if strings.Join(got, "|") != strings.Join(want, "|") { return fmt.Errorf("postconf -h of the hardened settings = %q, want %q", got, want) }