postfix-config: no whitespace in the port-25 service's -o values
Seen in the System log of the test server (2026-10-10): on every
connection to port 25
postfix/smtpd: fatal: unexpected command-line argument:
texthash:/data/postfix/dmarc_recipients,reject_unauth_destination,...
postfix/master: warning: .../smtpd: bad command startup -- throttling
With DMARC_REPORTS_ENABLE=true the script wrote
-o smtpd_recipient_restrictions=check_recipient_access texthash:...,...
into master.cf. Master splits a service line on whitespace, so smtpd got
the option cut at the space plus a stray argument and died at start:
port 25 greeted nobody - no inbound relay, no DMARC reports. postconf -P
smtp/inet/smtpd_recipient_restrictions showed the cut value,
"check_recipient_access". The server's logs have it under the previous
image too (2026-10-09), so it is not from the redesign; postfix check
does not notice, and the e2e check compared the text of master.cf with
an expectation that had the space in it.
- The restriction list is written with commas only (Postfix splits it
on commas as well as whitespace).
- master_cf_set_smtp_inet_o stops the container on a value with
whitespace instead of writing it.
- e2e: for every option it checks, the port-25 case also asks
postconf -P and requires Postfix to read the same value.
Not fixed here: Postfix's start-time warnings "not owned by root" for
the queue directory and its pid/ - the entrypoint's recursive chown
again; harmless, logged at every start.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -47,7 +47,7 @@ var port25Cases = []port25Case{
|
||||
"DMARC_REPORTS_ENABLE=true",
|
||||
},
|
||||
check: []optionCheck{
|
||||
{param: "smtpd_recipient_restrictions", contains: "check_recipient_access texthash:/data/postfix/dmarc_recipients,reject", wantExact: true},
|
||||
{param: "smtpd_recipient_restrictions", contains: "check_recipient_access,texthash:/data/postfix/dmarc_recipients,reject", wantExact: true},
|
||||
{param: "smtpd_milters", contains: "", wantExact: true},
|
||||
},
|
||||
},
|
||||
@@ -58,7 +58,7 @@ var port25Cases = []port25Case{
|
||||
"DMARC_REPORTS_ENABLE=true",
|
||||
},
|
||||
check: []optionCheck{
|
||||
{param: "smtpd_recipient_restrictions", contains: "check_recipient_access texthash:/data/postfix/dmarc_recipients,reject_unauth_destination,reject_unlisted_recipient", wantExact: true},
|
||||
{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},
|
||||
},
|
||||
},
|
||||
@@ -116,6 +116,18 @@ func checkPort25ConfigCombinations(s *stack) error {
|
||||
case !c.wantExact && !strings.Contains(got, c.contains):
|
||||
return fmt.Errorf("%s: -o %s = %q, want it to contain %q", tc.name, c.param, got, c.contains)
|
||||
}
|
||||
// What the file says is not yet what Postfix reads: master splits
|
||||
// a service line on whitespace, so a value with a space in it is
|
||||
// cut at the space and the rest becomes a stray smtpd argument
|
||||
// ("fatal: unexpected command-line argument", port 25 dead while
|
||||
// `postfix check` stays silent). Ask Postfix itself.
|
||||
seen, err := s.execIn("selfpost", "postconf", "-Ph", "smtp/inet/"+c.param)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: postconf -P smtp/inet/%s: %v\n%s", tc.name, c.param, err, seen)
|
||||
}
|
||||
if strings.TrimSpace(seen) != got {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user