base-trixie: the image on Debian trixie, Postfix 3.10.13; Received: hidden on 465/587
test / test (push) Canceled after 0s

The runtime base is debian:trixie-slim, the build stage golang:1.26-trixie:
Postfix 3.10.13 with upstream's 2026 security fixes up to the August
batch, libmilter 8.18.1, OpenSSL 3.5. Every citation of another program in
code, scripts and documents was re-read against the trixie sources
(dev/upstream; bookworm's kept in dev/upstream-bookworm) and updated;
development.md's cited-package table holds the trixie versions.

What the re-reading changed in behaviour:
- logtail: reject lines with sasl_method/sasl_username/sasl_sender
  (smtpd_check.c:1019-1026); cleanup's "removed (canceled)" forgets a
  port-25 id (cleanup_api.c:363-364).
- internal/postfix/retry.go reads postconf's stdout only: since 3.9 a
  deprecated-parameter warning on stderr made one value too many and the
  panel fell back to the compiled-in retry policy.
- logrotate-loop.sh: logrotate 3.22 exits 1 when it ignores a config;
  the loop refuses on that output whatever the status.
- postfix-config.sh: cyrus_sasl_config_path set explicitly (bookworm's
  07_sasl_config.diff is gone); maillog_file_permissions=0640; mynetworks
  explicit; Dockerfile: SYNC_CHROOT= in /etc/default/postfix,
  opendkim-tools dropped, openssl named, systemd-standalone-sysusers so
  cron does not bring the whole of systemd.
- The selfpost group is 10998, outside the system range 100-999: in
  trixie a package took 998 and the build failed (owner's decision:
  a conflict-free id, the distribution's own ids untouched). The
  entrypoint hands /data back by name when the user or the group differs.
  Guide, backup refusal text, help (design commit 68ec8b2), e2e.
- smtpd_hide_client_session=yes on smtps and submission (owner's
  decision): Received: names no client, TLS, login or protocol; port 25
  keeps the full header. e2e checks both.
- test/upstream/opendkim-probe.sh makes keys with openssl genrsa.

Tests: on the server, make e2e exit 0 (674 s, the image built on trixie),
go vet + go test 27 packages ok. CHANGELOG [Unreleased], guide,
architecture.md, security.md, roadmap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mix
2026-10-15 19:31:35 +03:00
co-authored by Claude Opus 5.5
parent 68ec8b274c
commit 1c6e04b7d3
80 changed files with 970 additions and 454 deletions
+4 -4
View File
@@ -55,7 +55,7 @@ func testDeliveryLogSurvivesRotation(t *testing.T, sc *scenario) {
// The rotation ends with `postfix reload`, which fails while Postfix is
// still starting - and this step follows one that restarts the container.
// (Postfix 3.7.11, conf/postfix-script: "reload" and "status" both test
// (Postfix 3.10.13, conf/postfix-script: "reload" and "status" both test
// "master -t" and exit 1 while master does not hold its pid lock.)
if err := waitFor("Postfix to be running", 30*time.Second, 500*time.Millisecond, func() (bool, error) {
if _, err := h.execIn("selfpost", "postfix", "status"); err != nil {
@@ -82,7 +82,7 @@ func testDeliveryLogSurvivesRotation(t *testing.T, sc *scenario) {
// testExpiredMessageIsClosed: a message Postfix gives up on is closed as
// bounced. Postfix logs expiry once per message, "<id>: from=<..>,
// status=expired, returned to sender" (here "force-expired"), and no line per
// recipient (Postfix 3.7.11, src/qmgr/qmgr_active.c,
// recipient (Postfix 3.10.13, src/qmgr/qmgr_active.c,
// qmgr_active_done_25_generic(); assumptions A-8). The queue lifetime cannot
// be waited out, so the message is deferred by hand - defer_transports=smtp,
// under which qmgr hands the recipient to the retry service, "4.3.2 deferred
@@ -154,7 +154,7 @@ print("" if r is None else r[0]+" "+r[1])`, token)
// testRecipientsAsPostfixLogsThem: each recipient's row reaches "sent". The
// row holds the recipient as Postfix logs it - "a b" stays quoted, "ab" loses
// its quotes - and a recipient given again in other capitals is one row, as
// cleanup keeps one (Postfix 3.7.11, src/global/quote_822_local.c,
// cleanup keeps one (Postfix 3.10.13, src/global/quote_822_local.c,
// src/cleanup/cleanup_out_recipient.c; assumptions B-G5, A-31). Before, the
// first was cut at its blank and its row closed as bounced by the sweep.
func testRecipientsAsPostfixLogsThem(t *testing.T, sc *scenario) {
@@ -184,7 +184,7 @@ print("; ".join(r[0]+" "+r[1] for r in rows))`, token)
// testAddressLiteralIsNotLocal: an address literal of an interface the
// container listens on is a local destination to Postfix even with an empty
// mydestination (Postfix 3.7.11, src/global/resolve_local.c,
// mydestination (Postfix 3.10.13, src/global/resolve_local.c,
// resolve_local()). Local delivery is Postfix's refusal now
// (local_transport=error:..., assumptions C-P8), and smtpd refuses such a
// recipient at RCPT with its text: smtpd_reject_unlisted_recipient (on by
+41 -1
View File
@@ -3,9 +3,11 @@ package e2e
import (
"bytes"
"fmt"
"net/mail"
"os"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/emersion/go-msgauth/dkim"
@@ -73,12 +75,50 @@ func verifyDKIM(raw []byte, domain string) error {
return fmt.Errorf("no DKIM signature found for domain %s (got %d signature(s))", domain, len(verifications))
}
// postfixReceived returns the Received: headers of raw that a Postfix smtpd
// wrote - "(Postfix)" after the host name, the only form it writes (Postfix
// 3.10.13, src/smtpd/smtpd.c:3627-3645, "by %s (%s)" with var_mail_name).
// In the stand only SelfPost's Postfix writes one: the sink is smtp-sink,
// which adds none, and the tests compose their messages without any.
func postfixReceived(raw []byte) ([]string, error) {
msg, err := mail.ReadMessage(bytes.NewReader(raw))
if err != nil {
return nil, fmt.Errorf("parse the delivered message: %w", err)
}
var out []string
for _, v := range msg.Header["Received"] {
if strings.Contains(v, "(Postfix)") {
out = append(out, v)
}
}
return out, nil
}
// checkReceivedHidden: the submission ports set smtpd_hide_client_session
// (build/postfix-config.sh), so SelfPost's Received: header is the redacted
// one - it opens with "by", names no client and no protocol (Postfix 3.10.13,
// src/smtpd/smtpd.c:3495, the "from" part written only when the parameter
// is off, and with_protocol emptied with it).
func checkReceivedHidden(raw []byte) error {
recv, err := postfixReceived(raw)
if err != nil {
return err
}
if len(recv) != 1 {
return fmt.Errorf("want one Received: header from SelfPost's Postfix, got %d: %q", len(recv), recv)
}
if !strings.HasPrefix(recv[0], "by ") || strings.Contains(recv[0], " with ") {
return fmt.Errorf("the Received: header names the client session: %q", recv[0])
}
return nil
}
// connectFromPattern matches Postfix's smtpd "connect from ...[ADDR]" log
// line, which is how the harness learns the source address Postfix itself
// observed for a just-made connection (needed to configure a level-2 rate
// limit's allowed-IPs list — the address a host-published port is seen as
// inside the container depends on Docker's NAT and isn't worth hard-coding).
// Postfix 3.7.11, src/smtpd/smtpd.c, smtpd_service(): msg_info("connect from
// Postfix 3.10.13, src/smtpd/smtpd.c, smtpd_service(): msg_info("connect from
// %s", namaddr), namaddr being "name[addr]".
var connectFromPattern = regexp.MustCompile(`connect from [^\[]*\[([0-9a-fA-F.:]+)\]`)
+3
View File
@@ -274,6 +274,9 @@ func TestE2E(t *testing.T) {
if err := verifyDKIM(raw, senderDomain); err != nil {
t.Fatalf("DKIM did not verify against the record the panel published: %v", err)
}
if err := checkReceivedHidden(raw); err != nil {
t.Fatalf("port 465 should hide the application's session: %v", err)
}
if err := waitFor("send-log row to reach status=sent", 30*time.Second, 500*time.Millisecond, func() (bool, error) {
rows, err := sc.panel.sendLogRows(senderDomain, "")
+5 -5
View File
@@ -48,7 +48,7 @@ func testLevel2RateLimit(t *testing.T, sc *scenario) {
from: from, to: recipient, subject: "e2e " + uniqueToken("l2-second"), body: "should be rejected",
})
// The milter answers tempfail at MAIL FROM, which Postfix words as below
// (Postfix 3.7.11, src/milter/milter8.c, milter8_event(), SMFIR_TEMPFAIL:
// (Postfix 3.10.13, src/milter/milter8.c, milter8_event(), SMFIR_TEMPFAIL:
// "451 4.7.1 Service unavailable - try again later").
if err := second.refusedAt("MAIL", 451, "4.7.1 Service unavailable"); err != nil {
t.Fatalf("the second message over the level-2 limit: %v", err)
@@ -60,7 +60,7 @@ func testLevel2RateLimit(t *testing.T, sc *scenario) {
// introduce the client again, so a second MAIL FROM on the same connection
// used to arrive with no client address and was let through (found by the
// technical review of 2026-10-12; the same hole let an application past its
// client-address list). Postfix's half: 3.7.11, src/smtpd/smtpd.c,
// client-address list). Postfix's half: 3.10.13, src/smtpd/smtpd.c,
// smtpd_proto(), calls milter_conn_event() once per connection.
errs := mailTwiceOnOneConnection(login, password, from)
for i, err := range errs {
@@ -94,7 +94,7 @@ func testSenderLoginMismatch(t *testing.T, sc *scenario) {
})
// Postfix evaluates smtpd_sender_restrictions with smtpd_delay_reject=yes
// (the default): the mismatch is detected at MAIL FROM but the reject is
// only sent back at RCPT TO. (Postfix 3.7.11, src/global/mail_params.h,
// only sent back at RCPT TO. (Postfix 3.10.13, src/global/mail_params.h,
// DEF_SMTPD_DELAY_REJECT 1; src/smtpd/smtpd_check.c,
// reject_auth_sender_login_mismatch(): 553 5.7.1 "Sender address rejected:
// not owned by user".)
@@ -110,7 +110,7 @@ func testSenderLoginMismatch(t *testing.T, sc *scenario) {
// The sender address used here belongs to no registered domain, so the
// rejection is unambiguously about the missing AUTH and not sender ownership.
// The reply: smtps's client restrictions end in "reject" (build/
// postfix-config.sh), Postfix 3.7.11, src/smtpd/smtpd_check.c,
// postfix-config.sh), Postfix 3.10.13, src/smtpd/smtpd_check.c,
// generic_checks(): var_reject_code (554) "<%s>: %s rejected: Access denied",
// delayed to RCPT by smtpd_delay_reject.
func testNoAuthRejected(t *testing.T, sc *scenario) {
@@ -200,7 +200,7 @@ func testSessionSurvivesRestart(t *testing.T, sc *scenario) {
// held to anvil's reply, it turned out the first message had always been
// refused for another reason: the new login was not yet in the sender map an
// smtpd from before the panel's reload held (waitForSenderMap). (An incident
// on the stand, date not recorded.) Anvil's reply: Postfix 3.7.11,
// on the stand, date not recorded.) Anvil's reply: Postfix 3.10.13,
// src/smtpd/smtpd.c, mail_cmd(): "450 4.7.1 Error: too much mail from %s".
func testLevel1RateLimit(t *testing.T, sc *scenario) {
login, password, err := sc.panel.addApplication(sc.domainID, "l1app", "wildcard", "")
+10 -5
View File
@@ -34,7 +34,7 @@ type port25Case struct {
// flags=, user= and argv= are arguments of pipe(8); set as -o parameters they
// are dropped, and a bare "pipe" dies on every report with "fatal: missing
// user= command-line attribute" - the state of 1.7.0 through 1.9.5 (date not
// recorded; Postfix 3.7.11, src/pipe/pipe.c, get_service_attr()).
// recorded; Postfix 3.10.13, src/pipe/pipe.c, get_service_attr()).
const ingestService = "dmarc-ingest unix - n n - 1 pipe flags=Rq user=panel argv=/usr/local/bin/dmarc-ingest ${recipient}"
var port25Cases = []port25Case{
@@ -51,8 +51,12 @@ var port25Cases = []port25Case{
// The log tailer tells this listener's refusals from the
// submission ports' by the name they are logged under.
{param: "syslog_name", contains: "postfix/inbound", wantExact: true},
// The SMTP-smuggling defence, off by default on this Postfix
// (own-mta-builds, § 5); testInboundBareNewline drives it.
// The SMTP-smuggling defence. Postfix 3.10.13 has it by default
// (src/global/mail_params.h, DEF_SMTPD_FORBID_BARE_LF "normalize");
// set explicitly since 2.6.1, when the image ran Postfix 3.7.11,
// whose default was "no" (own-mta-builds, § 5), and kept so the
// listener does not depend on the default. testInboundBareNewline
// drives it.
{param: "smtpd_forbid_bare_newline", contains: "normalize", wantExact: true},
// No antispam milter configured: the inbound journal milter
// alone. The submission chain (strict OpenDKIM + journal-milter)
@@ -150,8 +154,9 @@ func checkPort25ConfigCombinations(s *stack) error {
// 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 in 1.9.1,
// date not recorded; Postfix 3.7.11, man/man5/master.5, "-o
// name=value (short form)", NOTE 2.)
// date not recorded; Postfix 3.10.13, man/man5/master.5, "-o
// name=value (short form)": "does not support whitespace
// around the "=" or in parameter values".)
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)
+15 -3
View File
@@ -272,7 +272,7 @@ func testInboundRelay(t *testing.T, sc *scenario) {
// Port 25's limit is the whole limit: a 12 MB message - over the
// 10240000 bytes cleanup would hold it to on its own, under the 25 MiB the
// listener says - is accepted and passed on (Postfix 3.7.11,
// listener says - is accepted and passed on (Postfix 3.10.13,
// src/cleanup/cleanup_init.c, cleanup_post_jail(); assumptions C-P31).
big := uniqueToken("inbound-12mb")
line := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ab\r\n"
@@ -365,7 +365,7 @@ func testInboundRelay(t *testing.T, sc *scenario) {
t.Fatalf("the Inbound log holds %d rows, want the two relayed messages alone:\n%s", len(rows), strings.Join(rows, "\n"))
}
// Postfix 3.7.11, src/smtpd/smtpd_check.c: "User unknown in relay recipient
// Postfix 3.10.13, src/smtpd/smtpd_check.c: "User unknown in relay recipient
// table" (check_rcpt_maps(); unknown_relay_recipient_reject_code,
// DEF_RELAY_RCPT_CODE 550) and "Relay access denied"
// (reject_unauth_destination(); DEF_RELAY_CODE 554).
@@ -464,7 +464,8 @@ func testInboundRelay(t *testing.T, sc *scenario) {
// smuggling sequence, with a second transaction's commands behind it - is
// body, and the one message delivered carries all of it; nothing is read as
// a second transaction (smtpd_forbid_bare_newline=normalize on smtp/inet,
// build/postfix-config.sh; Postfix 3.7.11, src/smtpd/smtpd.c:3649-3652).
// build/postfix-config.sh; Postfix 3.10.13, src/smtpd/smtpd.c:3713-3716,
// receive_data_message()).
// Runs after testInboundRelay, whose domain, upstream and recipient it uses.
func testInboundBareNewline(t *testing.T, sc *scenario) {
token := uniqueToken("inbound-bare")
@@ -484,6 +485,17 @@ func testInboundBareNewline(t *testing.T, sc *scenario) {
if !bytes.Contains(raw, []byte(smuggled)) {
t.Fatalf("the message was cut at the bare-newline \".\": what followed it is not in the delivered message\n%s", raw)
}
// Port 25 keeps RFC 5321's trace: smtpd_hide_client_session is set on
// the submission ports only (build/postfix-config.sh; Postfix 3.10.13,
// proto/postconf.proto:19587-19589, "must be used for the port 25 MTA
// service"), so this Received: header opens with "from" and the HELO.
recv, err := postfixReceived(raw)
if err != nil {
t.Fatal(err)
}
if len(recv) != 1 || !strings.HasPrefix(recv[0], "from mx.stranger.e2e.test ") {
t.Fatalf("port 25's Received: header should name the sending server, got %q", recv)
}
// A second transaction, had Postfix read one, would be delivered right
// behind the first: give it the time and count the files that carry the
// smuggled token.
+2 -2
View File
@@ -73,7 +73,7 @@ func parseSupervisorStatus(out string) map[string]string {
// port 25: smtp/inet is absent from master.cf (Debian's stock listener is
// removed when INBOUND_RELAY_ENABLE is not true). For an absent service
// postconf -M prints nothing on stdout and "warning: unmatched request" on
// stderr (Postfix 3.7.11, src/postconf/postconf_master.c,
// stderr (Postfix 3.10.13, src/postconf/postconf_master.c,
// pcf_show_master_entries()).
func checkInboundRelayOff(s *stack) error {
out, err := s.execIn("selfpost", "postconf", "-M", "smtp/inet")
@@ -90,7 +90,7 @@ func checkInboundRelayOff(s *stack) error {
// checkCitedPackageVersions compares the Debian packages the image runs with
// the versions their sources were read at (docs/development.md, the table
// between the cited-package-versions markers). The image installs unpinned
// from a moving bookworm, so a point release changes them on a rebuild
// from a moving trixie, so a point release changes them on a rebuild
// without anyone reading what changed (assumptions C-D1).
func checkCitedPackageVersions(s *stack) error {
doc, err := os.ReadFile(filepath.Join(s.repoRoot, "docs", "development.md"))
+2 -2
View File
@@ -24,7 +24,7 @@ import (
// record that was published before the backup.
func testFullBackupRestore(t *testing.T, sc *scenario) {
// A private key the panel may not read - the state a server is in until
// its operator gives the key to group 998. The panel does not make an
// its operator gives the key to group 10998. The panel does not make an
// archive without it: it refuses, names the file and says what to do.
keyPath := filepath.Join(h.stageDir, "certs", "privkey.pem")
if err := os.Chmod(keyPath, 0o600); err != nil {
@@ -34,7 +34,7 @@ func testFullBackupRestore(t *testing.T, sc *scenario) {
if err != nil {
t.Fatalf("full backup with an unreadable key: %v", err)
}
if resp.StatusCode != 500 || !strings.Contains(body, "not allowed to read certs/privkey.pem") || !strings.Contains(body, "chgrp 998") {
if resp.StatusCode != 500 || !strings.Contains(body, "not allowed to read certs/privkey.pem") || !strings.Contains(body, "chgrp 10998") {
t.Fatalf("full backup with an unreadable key: status %d, want a refusal that names the file and the way out:\n%.800s", resp.StatusCode, body)
}
if err := os.Chmod(keyPath, 0o644); err != nil {
+3 -3
View File
@@ -53,7 +53,7 @@ func testSenderCheck(t *testing.T, sc *scenario) {
{"the listed address with an extension, in envelope and header", who{listLogin, listPassword, "billing+march@" + senderDomain}, "billing+march@" + senderDomain, true},
// What a mail library does with a long display name: the line is
// folded, and Postfix hands the milter both of its lines (Postfix
// 3.7.11, src/milter/milter8.c, milter8_header(): the value is the
// 3.10.13, src/milter/milter8.c, milter8_header(): the value is the
// whole header as cleanup's MIME state machine collected it,
// continuation line included).
{"a long name, folded onto a second line", wildcard, "\"The Shop, its orders and its returns desk\"\r\n <orders@" + senderDomain + ">", true},
@@ -112,7 +112,7 @@ func testSenderCheck(t *testing.T, sc *scenario) {
// What the header case above holds to: Postfix itself refuses the
// wildcard application billing@ as envelope sender, at RCPT, because the
// address's own key names app-list only (Postfix 3.7.11,
// address's own key names app-list only (Postfix 3.10.13,
// src/smtpd/smtpd_check.c, reject_auth_sender_login_mismatch(): "553
// 5.7.1 <%s>: Sender address rejected: not owned by user %s").
if res := attemptSend(sendAttempt{
@@ -250,7 +250,7 @@ func senderCheckDeferredToday(sc *scenario) (int, error) {
// message is deferred, and the count on Health sees it. Past the DATA
// command Postfix's cleanup, not smtpd, talks to the milters, so the warning
// that names the socket is cleanup's (audit 2026-10-12, M13: the counter read
// smtpd's lines only; Postfix 3.7.11, src/smtpd/smtpd.c, milter_send() to
// smtpd's lines only; Postfix 3.10.13, src/smtpd/smtpd.c, milter_send() to
// cleanup, src/cleanup/cleanup_milter.c). The panel is frozen with SIGSTOP
// once DATA is answered, and thawed after the client has its reply.
func testPanelHungMidMessage(t *testing.T, sc *scenario) {
+2 -2
View File
@@ -3,7 +3,7 @@
# the raw received message (headers + body, including the DKIM-Signature
# Postfix/OpenDKIM added) straight off disk. smtp-sink ships in the postfix
# package itself — no new dependency.
FROM debian:bookworm-slim
FROM debian:trixie-slim
ENV DEBIAN_FRONTEND=noninteractive
RUN echo "postfix postfix/mailname string localhost" | debconf-set-selections \
@@ -21,7 +21,7 @@ WORKDIR /mail
# smtp-sink refuses to run as root without an explicit -u to switch to after
# binding — "-u root" satisfies that check while staying root throughout,
# which is fine here: throwaway container, isolated e2e network, never
# exposed. (Postfix 3.7.11, src/smtpstone/smtp-sink.c: -d "Dump each mail
# exposed. (Postfix 3.10.13, src/smtpstone/smtp-sink.c: -d "Dump each mail
# transaction to a single-message file" named by the template plus "a
# pseudo-random hexadecimal number"; main(): "-u option is required if running
# as root".)
+1 -1
View File
@@ -237,7 +237,7 @@ func mailTwiceOnOneConnection(login, password, from string) [2]error {
// reloaded, and an smtpd that was idle from before the reload can still take
// a connection in the second that follows and answer 553 "not owned by user"
// (seen on the stand, date not recorded; two seconds after the save it no
// longer happened, at 0, 10, 40 or 110 s of waiting). Postfix 3.7.11: an smtpd
// longer happened, at 0, 10, 40 or 110 s of waiting). Postfix 3.10.13: an smtpd
// reads the texthash map once, when it starts (html/DATABASE_README.html,
// "texthash"), and one from before the reload exits only when it next sees
// master's status pipe closed (src/master/multi_server.c,
+1 -1
View File
@@ -117,7 +117,7 @@ func writeSelfSignedCert(certPath, keyPath string) error {
}
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
// Readable by everyone, which a real key must never be. On a server the
// operator gives the key to the container's selfpost group (998) so the
// operator gives the key to the container's selfpost group (10998) so the
// panel can put it into a full backup; the runner of this suite is not
// always root and cannot hand a file to a group it is not in. The key is
// thrown away with the stage directory.
+1 -1
View File
@@ -45,7 +45,7 @@ func waitProgramState(t *testing.T, name, want string, within time.Duration) {
}
// postfixStops counts the lines "postfix stop" writes to mail.log before it
// sends master TERM (Postfix 3.7.11, conf/postfix-script, "stop": $INFO
// sends master TERM (Postfix 3.10.13, conf/postfix-script, "stop": $INFO
// stopping the Postfix mail system; "... with force" when master had not
// gone after five seconds, which this does not count).
func postfixStops(t *testing.T) int {
+3 -3
View File
@@ -1,17 +1,17 @@
#!/bin/sh
# The From: parser port (internal/milter/mailparse.go) against OpenDKIM's own
# dkim_mail_parse(): builds internal/milter/testdata/mailparse_harness.c
# against the libopendkim source the image runs, in a debian:bookworm
# against the libopendkim source the image runs, in a debian:trixie
# container, and runs TestMailParseMatchesOpenDKIM with it.
#
# test/upstream/mailparse-diff.sh <opendkim source dir>
#
# The source dir is what `apt-get source opendkim` unpacks in debian:bookworm
# The source dir is what `apt-get source opendkim` unpacks in debian:trixie
# (docs/development.md); it must contain libopendkim/.
set -eu
src=$(cd "$1" && pwd)
repo=$(cd "$(dirname "$0")/../.." && pwd)
docker run --rm -v "$src":/src:ro -v "$repo/internal/milter/testdata":/h -w /h debian:bookworm sh -c '
docker run --rm -v "$src":/src:ro -v "$repo/internal/milter/testdata":/h -w /h debian:trixie sh -c '
apt-get update -qq >/dev/null && apt-get install -y -qq gcc libc6-dev >/dev/null 2>&1
gcc -O1 -static -o /h/mailparse_harness -I /src/libopendkim /h/mailparse_harness.c'
trap 'rm -f "$repo/internal/milter/testdata/mailparse_harness"' EXIT
+1 -1
View File
@@ -16,7 +16,7 @@ set -e
mkdir -p /tmp/odk && cd /tmp/odk
for d in sender.e2e.test other.e2e.test mail.e2e.test; do
mkdir -p keys/$d
opendkim-genkey -D keys/$d -d $d -s sel
openssl genrsa -out keys/$d/sel.private 2048 2>/dev/null
echo "$d $d:sel:/tmp/odk/keys/$d/sel.private" >> KeyTable
echo "*@$d $d" >> SigningTable
done