postfix: ETRN refused and not offered (audit S6)
Security audit 2026-10-17, finding S6 (low), stage 0 - proven from outside: `ETRN <an inbound domain>` on port 25 answered "250 Queuing started". smtpd_etrn_restrictions was empty (Postfix allows every client) and fast_flush_domains = $relay_domains, so a stranger could flush the deferred queue toward an inbound domain's upstream at will. Now smtpd_etrn_restrictions=reject (etrn_cmd -> smtpd_check_etrn -> reject_code 554, Postfix 3.10.13 src/smtpd/smtpd.c) and smtpd_discard_ehlo_keywords=etrn. e2e: testInboundRelay sends EHLO + ETRN for the inbound domain on port 25 and expects 554 and no ETRN in the EHLO reply (etrnPort25); checkPostfixHardening reads both settings back. To be run on the server. Docs: architecture.md § Postfix; CHANGELOG; plan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -262,6 +262,12 @@ func testInboundRelay(t *testing.T, sc *scenario) {
|
||||
to, token, time.Now().UTC().Format(time.RFC1123Z))
|
||||
}
|
||||
|
||||
// A stranger cannot flush the queue toward the domain's upstream: ETRN is
|
||||
// refused and not offered (security audit 2026-10-17, S6).
|
||||
if code, ehlo, err := etrnPort25(inboundDomain); err != nil || code != 554 || strings.Contains(strings.ToUpper(ehlo), "ETRN") {
|
||||
t.Errorf("ETRN %s on port 25 = %d (%v), EHLO offers:\n%s", inboundDomain, code, err, ehlo)
|
||||
}
|
||||
|
||||
token := uniqueToken("inbound")
|
||||
if res := deliverPort25("someone@stranger.e2e.test", inboundRecipient, message(inboundRecipient, token)); !res.ok() {
|
||||
t.Fatalf("port 25 did not accept mail for %s: %v", inboundRecipient, res.firstErr())
|
||||
|
||||
Reference in New Issue
Block a user