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:
+62
-26
@@ -6,7 +6,7 @@
|
||||
# docker build -f build/Dockerfile -t selfpost:dev --build-arg VERSION=dev .
|
||||
|
||||
# ---- build stage -------------------------------------------------------------
|
||||
FROM golang:1.26-bookworm AS build
|
||||
FROM golang:1.26-trixie AS build
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
@@ -30,61 +30,95 @@ RUN go vet ./... \
|
||||
-o /out/selfpost-backup ./cmd/selfpost-backup
|
||||
|
||||
# ---- runtime stage -----------------------------------------------------------
|
||||
FROM debian:bookworm-slim AS runtime
|
||||
FROM debian:trixie-slim AS runtime
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Preseed Postfix so its install is non-interactive and yields a working
|
||||
# main.cf. The real relay configuration is generated by the panel.
|
||||
# (postfix 3.7.11-0+deb12u1, debian/templates: postfix/main_mailer_type and
|
||||
# postfix/mailname are the questions the install asks.)
|
||||
# What else arrives through Depends, and is used here: openssl, by
|
||||
# ca-certificates ("Depends: openssl (>= 1.1.1)", packages.debian.org/bookworm/
|
||||
# ca-certificates) - postfix-cert-reload.sh; python3, by supervisor (supervisor
|
||||
# 4.2.5-1, debian/control, ${python3:Depends}) - crashexit.py. sasl2-bin brings
|
||||
# saslpasswd2 and libsasl2-modules the PLAIN and LOGIN plugins; the sasldb
|
||||
# auxprop plugin is in libsasl2-modules-db, a Depends of libsasl2-2
|
||||
# (cyrus-sasl2 2.1.28+dfsg-10, debian/control and debian/*.install). db-util
|
||||
# ships /usr/bin/db_dump (packages.debian.org/bookworm/amd64/db-util/filelist).
|
||||
# (postfix 3.10.13-0+deb13u1, debian/templates:14 and :50:
|
||||
# postfix/main_mailer_type and postfix/mailname are the questions the install
|
||||
# asks; debian/postfix.postinst:156-171 writes main.cf for any answer but "No
|
||||
# configuration".)
|
||||
# openssl is named because postfix-cert-reload.sh runs it, not left to arrive
|
||||
# with ca-certificates. python3 arrives with supervisor (supervisor 4.2.5-3,
|
||||
# debian/control:29, ${python3:Depends}) - crashexit.py. sasl2-bin brings
|
||||
# saslpasswd2 (cyrus-sasl2 2.1.28+dfsg1-9, debian/rules:207,
|
||||
# debian/sasl2-bin.install:3, usr/sbin) and depends on db-util
|
||||
# (debian/control:36); libsasl2-modules brings the PLAIN and LOGIN plugins
|
||||
# (debian/libsasl2-modules.install:4 and :6); the sasldb auxprop plugin is in
|
||||
# libsasl2-modules-db (debian/libsasl2-modules-db.install:1), a Depends of
|
||||
# libsasl2-2 (debian/control:65). db-util is named for /usr/bin/db_dump, which
|
||||
# the panel runs (internal/app/sasl.go); that db-util ships that path on
|
||||
# trixie is not in a source read here - the image build and the e2e sasldb
|
||||
# test say whether it does.
|
||||
# opendkim-tools is not installed: nothing in the image runs its programs
|
||||
# (DKIM keys are made in Go, internal/domain/dkim.go).
|
||||
# systemd-standalone-sysusers is named so that cron-daemon-common, which
|
||||
# logrotate brings (logrotate 3.22.0-1, debian/control: "Depends: cron |
|
||||
# anacron | cron-daemon | systemd-sysv"), creates its group with it rather
|
||||
# than with the whole of systemd, the first alternative of its "Depends:
|
||||
# systemd | systemd-standalone-sysusers | systemd-sysusers" (dpkg -s in the
|
||||
# image, 2026-10-15). Nothing here runs systemd.
|
||||
RUN echo "postfix postfix/mailname string localhost" | debconf-set-selections \
|
||||
&& echo "postfix postfix/main_mailer_type string Internet Site" | debconf-set-selections \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
postfix \
|
||||
opendkim \
|
||||
opendkim-tools \
|
||||
sasl2-bin \
|
||||
libsasl2-modules \
|
||||
db-util \
|
||||
supervisor \
|
||||
logrotate \
|
||||
ca-certificates \
|
||||
openssl \
|
||||
curl \
|
||||
systemd-standalone-sysusers \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Unprivileged user for the panel process.
|
||||
# Debian's postfix runs its chroot sync on every "postfix check" and "postfix
|
||||
# start-fg" (postfix 3.10.13-0+deb13u1, debian/patches/
|
||||
# debian-run-configure-instance-from-create-missing.patch); with no service
|
||||
# chrooted (build/postfix-config.sh) it would only copy /etc/resolv.conf and
|
||||
# the like into the queue under /data on each start. SYNC_CHROOT empty turns
|
||||
# it off (debian/configure-instance.in:22-24 and :44; debian/README.Debian:136-138).
|
||||
RUN printf 'SYNC_CHROOT=\n' > /etc/default/postfix
|
||||
|
||||
# Unprivileged user for the panel process. Its ids are left to useradd: the
|
||||
# entrypoint gives /data back to it by name on every start, so a number
|
||||
# that differs from one image to the next changes nothing.
|
||||
RUN useradd --system --no-create-home --shell /usr/sbin/nologin panel
|
||||
|
||||
# Shared group bridging the unprivileged services: the panel generates
|
||||
# per-domain DKIM keys, application SASL accounts (sasldb2) and the Postfix
|
||||
# sender map, while OpenDKIM and Postfix (different users) must read them.
|
||||
# Membership in this group — plus setgid dirs under /data (set up in
|
||||
# entrypoint.sh) — lets OpenDKIM read the panel-owned keys and lets Postfix read
|
||||
# the sasldb2/sender map. (The supervisor control socket has a group of its
|
||||
# own, `supervisor`, below.)
|
||||
# The id is fixed, and it is the one the group has always had: an operator
|
||||
# gives the TLS private key on the host to this group by number, so that the
|
||||
# panel can put it into a full backup (guide § Taking a full backup).
|
||||
# groupadd(8) refuses a --gid already in use, so a package that took 998 would
|
||||
# Membership in this group - plus setgid dirs under /data (set up in
|
||||
# entrypoint.sh) - lets OpenDKIM read the panel-owned keys and lets Postfix
|
||||
# read the sasldb2/sender map. (The supervisor control socket has a group of
|
||||
# its own, `supervisor`, below.)
|
||||
# The id is fixed, because the host sees it: an operator gives the TLS
|
||||
# private key on the host to this group by number, so that the panel can put
|
||||
# it into a full backup (guide § Taking a full backup). It lies outside the
|
||||
# range the distribution hands out to system groups, 100-999 (adduser 3.152,
|
||||
# /etc/adduser.conf: "Default: FIRST_SYSTEM_GID=100, LAST_SYSTEM_GID=999";
|
||||
# passwd 1:4.17.4-2, /etc/login.defs: SYS_GID_MIN 101, SYS_GID_MAX 999 - both
|
||||
# read in the image, 2026-10-15), so no package can take it, and far above
|
||||
# the 1000 a host starts its users' groups at, so a host is unlikely to have
|
||||
# a group of that number that would read the key too. Until 2.7.0 it was
|
||||
# 998, inside that range: in the trixie image systemd took 998 for
|
||||
# systemd-network and the build failed, and on the test server's host 998
|
||||
# is systemd-network too (seen 2026-10-15).
|
||||
# groupadd(8) refuses a --gid already in use, so an id taken after all would
|
||||
# fail this build rather than share the group.
|
||||
RUN groupadd --system --gid 998 selfpost \
|
||||
RUN groupadd --system --gid 10998 selfpost \
|
||||
&& usermod -aG selfpost panel \
|
||||
&& usermod -aG selfpost opendkim \
|
||||
&& usermod -aG selfpost postfix
|
||||
|
||||
# The panel reads the mail queue with `postqueue -p` (and the status page's
|
||||
# queue card). postqueue is setgid postdrop, which normally gives it the group
|
||||
# needed to reach Postfix's showq socket — but the documented deployment runs
|
||||
# needed to reach Postfix's showq socket - but the documented deployment runs
|
||||
# with `no-new-privileges`, which disables setgid transitions, so the panel
|
||||
# would always see "Permission denied". Making `panel` a real member of postdrop
|
||||
# grants the same access without relying on a setgid escalation the hardening
|
||||
@@ -93,8 +127,10 @@ RUN groupadd --system --gid 998 selfpost \
|
||||
# to write the maildrop directory (internal/notify/mailer.go). It does not let
|
||||
# the panel bypass any Postfix restriction that a local user does not already
|
||||
# have through the world-executable sendmail.
|
||||
# (Postfix 3.7.11, conf/postfix-files: postdrop and postqueue are
|
||||
# root:$setgid_group 2755. Linux, Documentation/userspace-api/no_new_privs.rst:
|
||||
# (Postfix 3.10.13, conf/postfix-files:140-141: postdrop and postqueue are
|
||||
# root:$setgid_group 2755; Debian sets them root:postdrop 02555, postfix
|
||||
# 3.10.13-0+deb13u1, debian/postfix.postinst:134-141. Linux,
|
||||
# Documentation/userspace-api/no_new_privs.rst:
|
||||
# with no_new_privs, execve() no longer changes the gid for a setgid file.)
|
||||
RUN usermod -aG postdrop panel
|
||||
# The supervisord control socket's group (supervisord.conf, [unix_http_server]):
|
||||
|
||||
+23
-16
@@ -70,8 +70,13 @@ chmod 755 /data
|
||||
# under /data; make sure they stay writable without disturbing anything that a
|
||||
# later phase deliberately hands to another service. /data/log is exempt: it is
|
||||
# deliberately owned by postfix (postlogd writes the delivery log there) and is
|
||||
# normalised on its own below.
|
||||
find /data -mindepth 1 -maxdepth 1 ! -user panel ! -name log ! -name postfix -exec chown -R panel:panel {} +
|
||||
# normalised on its own below, as are the trees shared through group selfpost.
|
||||
# A file's owner is a number: an entry whose user or group is not the panel's
|
||||
# by that number - unpacked by root, or written under another image whose
|
||||
# useradd gave the panel other ids (bookworm's 999:999, until 2.7.0) - is
|
||||
# handed over whole.
|
||||
find /data -mindepth 1 -maxdepth 1 ! -name log ! -name postfix ! -name opendkim ! -name sasl ! -name antispam \
|
||||
\( ! -user panel -o ! -group panel \) -exec chown -R panel:panel {} +
|
||||
|
||||
# DKIM key tree. The panel (user `panel`) generates keys and writes
|
||||
# the OpenDKIM tables; OpenDKIM (user `opendkim`) must read them. Normalise the
|
||||
@@ -133,7 +138,7 @@ chmod 2750 /data/postfix
|
||||
# so the directory and its files are readable by anyone: they hold sender
|
||||
# addresses and networks, no secrets.
|
||||
mkdir -p /data/antispam
|
||||
chown panel:selfpost /data/antispam
|
||||
chown -R panel:selfpost /data/antispam
|
||||
chmod 0755 /data/antispam
|
||||
|
||||
# Delivery log (architecture.md § Log tailer). postlogd writes it as user
|
||||
@@ -141,17 +146,19 @@ chmod 0755 /data/antispam
|
||||
# lives under /data — not the ephemeral /var/log — so the delivery lines that
|
||||
# resolve a "queued" send-log row survive a container recreate.
|
||||
#
|
||||
# postlogd creates a missing log itself, but at 0600, which the unprivileged
|
||||
# panel cannot read; so create it here (and re-normalise an existing one, plus
|
||||
# whatever logrotate left behind) at 0640 owned postfix:selfpost. The setgid
|
||||
# postlogd creates a missing log itself, by default at 0600, which the
|
||||
# unprivileged panel cannot read (postfix-config.sh now asks it for 0640,
|
||||
# maillog_file_permissions); so create it here (and re-normalise an existing
|
||||
# one, plus whatever logrotate left behind) at 0640 owned postfix:selfpost. The setgid
|
||||
# directory keeps the shared group on anything created inside it later, and
|
||||
# 2750 keeps it group-traversable but not group-writable — logrotate refuses to
|
||||
# rotate a log whose directory is writable by a non-root group.
|
||||
# Postfix 3.7.11: src/util/logwriter.c, logwriter_open_or_die(), creates the
|
||||
# file 0644, under master's umask(077) (src/master/master.c) - 0600. logrotate
|
||||
# 3.21.0, logrotate.c, findNeedRotating(): "skipping ... because parent
|
||||
# directory has insecure permissions" when the directory's group is not root
|
||||
# and has write permission.
|
||||
# Postfix 3.10.13: src/util/logwriter.c:99, logwriter_open_or_die(), creates the
|
||||
# file with logwriter_perms, 0600 (:86) unless maillog_file_permissions says
|
||||
# otherwise (proto/postconf.proto:18500, default 0600; Postfix 3.9 and later).
|
||||
# logrotate 3.22.0, logrotate.c:1406-1408, findNeedRotating(): "skipping ...
|
||||
# because parent directory has insecure permissions" when the directory's
|
||||
# group is not root and has write permission.
|
||||
mkdir -p /data/log
|
||||
[ -e /data/log/mail.log ] || : > /data/log/mail.log
|
||||
chown -R postfix:selfpost /data/log
|
||||
@@ -199,8 +206,8 @@ ln -sfn /run/syslog/log /dev/log
|
||||
|
||||
# The queue tree, on a first start or after a restore, is already built here:
|
||||
# postfix-config.sh ends with "postfix check", whose check-fatal runs
|
||||
# "post-install create-missing" (Postfix 3.7.11, conf/postfix-script;
|
||||
# conf/postfix-files, "$queue_directory/active:d:$mail_owner:-:700:ucr").
|
||||
# "post-install create-missing" (Postfix 3.10.13, conf/postfix-script:314;
|
||||
# conf/postfix-files:55, "$queue_directory/active:d:$mail_owner:-:700:ucr").
|
||||
# A "postfix set-permissions" for a missing active/ stood here and never ran
|
||||
# (assumptions C-P34).
|
||||
chown -R postfix:postfix /data/postfix/queue
|
||||
@@ -214,10 +221,10 @@ chgrp postdrop /data/postfix/queue/public /data/postfix/queue/maildrop
|
||||
# It would also leave the queue directory itself and pid/ owned by postfix.
|
||||
# Postfix keeps those two owned by root (master, which runs as root, is the only
|
||||
# writer) and logs "warning: not owned by root" for each at every start.
|
||||
# (Postfix 3.7.11, conf/postfix-files: $queue_directory root 755, maildrop
|
||||
# (Postfix 3.10.13, conf/postfix-files:51-68: $queue_directory root 755, maildrop
|
||||
# $setgid_group 730, public $setgid_group 710, pid root 755, the other queue
|
||||
# directories $mail_owner 700; conf/postfix-script, "check-warn": "not owned by
|
||||
# root".)
|
||||
# directories $mail_owner 700; conf/postfix-script:354-356, "check-warn": "not
|
||||
# owned by root".)
|
||||
chown root /data/postfix/queue /data/postfix/queue/pid
|
||||
|
||||
# [program:postfix]'s startsecs (build/supervisord.conf): longer than the
|
||||
|
||||
+36
-24
@@ -7,25 +7,29 @@
|
||||
# before the reload is lost. `create` (rather than `nocreate`) matters here
|
||||
# beyond timing: a postlogd-triggered recreate lands the file at 0600 owned by
|
||||
# postfix, which the unprivileged panel process cannot read — confirmed on a
|
||||
# live container (date not recorded), and so in the source: Postfix 3.7.11,
|
||||
# src/util/logwriter.c, logwriter_open_or_die(), creates it 0644 under master's
|
||||
# umask(077), src/master/master.c — so logrotate must be the one to create it.
|
||||
# The reload: Postfix 3.7.11, conf/postfix-script, "logrotate" (mv, then kill
|
||||
# -HUP master); master_conf.c, master_config() -> master_restart_service() ->
|
||||
# master_status_cleanup(), on which postlogd exits (src/master/dgram_server.c,
|
||||
# dgram_server_abort() on MASTER_STATUS_FD), and the next postlogd opens the
|
||||
# path anew (src/postlogd/postlogd.c, logwriter_open_or_die()).
|
||||
# live container (date not recorded, Postfix 3.7.11), and so in the source:
|
||||
# Postfix 3.10.13, src/util/logwriter.c:99, logwriter_open_or_die(), creates it
|
||||
# with logwriter_perms, 0600 (:86) unless maillog_file_permissions says
|
||||
# otherwise (Postfix 3.9 and later). build/postfix-config.sh now sets that to
|
||||
# 0640, so a file postlogd makes is readable too; logrotate still creates it,
|
||||
# so that the new log is there, with its mode and owner, at once.
|
||||
# The reload: Postfix 3.10.13, conf/postfix-script:535-566, "logrotate" (mv,
|
||||
# then kill -HUP master); master_conf.c:92, master_config() -> :147,
|
||||
# master_restart_service() -> master_status_cleanup(), on which postlogd exits
|
||||
# (src/master/dgram_server.c:241, dgram_server_abort(), :640 on
|
||||
# MASTER_STATUS_FD), and the next postlogd opens the path anew
|
||||
# (src/postlogd/postlogd.c:236, logwriter_open_or_die()).
|
||||
#
|
||||
# logrotate itself only rotates once the configured "daily" period has elapsed
|
||||
# (tracked in its state file), so it is safe to invoke this more often than
|
||||
# daily — polling merely bounds how late a legitimate rotation runs. The state
|
||||
# file is beside the log, under /data, not at the default
|
||||
# /var/lib/logrotate/status (logrotate 3.21.0-1: debian/rules,
|
||||
# /var/lib/logrotate/status (logrotate 3.22.0-1: debian/rules:11,
|
||||
# --with-state-file-path) in the container's own filesystem: a recreated
|
||||
# container started without it, logrotate.c, newState(), records a log it has
|
||||
# no state for as rotated today, and so a host that recreates the container
|
||||
# container started without it, logrotate.c:456, newState(), records a log it
|
||||
# has no state for as rotated today, and so a host that recreates the container
|
||||
# daily - an .env change, an image bump - never rotated mail.log at all (audit
|
||||
# 2026-10-12, M19). (logrotate 3.21.0, logrotate.8.in: "-s, --state
|
||||
# 2026-10-12, M19). (logrotate 3.22.0, logrotate.8.in:74: "-s, --state
|
||||
# statefile".) The directory is postfix:selfpost 2750 and logrotate runs as
|
||||
# root.
|
||||
set -eu
|
||||
@@ -34,13 +38,18 @@ CONFIG=/etc/logrotate.d/mail
|
||||
STATE=/data/log/.logrotate.status
|
||||
INTERVAL="${LOGROTATE_INTERVAL_SECONDS:-21600}"
|
||||
|
||||
# logrotate refuses configs writable by group or others and exits 0 while
|
||||
# ignoring them — fail here so supervisord reports the fault. (logrotate
|
||||
# 3.21.0, config.c, readConfigFile(): as root, mode & 0022 gives "Ignoring ...
|
||||
# because it is writable by group or others." and returns 0, not an error; a
|
||||
# config not owned by root is ignored the same way. "Potentially dangerous
|
||||
# mode" is only a warning there, for any mode & 07533 other than 0400 - this
|
||||
# loop chooses to treat it as fatal too.)
|
||||
# logrotate refuses configs writable by group or others and goes on without
|
||||
# them — fail here so supervisord reports the fault. (logrotate 3.22.0,
|
||||
# config.c:1099, readConfigFile(): as root, mode & 0022 gives (:1160-1165)
|
||||
# "Ignoring ... because it is writable by group or others." and returns 1; a
|
||||
# config not owned by root is ignored the same way (:1168-1173). Since 3.22.0
|
||||
# that makes logrotate's exit status 1 (logrotate.c:3278-3279 and :3301;
|
||||
# ChangeLog.md, 3.22.0: "in case a configuration file was skipped due to
|
||||
# unsafe permissions the exit status after rotattion will be `1` (#508)");
|
||||
# 3.21.0 returned 0 there and exited 0. So the output is looked at whatever
|
||||
# the status. "Potentially dangerous mode" is only a warning there
|
||||
# (:1154-1157), for any mode & 07533 other than 0400 - this loop chooses to
|
||||
# treat it as fatal too.)
|
||||
logrotate_config_ok() {
|
||||
mode=$(stat -c '%a' "$CONFIG")
|
||||
mode=${mode#0}
|
||||
@@ -61,16 +70,19 @@ if ! logrotate_config_ok; then
|
||||
fi
|
||||
|
||||
run_logrotate() {
|
||||
out=$(logrotate -s "$STATE" "$CONFIG" 2>&1) || {
|
||||
echo "$out" >&2
|
||||
return 1
|
||||
}
|
||||
status=0
|
||||
out=$(logrotate -s "$STATE" "$CONFIG" 2>&1) || status=$?
|
||||
case "$out" in
|
||||
*Ignoring*|*Potentially\ dangerous\ mode*)
|
||||
echo "$out" >&2
|
||||
logrotate_config_fatal
|
||||
echo "logrotate-loop: refusing to run: logrotate did not take $CONFIG as it is (above)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
if [ "$status" -ne 0 ]; then
|
||||
echo "$out" >&2
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# build/postfix-config.sh (maillog_file) and build/entrypoint.sh: postlogd
|
||||
# writes it as user postfix, the unprivileged panel reads it through the shared
|
||||
# selfpost group, hence create 0640 postfix selfpost rather than 0644 root root.
|
||||
# (logrotate 3.21.0, logrotate.8: "create mode owner group" makes the new log
|
||||
# (logrotate 3.22.0, logrotate.8:402: "create mode owner group" makes the new log
|
||||
# right after the rotation; "delaycompress" leaves the previous log, .1,
|
||||
# uncompressed until the next cycle.)
|
||||
/data/log/mail.log {
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
# copies - not the mounted files, which a renewer may be rewriting - are a
|
||||
# certificate and the key that belongs to it, renames both over the targets
|
||||
# and reloads Postfix. When nothing changed it does nothing.
|
||||
# (Postfix 3.7.11: smtpd loads the certificate in pre_jail_init(), once per
|
||||
# process, src/smtpd/smtpd.c; "postfix reload" is SIGHUP to master,
|
||||
# conf/postfix-script, after which the old smtpd processes exit on the closed
|
||||
# (Postfix 3.10.13: smtpd loads the certificate in pre_jail_init(), once per
|
||||
# process, src/smtpd/smtpd.c:6442; "postfix reload" is SIGHUP to master,
|
||||
# conf/postfix-script:277, after which the old smtpd processes exit on the closed
|
||||
# status pipe, src/master/master_conf.c, master_config() ->
|
||||
# master_restart_service(), and new ones load the files again.)
|
||||
#
|
||||
|
||||
+191
-120
@@ -24,18 +24,19 @@ set -eu
|
||||
# passed here is written with commas only; a value with whitespace is a bug in
|
||||
# this script and stops the container.
|
||||
#
|
||||
# Postfix 3.7.11, man/man5/master.5, "-o name=value (short form)", NOTE 2: no
|
||||
# whitespace in a short-form value (the long form "-o { name = value }" would
|
||||
# keep it; this script does not write it). src/postconf/postconf_edit.c,
|
||||
# Postfix 3.10.13, man/man5/master.5:215-226, "-o name=value (short form)":
|
||||
# "The "short form" does not support whitespace around the "=" or in
|
||||
# parameter values" (the long form "-o { name = value }" would keep it; this
|
||||
# script does not write it). src/postconf/postconf_edit.c:415,
|
||||
# pcf_edit_master(): "-Pe does not accept whitespace in parameter value".
|
||||
# src/smtpd/smtpd_check.c, smtpd_check_parse(), and src/milter/milter.c,
|
||||
# src/smtpd/smtpd_check.c:664, smtpd_check_parse(), and src/milter/milter.c:646,
|
||||
# milter_new(), split their lists on CHARS_COMMA_SP: commas or whitespace.
|
||||
#
|
||||
# master.5, SYNTAX: a line that starts with whitespace continues a logical
|
||||
# line, so the "-o" line awk inserts below the header belongs to the service.
|
||||
# A later postconf -F/-P re-parses a matched entry with its continuation lines
|
||||
# (postconf_edit.c, pcf_gobble_cf_line()) and prints it whole again, keeping
|
||||
# these options.
|
||||
# master.5:41-42, SYNTAX: a line that starts with whitespace continues a
|
||||
# logical line, so the "-o" line awk inserts below the header belongs to the
|
||||
# service. A later postconf -F/-P re-parses a matched entry with its
|
||||
# continuation lines (postconf_edit.c:134, pcf_gobble_cf_line()) and prints
|
||||
# it whole again, keeping these options.
|
||||
master_cf_set_smtp_inet_o() {
|
||||
param="$1"
|
||||
value="$2"
|
||||
@@ -75,7 +76,7 @@ master_cf_set_smtp_inet_o() {
|
||||
# sasldb2 accounts are looked up under. The panel creates accounts under realm
|
||||
# $SELFPOST_HOSTNAME (SASL_REALM), so myhostname MUST match or authentication
|
||||
# fails. Fall back to the container hostname only outside a real deployment.
|
||||
# Postfix 3.7.11, src/xsasl/xsasl_cyrus_server.c, xsasl_cyrus_server_create():
|
||||
# Postfix 3.10.13, src/xsasl/xsasl_cyrus_server.c:339, xsasl_cyrus_server_create():
|
||||
# sasl_server_new() gets var_myhostname as serverFQDN; Cyrus SASL 2.1.28,
|
||||
# plugins/sasldb.c, sasldb_auxprop_lookup(): with no user realm the account is
|
||||
# looked up under serverFQDN.
|
||||
@@ -83,17 +84,17 @@ HOSTNAME_VALUE="${SELFPOST_HOSTNAME:-$(hostname -f 2>/dev/null || hostname)}"
|
||||
|
||||
# TLS material supplied by the reverse-proxy through a read-only bind mount. The
|
||||
# relay requires TLS on 465; if these files are absent the master still starts
|
||||
# but TLS handshakes on 465 fail until they appear. Postfix 3.7.11,
|
||||
# but TLS handshakes on 465 fail until they appear. Postfix 3.10.13,
|
||||
# src/smtpd/smtpd.c: pre_jail_init() only warns when the TLS context cannot be
|
||||
# made, and smtpd_proto() then drops each wrapper-mode connection ("Wrapper-mode
|
||||
# request dropped ... TLS context initialization failed"); every new smtpd
|
||||
# process loads the files again in pre_jail_init().
|
||||
# made, and smtpd_proto() then drops each wrapper-mode connection (:5846,
|
||||
# "Wrapper-mode request dropped ... TLS context initialization failed"); every
|
||||
# new smtpd process loads the files again in pre_jail_init().
|
||||
#
|
||||
# Postfix's TLS_README advises a private key owned by root and readable by no
|
||||
# other user; Postfix does not enforce it (smtpd loads the key as root in
|
||||
# pre_jail_init(), Postfix 3.7.11 src/smtpd/smtpd.c), but the mount cannot
|
||||
# pre_jail_init(), Postfix 3.10.13 src/smtpd/smtpd.c), but the mount cannot
|
||||
# give that mode - it is :ro and owned by the host's user, often readable by
|
||||
# group 998 for the panel's backup. So the pair is copied into a directory of
|
||||
# group 10998 for the panel's backup. So the pair is copied into a directory of
|
||||
# Postfix's own, the key root:root 0600, before postconf and `postfix check`
|
||||
# (assumptions D-arch:154).
|
||||
TLS_CERT_SRC="${TLS_CERT_FILE:-/etc/postfix/tls/fullchain.pem}"
|
||||
@@ -123,11 +124,13 @@ RATE_WINDOW="${RATE_LIMIT_WINDOW_SECONDS:-3600}"
|
||||
# first connection, or anvil and with it the rate limit (audit 2026-10-12,
|
||||
# L31; review of stage 5) - so the start stops here, saying which and why.
|
||||
# Counts and sizes are whole numbers from min up to 2147483647, an int's top
|
||||
# (Postfix 3.7.11 reads them with get_mail_conf_int*, src/global/
|
||||
# mail_conf_int.c: an int, held to the parameter's own minimum). A time value
|
||||
# (Postfix 3.10.13 reads the counts with get_mail_conf_int*, src/global/
|
||||
# mail_conf_int.c, check_mail_conf_int(): an int, held to the parameter's own minimum;
|
||||
# message_size_limit is a long, src/global/mail_params.h:2014, and is held to
|
||||
# the int's top here all the same). A time value
|
||||
# is a whole number from 1 with an optional unit, s m h d or w, at most
|
||||
# 2147483647 seconds
|
||||
# (proto/postconf.proto: "Specify a non-zero time value (an integral value
|
||||
# (proto/postconf.proto:266-268: "Specify a non-zero time value (an integral value
|
||||
# plus an optional one-letter suffix ...). Time units: s (seconds), m
|
||||
# (minutes), h (hours), d (days), w (weeks)"; one unit - "1h30m" is read as
|
||||
# 1h, src/global/conv_time.c - so it is refused here).
|
||||
@@ -184,8 +187,8 @@ JOURNAL_SOCK="${JOURNAL_MILTER_SOCKET:-/run/selfpost/journal.sock}"
|
||||
SENDER_SOCK="${SENDER_CHECK_SOCKET:-/run/selfpost/sender.sock}"
|
||||
|
||||
# Persistent panel-managed sender map; texthash needs no postmap, so the
|
||||
# unprivileged panel can rewrite it and just ask for a reload. Postfix 3.7.11,
|
||||
# html/DATABASE_README.html, "texthash": no postmap(1), and the file is read
|
||||
# unprivileged panel can rewrite it and just ask for a reload. Postfix 3.10.13,
|
||||
# html/DATABASE_README.html:458-464, "texthash": no postmap(1), and the file is read
|
||||
# once by each process - a reload makes the new processes read it again.
|
||||
SENDER_LOGIN_MAPS="${POSTFIX_SENDER_LOGIN_MAPS:-/data/postfix/sender_login_maps}"
|
||||
# Transit mail queue under /data so deferred/active messages survive container
|
||||
@@ -239,25 +242,37 @@ postconf -PX "cleanup/unix/message_size_limit" 2>/dev/null || true
|
||||
# maillog_file lives under the persistent /data bind mount (architecture.md §
|
||||
# Log tailer). Postfix's default maillog_file_prefixes are only /var and
|
||||
# /dev/stdout — without /data, `postfix check` rejects the path.
|
||||
# inet_protocols=all in a bridge network without IPv6: Postfix 3.7.11,
|
||||
# src/util/inet_proto.c, inet_proto_init(), keeps IPv6 when socket(PF_INET6)
|
||||
# succeeds, and src/smtp/smtp_connect.c, smtp_connect_inet(), goes on to
|
||||
# the next address when a connection fails - an AAAA that cannot be reached
|
||||
# inet_protocols=all in a bridge network without IPv6: Postfix 3.10.13,
|
||||
# src/util/inet_proto.c:200, inet_proto_init(), keeps IPv6 when socket(PF_INET6)
|
||||
# succeeds, and src/smtp/smtp_connect.c:1102-1142, smtp_connect_inet(), goes on
|
||||
# to the next address when a connection fails - an AAAA that cannot be reached
|
||||
# costs one failed attempt, then IPv4. Postfix
|
||||
# 3.7.11, src/global/mail_params.h, DEF_MAILLOG_FILE_PFXS "/var, /dev/stdout";
|
||||
# src/global/maillog_client.c, maillog_client_init(): fatal "does not match any
|
||||
# 3.10.13, src/global/mail_params.h:4406, DEF_MAILLOG_FILE_PFXS "/var, /dev/stdout";
|
||||
# src/global/maillog_client.c:232, maillog_client_init(): fatal "does not match any
|
||||
# prefix", in every Postfix program that logs, postfix(1) itself included.
|
||||
#
|
||||
# maillog_file_permissions=0640: the log is the panel's to read, through
|
||||
# group selfpost (the setgid /data/log, build/entrypoint.sh). entrypoint.sh
|
||||
# and logrotate's "create" make it 0640 themselves; this is for a file that
|
||||
# Postfix makes - one removed while the container runs - which would
|
||||
# otherwise be 0600 and unreadable to the panel. (Postfix 3.10.13,
|
||||
# src/global/mail_params.h:4418, DEF_MAILLOG_FILE_PERMS "0600";
|
||||
# src/global/mail_params.c:997, set_logwriter_create_perms(); src/util/
|
||||
# logwriter.c:143-148: "644", "640" or "600", leading zeros ignored, anything
|
||||
# else a warning and 0600; :99, logwriter_open_or_die() creates with it;
|
||||
# Postfix 3.9 and later, proto/postconf.proto:18500-18509.)
|
||||
postconf -e \
|
||||
"myhostname=${HOSTNAME_VALUE}" \
|
||||
"maillog_file=${MAIL_LOG_PATH}" \
|
||||
"maillog_file_prefixes=/var,/dev/stdout,/data" \
|
||||
"maillog_file_permissions=0640" \
|
||||
"mydestination=" \
|
||||
"relayhost=" \
|
||||
"inet_interfaces=all" \
|
||||
"inet_protocols=all"
|
||||
|
||||
# postlogd is mandatory whenever maillog_file is set (Postfix 3.7.11,
|
||||
# html/MAILLOG_README.html, "Configuring logging to file"; conf/postfix-script,
|
||||
# postlogd is mandatory whenever maillog_file is set (Postfix 3.10.13,
|
||||
# html/MAILLOG_README.html:42, "Configuring logging to file"; conf/postfix-script:333,
|
||||
# "check-fatal": "missing 'postlog' service in master.cf"). Debian's
|
||||
# stock master.cf usually has it; pin it explicitly so a stripped/upgraded
|
||||
# image cannot lose the service.
|
||||
@@ -266,10 +281,10 @@ postconf -M "postlog/unix-dgram=postlog unix-dgram n - n - 1 postlogd"
|
||||
# This is an outbound relay: no local delivery, no per-user aliases. Empty
|
||||
# these so a misfiled recipient never gets delivered locally. An empty
|
||||
# mydestination is not enough: an address literal of an interface the
|
||||
# container listens on - x@[127.0.0.1] - is local too (Postfix 3.7.11,
|
||||
# container listens on - x@[127.0.0.1] - is local too (Postfix 3.10.13,
|
||||
# src/global/resolve_local.c, resolve_local(), own_inet_addr()), so local(8)
|
||||
# itself is replaced by a refusal, as Postfix's null-client example does
|
||||
# (README_FILES/STANDARD_CONFIGURATION_README, "local_transport = error:local
|
||||
# (README_FILES/STANDARD_CONFIGURATION_README:209, "local_transport = error:local
|
||||
# mail delivery is disabled"; assumptions C-P8).
|
||||
postconf -e \
|
||||
"local_recipient_maps=" \
|
||||
@@ -283,9 +298,9 @@ postconf -e \
|
||||
"smtp_tls_note_starttls_offer=yes"
|
||||
|
||||
# TLS server material shared by every inbound service. auth_only guarantees
|
||||
# credentials are never accepted before TLS is up on any port. Postfix 3.7.11,
|
||||
# src/smtpd/smtpd_sasl_proto.c, smtpd_sasl_auth_cmd(): "504 5.5.4 Encryption
|
||||
# required" while auth_only is set and TLS is not up; src/smtpd/smtpd.c,
|
||||
# credentials are never accepted before TLS is up on any port. Postfix 3.10.13,
|
||||
# src/smtpd/smtpd_sasl_proto.c:188, smtpd_sasl_auth_cmd(): "504 5.5.4 Encryption
|
||||
# required" while auth_only is set and TLS is not up; src/smtpd/smtpd.c:6546-6547,
|
||||
# pre_jail_init(): wrappermode implies enforce, and enforce implies auth_only.
|
||||
postconf -e \
|
||||
"smtpd_tls_cert_file=${TLS_CERT}" \
|
||||
@@ -297,12 +312,13 @@ postconf -e \
|
||||
# TLS 1.2 at least, opportunistic or required - and the client's too where an
|
||||
# inbound domain requires TLS to its upstream (smtp_tls_mandatory_protocols,
|
||||
# the security review of stage 5) (audit 2026-10-12, L31; Postfix
|
||||
# 3.7.11, proto/postconf.proto, smtpd_tls_protocols: since 3.6 the default is
|
||||
# ">=TLSv1", and a lower bound is written ">=version"). A sender that offers
|
||||
# less on port 25 sends in the clear, as one without TLS does. VRFY answers
|
||||
# no question about who is accepted on port 25 (relay_recipient_maps), and
|
||||
# the banner names no distribution (Debian's adds "(Debian/GNU)"; the
|
||||
# upstream default is "$myhostname ESMTP $mail_name").
|
||||
# 3.10.13, proto/postconf.proto:13240-13250, smtpd_tls_protocols: since 3.6 the
|
||||
# default is ">=TLSv1", and a lower bound is written ">=version"). A sender that
|
||||
# offers less on port 25 sends in the clear, as one without TLS does. VRFY
|
||||
# answers no question about who is accepted on port 25 (relay_recipient_maps),
|
||||
# and the banner names no distribution (Debian's adds "(Debian)": postfix
|
||||
# 3.10.13-0+deb13u1, debian/main.cf.in:15 "(@DEB_VENDOR@)", filled in by
|
||||
# debian/rules:118; the upstream default is "$myhostname ESMTP $mail_name").
|
||||
postconf -e \
|
||||
"smtpd_tls_protocols=>=TLSv1.2" \
|
||||
"smtpd_tls_mandatory_protocols=>=TLSv1.2" \
|
||||
@@ -315,7 +331,7 @@ postconf -e \
|
||||
# uses for sender_login_maps is the BARE login the panel writes into the map;
|
||||
# the sasldb2 lookup still resolves because Postfix hands Cyrus $myhostname as
|
||||
# the server realm, which equals the realm the accounts were created under.
|
||||
# (Postfix 3.7.11, src/smtpd/smtpd_sasl_glue.c, smtpd_sasl_activate(): an empty
|
||||
# (Postfix 3.10.13, src/smtpd/smtpd_sasl_glue.c:249, smtpd_sasl_activate(): an empty
|
||||
# smtpd_sasl_local_domain is a NULL user realm; see HOSTNAME_VALUE above.)
|
||||
postconf -e \
|
||||
"smtpd_sasl_auth_enable=yes" \
|
||||
@@ -330,7 +346,7 @@ postconf -e \
|
||||
# up in this map with mail_addr_find(), the order being smtpd's and not
|
||||
# texthash's: the full address, the address without its extension, then the
|
||||
# "@domain" wildcard - so both address modes work from the same map (Postfix
|
||||
# 3.7.11, src/smtpd/smtpd_check.c, reject_auth_sender_login_mismatch();
|
||||
# 3.10.13, src/smtpd/smtpd_check.c:3933, reject_auth_sender_login_mismatch();
|
||||
# src/global/mail_addr_find.c, mail_addr_find_opt() with MA_FIND_DEFAULT).
|
||||
#
|
||||
# recipient_delimiter is said here and not left to the distribution's main.cf:
|
||||
@@ -347,9 +363,9 @@ postconf -e \
|
||||
# Restrictions: authenticated clients only, no relay to foreign destinations,
|
||||
# and every authenticated sender address must be owned by its login. NO
|
||||
# permit_mynetworks anywhere — authorisation is by credentials, never by
|
||||
# network. This is what makes an open relay impossible. (Postfix 3.7.11,
|
||||
# src/smtpd/smtpd_check.c, smtpd_check_rcpt(): the relay and recipient lists
|
||||
# are evaluated in turn and the first REJECT ends them.)
|
||||
# network. This is what makes an open relay impossible. (Postfix 3.10.13,
|
||||
# src/smtpd/smtpd_check.c:5076, smtpd_check_rcpt(), :5160-5173: the relay and
|
||||
# recipient lists are evaluated in turn and the first REJECT ends them.)
|
||||
postconf -e \
|
||||
"smtpd_helo_required=yes" \
|
||||
"smtpd_relay_restrictions=permit_sasl_authenticated, reject_unauth_destination" \
|
||||
@@ -357,16 +373,24 @@ postconf -e \
|
||||
"smtpd_sender_restrictions=reject_sender_login_mismatch, permit"
|
||||
|
||||
# Level-1 rate limit by client IP. Backstop that keeps working even if the
|
||||
# journal-milter (level 2) is down. Postfix 3.7.11, src/smtpd/smtpd.c,
|
||||
# mail_cmd(): "450 4.7.1 Error: too much mail from", except for clients in
|
||||
# smtpd_client_event_limit_exceptions (default $mynetworks; Debian's debconf
|
||||
# default, debian/templates "postfix/mynetworks", is loopback only).
|
||||
# journal-milter (level 2) is down. Postfix 3.10.13, src/smtpd/smtpd.c:2566,
|
||||
# mail_cmd(), :2624: "450 4.7.1 Error: too much mail from", except for clients
|
||||
# in smtpd_client_event_limit_exceptions (:2619, hogger_list; default
|
||||
# $mynetworks, src/smtpd/smtpd.c:779).
|
||||
# anvil_rate_time_unit is one global window for every anvil limit (postconf(5)).
|
||||
# "Client IP" is the address the container sees: an IPv4 client's own, but
|
||||
# for a client over IPv6 the compose network's gateway, through Docker's
|
||||
# proxy - measured on a live server, 2026-10-15, Docker 29.6.1 (assumptions
|
||||
# C-K5; guide, "Rate limiting - level 1").
|
||||
#
|
||||
# mynetworks is said here, loopback only, and not left to the package: it is
|
||||
# what the exceptions above and smtpd_forbid_bare_newline_exclusions (below)
|
||||
# spare. The value is the one Debian's debconf default gives (postfix
|
||||
# 3.10.13-0+deb13u1, debian/templates:124-126, "postfix/mynetworks", written
|
||||
# by debian/postfix.postinst:249-262; debian/main.cf.in:28 has 127.0.0.0/8
|
||||
# alone), so on this image it changes nothing.
|
||||
postconf -e \
|
||||
"mynetworks=127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128" \
|
||||
"smtpd_client_message_rate_limit=${RATE_MSGS}" \
|
||||
"anvil_rate_time_unit=${RATE_WINDOW}s"
|
||||
|
||||
@@ -386,12 +410,12 @@ postconf -e \
|
||||
# on this chain, since they are not an application's mail. Bounces Postfix
|
||||
# generates itself do not pass it (internal_mail_filter_classes is empty).
|
||||
#
|
||||
# Postfix 3.7.11: html/MILTER_README.html, "Different settings for different
|
||||
# Milter applications" (the braces, Postfix 3.0 and later) and "Signing
|
||||
# internally-generated bounce messages"; src/milter/milter.c, milter_*_event()
|
||||
# and milter_message(): milters are asked in list order and the first non-empty
|
||||
# reply ends the round; src/global/mail_params.h, DEF_INT_FILT_CLASSES
|
||||
# INT_FILT_CLASS_NONE.
|
||||
# Postfix 3.10.13: html/MILTER_README.html:584, "Different settings for different
|
||||
# Milter applications" (the braces, Postfix 3.0 and later) and :449, "Signing
|
||||
# internally-generated bounce messages"; src/milter/milter.c:399-564,
|
||||
# milter_*_event() and milter_message(): milters are asked in list order and
|
||||
# the first non-empty reply ends the round; src/global/mail_params.h:3599,
|
||||
# DEF_INT_FILT_CLASSES INT_FILT_CLASS_NONE.
|
||||
postconf -e \
|
||||
"milter_protocol=6" \
|
||||
"milter_default_action=tempfail" \
|
||||
@@ -403,7 +427,7 @@ postconf -e \
|
||||
# application's client-address list) and record it (the inbound journal), and
|
||||
# the address given once at connect is not there for a connection's later
|
||||
# messages - see MailFrom in internal/milter/milter.go. The defaults: Postfix
|
||||
# 3.7.11, src/global/mail_params.h, DEF_MILT_MAIL_MACROS.
|
||||
# 3.10.13, src/global/mail_params.h:3532-3534, DEF_MILT_MAIL_MACROS.
|
||||
postconf -e \
|
||||
"milter_mail_macros=i {auth_type} {auth_authen} {auth_author} {mail_addr} {mail_host} {mail_mailer} {client_addr}"
|
||||
|
||||
@@ -413,7 +437,7 @@ postconf -e \
|
||||
# journal-milter hang then resolves to accept and an OpenDKIM hang to tempfail,
|
||||
# but within seconds rather than minutes. Values are well above any healthy
|
||||
# response time (signing/DB insert are sub-second), so they never fire in normal
|
||||
# operation. The defaults: Postfix 3.7.11, src/global/mail_params.h,
|
||||
# operation. The defaults: Postfix 3.10.13, src/global/mail_params.h:3559-3567,
|
||||
# DEF_MILT_CONN_TIME "30s", DEF_MILT_CMD_TIME "30s", DEF_MILT_MSG_TIME "300s".
|
||||
postconf -e \
|
||||
"milter_connect_timeout=${MILTER_CONNECT_TIMEOUT:-15s}" \
|
||||
@@ -424,12 +448,26 @@ postconf -e \
|
||||
# smtps (465, implicit/wrapper TLS) — the primary, always-on submission
|
||||
# service. chroot=n so smtpd can read the sasldb2 and sender map under /data
|
||||
# and the Cyrus config outside any chroot.
|
||||
# The names smtps, submission and smtp need no /etc/services: Postfix 3.7.11,
|
||||
# src/global/mail_params.h, DEF_KNOWN_TCP_PORTS "lmtp=24, smtp=25,
|
||||
# smtps=submissions=465, submission=587" (src/util/find_inet.c,
|
||||
# The names smtps, submission and smtp need no /etc/services: Postfix 3.10.13,
|
||||
# src/global/mail_params.h:4450-4451, DEF_KNOWN_TCP_PORTS "lmtp=24, smtp=25,
|
||||
# smtps=submissions=465, submission=587" (src/util/find_inet.c:94-99,
|
||||
# find_inet_port() via filter_known_tcp_port()).
|
||||
#
|
||||
# smtpd_hide_client_session=yes on both submission services (owner's
|
||||
# decision, 2026-10-15): the Received: header Postfix adds names neither the
|
||||
# application's HELO name, reverse name and address, nor its TLS session,
|
||||
# SASL login or the protocol - only "Received: by <myhostname> (Postfix) id
|
||||
# <queue id>", "for <rcpt>" when there is one recipient, and the date
|
||||
# (Postfix 3.10.13, src/smtpd/smtpd.c:3495, the client part written only
|
||||
# when the parameter is off; :3627-3645, the "by" line opening the header
|
||||
# when it is on). proto/postconf.proto:19580-19594: "may be used for the
|
||||
# port 587 and 465 MUA services"; port 25 must keep the default, RFC 5321's
|
||||
# trace, and does (smtp/inet below sets nothing). The queue id stays in the
|
||||
# header, so a complaint about a message still leads to its row in the
|
||||
# panel's log and to smtpd's "client=" line in mail.log.
|
||||
postconf -M "smtps/inet=smtps inet n - n - - smtpd"
|
||||
postconf -P \
|
||||
"smtps/inet/smtpd_hide_client_session=yes" \
|
||||
"smtps/inet/smtpd_tls_wrappermode=yes" \
|
||||
"smtps/inet/smtpd_sasl_auth_enable=yes" \
|
||||
"smtps/inet/smtpd_client_restrictions=permit_sasl_authenticated,reject"
|
||||
@@ -437,13 +475,15 @@ postconf -P \
|
||||
# submission (587, STARTTLS) — optional. Same SASL/milter/limits; the only
|
||||
# difference is TLS is negotiated via STARTTLS, so require encryption before
|
||||
# auth. Added only when SUBMISSION_ENABLE=true, otherwise removed so a restart
|
||||
# after disabling it does not leave the port listening. (Postfix 3.7.11,
|
||||
# src/smtpd/smtpd.c, pre_jail_init(): smtpd_tls_security_level=encrypt sets
|
||||
# enforce_tls, which implies smtpd_tls_auth_only; src/postconf/postconf_edit.c,
|
||||
# pcf_edit_master(): -MX drops the matched entry, continuation lines included.)
|
||||
# after disabling it does not leave the port listening. (Postfix 3.10.13,
|
||||
# src/smtpd/smtpd.c:6525-6526, pre_jail_init(): smtpd_tls_security_level=encrypt
|
||||
# sets enforce_tls, which implies smtpd_tls_auth_only (:6547);
|
||||
# src/postconf/postconf_edit.c:319, pcf_edit_master(): -MX drops the matched
|
||||
# entry, continuation lines included.)
|
||||
if [ "${SUBMISSION_ENABLE}" = "true" ]; then
|
||||
postconf -M "submission/inet=submission inet n - n - - smtpd"
|
||||
postconf -P \
|
||||
"submission/inet/smtpd_hide_client_session=yes" \
|
||||
"submission/inet/smtpd_tls_security_level=encrypt" \
|
||||
"submission/inet/smtpd_sasl_auth_enable=yes" \
|
||||
"submission/inet/smtpd_client_restrictions=permit_sasl_authenticated,reject"
|
||||
@@ -452,9 +492,9 @@ else
|
||||
fi
|
||||
|
||||
# --- inbound smtpd on port 25 (optional inbound relay and/or DMARC ingest) -----
|
||||
# Debian's stock master.cf enables smtp/inet (postfix 3.7.11-0+deb12u1,
|
||||
# debian/patches/40_chroot_by_default.diff: "smtp inet n - y - - smtpd",
|
||||
# uncommented). When both flags are off, remove
|
||||
# Debian's stock master.cf enables smtp/inet (postfix 3.10.13-0+deb13u1,
|
||||
# debian/patches/40_chroot_by_default.diff:16: "smtp inet n - y - - smtpd",
|
||||
# uncommented; conf/master.cf:12). When both flags are off, remove
|
||||
# that listener so port 25 is not an inbound smtpd (outbound delivery uses
|
||||
# smtp/unix). When either is on: no SASL, no OpenDKIM; relay accepts only
|
||||
# configured domains/recipients; DMARC accepts only allow-listed report addresses.
|
||||
@@ -479,15 +519,15 @@ if [ "${INBOUND_RELAY_ENABLE}" = "true" ] || [ "${DMARC_REPORTS_ENABLE}" = "true
|
||||
# postconf -P refuses them ("unmatched service_name/type") and leaves a
|
||||
# bare "pipe", which dies with "fatal: missing user= command-line
|
||||
# attribute" on every report. They go into the service line itself.
|
||||
# (Seen in 1.7.0 through 1.9.5, date not recorded. Postfix 3.7.11:
|
||||
# src/postconf/postconf_master.c, pcf_edit_master_param(), -P writes
|
||||
# "-o name=value" and nothing else; postconf_edit.c warns "unmatched
|
||||
# service_name/type" for a service master.cf does not have;
|
||||
# src/pipe/pipe.c, get_service_attr(): "missing user= command-line
|
||||
# attribute".)
|
||||
# (Seen in 1.7.0 through 1.9.5, date not recorded, on Postfix 3.7.11.
|
||||
# Postfix 3.10.13: src/postconf/postconf_master.c:1029,
|
||||
# pcf_edit_master_param(), -P writes "-o name=value" and nothing else;
|
||||
# postconf_edit.c:617 warns "unmatched service_name/type" for a service
|
||||
# master.cf does not have; src/pipe/pipe.c:867, get_service_attr(),
|
||||
# :1018: "missing user= command-line attribute".)
|
||||
# ${recipient} is expanded by pipe(8), not by the shell: the command
|
||||
# learns which report address the message came to from its argument
|
||||
# (man/man8/pipe.8, ${recipient}: as many arguments as recipients).
|
||||
# (man/man8/pipe.8:289, ${recipient}: as many arguments as recipients).
|
||||
# One at a time (the 1 before "pipe"): reports are in no hurry, every run
|
||||
# writes to the database the panel writes to, and a hundred of them in
|
||||
# parallel - Postfix's default - is what a flood of reports would start.
|
||||
@@ -498,9 +538,9 @@ if [ "${INBOUND_RELAY_ENABLE}" = "true" ] || [ "${DMARC_REPORTS_ENABLE}" = "true
|
||||
# command reads from the container's environment is named there.
|
||||
# export_environment is a list split at whitespace in which Postfix
|
||||
# expands "$": a path with either would reach the command as another
|
||||
# path, and it would quietly open a database of its own. (Postfix 3.7.11,
|
||||
# man/man8/pipe.8, export_environment; src/global/mail_params.h,
|
||||
# DEF_EXPORT_ENVIRON "TZ MAIL_CONFIG LANG"; src/global/clean_env.c. The
|
||||
# path, and it would quietly open a database of its own. (Postfix 3.10.13,
|
||||
# man/man8/pipe.8:417, export_environment; src/global/mail_params.h:2650,
|
||||
# DEF_EXPORT_ENVIRON "TZ MAIL_CONFIG LANG"; src/util/clean_env.c. The
|
||||
# parameter is main.cf's, so every Postfix process exports these too.)
|
||||
case "${SELFPOST_DATA_DIR:-}${SELFPOST_DB_PATH:-}" in
|
||||
*[[:space:]]* | *'$'*)
|
||||
@@ -520,8 +560,8 @@ if [ "${INBOUND_RELAY_ENABLE}" = "true" ] || [ "${DMARC_REPORTS_ENABLE}" = "true
|
||||
TRANSPORT_SETTING="texthash:${DMARC_TRANSPORT_MAP}"
|
||||
fi
|
||||
# Comma, not a space, between check_recipient_access and its table: see
|
||||
# master_cf_set_smtp_inet_o (master.5, NOTE 2: "or use commas instead of
|
||||
# spaces").
|
||||
# master_cf_set_smtp_inet_o (master.5, "-o name=value (short form)": "use
|
||||
# commas instead of spaces if the parameter supports that").
|
||||
RECIPIENT_RESTRICTIONS="check_recipient_access,texthash:${DMARC_RECIPIENTS_MAP},${RECIPIENT_RESTRICTIONS}"
|
||||
else
|
||||
postconf -MX "dmarc-ingest/unix" 2>/dev/null || true
|
||||
@@ -539,16 +579,17 @@ if [ "${INBOUND_RELAY_ENABLE}" = "true" ] || [ "${DMARC_REPORTS_ENABLE}" = "true
|
||||
# relay domain too (the server's own name), so their table is a recipient
|
||||
# table as well; without it they are "User unknown in relay recipient
|
||||
# table" and no report is ever accepted while both features are on.
|
||||
# (Postfix 3.7.11, src/smtpd/smtpd_check.c, smtpd_check_rcpt(): after
|
||||
# the lists, check_recipient_rcpt_maps() runs unless the status is
|
||||
# REJECT - an OK from check_recipient_access does not skip it.)
|
||||
# (Postfix 3.10.13, src/smtpd/smtpd_check.c, smtpd_check_rcpt(),
|
||||
# :5196-5200: after the lists, check_recipient_rcpt_maps() runs unless
|
||||
# the status is REJECT - an OK from check_recipient_access does not
|
||||
# skip it.)
|
||||
RELAY_RECIPIENT_SETTING="texthash:${RELAY_RECIPIENTS_MAP}"
|
||||
if [ "${DMARC_REPORTS_ENABLE}" = "true" ]; then
|
||||
RELAY_RECIPIENT_SETTING="${RELAY_RECIPIENT_SETTING} texthash:${DMARC_RECIPIENTS_MAP}"
|
||||
fi
|
||||
# The panel writes the same "[host]:port" into transport and
|
||||
# tls_policy: the policy lookup key is the verbatim next-hop, port and
|
||||
# brackets included (Postfix 3.7.11, README_FILES/TLS_README, "TLS
|
||||
# brackets included (Postfix 3.10.13, README_FILES/TLS_README:914, "TLS
|
||||
# policy table" note: ":smtp", ":25" and no port are different keys).
|
||||
postconf -e \
|
||||
"relay_recipient_maps=${RELAY_RECIPIENT_SETTING}" \
|
||||
@@ -566,10 +607,10 @@ if [ "${INBOUND_RELAY_ENABLE}" = "true" ] || [ "${DMARC_REPORTS_ENABLE}" = "true
|
||||
# domain — polluting Deliveries and letting a forged From: consume that
|
||||
# domain's level-2 rate-limit budget. So smtpd_milters is always overridden
|
||||
# here: the optional antispam milter, then - with the inbound relay on - the
|
||||
# panel's inbound journal milter, or nothing at all. (Postfix 3.7.11,
|
||||
# panel's inbound journal milter, or nothing at all. (Postfix 3.10.13,
|
||||
# man/man5/master.5: "-o" overrides the main.cf parameter for this service;
|
||||
# src/smtpd/smtpd.c hands its own milter list to cleanup, milter_send(),
|
||||
# and src/cleanup/cleanup_milter.c, cleanup_milter_receive(), uses it.)
|
||||
# src/smtpd/smtpd.c:3475 hands its own milter list to cleanup, milter_send(),
|
||||
# and src/cleanup/cleanup_milter.c:1997, cleanup_milter_receive(), uses it.)
|
||||
INBOUND_MILTERS=""
|
||||
if [ "${INBOUND_RELAY_ENABLE}" = "true" ] && [ -n "${INBOUND_ANTISPAM_MILTER}" ]; then
|
||||
# A glob's "*" matches anything, so the shape alone would pass
|
||||
@@ -602,11 +643,12 @@ if [ "${INBOUND_RELAY_ENABLE}" = "true" ] || [ "${DMARC_REPORTS_ENABLE}" = "true
|
||||
# never refuses, and must never hold up someone else's mail: accept when
|
||||
# it is down. A comma, not a space, between the two - see
|
||||
# master_cf_set_smtp_inet_o.
|
||||
# Postfix 3.7.11: src/milter/milter.c, milter_message(), sends the
|
||||
# message to each milter in list order; src/milter/milter8.c,
|
||||
# milter8_event(), applies a header a milter adds (SMFIR_ADDHEADER) to
|
||||
# the queue file at once, and the next milter8_message() reads that
|
||||
# file with rec_get(), which follows the added records.
|
||||
# Postfix 3.10.13: src/milter/milter.c:564, milter_message(), sends the
|
||||
# message to each milter in list order; src/milter/milter8.c:960,
|
||||
# milter8_event(), applies a header a milter adds (SMFIR_ADDHEADER,
|
||||
# :1424) to the queue file at once, and the next milter8_message()
|
||||
# (:2544) reads that file with rec_get() (:2612), which follows the
|
||||
# added records.
|
||||
case "${INBOUND_JOURNAL_SOCK}" in
|
||||
*[!A-Za-z0-9._/-]*)
|
||||
echo "FATAL: INBOUND_JOURNAL_MILTER_SOCKET must be an absolute path, got: ${INBOUND_JOURNAL_SOCK}" >&2
|
||||
@@ -638,23 +680,29 @@ if [ "${INBOUND_RELAY_ENABLE}" = "true" ] || [ "${DMARC_REPORTS_ENABLE}" = "true
|
||||
|
||||
# syslog_name: this listener's lines read postfix/inbound/smtpd, the
|
||||
# submission ports' postfix/smtpd. The log tailer files a refusal on port 25
|
||||
# into the inbound journal and has to tell the two apart. (Postfix 3.7.11,
|
||||
# src/global/mail_task.c, mail_task(): the log tag is syslog_name "/"
|
||||
# into the inbound journal and has to tell the two apart. (Postfix 3.10.13,
|
||||
# src/global/mail_task.c:56, mail_task(): the log tag is syslog_name "/"
|
||||
# program name. Only smtpd carries it: cleanup and the rest are other
|
||||
# services and log as postfix/.)
|
||||
postconf -M "smtp/inet=smtp inet n - n - - smtpd"
|
||||
# smtpd_forbid_bare_newline: the 2023 SMTP-smuggling defence, which this
|
||||
# Postfix leaves off. normalize takes a bare <LF> as a line end but ends
|
||||
# DATA only at the standard <CR><LF>.<CR><LF>: a "." line that a bare
|
||||
# <LF> precedes or ends is dropped, not taken as the end of the message
|
||||
# (Postfix 3.7.11, src/smtpd/smtpd.c:807, the default "no" for Postfix
|
||||
# < 3.9; :1656, bare_lf_mask_table, normalize = BARE_LF_FLAG_WANT_STD_EOD;
|
||||
# :3649-3652, the DATA loop; RELEASE_NOTES, "Major changes with Postfix
|
||||
# 3.8.5, 3.7.10": normalize is the recommended setting and 3.9's
|
||||
# default). Its exclusions default to $mynetworks, loopback only here
|
||||
# (above), so no client is spared. The submission ports keep the
|
||||
# default: their smuggled transaction would still be the authenticated
|
||||
# session's, under its bindings and milters (own-mta-builds, § 5).
|
||||
# smtpd_forbid_bare_newline: the 2023 SMTP-smuggling defence. normalize
|
||||
# takes a bare <LF> as a line end but ends DATA only at the standard
|
||||
# <CR><LF>.<CR><LF>: a "." line that a bare <LF> precedes or ends is
|
||||
# dropped, not taken as the end of the message (Postfix 3.10.13,
|
||||
# src/smtpd/smtpd.c:1693-1694, bare_lf_mask_table, normalize =
|
||||
# BARE_LF_FLAG_WANT_STD_EOD; :3712-3716, the DATA loop; RELEASE_NOTES-3.9:
|
||||
# 166-167, "the recommended setting "smtpd_forbid_bare_newline
|
||||
# = normalize""). Since 3.9 normalize is also the default (src/smtpd/
|
||||
# smtpd.c:830, "Postfix >= 3.9: normalize"; src/global/mail_params.h:4313,
|
||||
# DEF_SMTPD_FORBID_BARE_LF "normalize"); the line stays so that port 25
|
||||
# does not depend on a default (it came in 2.6.1, while the image's
|
||||
# Postfix 3.7.11 defaulted to "no").
|
||||
# Its exclusions default to $mynetworks (mail_params.h:4316), loopback
|
||||
# only here (above), so no client is spared. The submission ports take
|
||||
# the default, normalize as well since this Postfix: an application whose
|
||||
# SMTP library ends DATA with a bare <LF>.<LF> is no longer read to its
|
||||
# end there (own-mta-builds, § 5: their smuggled transaction would still
|
||||
# be the authenticated session's, so they were left to the default).
|
||||
postconf -P \
|
||||
"smtp/inet/smtpd_forbid_bare_newline=normalize" \
|
||||
"smtp/inet/syslog_name=postfix/inbound" \
|
||||
@@ -671,12 +719,12 @@ if [ "${INBOUND_RELAY_ENABLE}" = "true" ] || [ "${DMARC_REPORTS_ENABLE}" = "true
|
||||
|
||||
# smtpd's -o limit is not the whole of it: cleanup writes the queue file
|
||||
# under its own message_size_limit, main.cf's, which is never set here and
|
||||
# so is 10240000 (Postfix 3.7.11, src/cleanup/cleanup_init.c,
|
||||
# cleanup_post_jail(), set_file_limit(); src/global/mail_params.h,
|
||||
# so is 10240000 (Postfix 3.10.13, src/cleanup/cleanup_init.c:444,
|
||||
# cleanup_post_jail(), :455, set_file_limit(); src/global/mail_params.h:2013,
|
||||
# DEF_MESSAGE_LIMIT), and a larger message is refused 552 after DATA
|
||||
# (assumptions C-P31). cleanup gets port 25's limit when it is larger;
|
||||
# smtpd on 465 and 587 still holds its own, main.cf's (src/smtpd/smtpd.c,
|
||||
# the DATA and BDAT size checks).
|
||||
# smtpd on 465 and 587 still holds its own, main.cf's (src/smtpd/smtpd.c:3721,
|
||||
# :3964, :4136, :4250, the DATA and BDAT size checks).
|
||||
if [ "${PORT25_SIZE}" -gt "$(postconf -h message_size_limit)" ]; then
|
||||
postconf -P "cleanup/unix/message_size_limit=${PORT25_SIZE}"
|
||||
fi
|
||||
@@ -697,23 +745,44 @@ fi
|
||||
# Inside a container the chroot buys little (the container is the isolation
|
||||
# boundary) and breaks DNS/TLS trust-store access, so turn it off uniformly. Our
|
||||
# own smtps/submission services are already n; this covers the delivery agents
|
||||
# and the rest. (postfix 3.7.11-0+deb12u1: debian/patches/40_chroot_by_default.diff
|
||||
# sets chroot y; debian/configure-instance.sh copies etc/resolv.conf and the
|
||||
# like into the chroot, and nothing runs it in this container. chroot(2) also
|
||||
# needs CAP_SYS_CHROOT, which deploy/docker-compose.yml drops with cap_drop:
|
||||
# ALL - capabilities(7).)
|
||||
# and the rest. (postfix 3.10.13-0+deb13u1: debian/patches/40_chroot_by_default.diff
|
||||
# sets chroot y. chroot(2) also needs CAP_SYS_CHROOT, which
|
||||
# deploy/docker-compose.yml drops with cap_drop: ALL - capabilities(7).)
|
||||
#
|
||||
# Debian's chroot setup now runs on every "postfix check" and "postfix
|
||||
# start-fg": conf/post-install:587 (debian/patches/
|
||||
# debian-run-configure-instance-from-create-missing.patch) calls
|
||||
# /usr/lib/postfix/configure-instance.sh at the end of create-missing, and
|
||||
# that copies etc/localtime, etc/resolv.conf, etc/hosts and the like into
|
||||
# $queue_directory/etc - here /data/postfix/queue/etc - whether a service is
|
||||
# chrooted or not (debian/configure-instance.in:80-91, before its
|
||||
# need_chroot test at :93). With nothing chrooted those copies are read by
|
||||
# nobody, so the image turns the sync off with SYNC_CHROOT= in
|
||||
# /etc/default/postfix (build/Dockerfile; configure-instance.in:22-24, :44,
|
||||
# "[ -n "$SYNC_CHROOT" ] || exit 0"; debian/README.Debian:136-138).
|
||||
postconf -F "*/*/chroot=n"
|
||||
|
||||
# --- Cyrus SASL app config for smtpd -----------------------------------------
|
||||
# Tells the Cyrus library (invoked by smtpd via smtpd_sasl_path=smtpd) to verify
|
||||
# passwords straight from the panel-maintained sasldb2. PLAIN/LOGIN
|
||||
# only — both are safe because TLS is mandatory before auth on every port.
|
||||
# The file is found by a Debian patch, not by upstream Postfix: postfix
|
||||
# 3.7.11-0+deb12u1, debian/patches/07_sasl_config.diff, xsasl_getconfpath() in
|
||||
# src/xsasl/xsasl_cyrus_log.c gives Cyrus "$config_directory/sasl:/usr/lib/sasl",
|
||||
# and smtpd_sasl_path=smtpd names smtpd.conf there. The keys: Cyrus SASL
|
||||
# The directory is said here, cyrus_sasl_config_path, and not left to the
|
||||
# package's main.cf. Postfix 3.10.13, src/xsasl/xsasl_cyrus_server.c:227-232,
|
||||
# xsasl_cyrus_server_init(): a non-empty cyrus_sasl_config_path goes to Cyrus
|
||||
# with sasl_set_path(SASL_PATH_TYPE_CONFIG, ...), and :246,
|
||||
# sasl_server_init(callbacks, path_info) with smtpd_sasl_path=smtpd
|
||||
# (src/smtpd/smtpd_sasl_glue.c:188-189) names smtpd.conf there; the default
|
||||
# is empty (src/global/mail_params.h:1690, DEF_CYRUS_CONF_PATH ""), and then
|
||||
# Cyrus looks in its own path, /etc/sasl2:/etc/sasl:/usr/lib/<triplet>/sasl2:
|
||||
# /usr/lib/sasl2 (cyrus-sasl2 2.1.28+dfsg1, debian/rules:96, --with-configdir;
|
||||
# lib/common.c:210, sasl_set_path()), which this file is not in. Bookworm's
|
||||
# package found it with a patch (07_sasl_config.diff, gone in 3.10.13); trixie's
|
||||
# writes cyrus_sasl_config_path = /etc/postfix/sasl into a new main.cf
|
||||
# (postfix 3.10.13-0+deb13u1, debian/main.cf.in:71) and into an upgraded one
|
||||
# only when it is empty (debian/postfix.postinst:88-97). The keys: Cyrus SASL
|
||||
# 2.1.28, doc/legacy/options.html (pwcheck_method, auxprop_plugin,
|
||||
# sasldb_path, mech_list).
|
||||
postconf -e "cyrus_sasl_config_path=/etc/postfix/sasl"
|
||||
mkdir -p /etc/postfix/sasl
|
||||
cat > /etc/postfix/sasl/smtpd.conf <<EOF
|
||||
pwcheck_method: auxprop
|
||||
@@ -724,8 +793,10 @@ EOF
|
||||
|
||||
# Validate the generated configuration; fail loudly if postconf produced
|
||||
# anything Postfix rejects, before the wrapper tries to start it. (Postfix
|
||||
# 3.7.11, conf/postfix-script, "check": "check-fatal || exit 1"; check-fatal
|
||||
# also runs "post-install create-missing", which builds the queue tree.)
|
||||
# 3.10.13, conf/postfix-script:292, "check": "check-fatal || exit 1"; check-fatal
|
||||
# also runs "post-install create-missing" (:314), which builds the queue tree
|
||||
# and, in Debian's package, runs configure-instance.sh - see the chroot note
|
||||
# above.)
|
||||
set +e
|
||||
check_out=$(postfix check 2>&1)
|
||||
ec=$?
|
||||
|
||||
@@ -6,11 +6,14 @@
|
||||
# behind the milter wait (build/postfix-wrapper.sh) - and the master that
|
||||
# starts next reads the files as they are now, so the panel takes that as
|
||||
# done. Anything else is "postfix reload" itself: 0, or its failure (Postfix
|
||||
# 3.7.11, conf/postfix-script, "reload": exit 1 when not running or when
|
||||
# postsuper fails; src/postfix/postfix.c, a main.cf it cannot read). "master
|
||||
# -t" exits 0, silently, while no master holds the lock (src/master/master.c,
|
||||
# 3.10.13, conf/postfix-script:266-279, "reload": exit 1 when not running or
|
||||
# when postsuper fails; src/postfix/postfix.c, a main.cf it cannot read). "master
|
||||
# -t" exits 0, silently, while no master holds the lock (src/master/master.c:495-499,
|
||||
# test_lock). A master that stops between the two is a failure the panel
|
||||
# reports - the rarer mistake (review of stage 2c, F5).
|
||||
# reports - the rarer mistake (review of stage 2c, F5). Since Postfix 3.8 the
|
||||
# reload also writes "refreshing the Postfix mail system" to stderr, which
|
||||
# goes to the container's log (postfix-wrapper.sh, the note on 3.8's stderr
|
||||
# logging); the panel reads only the exit status.
|
||||
d=$(postconf -h daemon_directory) || exit 1
|
||||
if "$d/master" -t 2>/dev/null; then
|
||||
echo "postfix-reload: Postfix is not running; its start reads the files" >&2
|
||||
|
||||
@@ -44,23 +44,37 @@ for sock in $SOCKS; do
|
||||
done
|
||||
|
||||
echo "postfix-wrapper: milter sockets ready, starting postfix"
|
||||
# Postfix 3.7.11: postfix(1) execs conf/postfix-script (src/postfix/postfix.c),
|
||||
# whose "start-fg" execs master only as PID 1 and otherwise runs "master -s" as
|
||||
# its child; master then makes itself a session leader (src/master/master.c,
|
||||
# setsid()). A signal supervisord sends this program never reaches master
|
||||
# Postfix 3.10.13: postfix(1) execs conf/postfix-script (src/postfix/postfix.c:669),
|
||||
# whose "start-fg" (:200-210) execs master only as PID 1 and otherwise runs
|
||||
# "master -s" as its child; master then makes itself a session leader
|
||||
# (src/master/master.c:441, setsid()). A signal supervisord sends this
|
||||
# program never reaches master
|
||||
# (supervisor 4.2.5, supervisor/process.py, Subprocess.kill(): to the pid
|
||||
# alone without stopasgroup, and master is not in its group anyway): before,
|
||||
# a stop left master running - a restart of the program then found Postfix
|
||||
# "already running", and a stop of the container cut Postfix off with
|
||||
# SIGKILL (assumptions C-P42). So this shell stays, and on TERM or INT runs
|
||||
# "postfix stop" (conf/postfix-script, "stop": TERM to master, then waits up
|
||||
# to five seconds and kills its group; src/master/master_sig.c,
|
||||
# master_sigdeath(): "terminating on signal 15").
|
||||
# "postfix stop" (conf/postfix-script:237-253, "stop": TERM to master, then
|
||||
# waits up to five seconds and kills its group; src/master/master_sig.c:205,
|
||||
# master_sigdeath(): "terminating on signal 15"). Debian's redirect of
|
||||
# "postfix start" to systemd (debian/patches/
|
||||
# debian-re-run-startup-through-systemd.patch, conf/postfix-script:170-189)
|
||||
# leaves start-fg alone and needs /run/systemd/system, which this container
|
||||
# has not.
|
||||
#
|
||||
# Since Postfix 3.8 postfix(1) and postlog(1) - postfix-script's $INFO, $WARN
|
||||
# and $FATAL - write their messages to stderr even when it is not a terminal
|
||||
# (src/postfix/postfix.c:546, msg_vstream_init() no longer under isatty();
|
||||
# RELEASE_NOTES-3.8:114-115, [Incompat 20230308]), besides maillog_file. So
|
||||
# "starting the Postfix mail system", "stopping ..." and check-warn's
|
||||
# warnings now reach the container's log through this program's stderr
|
||||
# (build/supervisord.conf) as well as /data/log/mail.log; nothing reads them
|
||||
# there but a person.
|
||||
#
|
||||
# "postfix start-fg" spends its first seconds in its checks before master
|
||||
# runs; a stop asked then would find nothing to stop, so it is asked again
|
||||
# until the child is gone. "master -t" exits 0, silently, while no master
|
||||
# holds the lock (src/master/master.c, test_lock).
|
||||
# holds the lock (src/master/master.c:495-499, test_lock).
|
||||
daemon_directory=$(postconf -h daemon_directory)
|
||||
child=
|
||||
stop_asked=
|
||||
|
||||
Reference in New Issue
Block a user