From f6fcfe2cc8eac05c827d9aef65f0452bd064f9fe Mon Sep 17 00:00:00 2001 From: KiekerJan Date: Sun, 18 May 2025 12:21:35 +0200 Subject: [PATCH 1/5] Add support for Spamhaus DQS (https://docs.spamhaus.com/datasets/docs/source/40-real-world-usage/index.html) --- management/status_checks.py | 25 +++++++++++-- setup/mail-postfix.sh | 75 +++++++++++++++++++++++++++++++++++-- 2 files changed, 93 insertions(+), 7 deletions(-) diff --git a/management/status_checks.py b/management/status_checks.py index 68755cb7..02ccf519 100755 --- a/management/status_checks.py +++ b/management/status_checks.py @@ -283,7 +283,7 @@ def run_network_checks(env, output): # by a spammer, or the user may be deploying on a residential network. We # will not be able to reliably send mail in these cases. rev_ip4 = ".".join(reversed(env['PUBLIC_IP'].split('.'))) - zen = query_dns(rev_ip4+'.zen.spamhaus.org', 'A', nxdomain=None) + zen = query_dns(rev_ip4+get_spamhaus_query_url(env, 'zen'), 'A', nxdomain=None) evaluate_spamhaus_lookup(env['PUBLIC_IP'], 'IPv4', rev_ip4, output, zen) if not env['PUBLIC_IPV6']: @@ -292,8 +292,26 @@ def run_network_checks(env, output): from ipaddress import IPv6Address rev_ip6 = ".".join(reversed(IPv6Address(env['PUBLIC_IPV6']).exploded.split(':'))) - zen = query_dns(rev_ip6+'.zen.spamhaus.org', 'A', nxdomain=None) + zen = query_dns(rev_ip6+get_spamhaus_query_url(env, 'zen'), 'A', nxdomain=None) evaluate_spamhaus_lookup(env['PUBLIC_IPV6'], 'IPv6', rev_ip6, output, zen) + + +def get_spamhaus_query_url(env, selector): + # Filter on valid selectors + if not selector in ('zen', 'dbl'): + # Set default so at least something is returned + selector = 'zen' + + # Default public block list + spamhaus_url = '.'+selector+'.spamhaus.org' + + # Check if system makes use of Spamhaus Data Query Service, see https://portal.spamhaus.com/dqs/?ft=1#3.1 + env_key = 'SPAMHAUS_DQS_KEY' + if env_key in env and len(env[env_key]) > 0: + dqs_key = env[env_key] + spamhaus_url = '.'+dqs_key+'.'+selector+'.dq.spamhaus.net' + + return spamhaus_url def evaluate_spamhaus_lookup(lookupaddress, lookuptype, lookupdomain, output, zen): @@ -766,7 +784,8 @@ def check_mail_domain(domain, env, output): # See https://www.spamhaus.org/news/article/807/using-our-public-mirrors-check-your-return-codes-now. for # information on spamhaus return codes - dbl = query_dns(domain+'.dbl.spamhaus.org', "A", nxdomain=None) + dbl = query_dns(domain+get_spamhaus_query_url(env, 'dbl'), "A", nxdomain=None) + if dbl is None: output.print_ok("Domain is not blacklisted by dbl.spamhaus.org.") elif dbl == "[timeout]": diff --git a/setup/mail-postfix.sh b/setup/mail-postfix.sh index 5a4c7fec..5131b061 100755 --- a/setup/mail-postfix.sh +++ b/setup/mail-postfix.sh @@ -223,10 +223,12 @@ tools/editconf.py /etc/postfix/main.cf -e lmtp_destination_recipient_limit= # * `reject_non_fqdn_sender`: Reject not-nice-looking return paths. # * `reject_unknown_sender_domain`: Reject return paths with invalid domains. # * `reject_authenticated_sender_login_mismatch`: Reject if mail FROM address does not match the client SASL login -# * `reject_rhsbl_sender`: Reject return paths that use blacklisted domains. # * `permit_sasl_authenticated`: Authenticated users (i.e. on port 587) can skip further checks. # * `permit_mynetworks`: Mail that originates locally can skip further checks. # * `reject_rbl_client`: Reject connections from IP addresses blacklisted in zen.spamhaus.org +# * `reject_rhsbl_sender`: Reject the request when the MAIL FROM domain is listed in spamhaus.org +# * `reject_rhsbl_helo`: Reject the request when the HELO or EHLO hostname is listed in spamhaus.org +# * `reject_rhsbl_reverse_client`: Reject the request when the unverified reverse client hostname is listed in spamhaus.org # * `reject_unlisted_recipient`: Although Postfix will reject mail to unknown recipients, it's nicer to reject such mail ahead of greylisting rather than after. # * `check_policy_service`: Apply greylisting using postgrey. # @@ -236,9 +238,74 @@ tools/editconf.py /etc/postfix/main.cf -e lmtp_destination_recipient_limit= # so these IPs get mail delivered quickly. But when an IP is not listed in the permit_dnswl_client list (i.e. it is not #NODOC # whitelisted) then postfix does a DEFER_IF_REJECT, which results in all "unknown user" sorts of messages turning into #NODOC # "450 4.7.1 Client host rejected: Service unavailable". This is a retry code, so the mail doesn't properly bounce. #NODOC -tools/editconf.py /etc/postfix/main.cf \ - smtpd_sender_restrictions="reject_non_fqdn_sender,reject_unknown_sender_domain,reject_authenticated_sender_login_mismatch,reject_rhsbl_sender dbl.spamhaus.org=127.0.1.[2..99]" \ - smtpd_recipient_restrictions="permit_sasl_authenticated,permit_mynetworks,reject_rbl_client zen.spamhaus.org=127.0.0.[2..11],reject_unlisted_recipient,check_policy_service inet:127.0.0.1:10023,check_policy_service inet:127.0.0.1:12340" +# In case the Spamhaus Data Query Service is used, slightly different configuration is needed. Source: https://portal.spamhaus.com/dqs/?ft=1#3.1.2 +# Zero Reputation Domain (ZRD) blocklist is limited to two hour old domains, not 24 like suggested by Spamhaus. + +# smtpd_recipient_restrictions is different whether Spamhaus DQS is used or not. +# Start definition of the configuration here +CONF_SMTPD_RECIPIENT_RESTRICTIONS=$(cat <<-END + permit_sasl_authenticated, + permit_mynetworks, + check_sender_access hash:/etc/postfix/sender_access, + check_recipient_access hash:/etc/postfix/recipient_access, +END +) + +if [ -z "${SPAMHAUS_DQS_KEY:-}" ]; then + # Public spamhaus blocklist servers queried + DBL_QUERY=dbl.spamhaus.org + ZEN_QUERY=zen.spamhaus.org + +CONF_SMTPD_RECIPIENT_RESTRICTIONS=$(cat <<-END + $CONF_SMTPD_RECIPIENT_RESTRICTIONS + reject_rbl_client $ZEN_QUERY=127.0.0.[2..11], + reject_rhsbl_sender $DBL_QUERY=127.0.1.[2..99], + reject_rhsbl_helo $DBL_QUERY=127.0.1.[2..99], + reject_rhsbl_reverse_client $DBL_QUERY=127.0.1.[2..99], + warn_if_reject reject_rbl_client $ZEN_QUERY=127.255.255.[1..255], +END +) +else + # Use Data Query Service for blocklist query URLs + DBL_QUERY=$SPAMHAUS_DQS_KEY.dbl.dq.spamhaus.net + ZEN_QUERY=$SPAMHAUS_DQS_KEY.zen.dq.spamhaus.net + ZRD_QUERY=$SPAMHAUS_DQS_KEY.zrd.dq.spamhaus.net + +CONF_SMTPD_RECIPIENT_RESTRICTIONS=$(cat <<-END + $CONF_SMTPD_RECIPIENT_RESTRICTIONS + reject_rhsbl_sender $DBL_QUERY=127.0.1.[2..99], + reject_rhsbl_helo $DBL_QUERY=127.0.1.[2..99], + reject_rhsbl_reverse_client $DBL_QUERY=127.0.1.[2..99], + reject_rhsbl_sender $ZRD_QUERY=127.0.2.2, + reject_rhsbl_helo $ZRD_QUERY=127.0.2.2, + reject_rhsbl_reverse_client $ZRD_QUERY=127.0.2.2, + reject_rbl_client $ZEN_QUERY=127.0.0.[2..255], +END +) +fi + +# Define configuration for smtpd_sender_restrictions +CONF_SMTPD_SENDER_RESTRICTIONS=$(cat <<-END + reject_non_fqdn_sender, + reject_unknown_sender_domain, + reject_authenticated_sender_login_mismatch, + reject_rhsbl_sender $DBL_QUERY=127.0.1.[2..99] +END +) + +# Finalize configuration for smtpd_recipient_restrictions +CONF_SMTPD_RECIPIENT_RESTRICTIONS=$(cat <<-END + $CONF_SMTPD_RECIPIENT_RESTRICTIONS + reject_unlisted_recipient, + check_policy_service inet:127.0.0.1:10023, + check_policy_service inet:127.0.0.1:12340 +END +) + +# Apply configuration +management/editconf.py /etc/postfix/main.cf -w \ + smtpd_sender_restrictions="$CONF_SMTPD_SENDER_RESTRICTIONS" \ + smtpd_recipient_restrictions="$CONF_SMTPD_RECIPIENT_RESTRICTIONS" # Postfix connects to Postgrey on the 127.0.0.1 interface specifically. Ensure that # Postgrey listens on the same interface (and not IPv6, for instance). From d7838cf9ecf88ca7c2e11d6ffcc751e214a32b4a Mon Sep 17 00:00:00 2001 From: KiekerJan Date: Sun, 18 May 2025 12:43:14 +0200 Subject: [PATCH 2/5] add support for spamhaus dqs --- setup/mail-postfix.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/setup/mail-postfix.sh b/setup/mail-postfix.sh index 5131b061..22eb5ebb 100755 --- a/setup/mail-postfix.sh +++ b/setup/mail-postfix.sh @@ -265,6 +265,11 @@ CONF_SMTPD_RECIPIENT_RESTRICTIONS=$(cat <<-END warn_if_reject reject_rbl_client $ZEN_QUERY=127.255.255.[1..255], END ) + + # Cleanup dnsbl reply mapping, potentially set when DQS was enabled previously + management/editconf.py /etc/postfix/main.cf -e rbl_reply_maps= + + rm -rf /etc/postfix/dnsbl-reply-map else # Use Data Query Service for blocklist query URLs DBL_QUERY=$SPAMHAUS_DQS_KEY.dbl.dq.spamhaus.net @@ -282,6 +287,18 @@ CONF_SMTPD_RECIPIENT_RESTRICTIONS=$(cat <<-END reject_rbl_client $ZEN_QUERY=127.0.0.[2..255], END ) + + # Setup dnsbl reply mapping, to avoid leaking your DQS key in reject messages + cat > /etc/postfix/dnsbl-reply-map <<- EOF; + $ZEN_QUERY=127.0.0.[2.255] \$rbl_code Service unavailable; \$rbl_class [\$rbl_what] blocked using zen.spamhaus.org\${rbl_reason?; \$rbl_reason} + $DBL_QUERY=127.0.1.[2..99] \$rbl_code Service unavailable; \$rbl_class [\$rbl_what] blocked using dbl.spamhaus.org\${rbl_reason?; \$rbl_reason} + $ZRD_QUERY=127.0.2.[2..24] \$rbl_code Service unavailable; \$rbl_class [\$rbl_what] blocked using zrd.spamhaus.org\${rbl_reason?; \$rbl_reason} +EOF + + postmap hash:/etc/postfix/dnsbl-reply-map + + management/editconf.py /etc/postfix/main.cf \ + rbl_reply_maps=hash:/etc/postfix/dnsbl-reply-map fi # Define configuration for smtpd_sender_restrictions From 553b49a29ef98f3c73be33533a3493b176518b7b Mon Sep 17 00:00:00 2001 From: KiekerJan Date: Sun, 18 May 2025 14:10:28 +0200 Subject: [PATCH 3/5] Fixes to dns bl mapping --- setup/mail-postfix.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup/mail-postfix.sh b/setup/mail-postfix.sh index 22eb5ebb..35281186 100755 --- a/setup/mail-postfix.sh +++ b/setup/mail-postfix.sh @@ -290,9 +290,9 @@ END # Setup dnsbl reply mapping, to avoid leaking your DQS key in reject messages cat > /etc/postfix/dnsbl-reply-map <<- EOF; - $ZEN_QUERY=127.0.0.[2.255] \$rbl_code Service unavailable; \$rbl_class [\$rbl_what] blocked using zen.spamhaus.org\${rbl_reason?; \$rbl_reason} + $ZEN_QUERY=127.0.0.[2..255] \$rbl_code Service unavailable; \$rbl_class [\$rbl_what] blocked using zen.spamhaus.org\${rbl_reason?; \$rbl_reason} $DBL_QUERY=127.0.1.[2..99] \$rbl_code Service unavailable; \$rbl_class [\$rbl_what] blocked using dbl.spamhaus.org\${rbl_reason?; \$rbl_reason} - $ZRD_QUERY=127.0.2.[2..24] \$rbl_code Service unavailable; \$rbl_class [\$rbl_what] blocked using zrd.spamhaus.org\${rbl_reason?; \$rbl_reason} + $ZRD_QUERY=127.0.2.2 \$rbl_code Service unavailable; \$rbl_class [\$rbl_what] blocked using zrd.spamhaus.org\${rbl_reason?; \$rbl_reason} EOF postmap hash:/etc/postfix/dnsbl-reply-map From b5864f43f0c2c3d4082203a7165536800332df57 Mon Sep 17 00:00:00 2001 From: KiekerJan Date: Mon, 19 May 2025 22:08:09 +0200 Subject: [PATCH 4/5] Use DQS in spamassassin --- setup/spamassassin.sh | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/setup/spamassassin.sh b/setup/spamassassin.sh index d71a8efd..77446119 100755 --- a/setup/spamassassin.sh +++ b/setup/spamassassin.sh @@ -141,6 +141,35 @@ tools/editconf.py /etc/spamassassin/local.cf -s \ mkdir -p "$STORAGE_ROOT/mail/spamassassin" chown -R spampd:spampd "$STORAGE_ROOT/mail/spamassassin" +## Configure usage of Spamhaus DQS Key (see https://github.com/spamhaus/spamassassin-dqs?tab=readme-ov-file#instructions-for-spamassassin-341-to-346) + +if [ -z "${SPAMHAUS_DQS_KEY:-}" ]; then + # Using public spamhaus servers, cleanup possible dqs configuration + rm -f /etc/spamassassin/SH.pm + rm -f /etc/spamassassin/sh.cf + rm -f /etc/spamassassin/sh.pre + rm -f /etc/spamassassin/sh_scores.cf +else + # Using Spamhaus DQS servers + + # Get the source files + wget_verify https://raw.githubusercontent.com/spamhaus/spamassassin-dqs/f1baa2597443bc99b2777050383717de50eca2ce/3.4.1%2B/SH.pm 8e58b56e8a34899b50ba1a7e3d047ad1bef2e69c /tmp/SH.pm + wget_verify https://raw.githubusercontent.com/spamhaus/spamassassin-dqs/f1baa2597443bc99b2777050383717de50eca2ce/3.4.1%2B/sh.cf bdee2576b2400e3b284f5ab4b9c99faa39ad49c7 /tmp/sh.cf + wget_verify https://raw.githubusercontent.com/spamhaus/spamassassin-dqs/f1baa2597443bc99b2777050383717de50eca2ce/3.4.1%2B/sh.pre c73b2d9b5dae37864acf5479966f248dc6be4ee9 /tmp/sh.pre + wget_verify https://raw.githubusercontent.com/spamhaus/spamassassin-dqs/f1baa2597443bc99b2777050383717de50eca2ce/3.4.1%2B/sh_scores.cf 0e7360514245760754ee92172c275545a77a5860 /tmp/sh_scores.cf + + # Insert the DQS Key + sed -i -e 's/your_DQS_key/'$SPAMHAUS_DQS_KEY'/g' /tmp/sh.cf + + # Modify the configuration directory + sed -i -e 's//\/etc\/spamassassin/g' /tmp/sh.pre + + mv -f /tmp/SH.pm /etc/spamassassin/ + mv -f /tmp/sh.cf /etc/spamassassin/ + mv -f /tmp/sh.pre /etc/spamassassin/ + mv -f /tmp/sh_scores.cf /etc/spamassassin/ +fi + # To mark mail as spam or ham, just drag it in or out of the Spam folder. We'll # use the Dovecot antispam plugin to detect the message move operation and execute # a shell script that invokes learning. From b41c34034f0310db0d6c90e5a233d4849dd455bb Mon Sep 17 00:00:00 2001 From: KiekerJan Date: Sat, 24 May 2025 14:43:16 +0200 Subject: [PATCH 5/5] fix incorrect path --- setup/mail-postfix.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup/mail-postfix.sh b/setup/mail-postfix.sh index 35281186..9c144ebe 100755 --- a/setup/mail-postfix.sh +++ b/setup/mail-postfix.sh @@ -267,7 +267,7 @@ END ) # Cleanup dnsbl reply mapping, potentially set when DQS was enabled previously - management/editconf.py /etc/postfix/main.cf -e rbl_reply_maps= + tools/editconf.py /etc/postfix/main.cf -e rbl_reply_maps= rm -rf /etc/postfix/dnsbl-reply-map else @@ -297,7 +297,7 @@ EOF postmap hash:/etc/postfix/dnsbl-reply-map - management/editconf.py /etc/postfix/main.cf \ + tools/editconf.py /etc/postfix/main.cf \ rbl_reply_maps=hash:/etc/postfix/dnsbl-reply-map fi @@ -320,7 +320,7 @@ END ) # Apply configuration -management/editconf.py /etc/postfix/main.cf -w \ +tools/editconf.py /etc/postfix/main.cf -w \ smtpd_sender_restrictions="$CONF_SMTPD_SENDER_RESTRICTIONS" \ smtpd_recipient_restrictions="$CONF_SMTPD_RECIPIENT_RESTRICTIONS"