Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions management/status_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']:
Expand All @@ -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):
Expand Down Expand Up @@ -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]":
Expand Down
92 changes: 88 additions & 4 deletions setup/mail-postfix.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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.
#
Expand All @@ -236,9 +238,91 @@ 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
)

# Cleanup dnsbl reply mapping, potentially set when DQS was enabled previously
tools/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
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
)

# 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 \$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

tools/editconf.py /etc/postfix/main.cf \
rbl_reply_maps=hash:/etc/postfix/dnsbl-reply-map
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
tools/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).
Expand Down
29 changes: 29 additions & 0 deletions setup/spamassassin.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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/<config_directory>/\/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.
Expand Down