test / test (push) Canceled after 0s
Audit 2026-10-12, stage 2c, the owner's decision of 2026-10-14: the check
decides by the address OpenDKIM signs for, whatever a display name or a
comment holds.
internal/milter/mailparse.go ports libopendkim's dkim_mail_parse() with
dkim_mail_first_special(), dkim_mail_token() and
dkim_mail_matching_paren() from Debian's opendkim 2.11.0~beta2-8+deb12u1,
line for line: the '<' form, group and list punctuation, an '@' before the
first ':', ';' or ',' deciding where the address starts, source routes
dropped, quoted strings and domain literals kept with their delimiters,
comments skipped, whitespace between atoms one space, no encoded word
decoded; and the C's own quirks ("@d" gives d as the local part too).
testdata/mailparse_harness.c compiles the C from the image's source; the
test feeds it and the port 200 019 lines (the hand-written cases and
200 000 random ones over the specials) - 93 443 parse and agree byte for
byte, 106 576 fail and agree on the error (on an error the C leaves its
in-place result unterminated, and OpenDKIM uses neither part then).
judge(): one From: field and one address stay required (moreAfter: only
list or group punctuation may follow); what OpenDKIM cannot read is
refused (mlfi_eoh() would not sign it); the address is lower-cased in
ASCII only, as dkimf_lowercase() and Postfix's tables do. Found reading
dkimf_apply_signtable(): OpenDKIM matches its refile SigningTable against
"user@domain" printed into MAXADDRESS + 1 = 257 bytes, so a longer address
is matched cut short - a quoted local part padded to end there in
"@another.tenant" would be signed with that tenant's key while its real
domain is the application's. Such an address (over 256 bytes) is refused;
one that fits is matched whole. The display-name and comment scan, the
"one @ outside quotes" rule and the SigningTable reader (Signers) go; the
check asks for header values with their leading space as OpenDKIM does
(OptHeaderLeadingSpace; Postfix keeps a v2 milter's event mask, milter8.c
milter8_negotiate()).
L8 closed: refused or passed no longer depends on other domains of the
server; security.md's accepted risk is struck. The shared other-sender
lines are now held to "never signed for the other sender": unit test (a
passed line is read at the application's domain) and e2e (a passed line
arrives signed for the application's domain, none for the other).
Documents: architecture "How the header is read", security's sender
check paragraph, the guide; the help topic per 2cf118c.
Stand (2026-10-15, make e2e, exit 0): sender_check_holds_the_from_line -
another sender's address in the display name now passes; a From: whose
quoted local part is padded so that, cut at 256 bytes, it ends in
"@other.e2e.test" is refused 550 5.7.1 "the address is longer than
OpenDKIM reads whole"; lines_naming_another_sender_never_signed_for_it
and passed_from_lines_signed_for_their_domain_only pass. go vet and go
test ./... pass on the server (24 packages); the differential test ran
there against the compiled C.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
27 lines
1.2 KiB
Plaintext
27 lines
1.2 KiB
Plaintext
# From: lines that name another sender of this server, {other}, beside or in
|
|
# place of the application's own address at {domain} (review of audit stage
|
|
# 2). None may be signed with {other}'s key. Since stage 2c the check reads a
|
|
# line with OpenDKIM's own parser, so it passes those whose address OpenDKIM
|
|
# reads at {domain} - {other} in a display name, a comment or a quoted local
|
|
# part - and refuses the rest. The unit test
|
|
# (internal/milter/sender_test.go) wants every line it passes read at
|
|
# {domain}; the e2e (test/e2e/dkim_from_test.go) sends each and wants
|
|
# nothing signed for {other}. {OTHER} is {other} in capitals; {other_qp} is {other} with each
|
|
# dot escaped as a quoted pair. One Go-quoted string per line. The lines are
|
|
# composed by hand; that OpenDKIM signs none for {other} is measured, by the
|
|
# e2e on every run.
|
|
"b@{other}"
|
|
"<b@{other}>"
|
|
"\"a@{domain}\"@{other}"
|
|
"\"b@{other}\"@{domain}"
|
|
"\"x\\\" <b@{other}>\" <a@{domain}>"
|
|
"\"x\\\" <b@{other_qp}>\" <a@{domain}>"
|
|
"\"b@{other}\" <a@{domain}>"
|
|
"\"b@{OTHER}\" <a@{domain}>"
|
|
"b@{other} <a@{domain}>"
|
|
"a@{domain} (b@{other})"
|
|
"<a@{domain}> (b@{other})"
|
|
"(b@{other}) a@{domain}"
|
|
"=?utf-8?q?b=40{other}?= <a@{domain}>"
|
|
"\"x\" <b@{other}>, <a@{domain}>"
|