base-trixie: the image on Debian trixie, Postfix 3.10.13; Received: hidden on 465/587
test / test (push) Canceled after 0s
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:
@@ -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
|
||||
|
||||
@@ -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.:]+)\]`)
|
||||
|
||||
|
||||
@@ -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, "")
|
||||
|
||||
@@ -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", "")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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".)
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user