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:
+22
-9
@@ -191,9 +191,22 @@ smtp/inet` removes Debian's stock smtpd). Outbound delivery still uses the
|
||||
`anvil_rate_time_unit` from `RATE_LIMIT_*` env vars; independent of milter.
|
||||
- **Port 25 ends DATA only at `<CR><LF>.<CR><LF>`** —
|
||||
`smtpd_forbid_bare_newline=normalize` on the `smtp/inet` listener (Postfix
|
||||
3.7.11, `src/smtpd/smtpd.c`, the DATA loop at 3649: a `.` line next to a
|
||||
bare `<LF>` is dropped, not taken as the end); a bare `<LF>` elsewhere is
|
||||
read as `<CR><LF>`. The submission ports keep Postfix's default.
|
||||
3.10.13, `src/smtpd/smtpd.c`, `receive_data_message()`, the DATA loop at
|
||||
3713: a `.` line next to a bare `<LF>` is dropped, not taken as the end); a
|
||||
bare `<LF>` elsewhere is read as `<CR><LF>`. The submission ports keep
|
||||
Postfix's default, which is the same `normalize` since Postfix 3.9
|
||||
(`src/global/mail_params.h`, `DEF_SMTPD_FORBID_BARE_LF`); the image ran
|
||||
Postfix 3.7.11 until `2.7.0`, whose default was `no`, hence the explicit
|
||||
setting on port 25.
|
||||
- **The submission ports hide the application in `Received:`** (`2.7.0`) —
|
||||
`smtpd_hide_client_session=yes` on `smtps/inet` and `submission/inet`:
|
||||
Postfix's header reads `Received: by <hostname> (Postfix) id <queue id>`,
|
||||
with the recipient when there is one, and the date - no HELO name, client
|
||||
name or address, TLS, login or protocol (Postfix 3.10.13,
|
||||
`src/smtpd/smtpd.c:3495` and `:3627-3645`). Port 25 keeps the full
|
||||
header, which RFC 5321 requires there (`proto/postconf.proto:19587-19589`).
|
||||
The queue id in the header leads to the message's row in the panel's log
|
||||
and to smtpd's `client=` line in mail.log.
|
||||
- **Chroot disabled** for all services (DNS/TLS inside container).
|
||||
- **TLS certs** — read-only mount at `TLS_CERT_FILE` / `TLS_KEY_FILE`, copied
|
||||
at start into `/etc/postfix/tls-internal`, owned by root and `0600` as
|
||||
@@ -234,7 +247,7 @@ milter, § Postfix above, port 25):
|
||||
`delay_warning_time`), reads each as Postfix does - one number and one
|
||||
unit, the parameter's own default unit when there is none (`5d` for the
|
||||
lifetimes, so a bare `3` is three days), `bounce_queue_lifetime` capped at
|
||||
`maximal_queue_lifetime` as qmgr caps it (Postfix 3.7.11,
|
||||
`maximal_queue_lifetime` as qmgr caps it (Postfix 3.10.13,
|
||||
`src/global/conv_time.c`, `src/qmgr/qmgr.c`) - and caches the snapshot on
|
||||
the handlers config. The
|
||||
Outbound queue box and a delivery's `deferred` / `bounced` history print those
|
||||
@@ -295,7 +308,7 @@ server itself.
|
||||
reason), then `@domain` - and the **first of these keys in the table
|
||||
decides**, its owners alone: an address listed for one application is not
|
||||
open to another that holds the whole domain. Logins are compared without
|
||||
regard to case, as Postfix compares them (Postfix 3.7.11,
|
||||
regard to case, as Postfix compares them (Postfix 3.10.13,
|
||||
`src/smtpd/smtpd_check.c`, `reject_auth_sender_login_mismatch()`, over
|
||||
`src/global/mail_addr_find.c`, `mail_addr_find_opt()`). A key written
|
||||
twice makes Postfix refuse the whole table, and the check defers then too.
|
||||
@@ -461,7 +474,7 @@ recreates the file on its first line after the reload, as root and mode
|
||||
`0600`, which the unprivileged panel user cannot read). `follow()` drains the
|
||||
old inode once more before switching descriptors, and keeps it open and reads
|
||||
it for a minute after: postlogd keeps writing to the renamed file until the
|
||||
reload makes it exit, seconds later (Postfix 3.7.11, src/postlogd/postlogd.c;
|
||||
reload makes it exit, seconds later (Postfix 3.10.13, src/postlogd/postlogd.c;
|
||||
src/master/dgram_server.c, `dgram_server_abort()`). The panel treats a missing
|
||||
log file as an empty tail, not an error.
|
||||
|
||||
@@ -494,7 +507,7 @@ the lines stored under it - only those of the minutes around the refusal, since
|
||||
Postfix frees the id at once. A message the journal milter refused at
|
||||
`MAIL FROM` has none: the tailer reads smtpd's `NOQUEUE: milter-reject: MAIL
|
||||
from <client>[<address>]: …; from=<…> … sasl_username=<login>` - the login is
|
||||
Postfix's last field on an authenticated session (Postfix 3.7.11,
|
||||
Postfix's last field on an authenticated session (Postfix 3.10.13,
|
||||
`src/smtpd/smtpd.c`, `smtpd_whatsup()`), and a line without one is no
|
||||
application's - and puts it on the nearest refused row of that client
|
||||
address, login and sender within two minutes
|
||||
@@ -513,7 +526,7 @@ a kill is covered as well as a clean stop.
|
||||
**Expiry.** A message Postfix gives up on at the end of its queue lifetime has
|
||||
no line per recipient: qmgr writes one line for the message, `<id>:
|
||||
from=<...>, status=expired, returned to sender` (`force-expired` after
|
||||
`postsuper -e`; Postfix 3.7.11, `src/qmgr/qmgr_active.c`,
|
||||
`postsuper -e`; Postfix 3.10.13, `src/qmgr/qmgr_active.c`,
|
||||
`qmgr_active_done_25_generic()`), and the recipients' last lines say
|
||||
`deferred`. The tailer reads that line and closes the message's open rows -
|
||||
each recipient's newest, `queued` or `deferred` - as `bounced`.
|
||||
@@ -542,7 +555,7 @@ of the id's flags (`src/postqueue/showq_compat.c`, `showq_message()`).
|
||||
**Which row a delivery line is.** The journal milter writes a row's
|
||||
recipient as Postfix will log it: the address the client gave, its quoted
|
||||
local part unquoted and quoted again only where it needs to be (`"ab"@x`
|
||||
becomes `ab@x`, `"a b"@x` stays; Postfix 3.7.11, `tok822_parse.c`,
|
||||
becomes `ab@x`, `"a b"@x` stays; Postfix 3.10.13, `tok822_parse.c`,
|
||||
`quote_822_local.c`), case kept, and a recipient given twice - in any case -
|
||||
once, since cleanup keeps one (`cleanup_out_recipient.c`, `been_here()`
|
||||
folding case). A delivery line names it in `orig_to=` when Postfix rewrote
|
||||
|
||||
@@ -38,6 +38,14 @@ versions `go.mod` pins (go-milter v0.4.1, modernc.org/sqlite v1.60.1 with
|
||||
SQLite 3.53.4); htmx 2.0.4 as vendored. Upstream paths below are relative
|
||||
to the package's source tree.
|
||||
|
||||
Since base-trixie (`2.7.0`) the image runs Debian trixie (Postfix 3.10.13,
|
||||
opendkim 2.11.0~beta2-9.1+b1, cyrus-sasl2 2.1.28+dfsg1-9, supervisor
|
||||
4.2.5-3, logrotate 3.22.0-1; development.md § Code that relies on another
|
||||
component's behaviour), and the citations in the code were re-read against
|
||||
the trixie sources. This register is left as it was made: its line numbers
|
||||
and versions are those of the bookworm sources above, which are now kept
|
||||
under `dev/upstream-bookworm/` (where it says `dev/upstream/`).
|
||||
|
||||
Counts at the inventory, by the justification the tree had:
|
||||
|
||||
| Part | Entries | CITED | MEASURED | GUESSED | WRONG? |
|
||||
|
||||
+19
-18
@@ -152,8 +152,8 @@ commit rule but **no** version cut until the next product stage ships.
|
||||
| **SQLite** | `modernc.org/sqlite` (pure Go, no cgo) |
|
||||
| **Build** | [Makefile](../Makefile): primary targets `vet`, `test`, `build`, `e2e` (also `all`, `clean`) |
|
||||
| **Container** | Docker + Compose v2 on the dev host and in CI |
|
||||
| **Image (build stage)** | `golang:1.26-bookworm` — [build/Dockerfile](../build/Dockerfile). The tag floats to the newest 1.26 patch release, and most standard-library security fixes (`net/http`, `html/template`, …) arrive only that way: rebuild the image after each Go patch release, no edit needed. Dependencies are raised by hand (`go get …@latest`, `go mod tidy`), at least at each audit |
|
||||
| **Image (runtime)** | `debian:bookworm-slim` + Postfix, OpenDKIM, supervisord, SASL, logrotate |
|
||||
| **Image (build stage)** | `golang:1.26-trixie` (bookworm until `2.7.0`) — [build/Dockerfile](../build/Dockerfile). The tag floats to the newest 1.26 patch release, and most standard-library security fixes (`net/http`, `html/template`, …) arrive only that way: rebuild the image after each Go patch release, no edit needed. Dependencies are raised by hand (`go get …@latest`, `go mod tidy`), at least at each audit |
|
||||
| **Image (runtime)** | `debian:trixie-slim` (bookworm until `2.7.0`) + Postfix, OpenDKIM, supervisord, SASL, logrotate |
|
||||
| **CI** | GitHub Actions — [.github/workflows/](../.github/workflows/) |
|
||||
| **Image registry** | `ghcr.io/mixeme/selfpost` |
|
||||
|
||||
@@ -209,8 +209,8 @@ the image.
|
||||
### Debian packages in the runtime image
|
||||
|
||||
Postfix, OpenDKIM, `supervisord`, `sasl2-bin`, `logrotate`, and others come
|
||||
from Debian bookworm repositories; licenses are in each package's `copyright`
|
||||
file on <https://packages.debian.org/bookworm/>.
|
||||
from Debian trixie repositories; licenses are in each package's `copyright`
|
||||
file on <https://packages.debian.org/trixie/>.
|
||||
The image also ships [LICENSE](../LICENSE), [NOTICE](../NOTICE), and the IBM
|
||||
Plex [OFL.txt](../internal/web/view/static/OFL.txt) under
|
||||
`/usr/share/doc/selfpost/`. The panel serves the AGPL text at `/license` and
|
||||
@@ -225,21 +225,22 @@ lines and reply codes, feeds OpenDKIM's tables, parses `postqueue`,
|
||||
through go-milter, relies on SQLite's locking, on logrotate's and Docker's
|
||||
behaviour. Every block of code that does so carries a comment that says
|
||||
what it relies on and where that is written, for the version the image runs
|
||||
(the Debian bookworm package, not upstream's head):
|
||||
(the Debian trixie package, not upstream's head):
|
||||
|
||||
```go
|
||||
// Postfix 3.7.11, src/smtpd/smtpd_check.c,
|
||||
// Postfix 3.10.13, src/smtpd/smtpd_check.c,
|
||||
// reject_auth_sender_login_mismatch(): the SASL login is compared with
|
||||
// each owner in sender_login_maps by strcasecmp_utf8.
|
||||
```
|
||||
|
||||
The Debian sources of the image's versions are fetched with `apt-get source`
|
||||
inside a `debian:bookworm` container (`postfix`, `opendkim`, `cyrus-sasl2`,
|
||||
inside a `debian:trixie` container (`postfix`, `opendkim`, `cyrus-sasl2`,
|
||||
`supervisor`, `logrotate`, and `sendmail` for libmilter); `dev/upstream/`
|
||||
(not in git) is where they are kept for reading. Go modules are read in the
|
||||
module cache at the version `go.mod` pins.
|
||||
(not in git) is where they are kept for reading, and `dev/upstream-bookworm/`
|
||||
holds the bookworm ones the citations named before `2.7.0`. Go modules are
|
||||
read in the module cache at the version `go.mod` pins.
|
||||
|
||||
The image takes `debian:bookworm-slim` and installs without pinning (pinned
|
||||
The image takes `debian:trixie-slim` and installs without pinning (pinned
|
||||
Debian versions leave the mirrors), so a rebuild after a point or security
|
||||
release runs packages the citations were not read against. The versions
|
||||
read are these; the e2e stand compares them with `dpkg-query` in the image
|
||||
@@ -249,14 +250,14 @@ re-read what changed, update the citations, then this table:
|
||||
<!-- cited-package-versions -->
|
||||
| Package | Version read |
|
||||
|---|---|
|
||||
| `postfix` | `3.7.11-0+deb12u1` |
|
||||
| `opendkim` | `2.11.0~beta2-8+deb12u1` |
|
||||
| `libopendkim11` | `2.11.0~beta2-8+deb12u1` |
|
||||
| `libmilter1.0.1` | `8.17.1.9-2+deb12u2` |
|
||||
| `libsasl2-2` | `2.1.28+dfsg-10` |
|
||||
| `sasl2-bin` | `2.1.28+dfsg-10` |
|
||||
| `supervisor` | `4.2.5-1` |
|
||||
| `logrotate` | `3.21.0-1` |
|
||||
| `postfix` | `3.10.13-0+deb13u1` |
|
||||
| `opendkim` | `2.11.0~beta2-9.1+b1` |
|
||||
| `libopendkim11` | `2.11.0~beta2-9.1+b1` |
|
||||
| `libmilter1.0.1` | `8.18.1-6+b1` |
|
||||
| `libsasl2-2` | `2.1.28+dfsg1-9` |
|
||||
| `sasl2-bin` | `2.1.28+dfsg1-9` |
|
||||
| `supervisor` | `4.2.5-3` |
|
||||
| `logrotate` | `3.22.0-1` |
|
||||
<!-- /cited-package-versions -->
|
||||
|
||||
A document instead of source: its URL and section. Where neither answers the
|
||||
|
||||
+21
-7
@@ -73,6 +73,20 @@ listened on: refused from outside over IPv4 and from the host's public IPv4
|
||||
address; accepted and closed through `127.0.0.1`, the same proxy. IPv6 itself
|
||||
was not probed for this.
|
||||
|
||||
Since `2.7.0` the image runs Postfix 3.10, whose defaults hold every
|
||||
listener - 465 and 587 as well as 25 - to two rules of SMTP: a message ends
|
||||
only at the standard `<CR><LF>.<CR><LF>` (a client that ends DATA with a
|
||||
bare newline waits for a reply that does not come), and a client that sends
|
||||
commands ahead of the server's replies where RFC 2920 does not allow it is
|
||||
answered `554 5.5.0 Error: SMTP protocol synchronization` and disconnected.
|
||||
|
||||
Since `2.7.0` a message sent on 465 or 587 does not tell its recipients
|
||||
where the application runs: the `Received:` header SelfPost adds reads
|
||||
`Received: by mail.example.com (Postfix) id 4AbC12xyz for <...>; <date>`,
|
||||
without the application's address, host name or login. To trace a message
|
||||
someone complains about, take that queue id to *Outbound › Log*; mail.log
|
||||
keeps the client address on smtpd's `client=` line for the same id.
|
||||
|
||||
### Local trial
|
||||
|
||||
The [README quick start](../README.md#quick-start) runs a single container
|
||||
@@ -312,13 +326,13 @@ ln -s /etc/letsencrypt/live/mail.example.com/privkey.pem certs/privkey.pem
|
||||
```
|
||||
|
||||
The panel reads them as its own user (Health shows the certificate, a full
|
||||
backup carries both files), through the container's group 998. certbot's
|
||||
backup carries both files), through the container's group 10998. certbot's
|
||||
documentation gives the way for a server that is not root, and certbot keeps
|
||||
the key's group and mode at every renewal:
|
||||
|
||||
```sh
|
||||
sudo chmod 0755 /etc/letsencrypt/live /etc/letsencrypt/archive
|
||||
sudo chgrp 998 /etc/letsencrypt/archive/mail.example.com/privkey*.pem
|
||||
sudo chgrp 10998 /etc/letsencrypt/archive/mail.example.com/privkey*.pem
|
||||
sudo chmod 0640 /etc/letsencrypt/archive/mail.example.com/privkey*.pem
|
||||
```
|
||||
|
||||
@@ -339,7 +353,7 @@ docker compose -f docker-compose.yml -f nginx/docker-compose.nginx.yml \
|
||||
The fragment mounts certbot's directory into both nginx and SelfPost, at
|
||||
`/etc/letsencrypt`; `./certs` holds two links to
|
||||
`/etc/letsencrypt/live/mail.example.com/` — a path that exists inside the
|
||||
container, not on the host — and the panel reads the key through group 998,
|
||||
container, not on the host — and the panel reads the key through group 10998,
|
||||
as in the Apache case. Then start everything:
|
||||
|
||||
```sh
|
||||
@@ -347,7 +361,7 @@ mkdir certs
|
||||
ln -s /etc/letsencrypt/live/mail.example.com/fullchain.pem certs/fullchain.pem
|
||||
ln -s /etc/letsencrypt/live/mail.example.com/privkey.pem certs/privkey.pem
|
||||
sudo chmod 0755 nginx/certbot-etc/live nginx/certbot-etc/archive
|
||||
sudo chgrp 998 nginx/certbot-etc/archive/mail.example.com/privkey*.pem
|
||||
sudo chgrp 10998 nginx/certbot-etc/archive/mail.example.com/privkey*.pem
|
||||
sudo chmod 0640 nginx/certbot-etc/archive/mail.example.com/privkey*.pem
|
||||
|
||||
docker compose -f docker-compose.yml -f nginx/docker-compose.nginx.yml up -d
|
||||
@@ -714,11 +728,11 @@ instance keeps running, what goes into the archive, and what happens on restore.
|
||||
→ *Full backup*. *Encrypt with a password* is ticked; untick it for a plain
|
||||
archive. Same archive as the CLI below. The panel runs as its own user, not as
|
||||
root, so **the private key in `certs/` has to be readable by it**: give the
|
||||
file to the container's `selfpost` group, which has the fixed id 998 and holds
|
||||
file to the container's `selfpost` group, which has the fixed id 10998 and holds
|
||||
both the panel and Postfix —
|
||||
|
||||
```sh
|
||||
chgrp 998 certs/privkey.pem && chmod 640 certs/privkey.pem
|
||||
chgrp 10998 certs/privkey.pem && chmod 640 certs/privkey.pem
|
||||
```
|
||||
|
||||
— and have whatever renews the certificate do the same; certbot does it by
|
||||
@@ -795,7 +809,7 @@ methods avoid it by stopping first.
|
||||
| SQLite consistency | `VACUUM INTO` snapshot | Safe when stopped | Safe when stopped |
|
||||
| `data/log/` | Excluded | Included (optional exclude) | Included (optional exclude) |
|
||||
| Postfix queue | No | Yes | Yes |
|
||||
| `certs/` private key | Yes (the panel needs it readable by group 998; links are followed) | Yes when `certs/` holds the files; links are stored as links unless `tar -h` | No — deploy files separate |
|
||||
| `certs/` private key | Yes (the panel needs it readable by group 10998; links are followed) | Yes when `certs/` holds the files; links are stored as links unless `tar -h` | No — deploy files separate |
|
||||
| `data/manifest.json` | Written (version stamp) | Absent | Absent |
|
||||
| Restore version check | Yes — must match image tag | No | No |
|
||||
| Post-restore *Resync* | Yes, on first boot | No | No |
|
||||
|
||||
+3
-3
@@ -827,9 +827,9 @@ supervisord; `dev/upstream` refetched from trixie; the cited-package table
|
||||
and `checkCitedPackageVersions`; the e2e sink image; `opendkim-tools`
|
||||
dropped from the image (used by nothing at run time). OpenDKIM's code is
|
||||
the same in trixie, its citations stand. One stage, a MINOR release. Also
|
||||
gained: `smtpd_hide_client_session` (3.10) is available - whether the
|
||||
relay should hide the application's address from `Received:` is a product
|
||||
question to decide separately.
|
||||
gained: `smtpd_hide_client_session` (3.10); the owner decided on
|
||||
2026-10-15 to turn it on for 465 and 587 in the same release, so the
|
||||
`Received:` header no longer shows the application's address.
|
||||
|
||||
## panel-dkim-signing
|
||||
|
||||
|
||||
+18
-6
@@ -218,6 +218,14 @@ operator can act on it.
|
||||
|
||||
- **Not an open relay** — SASL only on 465/587; `reject_unauth_destination`;
|
||||
`smtpd_sender_login_maps` + `reject_sender_login_mismatch`.
|
||||
- **A recipient does not learn where an application runs (`2.7.0`).** The
|
||||
`Received:` header Postfix adds on 465 and 587 carries no client name,
|
||||
address, HELO name, TLS details or login (`smtpd_hide_client_session=yes`,
|
||||
the owner's decision of 2026-10-15): before, every message told its
|
||||
recipients the address and the internal host name of the machine the
|
||||
application sent from. What a complaint needs stays with the operator - the
|
||||
queue id in the header, the panel's log and smtpd's `client=` line in
|
||||
mail.log. Port 25 keeps the full header, as RFC 5321 requires of an MTA.
|
||||
- **An application signs only as itself (`2.4.0`).** OpenDKIM chooses the
|
||||
key by the `From:` header and Postfix holds only the envelope sender
|
||||
against the login, so until `2.4.0` an application of one sending domain
|
||||
@@ -264,12 +272,16 @@ operator can act on it.
|
||||
on inbound. An optional antispam milter is inbound-only; default action is
|
||||
fail-open (`accept`). Since `2.6.1` the listener ends DATA only at the
|
||||
standard `<CR><LF>.<CR><LF>` (`smtpd_forbid_bare_newline=normalize`, the
|
||||
2023 SMTP-smuggling defence, which Postfix 3.7 leaves off by default): a
|
||||
2023 SMTP-smuggling defence, which Postfix 3.7 left off by default): a
|
||||
`.` line next to a bare newline is body, and what a smuggler would put
|
||||
behind it is delivered as part of the one message, not read as a second
|
||||
transaction. The submission ports keep the default - there a smuggled
|
||||
transaction is still the authenticated session's, under its sender
|
||||
bindings and milters ([own-mta-builds](own-mta-builds-2026-10-15.md) § 5).
|
||||
transaction. The submission ports keep the default. Since `2.7.0` the
|
||||
image runs Postfix 3.10.13, whose default is `normalize` too (Postfix 3.9
|
||||
and later; `src/global/mail_params.h`, `DEF_SMTPD_FORBID_BARE_LF`), so
|
||||
they end DATA the same way; the port-25 setting stays explicit. Before
|
||||
`2.7.0` a smuggled transaction on a submission port was still the
|
||||
authenticated session's, under its sender bindings and milters
|
||||
([own-mta-builds](own-mta-builds-2026-10-15.md) § 5).
|
||||
- **The panel's milters judge every message of a connection.** Postfix
|
||||
introduces a client to a milter once per connection, and the milter library
|
||||
replaces the milter's session after any reply that is not "continue". Until
|
||||
@@ -303,7 +315,7 @@ operator can act on it.
|
||||
parsed from `mail.log` from the head of the line, with the reply ending at
|
||||
Postfix's own `; from=<` so that the HELO name after it - the client's text,
|
||||
which Postfix logs with non-printable characters and ` <>()\";@` turned into
|
||||
`?` (Postfix 3.7.11, src/smtpd/smtpd.c `helo_cmd()`) - cannot stand in for
|
||||
`?` (Postfix 3.10.13, src/smtpd/smtpd.c `helo_cmd()`) - cannot stand in for
|
||||
the sender, the recipient or the reply; an address with a quote, a bracket or a space in it is not kept.
|
||||
Only a recipient at an inbound domain makes a row. The journal is cut to
|
||||
10 000 rows every 500 rows the tailer writes, and by age every six hours.
|
||||
@@ -524,7 +536,7 @@ deferred item from the roadmap.
|
||||
§ Roles.
|
||||
- **Level-2 in-flight reservations can outlive a broken SMTP session until TTL.**
|
||||
Postfix tells the milters ABORT when a client drops the connection
|
||||
mid-transaction (Postfix 3.7.11, src/smtpd/smtpd.c `mail_reset()` ->
|
||||
mid-transaction (Postfix 3.10.13, src/smtpd/smtpd.c `mail_reset()` ->
|
||||
`milter_abort()`), and the journal milter releases the reservation then.
|
||||
When that never arrives - smtpd killed, the milter connection lost - go-milter
|
||||
has no close callback, and the in-memory reservation is released by TTL
|
||||
|
||||
Reference in New Issue
Block a user