postfix: long, non-repeating queue ids
The owner, 2026-10-16, of three ways to stop a reused queue id reaching an older row (found by the technical review of inbound-upstream-result, L6): "1" - enable_long_queue_ids=yes. A short id is the microsecond of arrival and the queue file's inode in hex (Postfix 3.10.13, src/global/mail_queue.h, MQID_SH_*) and repeats once the inode comes round; a later message with no send-log row of its own (port-25 mail, a bounce Postfix sends) could then rewrite an older Outbound log row of the same id and recipient, its page could show the later message's stored lines (security.md's known exposure, "enable_long_queue_ids ... ends it"), and the inbound journal's rows needed detaching. A long id adds the time in seconds and does not repeat (MQID_LG_*; proto/postconf.proto: existing queue files keep their names, postqueue -p gets a wider Queue ID column). Every pattern of the panel takes both shapes (queueLineRe's long branch, the [0-9A-Za-z]+ of the others, queueEntryRe) - checked one by one. Tests: e2e reads a long id on an outbound send-log row (testExpiredMessageIsClosed) and on an inbound journal row (testInboundUpstreamDown). go vet + go test (26 packages) and make e2e on the test server for this tree. Docs: security.md (the collision bullet closed for new mail, the upstream bullet), architecture § Log tailer, delivery-log-storage's note, the ForgetInboundQueueID comment; CHANGELOG [Unreleased] § Fixed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -687,7 +687,11 @@ folding case). A delivery line names it in `orig_to=` when Postfix rewrote
|
||||
the address, in `to=` otherwise (`log_adhoc.c` writes `orig_to=` only then),
|
||||
and is matched without regard to case. It updates only the newest row of its
|
||||
queue id and recipient: Postfix reuses a short queue id once the message has
|
||||
left the queue, and an old row must not be rewritten. `UpdateStatus`'s transaction
|
||||
left the queue, and an old row must not be rewritten. Since `2.10.1` the
|
||||
image makes long, non-repeating queue ids (`enable_long_queue_ids=yes`,
|
||||
[build/postfix-config.sh](../build/postfix-config.sh)): the time in seconds,
|
||||
then the microseconds, `z` and the inode; a queue file from before keeps its
|
||||
short name, and every pattern of the panel takes both. `UpdateStatus`'s transaction
|
||||
starts with a write, so a commit by the `dmarc-ingest` process in between
|
||||
cannot fail it with `database is locked`.
|
||||
Now that the log survives the container, reaching this path should mean the
|
||||
|
||||
@@ -68,6 +68,7 @@ to the days the row's own message could still be in the queue; this is the
|
||||
bound the roadmap item asked for. The search of the current file that the
|
||||
page did before had the same exposure for a day. `enable_long_queue_ids`
|
||||
would end it and is a change to the mail path, not part of this item.
|
||||
*It was made in `2.10.1` (owner, 2026-10-16).*
|
||||
|
||||
### Retention
|
||||
|
||||
|
||||
+15
-9
@@ -384,10 +384,11 @@ operator can act on it.
|
||||
quoted RCPT TO holding `>`, `,` or `status=` cannot end the address early
|
||||
and pass fields of its own for another recipient's row - and one holding an
|
||||
`@` is a routed address, which port 25 refuses as relaying
|
||||
(`allow_untrusted_routing=no`). Short queue ids repeat (Postfix's default
|
||||
`enable_long_queue_ids=no`): before writing a message's rows the milter
|
||||
detaches older rows of its queue id, so a message that is gone cannot take
|
||||
a new one's results. Accepted: a row whose insert waited longer than the
|
||||
(`allow_untrusted_routing=no`). Queue ids do not repeat since `2.10.1`
|
||||
(`enable_long_queue_ids=yes`, below); for a queue file from before, which
|
||||
keeps its short name, the milter detaches older rows of a queue id before
|
||||
writing a message's rows, so a message that is gone cannot take a new
|
||||
one's results. Accepted: a row whose insert waited longer than the
|
||||
milter's 30 s timeout - the database held - can miss its first delivery
|
||||
line and stay `queued`.
|
||||
- **The inbound journal is written from what strangers send.** The inbound
|
||||
@@ -765,8 +766,13 @@ deferred item from the roadmap.
|
||||
`deploy/docker-compose.yml` sets no memory limit: what is left - many
|
||||
messages at once, each held to the bounds above - is the host's to
|
||||
size.
|
||||
- **A message's page can show another message's log lines after a queue-id
|
||||
collision.** Postfix's short queue ids repeat: the id is the microsecond of
|
||||
- **A message's page could show another message's log lines after a
|
||||
queue-id collision - until `2.10.1`.** Since then the image makes long,
|
||||
non-repeating queue ids (`enable_long_queue_ids=yes`,
|
||||
`build/postfix-config.sh`; owner, 2026-10-16), which ends it, and with it a
|
||||
delivery line of a later message rewriting an older send-log row of the
|
||||
same id and recipient. What follows is the exposure of rows from before,
|
||||
whose ids are short. Postfix's short queue ids repeat: the id is the microsecond of
|
||||
arrival and the queue file's inode, and inodes come round within seconds.
|
||||
The page takes the stored lines of the row's queue id dated within the
|
||||
row's own queue lifetime (about six days), so a second message has to get
|
||||
@@ -774,9 +780,9 @@ deferred item from the roadmap.
|
||||
million per reuse of the inode, and not something a sender can aim. When it
|
||||
happens, a `domain` user could read the log lines of a message that is not
|
||||
theirs. The search of the current `mail.log` that the page did before
|
||||
2.2.0 had the same exposure over a day. Trigger to revisit: a report of
|
||||
foreign lines on a page, or a reason to turn on `enable_long_queue_ids`,
|
||||
which ends it.
|
||||
2.2.0 had the same exposure over a day. It lasts for a short-id row only
|
||||
as long as the row's queue lifetime, so it is gone from the server some
|
||||
six days after `2.10.1`.
|
||||
- **A `domain` user may point an inbound domain's upstream at any host and
|
||||
port.** The upstream form checks that the host is a well-formed name or IP
|
||||
and the port is 1–65535; loopback, private and link-local addresses are
|
||||
|
||||
Reference in New Issue
Block a user