From 82e893a5020ad12e5f28d5bde6bb3bfc75011eb8 Mon Sep 17 00:00:00 2001 From: Andrew Savinykh <658865+AndrewSav@users.noreply.github.com> Date: Sun, 13 Sep 2026 22:15:42 +0000 Subject: [PATCH 1/5] Log what the DBL answers the test resolver before sending fixtures reverse and ldap2 run with DISABLE_DNS_RESOLVER=true and query dbl.spamhaus.org through whatever resolver the host provides. Spamhaus refuses some resolvers with 127.255.255.254, which the unqualified reject_rhsbl_sender in main.cf treats as a listing, so every fixture sender is rejected and the suite fails in ways that name delivery, rspamd and zeyple rather than DNS. A passing run recorded nothing about the blocklist at all, so it could not be told apart from a run where the DBL answered nothing. fixtures_reverse, fixtures_ldap2 and fixtures_default (the unbound control) now print, before any mail is sent, the DNS status, A and TXT answers for dbltest.com (the permanent test listing), gmail.com (the fixtures' sender domain, the name postfix actually looks up) and example.com (unlisted): 127.0.1.2 healthy 127.255.255.254 refused; the TXT names the address Spamhaus refused NXDOMAIN answered nothing; the blocklist is inert The probe is diagnostic only and cannot fail a run. --- Makefile | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Makefile b/Makefile index 11bcf51..338d32a 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,20 @@ WAIT_TIMEOUT = 300 TEST_DNS ?= DNS_FLAG = $(if $(TEST_DNS),--dns $(TEST_DNS),) +# Log what dbl.spamhaus.org answers the container's resolver, before any mail +# is sent. reverse and ldap2 inherit the host resolver, so the answer depends +# on where the suite runs; without this line a refused resolver shows up only +# as unexplained delivery failures, and a run that passed says nothing about +# whether the blocklist answered at all. +# dbltest.com -> 127.0.1.2 the permanent test listing: the blocklist works +# dbltest.com -> 127.255.255.x this resolver is refused: every sender gets rejected +# dbltest.com -> (nothing) answered nothing: the blocklist is inert +# gmail.com -> (nothing) the fixtures' sender domain, the name postfix actually looks up +# example.com -> (nothing) expected, it is not listed +define dnsbl_probe + -@docker exec $(1) sh -c 'for q in dbltest.com.dbl.spamhaus.org gmail.com.dbl.spamhaus.org example.com.dbl.spamhaus.org; do out=$$(dig +time=3 +tries=1 +noall +comments +answer A $$q 2>&1); st=$$(printf "%s\n" "$$out" | sed -n "s/.*status: \([A-Z]*\).*/\1/p" | head -1); [ -z "$$st" ] && st=unreachable; a=$$(printf "%s\n" "$$out" | grep "[[:space:]]A[[:space:]]" | sed "s/.*[[:space:]]//" | tr "\n" "," | sed "s/,$$//"); [ -z "$$a" ] && a=-; t=$$(dig +short +time=3 +tries=1 TXT $$q 2>/dev/null | grep "^\"" | head -1); [ -z "$$t" ] && t=-; printf "[dnsbl] %-32s status=%-11s A=%-16s %s\n" "$$q" "$$st" "$$a" "$$t"; done' +endef + all: build-no-cache default reverse ldap ldap2 sieve ecdsa traefik_acmev1 traefik_acmev2 clean no-build: default reverse ldap ldap2 sieve ecdsa traefik_acmev1 traefik_acmev2 clean default: init_default fixtures_default run_default stop_default @@ -232,6 +246,7 @@ fixtures_ldap2: docker exec mailserver_ldap2 /bin/sh -c "while ! echo PING | nc -z 0.0.0.0 11332 ; do sleep 1 ; done" # rspamd docker exec mailserver_ldap2 /bin/sh -c "while ! echo PING | nc -z 0.0.0.0 993 ; do sleep 1 ; done" # dovecot imaps docker exec mailserver_ldap2 /bin/sh -c "while ! echo PING | nc -z 0.0.0.0 587 ; do sleep 1 ; done" # submission + $(call dnsbl_probe,mailserver_ldap2) docker exec mailserver_ldap2 /bin/sh -c "python3 /tmp/tests/smtp-send.py 0.0.0.0 25 /tmp/tests/email-templates/external-to-existing-user.txt" docker exec mailserver_ldap2 /bin/sh -c "python3 /tmp/tests/smtp-send.py 0.0.0.0 25 /tmp/tests/email-templates/external-to-valid-user-subaddress.txt" docker exec mailserver_ldap2 /bin/sh -c "python3 /tmp/tests/smtp-send.py 0.0.0.0 25 /tmp/tests/email-templates/external-to-non-existing-user.txt" @@ -321,6 +336,7 @@ fixtures_reverse: docker exec mailserver_reverse /bin/sh -c "while ! echo PING | nc -z 0.0.0.0 11332 ; do sleep 1 ; done" # rspamd docker exec mailserver_reverse /bin/sh -c "while ! echo PING | nc -z 0.0.0.0 993 ; do sleep 1 ; done" # dovecot imaps docker exec mailserver_reverse /bin/sh -c "while ! echo PING | nc -z 0.0.0.0 587 ; do sleep 1 ; done" # submission + $(call dnsbl_probe,mailserver_reverse) docker exec mailserver_reverse /bin/sh -c "python3 /tmp/tests/smtp-send.py 0.0.0.0 25 /tmp/tests/email-templates/external-to-existing-user.txt" docker exec mailserver_reverse /bin/sh -c "python3 /tmp/tests/smtp-send.py 0.0.0.0 25 /tmp/tests/email-templates/external-to-valid-user-subaddress-with-default-separator.txt" docker exec mailserver_reverse /bin/sh -c "python3 /tmp/tests/smtp-send.py 0.0.0.0 25 /tmp/tests/email-templates/external-to-non-existing-user.txt" @@ -423,6 +439,7 @@ fixtures_default: # Wait for rspamd to start (default) docker exec mailserver_default /bin/sh -c "while ! echo PING | nc -z 0.0.0.0 11332 ; do sleep 1 ; done" + $(call dnsbl_probe,mailserver_default) docker exec mailserver_default /bin/sh -c "python3 /tmp/tests/smtp-send.py 0.0.0.0 25 /tmp/tests/email-templates/external-to-existing-user.txt" docker exec mailserver_default /bin/sh -c "python3 /tmp/tests/smtp-send.py 0.0.0.0 25 /tmp/tests/email-templates/external-to-existing-user-spam-learning.txt" docker exec mailserver_default /bin/sh -c "python3 /tmp/tests/smtp-send.py 0.0.0.0 25 /tmp/tests/email-templates/external-to-valid-user-subaddress.txt" From aab773b87d8bd1163470b18241cf0e12a419f73d Mon Sep 17 00:00:00 2001 From: Andrew Savinykh <658865+AndrewSav@users.noreply.github.com> Date: Sun, 13 Sep 2026 22:32:22 +0000 Subject: [PATCH 2/5] Record the resolver egress and answer TTLs in the DBL probe A healthy Spamhaus answer does not say which resolver produced it; only a refusal names the egress address it refused. So on a passing run the probe could not tell which resolver the runner had used. It now prints, first, the nameserver the container is configured with and the resolver's egress address as seen by two whoami services (whoami.akamai.net and o-o.myaddr.l.google.com), and, for every DBL answer, the remaining TTL: a full value is a fresh answer, a lower one was served from cache and shows how long ago it was fetched. --- Makefile | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 338d32a..411477e 100644 --- a/Makefile +++ b/Makefile @@ -16,16 +16,19 @@ DNS_FLAG = $(if $(TEST_DNS),--dns $(TEST_DNS),) # Log what dbl.spamhaus.org answers the container's resolver, before any mail # is sent. reverse and ldap2 inherit the host resolver, so the answer depends -# on where the suite runs; without this line a refused resolver shows up only -# as unexplained delivery failures, and a run that passed says nothing about -# whether the blocklist answered at all. +# on where the suite runs; without this a refused resolver shows up only as +# unexplained delivery failures, and a run that passed says nothing about +# whether the blocklist answered at all. The first line records which +# resolver was used: its address as seen by two whoami services, since a +# healthy Spamhaus answer does not name it (only a refusal does). ttl is the +# remaining TTL: a full value is a fresh answer, a lower one came from cache. # dbltest.com -> 127.0.1.2 the permanent test listing: the blocklist works # dbltest.com -> 127.255.255.x this resolver is refused: every sender gets rejected # dbltest.com -> (nothing) answered nothing: the blocklist is inert # gmail.com -> (nothing) the fixtures' sender domain, the name postfix actually looks up # example.com -> (nothing) expected, it is not listed define dnsbl_probe - -@docker exec $(1) sh -c 'for q in dbltest.com.dbl.spamhaus.org gmail.com.dbl.spamhaus.org example.com.dbl.spamhaus.org; do out=$$(dig +time=3 +tries=1 +noall +comments +answer A $$q 2>&1); st=$$(printf "%s\n" "$$out" | sed -n "s/.*status: \([A-Z]*\).*/\1/p" | head -1); [ -z "$$st" ] && st=unreachable; a=$$(printf "%s\n" "$$out" | grep "[[:space:]]A[[:space:]]" | sed "s/.*[[:space:]]//" | tr "\n" "," | sed "s/,$$//"); [ -z "$$a" ] && a=-; t=$$(dig +short +time=3 +tries=1 TXT $$q 2>/dev/null | grep "^\"" | head -1); [ -z "$$t" ] && t=-; printf "[dnsbl] %-32s status=%-11s A=%-16s %s\n" "$$q" "$$st" "$$a" "$$t"; done' + -@docker exec $(1) sh -c 'ns=$$(awk "/^nameserver/{print \$$2; exit}" /etc/resolv.conf); ak=$$(dig +short +time=3 +tries=1 A whoami.akamai.net 2>/dev/null | grep -v "^;" | head -1); gg=$$(dig +short +time=3 +tries=1 TXT o-o.myaddr.l.google.com 2>/dev/null | grep -v "^;" | grep -v edns0 | head -1 | tr -d "\""); printf "[dnsbl] resolver: nameserver=%s egress-seen-by-akamai=%s egress-seen-by-google=%s\n" "$${ns:--}" "$${ak:--}" "$${gg:--}"; for q in dbltest.com.dbl.spamhaus.org gmail.com.dbl.spamhaus.org example.com.dbl.spamhaus.org; do out=$$(dig +time=3 +tries=1 +noall +comments +answer +authority A $$q 2>&1); st=$$(printf "%s\n" "$$out" | sed -n "s/.*status: \([A-Z]*\).*/\1/p" | head -1); [ -z "$$st" ] && st=unreachable; a=$$(printf "%s\n" "$$out" | grep "[[:space:]]A[[:space:]]" | sed "s/.*[[:space:]]//" | tr "\n" "," | sed "s/,$$//"); [ -z "$$a" ] && a=-; ttl=$$(printf "%s\n" "$$out" | grep -v "^;" | awk "NF>=5{print \$$2; exit}"); [ -z "$$ttl" ] && ttl=-; t=$$(dig +short +time=3 +tries=1 TXT $$q 2>/dev/null | grep "^\"" | head -1); [ -z "$$t" ] && t=-; printf "[dnsbl] %-32s status=%-11s A=%-16s ttl=%-5s %s\n" "$$q" "$$st" "$$a" "$$ttl" "$$t"; done' endef all: build-no-cache default reverse ldap ldap2 sieve ecdsa traefik_acmev1 traefik_acmev2 clean From 060bd070587f511a700df37564d4ee635c27c0dd Mon Sep 17 00:00:00 2001 From: Andrew Savinykh <658865+AndrewSav@users.noreply.github.com> Date: Sun, 13 Sep 2026 22:48:01 +0000 Subject: [PATCH 3/5] Run the all-tests workflow on pull requests too master.yml only ran on pushes to master, so a PR could never exercise the single-runner all-suites job that failed on master. With pull_request added it runs on the PR alongside the per-suite workflows, and re-running it samples a fresh runner each time. --- .github/workflows/master.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index cc2102a..d00cc2f 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -3,6 +3,8 @@ name: all tests on: push: branches: master + pull_request: + branches: master jobs: # Run tests From 4bac21d3c03c400f295fda218f01fe0c505f5a53 Mon Sep 17 00:00:00 2001 From: Andrew Savinykh <658865+AndrewSav@users.noreply.github.com> Date: Sun, 13 Sep 2026 22:58:04 +0000 Subject: [PATCH 4/5] Revert "Run the all-tests workflow on pull requests too" This reverts commit 060bd070587f511a700df37564d4ee635c27c0dd. --- .github/workflows/master.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index d00cc2f..cc2102a 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -3,8 +3,6 @@ name: all tests on: push: branches: master - pull_request: - branches: master jobs: # Run tests From 843f400cf609e512b99147b5f90cf1d54b0f9ad1 Mon Sep 17 00:00:00 2001 From: Andrew Savinykh <658865+AndrewSav@users.noreply.github.com> Date: Sun, 13 Sep 2026 23:00:44 +0000 Subject: [PATCH 5/5] Add a dnsbl suite that repeats the sender check until it is refused make dnsbl starts a container like default's, without ClamAV and with DISABLE_DNS_RESOLVER=true so postfix queries dbl.spamhaus.org through the host's resolver, then runs test/dnsbl-loop.sh: before every attempt it logs the resolver and the blocklist's answers, sends one message with the fixtures' sender (user@gmail.com), and reports the reply. It stops at the first refusal, exits 1 so the run is red with the probe output directly above the 554, and exits 0 after DNSBL_ITERATIONS (20) accepted attempts DNSBL_INTERVAL (15 s) apart. Both outcomes are logged. Spamhaus publishes no numeric limit for its public mirrors, only that volume must stay within what non-commercial use reasonably needs, and answers 127.255.255.255 to excessive queries. An iteration costs 9 queries (8 from the probe, 1 from postfix), so a run is 180 spaced over five minutes; the loop stops immediately if it ever sees 127.255.255.255. The interval exceeds the 10 s negative TTL observed on the sender-domain answer, so each attempt reaches Spamhaus rather than the resolver's cache. The probe moves from the Makefile into test/share/tests/dnsbl-probe.sh, which is mounted into every suite's container, so the fixtures and the loop share it. The suite is not part of make all; it has its own workflow. --- .github/workflows/dnsbl.yml | 22 +++++++++++++ Makefile | 56 ++++++++++++++++++++++++--------- test/dnsbl-loop.sh | 43 +++++++++++++++++++++++++ test/share/tests/dnsbl-probe.sh | 30 ++++++++++++++++++ 4 files changed, 137 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/dnsbl.yml create mode 100755 test/dnsbl-loop.sh create mode 100644 test/share/tests/dnsbl-probe.sh diff --git a/.github/workflows/dnsbl.yml b/.github/workflows/dnsbl.yml new file mode 100644 index 0000000..b91428f --- /dev/null +++ b/.github/workflows/dnsbl.yml @@ -0,0 +1,22 @@ +name: dnsbl loop + +on: + pull_request: + branches: master + +jobs: + # Repeat the Spamhaus DBL sender check until it is refused or the iterations run out + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository and submodules + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Build docker image + run: make build-no-cache + + - name: Run the loop + run: make dnsbl diff --git a/Makefile b/Makefile index 411477e..3d6e37b 100644 --- a/Makefile +++ b/Makefile @@ -14,23 +14,20 @@ WAIT_TIMEOUT = 300 TEST_DNS ?= DNS_FLAG = $(if $(TEST_DNS),--dns $(TEST_DNS),) -# Log what dbl.spamhaus.org answers the container's resolver, before any mail -# is sent. reverse and ldap2 inherit the host resolver, so the answer depends -# on where the suite runs; without this a refused resolver shows up only as -# unexplained delivery failures, and a run that passed says nothing about -# whether the blocklist answered at all. The first line records which -# resolver was used: its address as seen by two whoami services, since a -# healthy Spamhaus answer does not name it (only a refusal does). ttl is the -# remaining TTL: a full value is a fresh answer, a lower one came from cache. -# dbltest.com -> 127.0.1.2 the permanent test listing: the blocklist works -# dbltest.com -> 127.255.255.x this resolver is refused: every sender gets rejected -# dbltest.com -> (nothing) answered nothing: the blocklist is inert -# gmail.com -> (nothing) the fixtures' sender domain, the name postfix actually looks up -# example.com -> (nothing) expected, it is not listed +# Log which resolver the container uses and what dbl.spamhaus.org answers it, +# before any mail is sent (see test/share/tests/dnsbl-probe.sh). reverse and +# ldap2 inherit the host resolver, so the answer depends on where the suite +# runs; without this a refused resolver shows up only as unexplained delivery +# failures, and a run that passed says nothing about the blocklist at all. define dnsbl_probe - -@docker exec $(1) sh -c 'ns=$$(awk "/^nameserver/{print \$$2; exit}" /etc/resolv.conf); ak=$$(dig +short +time=3 +tries=1 A whoami.akamai.net 2>/dev/null | grep -v "^;" | head -1); gg=$$(dig +short +time=3 +tries=1 TXT o-o.myaddr.l.google.com 2>/dev/null | grep -v "^;" | grep -v edns0 | head -1 | tr -d "\""); printf "[dnsbl] resolver: nameserver=%s egress-seen-by-akamai=%s egress-seen-by-google=%s\n" "$${ns:--}" "$${ak:--}" "$${gg:--}"; for q in dbltest.com.dbl.spamhaus.org gmail.com.dbl.spamhaus.org example.com.dbl.spamhaus.org; do out=$$(dig +time=3 +tries=1 +noall +comments +answer +authority A $$q 2>&1); st=$$(printf "%s\n" "$$out" | sed -n "s/.*status: \([A-Z]*\).*/\1/p" | head -1); [ -z "$$st" ] && st=unreachable; a=$$(printf "%s\n" "$$out" | grep "[[:space:]]A[[:space:]]" | sed "s/.*[[:space:]]//" | tr "\n" "," | sed "s/,$$//"); [ -z "$$a" ] && a=-; ttl=$$(printf "%s\n" "$$out" | grep -v "^;" | awk "NF>=5{print \$$2; exit}"); [ -z "$$ttl" ] && ttl=-; t=$$(dig +short +time=3 +tries=1 TXT $$q 2>/dev/null | grep "^\"" | head -1); [ -z "$$t" ] && t=-; printf "[dnsbl] %-32s status=%-11s A=%-16s ttl=%-5s %s\n" "$$q" "$$st" "$$a" "$$ttl" "$$t"; done' + -@docker exec $(1) sh /tmp/tests/dnsbl-probe.sh endef +# The dnsbl suite repeats the sender check that fails when a resolver is +# refused; see test/dnsbl-loop.sh for the cost per iteration. +DNSBL_ITERATIONS ?= 20 +DNSBL_INTERVAL ?= 15 + all: build-no-cache default reverse ldap ldap2 sieve ecdsa traefik_acmev1 traefik_acmev2 clean no-build: default reverse ldap ldap2 sieve ecdsa traefik_acmev1 traefik_acmev2 clean default: init_default fixtures_default run_default stop_default @@ -41,6 +38,7 @@ sieve: init_sieve fixtures_sieve run_sieve stop_sieve ecdsa: init_ecdsa run_ecdsa stop_ecdsa traefik_acmev1: init_traefik_acmev1 run_traefik_acmev1 stop_traefik_acmev1 traefik_acmev2: init_traefik_acmev2 run_traefik_acmev2 stop_traefik_acmev2 +dnsbl: init_dnsbl run_dnsbl stop_dnsbl build-no-cache: docker build --no-cache -t $(NAME) . @@ -433,6 +431,36 @@ stop_traefik_acmev2: -docker rm -f \ mailserver_traefik_acmev2 || true +init_dnsbl: init_redis init_mariadb + -docker rm -f \ + mailserver_dnsbl || true + + docker run \ + -d \ + --name mailserver_dnsbl \ + --link mariadb:mariadb \ + --link redis:redis \ + $(DNS_FLAG) \ + -e DBPASS=testpasswd \ + -e RSPAMD_PASSWORD=testpasswd \ + -e VMAILUID=`id -u` \ + -e VMAILGID=`id -g` \ + -e DISABLE_CLAMAV=true \ + -e DISABLE_DNS_RESOLVER=true \ + -e TESTING=true \ + -v "`pwd`/test/share/tests":/tmp/tests \ + -v "`pwd`/test/share/ssl/rsa":/var/mail/ssl \ + -h mail.domain.tld \ + -t $(NAME) + +run_dnsbl: + docker exec mailserver_dnsbl /bin/sh -c "while ! echo PING | nc -z 0.0.0.0 25 ; do sleep 1 ; done" + docker exec mailserver_dnsbl /bin/sh -c "while ! echo PING | nc -z 0.0.0.0 11332 ; do sleep 1 ; done" # rspamd + ./test/dnsbl-loop.sh mailserver_dnsbl $(DNSBL_ITERATIONS) $(DNSBL_INTERVAL) +stop_dnsbl: + -docker rm -f \ + mailserver_dnsbl || true + fixtures_default: # Wait for clamav unofficial sigs database update (default) diff --git a/test/dnsbl-loop.sh b/test/dnsbl-loop.sh new file mode 100755 index 0000000..a20546e --- /dev/null +++ b/test/dnsbl-loop.sh @@ -0,0 +1,43 @@ +#!/bin/sh +# Exercise the dbl.spamhaus.org sender check repeatedly, logging the resolver +# and the blocklist's answers before every attempt, until an attempt is +# refused or the iterations run out. Both outcomes are logged. +# +# dnsbl-loop.sh +# +# Each iteration costs Spamhaus 9 queries (8 from the probe, 1 from postfix), +# so the default 20 iterations are 180 queries per run, spaced 15 s apart: +# a small fraction of what one quiet mail server sends in a day. The interval +# is longer than the 10 s negative TTL observed on the sender-domain answer, +# so each attempt reaches Spamhaus rather than the resolver's cache. If the +# blocklist ever answers 127.255.255.255 ("excessive number of queries") the +# loop stops at once. +set -u +container=$1; iterations=$2; interval=$3 +template=/tmp/tests/email-templates/external-to-existing-user.txt # MAIL FROM user@gmail.com + +i=1 +while [ "$i" -le "$iterations" ]; do + echo "[dnsbl-loop] iteration $i/$iterations at $(date -u +%Y-%m-%dT%H:%M:%SZ)" + probe=$(docker exec "$container" sh /tmp/tests/dnsbl-probe.sh) + printf '%s\n' "$probe" + if printf '%s\n' "$probe" | grep -q '127\.255\.255\.255'; then + echo "[dnsbl-loop] STOP: Spamhaus answered 127.255.255.255 (excessive number of queries); not continuing" + exit 2 + fi + reply=$(docker exec "$container" python3 /tmp/tests/smtp-send.py 0.0.0.0 25 "$template" 2>&1) + if printf '%s\n' "$reply" | grep -q 'blocked using dbl'; then + echo "[dnsbl-loop] REFUSED at iteration $i:" + printf '%s\n' "$reply" | grep 'blocked using dbl' | sed 's/^/ /' + exit 1 + elif printf '%s\n' "$reply" | grep -q '250 2.0.0 Ok: queued'; then + echo "[dnsbl-loop] accepted: $(printf '%s\n' "$reply" | grep '250 2.0.0 Ok: queued' | head -1)" + else + echo "[dnsbl-loop] UNEXPECTED reply at iteration $i:" + printf '%s\n' "$reply" | grep '^S:' | sed 's/^/ /' + exit 1 + fi + [ "$i" -lt "$iterations" ] && sleep "$interval" + i=$((i + 1)) +done +echo "[dnsbl-loop] $iterations iterations, all accepted" diff --git a/test/share/tests/dnsbl-probe.sh b/test/share/tests/dnsbl-probe.sh new file mode 100644 index 0000000..77ac84a --- /dev/null +++ b/test/share/tests/dnsbl-probe.sh @@ -0,0 +1,30 @@ +#!/bin/sh +# Print what this container's resolver answers for dbl.spamhaus.org, and which +# resolver that is. Runs inside the mailserver container (needs dig). +# +# dbltest.com -> 127.0.1.2 the permanent test listing: the blocklist works +# dbltest.com -> 127.255.255.x this resolver is refused: every sender gets rejected +# dbltest.com -> (nothing) answered nothing: the blocklist is inert +# gmail.com -> (nothing) the fixtures' sender domain, the name postfix looks up +# example.com -> (nothing) expected, it is not listed +# +# The resolver line gives the nameserver the container is configured with and +# the resolver's egress address as seen by two whoami services, because a +# healthy Spamhaus answer does not name it (only a refusal does). ttl is the +# remaining TTL: a full value is a fresh answer, a lower one came from cache. + +q() { dig +time=3 +tries=1 "$@" 2>/dev/null | grep -v '^;'; } + +ns=$(awk '/^nameserver/{print $2; exit}' /etc/resolv.conf) +ak=$(q +short A whoami.akamai.net | head -1) +gg=$(q +short TXT o-o.myaddr.l.google.com | grep -v edns0 | head -1 | tr -d '"') +printf '[dnsbl] resolver: nameserver=%s egress-seen-by-akamai=%s egress-seen-by-google=%s\n' "${ns:--}" "${ak:--}" "${gg:--}" + +for name in dbltest.com.dbl.spamhaus.org gmail.com.dbl.spamhaus.org example.com.dbl.spamhaus.org; do + out=$(dig +time=3 +tries=1 +noall +comments +answer +authority A "$name" 2>&1) + st=$(printf '%s\n' "$out" | sed -n 's/.*status: \([A-Z]*\).*/\1/p' | head -1); [ -z "$st" ] && st=unreachable + a=$(printf '%s\n' "$out" | grep '[[:space:]]A[[:space:]]' | sed 's/.*[[:space:]]//' | tr '\n' ',' | sed 's/,$//'); [ -z "$a" ] && a=- + ttl=$(printf '%s\n' "$out" | grep -v '^;' | awk 'NF>=5{print $2; exit}'); [ -z "$ttl" ] && ttl=- + t=$(q +short TXT "$name" | grep '^"' | head -1); [ -z "$t" ] && t=- + printf '[dnsbl] %-32s status=%-11s A=%-16s ttl=%-5s %s\n' "$name" "$st" "$a" "$ttl" "$t" +done