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>
56 lines
1.4 KiB
C
56 lines
1.4 KiB
C
/*
|
|
* Differential harness for internal/milter/mailparse.go: OpenDKIM's own
|
|
* dkim_mail_parse() (libopendkim/dkim-mailparse.c of the opendkim source the
|
|
* image runs, compiled in by #include), fed one hex-encoded line per input
|
|
* line. Output per line: the error, then the local part and the domain, each
|
|
* "-" when NULL and "=" followed by its bytes in hex otherwise.
|
|
*
|
|
* cc -o mailparse_harness -I <opendkim>/libopendkim mailparse_harness.c
|
|
* MAILPARSE_C=./mailparse_harness go test -run TestMailParseMatchesOpenDKIM ./internal/milter
|
|
*/
|
|
#include <sys/types.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "dkim-mailparse.c"
|
|
|
|
static int hexval(int c)
|
|
{
|
|
if (c >= '0' && c <= '9') return c - '0';
|
|
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
|
return -1;
|
|
}
|
|
|
|
static void put(const unsigned char *s)
|
|
{
|
|
if (s == NULL) {
|
|
fputs(" -", stdout);
|
|
return;
|
|
}
|
|
fputs(" =", stdout);
|
|
for (; *s != '\0'; s++)
|
|
printf("%02x", *s);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
static char hex[1 << 20];
|
|
static unsigned char buf[1 << 19];
|
|
|
|
while (fgets(hex, sizeof hex, stdin) != NULL) {
|
|
size_t n = strcspn(hex, "\r\n"), i;
|
|
unsigned char *user, *domain;
|
|
int err;
|
|
|
|
for (i = 0; i + 1 < n; i += 2)
|
|
buf[i / 2] = (unsigned char) (hexval(hex[i]) << 4 | hexval(hex[i + 1]));
|
|
buf[n / 2] = '\0';
|
|
err = dkim_mail_parse(buf, &user, &domain);
|
|
printf("%d", err);
|
|
put(user);
|
|
put(domain);
|
|
putchar('\n');
|
|
}
|
|
return 0;
|
|
}
|