From daf8266b0d5355204af14086c3677fe2e0dbd452 Mon Sep 17 00:00:00 2001 From: Andrew Savinykh <658865+AndrewSav@users.noreply.github.com> Date: Tue, 10 Nov 2026 08:43:40 +0000 Subject: [PATCH 1/2] Stop startup when rbl_override is a directory A bind mount whose host file is missing makes Docker create a directory in its place. setup.sh meant to catch that, but three faults kept it from working: - The check tested /etc/postifx/rbl_override, so it never fired. Setup then reported creating a default file, skipped postmap, and Postfix answered unauthenticated clients' RCPT with 451 4.3.5 for want of the map. - The error message put the path in backticks inside double quotes, so the shell tried to run it and the message printed without the path. - exit 1 only ended setup.sh. run.sh ignores its exit status, so services started with the rest of setup skipped. The check now looks at the right path, prints it, and marks the error in /etc/setup-error as the other setup checks do, so run.sh stops the container. The default-file branch is unchanged. Two tests: a container with a directory at that path must exit 1 with the error, and a default start must still build the map. Closes #99 --- rootfs/usr/local/bin/setup.sh | 10 ++++------ test/default.bats | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/rootfs/usr/local/bin/setup.sh b/rootfs/usr/local/bin/setup.sh index f797768..abb1966 100644 --- a/rootfs/usr/local/bin/setup.sh +++ b/rootfs/usr/local/bin/setup.sh @@ -313,12 +313,10 @@ fi # # Initialize rbl_override configuration # -if [ -d "/etc/postifx/rbl_override" ]; then - echo "[ERROR] The `/etc/postfix/rbl_override` is a folder instead of a file. Please fix it." - exit 1; -fi - -if [ ! -f "/etc/postfix/rbl_override" ]; then +if [ -d "/etc/postfix/rbl_override" ]; then + echo "[ERROR] /etc/postfix/rbl_override is a folder instead of a file. Please fix it." + touch /etc/setup-error +elif [ ! -f "/etc/postfix/rbl_override" ]; then touch /etc/postfix/rbl_override echo "[INFO] Created default (empty) rbl_override file" else diff --git a/test/default.bats b/test/default.bats index 5056f97..09a7e86 100644 --- a/test/default.bats +++ b/test/default.bats @@ -45,6 +45,28 @@ load 'test_helper/bats-assert/load' assert_failure } +# A bind mount whose host file is missing makes Docker create a directory in its +# place, and Postfix cannot use that as a map. The timeout turns a container that +# starts anyway into a failed test rather than a hung suite. +@test "checking system: startup stops when rbl_override is a directory" { + run timeout 180 docker run --rm \ + --name mailserver_default_rbl_dir \ + --link mariadb:mariadb \ + --link redis:redis \ + -e DBPASS=testpasswd \ + -e RSPAMD_PASSWORD=testpasswd \ + -e VMAILUID=`id -u` \ + -e VMAILGID=`id -g` \ + -e DISABLE_CLAMAV=true \ + -e TESTING=true \ + --tmpfs /etc/postfix/rbl_override \ + -h mail.domain.tld \ + mailserver2/mailserver:testing + docker rm -f mailserver_default_rbl_dir > /dev/null 2>&1 || true + assert_failure 1 + assert_output --partial "[ERROR] /etc/postfix/rbl_override is a folder instead of a file" +} + # # processes (default configuration) # @@ -522,6 +544,11 @@ load 'test_helper/bats-assert/load' assert_output "/tmp" } +@test "checking postfix: rbl_override map is built when none is provided" { + run docker exec mailserver_default test -f /etc/postfix/rbl_override.db + assert_success +} + @test "checking postfix: headers cleanup" { run docker exec mailserver_default /bin/sh -c "grep -i 'replace: header Received' /var/log/mail.log | wc -l" assert_success From 12ee7d2ce04ca9819135a2ca5478a7a66a7b01e8 Mon Sep 17 00:00:00 2001 From: Andrew Savinykh <658865+AndrewSav@users.noreply.github.com> Date: Sun, 15 Nov 2026 14:06:08 +0000 Subject: [PATCH 2/2] Build the rbl_override map when the file isn't owned by root The administrator creates rbl_override on the host. If a regular user creates it, the bind mount keeps that UID, so inside the container the file is owned by a non-root UID. setup.sh ran postmap on it as root, but postmap drops root privileges and runs as the owner of a non-root input file unless given -o. As that UID it could not create rbl_override.db in /etc/postfix, which is root:root 755. setup.sh ignored the exit status and logged "RBL Override initialized", and Postfix answered unauthenticated clients' RCPT from outside mynetworks with 451 4.3.5. setup.sh now runs postmap -o, and a failed postmap marks /etc/setup-error, so run.sh stops the container instead of starting Postfix without the map. Two tests: - reverse bind-mounts an rbl_override owned by the user running the suite, checks that this is not root, and looks up an entry in the built map. - default starts a container with a directory where the .db goes and expects startup to stop with the error. The startup tests in default.bats start their own container and named the :testing image directly. run_default now passes NAME to them as MAILSERVER_IMAGE, so make default NAME=... tests that image. --- Makefile | 4 +++- rootfs/usr/local/bin/setup.sh | 10 ++++++++-- test/default.bats | 24 +++++++++++++++++++++++- test/reverse.bats | 14 ++++++++++++++ test/share/postfix/rbl_override | 1 + 5 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 test/share/postfix/rbl_override diff --git a/Makefile b/Makefile index 75afc05..b2b49ce 100644 --- a/Makefile +++ b/Makefile @@ -332,6 +332,7 @@ init_reverse: init_redis init_postgres -v "`pwd`/test/share/passwd":/tmp/passwd \ -v "`pwd`/test/share/ssl/rsa":/var/mail/ssl \ -v "`pwd`/test/share/sieve/custom.sieve":/var/mail/sieve/custom.sieve \ + -v "`pwd`/test/share/postfix/rbl_override":/etc/postfix/rbl_override \ -v "`pwd`/test/share/letsencrypt":/etc/letsencrypt \ -t $(NAME) fixtures_reverse: @@ -556,7 +557,8 @@ fixtures_default: @timeout $(WAIT_TIMEOUT) sh -c 'until [ -z "$$(docker exec mailserver_default find /var/spool/postfix/incoming /var/spool/postfix/active /var/spool/postfix/maildrop -type f 2>/dev/null)" ]; do sleep 2; done' \ || { echo "TIMEOUT: postfix queue did not drain"; docker exec mailserver_default postqueue -p; exit 1; } run_default: - ./test/bats/bin/bats test/default.bats + # Some tests start their own container, from this image + MAILSERVER_IMAGE=$(NAME) ./test/bats/bin/bats test/default.bats stop_default: -docker rm -fv \ mailserver_default || true diff --git a/rootfs/usr/local/bin/setup.sh b/rootfs/usr/local/bin/setup.sh index abb1966..589bf40 100644 --- a/rootfs/usr/local/bin/setup.sh +++ b/rootfs/usr/local/bin/setup.sh @@ -323,9 +323,15 @@ else echo "[INFO] /etc/postfix/rbl_override file found" fi +# -o keeps root privileges. Without it postmap runs as the owner of a non-root +# file, typically a bind mount, and cannot write the .db into /etc/postfix. if [ -f "/etc/postfix/rbl_override" ]; then - postmap /etc/postfix/rbl_override - echo "[INFO] RBL Override initialized" + if postmap -o /etc/postfix/rbl_override; then + echo "[INFO] RBL Override initialized" + else + echo "[ERROR] Could not build the rbl_override map, see the postmap error above." + touch /etc/setup-error + fi fi # DOVECOT TUNING diff --git a/test/default.bats b/test/default.bats index 09a7e86..c796ed0 100644 --- a/test/default.bats +++ b/test/default.bats @@ -61,12 +61,34 @@ load 'test_helper/bats-assert/load' -e TESTING=true \ --tmpfs /etc/postfix/rbl_override \ -h mail.domain.tld \ - mailserver2/mailserver:testing + "${MAILSERVER_IMAGE:-mailserver2/mailserver:testing}" docker rm -f mailserver_default_rbl_dir > /dev/null 2>&1 || true assert_failure 1 assert_output --partial "[ERROR] /etc/postfix/rbl_override is a folder instead of a file" } +# Without the map, Postfix answers RCPT from clients outside mynetworks with +# 451 4.3.5, so a failed postmap must stop startup. A directory mounted where +# the .db goes makes postmap fail. +@test "checking system: startup stops when the rbl_override map cannot be built" { + run timeout 180 docker run --rm \ + --name mailserver_default_rbl_db \ + --link mariadb:mariadb \ + --link redis:redis \ + -e DBPASS=testpasswd \ + -e RSPAMD_PASSWORD=testpasswd \ + -e VMAILUID=`id -u` \ + -e VMAILGID=`id -g` \ + -e DISABLE_CLAMAV=true \ + -e TESTING=true \ + --tmpfs /etc/postfix/rbl_override.db \ + -h mail.domain.tld \ + "${MAILSERVER_IMAGE:-mailserver2/mailserver:testing}" + docker rm -f mailserver_default_rbl_db > /dev/null 2>&1 || true + assert_failure 1 + assert_output --partial "[ERROR] Could not build the rbl_override map" +} + # # processes (default configuration) # diff --git a/test/reverse.bats b/test/reverse.bats index 94c43ad..cf27b60 100644 --- a/test/reverse.bats +++ b/test/reverse.bats @@ -424,6 +424,20 @@ load 'test_helper/bats-assert/load' assert_success } +# By default postmap drops root privileges and runs as the source file's owner, +# who cannot write the .db into /etc/postfix. The fixture is bind-mounted from +# the checkout, so it belongs to the user running the suite; the first check +# makes sure that is not root, or this test could not fail. +@test "checking postfix: rbl_override map is built from a file not owned by root" { + run docker exec mailserver_reverse stat -c %u /etc/postfix/rbl_override + assert_success + refute_output "0" + + run docker exec mailserver_reverse postmap -q 192.0.2.1 hash:/etc/postfix/rbl_override + assert_success + assert_output "OK" +} + # # dovecot # diff --git a/test/share/postfix/rbl_override b/test/share/postfix/rbl_override new file mode 100644 index 0000000..b8b876e --- /dev/null +++ b/test/share/postfix/rbl_override @@ -0,0 +1 @@ +192.0.2.1 OK