postfix-config: report addresses are relay recipients when the relay is on
Found by talking SMTP to port 25 of the test server once it answered again (2026-10-10): RCPT TO:<dmarc-reports+bs.mixdep.ru@selfpost.mixfed.ru> was refused with "550 5.1.1 User unknown in relay recipient table". With INBOUND_RELAY_ENABLE and DMARC_REPORTS_ENABLE both true the server's own name is a relay domain (for the reports) and relay_recipient_maps lists the relay's recipients only. smtpd_reject_unlisted_recipient applies after the restriction list, so the OK that check_recipient_access gives a report address does not help: every report was refused. The table of report addresses is added to relay_recipient_maps in that combination. Checked on the server by setting the parameter in the running container and reloading Postfix: the domain's report address is answered 250, the former server-wide mailbox and an unknown local part 550, a foreign domain and an inbound domain without recipients 554. e2e: the combined case requires relay_recipient_maps to name both tables. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -227,6 +227,12 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
|
||||
value with whitespace, and the e2e check asks Postfix what it reads.
|
||||
Present since DMARC ingest was fitted into the 1.9.1 fix of the same
|
||||
stanza.
|
||||
- **DMARC reports are accepted with the inbound relay on.** With both
|
||||
features enabled every report address was refused as "User unknown in
|
||||
relay recipient table": the relay's unlisted-recipient check runs after the
|
||||
restriction list, and the report addresses - on the server's own name, a
|
||||
relay domain - were not in `relay_recipient_maps`. Their table is a
|
||||
recipient table now. Hidden until now by the port-25 failure above.
|
||||
- Overview's Processes card counts the programs that are meant to stay up
|
||||
("6 of 6 running"); the idle one-shot `postfix-reload` was counted as
|
||||
running. The Milter sockets card no longer clips its second line with three
|
||||
|
||||
+12
-1
@@ -306,8 +306,19 @@ if [ "${INBOUND_RELAY_ENABLE}" = "true" ] || [ "${DMARC_REPORTS_ENABLE}" = "true
|
||||
"transport_maps=${TRANSPORT_SETTING}" \
|
||||
"smtpd_reject_unlisted_recipient=yes"
|
||||
if [ "${INBOUND_RELAY_ENABLE}" = "true" ]; then
|
||||
# With the relay on, Postfix refuses every recipient of a relay domain
|
||||
# that relay_recipient_maps does not list (smtpd_reject_unlisted_recipient
|
||||
# above) - and it does so after the restriction list, whatever
|
||||
# check_recipient_access answered. The DMARC report addresses live on a
|
||||
# relay domain too (the server's own name), so their table is a recipient
|
||||
# table as well; without it they are "User unknown in relay recipient
|
||||
# table" and no report is ever accepted while both features are on.
|
||||
RELAY_RECIPIENT_SETTING="texthash:${RELAY_RECIPIENTS_MAP}"
|
||||
if [ "${DMARC_REPORTS_ENABLE}" = "true" ]; then
|
||||
RELAY_RECIPIENT_SETTING="${RELAY_RECIPIENT_SETTING} texthash:${DMARC_RECIPIENTS_MAP}"
|
||||
fi
|
||||
postconf -e \
|
||||
"relay_recipient_maps=texthash:${RELAY_RECIPIENTS_MAP}" \
|
||||
"relay_recipient_maps=${RELAY_RECIPIENT_SETTING}" \
|
||||
"smtp_tls_policy_maps=texthash:${TLS_POLICY_MAP}"
|
||||
else
|
||||
postconf -e \
|
||||
|
||||
@@ -22,6 +22,9 @@ type port25Case struct {
|
||||
name string
|
||||
env []string
|
||||
check []optionCheck
|
||||
// main are main.cf parameters the combination must end up with, read with
|
||||
// postconf -h.
|
||||
main []optionCheck
|
||||
}
|
||||
|
||||
var port25Cases = []port25Case{
|
||||
@@ -61,6 +64,13 @@ var port25Cases = []port25Case{
|
||||
{param: "smtpd_recipient_restrictions", contains: "check_recipient_access,texthash:/data/postfix/dmarc_recipients,reject_unauth_destination,reject_unlisted_recipient", wantExact: true},
|
||||
{param: "smtpd_milters", contains: "", wantExact: true},
|
||||
},
|
||||
// Both on: the report addresses sit on a relay domain, and the relay's
|
||||
// unlisted-recipient check runs after the restriction list. Unless their
|
||||
// table is a recipient table too, every report is refused as "User
|
||||
// unknown in relay recipient table".
|
||||
main: []optionCheck{
|
||||
{param: "relay_recipient_maps", contains: "texthash:/data/postfix/relay_recipients texthash:/data/postfix/dmarc_recipients", wantExact: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "inbound relay with antispam milter",
|
||||
@@ -129,6 +139,15 @@ func checkPort25ConfigCombinations(s *stack) error {
|
||||
return fmt.Errorf("%s: master.cf has -o %s = %q but Postfix reads %q - the value contains whitespace", tc.name, c.param, got, strings.TrimSpace(seen))
|
||||
}
|
||||
}
|
||||
for _, c := range tc.main {
|
||||
got, err := s.execIn("selfpost", "postconf", "-h", c.param)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: postconf -h %s: %v\n%s", tc.name, c.param, err, got)
|
||||
}
|
||||
if got = strings.TrimSpace(got); got != c.contains {
|
||||
return fmt.Errorf("%s: %s = %q, want %q", tc.name, c.param, got, c.contains)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Back to the stand's own configuration: flags off, no port-25 listener.
|
||||
|
||||
Reference in New Issue
Block a user