Spam blokkeren op DirectAdmin met Rspamd en Exim
De complete aanpak die op onze eigen DirectAdmin-servers draait: bewezen spam en fraude weigeren op afzender, domein, extensie, Reply-To en afzendernaam, met een eigen melding en een link naar je contactformulier. Wie onterecht geblokkeerd is, kan je dus altijd nog bereiken.
Dit artikel vervangt onze twee eerdere Rspamd-handleidingen (de gewone en de pro-versie). Bij het naast elkaar leggen bleek dat geen van beide op DirectAdmin helemaal deed wat het beloofde. Hieronder staat waarom, en hoe het wel werkt.
Zo werkt het, in het kort
OnderzoekEen mail komt binnen bij Exim, de mailserver van DirectAdmin. Exim controleert de afzender op twee momenten: bij het adresseren (RCPT) en na het ontvangen van de inhoud (DATA). Staat de afzender op een van onze lijsten, dan weigert Exim de mail met onze eigen tekst. Komt de mail door, dan kijkt Rspamd er nog naar met dezelfde lijsten en met een score voor twijfelgevallen.
| 1. Verbinding | 2. Exim bij RCPT | 3. Exim na DATA | 4. Rspamd |
|---|---|---|---|
| IP-adres en hostnaam van de verzendende server | IP, afzenderadres, afzenderdomein, extensie, reverse DNS | From-kop (alle adressen), Reply-To, afzendernaam | dezelfde lijsten als vangnet, plus score en spammap |
Je beheert alles op één plek: de lijsten in /etc/rspamd/local.d/maps/. Een klein script controleert die lijsten elke vijf minuten en zet een goedgekeurde kopie klaar voor Exim. Zit er een fout in een lijst, dan blijft de vorige kopie gelden en krijg je een mail. Een tikfout kan zo nooit al je mail tegenhouden.
Getest op DirectAdmin 1.713 met Exim 4.100, exim.conf 4.5.70 en Rspamd 4.2 op CentOS Stream 9.
Waarom deze aanpak: wat we vonden
OnderzoekOnze oude pro-handleiding zei: leeg de DirectAdmin-lijsten en laat Rspamd alles weigeren, dan krijgt de afzender je nette melding. Op een echte DirectAdmin-server klopt dat niet. Drie dingen die we in de configuratie en de logboeken terugzagen:
- DirectAdmin vervangt de tekst van Rspamd. Wil Rspamd een mail weigeren, dan doet Exim dat met een vaste eigen tekst:
High scoring spam message has been dropped, likely blacklist. Dat staat in/etc/exim/spamd.acl_smtp_data.conf. Jouwmessage =uit multimap.conf komt nooit bij de afzender aan. - Rspamd scant niet elke gebruiker. Met Easy Spam Fighter scant Rspamd de mail van gebruikers die spamfilterinstellingen hebben (
/etc/rspamd/users.d/). Een domein zonder die instellingen gaat er langs. - Exim kan het zelf, en voor alle domeinen tegelijk. Met een paar regels in de officiële inhaakbestanden van DirectAdmin (
/etc/exim.acl_check_recipient.pre.confen/etc/exim.acl_check_message.pre.conf) weigert Exim op dezelfde lijsten, met jouw tekst. Die bestanden overleven een update van DirectAdmin.
| Wat wil je | Alleen DirectAdmin-lijsten | Alleen Rspamd | Rspamd + Exim (dit artikel) |
|---|---|---|---|
| Eigen melding met contactlink | nee | nee | ja |
| Geldt voor elk domein op de server | ja | alleen met spamfilter aan | ja |
| From-kop, Reply-To en afzendernaam | nee | ja | ja |
| Patronen (regex), zoals extensies | nee | ja | ja |
| Eén plek om te beheren | ja | ja | ja (de Rspamd-lijsten) |
| Beschermd tegen een tikfout | deels | Rspamd slaat een kapotte regel over | ja, kapotte lijst gaat niet door |
Waarom die bescherming zo belangrijk is: in onze eerste proef gaf één regex die Exim niet kon lezen een tijdelijke fout (451) op alle inkomende mail. Rspamd slaat zo'n regel stilletjes over, Exim niet. Daarom leest Exim nooit rechtstreeks de lijsten die jij bewerkt, maar een kopie die eerst is gecontroleerd.
Stap 1: de lijsten aanmaken
LijstenElke soort blokkade krijgt een eigen bestand. Een .map bevat exacte waarden (één per regel), een .re bevat patronen (reguliere expressies). Een reden achter een waarde zet je na een spatie en een #; in een .re-bestand zet je commentaar op een eigen regel.
mkdir -p /etc/rspamd/local.d/maps
cd /etc/rspamd/local.d/maps
touch block_sender_addresses.map block_sender_domains.map block_replyto_domains.map \
block_sender_ips.map block_sender_hosts.map \
bad_tlds.re bad_replyto_domains.re bad_from_headers.re bad_messageid.re google_domeinen.re
chown root:_rspamd /etc/rspamd/local.d/maps /etc/rspamd/local.d/maps/*
chmod 755 /etc/rspamd/local.d/maps
chmod 644 /etc/rspamd/local.d/maps/*
| Bestand | Wat erin staat | Voorbeeld |
|---|---|---|
block_sender_addresses.map | losse afzenderadressen, exact; voor een gekaapt postvak bij een verder nette partij | rvo@kpnmail.nl # nep-RVO |
block_sender_domains.map | hele afzenderdomeinen, exact; alleen domeinen die uitsluitend troep sturen | engine-shop.beauty # spam |
block_replyto_domains.map | domeinen in het Reply-To-adres, exact | autoinkoopholland.com |
block_sender_ips.map | IP-adressen van eigen spamservers; nooit een gedeeld platform zoals Gmail of Microsoft | 143.246.223.105 |
block_sender_hosts.map | hostnaam (reverse DNS) van de verzendende server | wp.zapto.org |
bad_tlds.re | extensies en domeinpatronen van de afzender | \.(ru|su|xyz|top)$ |
bad_replyto_domains.re | patronen voor het Reply-To-domein | /(^|\.)caroptions\.nl$/i |
bad_from_headers.re | patronen voor de hele From-kop, inclusief de naam | /CarOptions\s+B\.V\./i |
Stap 2: de lijsten vullen
LijstenBegin met onze lijsten (onderaan bij Downloads) of met een lege lijst. Loop een overgenomen lijst altijd na: een domein dat bij ons alleen spam stuurt, kan bij jou een klant zijn. Een paar dingen die we onderweg leerden:
Blokkeer de partij, niet het platform
Veel bedrijven sturen facturen en nieuwsbrieven via Moneybird, Mailgun, SendGrid of Amazon SES. Blokkeer je zo'n heel platform, dan mis je ook de facturen van anderen. Weiger in dat geval op het Reply-To-domein of op de naam in de From-kop. Zo blokkeer je één partij en blijft het platform werken.
Extensies: voorzichtig met landen
Op onze eigen server blokkeren we ook landen waar we nooit mail uit verwachten. Op de server met klanten doen we dat niet: daar staan alleen echte spamextensies in, en bewust geen cn, omdat een klant met Chinese leveranciers mailt. Onze extensieregel slaat je eigen domeinen altijd over, ook als die op een geblokkeerde extensie eindigen.
# alleen spamextensies (onze server met klanten) \.(ru|su|kp|xyz|top|icu|work|watch|company|onl|store|pro|tech|art|services|boats|homes|beauty|one|lol|cfd)$
Met email:domain zie je alleen het domein
De Reply-To-regel geeft alleen het domein door. @caroptions\.nl$ raakt dus nooit iets; schrijf /(^|\.)caroptions\.nl$/i. Het stukje (^|\.) pakt ook subdomeinen mee zonder notcaroptions.nl te raken.
Nagemaakte merken met een uitzondering voor het echte bedrijf
Phishing gebruikt vaak een bekende naam met een ander adres. Met een negatieve blik vooruit weiger je de naam, behalve als hij van het echte domein komt. Test zo'n regel eerst op een stapel echte afzenders voordat je hem live zet.
/PayPal.*@(?!([\w-]+\.)*paypal\.[a-z.]+>?\s*$)/i /Belastingdienst.*@(?!([\w-]+\.)*belastingdienst\.nl>?\s*$)/i /ANWB\s+(Service|Leden|Klanten|Voordeel|Wegen|Onderweg|Winter|Reis)\w*.*@(?!([\w-]+\.)*anwb\.nl>?\s*$)/i
Onzichtbare tekens en nep-letters
Oplichters zetten onzichtbare tekens of Cyrillische letters midden in een naam, zodat een gewone regel niet raakt. Deze twee regels vangen dat. De /u achteraan zegt dat het om Unicode gaat; de Exim-laag zet dat zelf om naar wat Exim begrijpt.
/[\x{200B}\x{200C}\x{200D}\x{2060}\x{FEFF}]/u
/[A-Za-z][\x{0400}-\x{04FF}]|[\x{0400}-\x{04FF}][A-Za-z]/u
Stap 3: Rspamd, multimap en composites
RspamdRspamd gebruikt dezelfde lijsten. Met prefilter = true en action = "reject" weigert Rspamd zo vroeg mogelijk. Zet dit bestand in /etc/rspamd/local.d/: DirectAdmin en Easy Spam Fighter laten die map bij een update met rust. Vervang jouwdomein.nl/contact door je eigen contactformulier.
# /etc/rspamd/local.d/multimap.conf
#
# Lodewijk.FRL - Rspamd-blokkade op DirectAdmin, zoals uitgelegd op https://lodewijk.frl/da-rspamd (24-10-2026).
# Elke soort blokkade heeft een eigen lijst in /etc/rspamd/local.d/maps/:
# bad_tlds.re - extensies en domeinpatronen (regex)
# bad_replyto_domains.re - Reply-To-domeinen (regex)
# bad_from_headers.re - tekst in de From-kop of afzendernaam (regex)
# block_sender_domains.map - afzenderdomeinen (envelope en From-kop, exact)
# block_sender_addresses.map - losse afzenderadressen (envelope en From-kop, exact)
# block_replyto_domains.map - Reply-To-domeinen (exact)
# block_sender_ips.map - IP-adressen van eigen spamservers (nooit gedeelde platforms)
# block_sender_hosts.map - hostnamen (reverse DNS) van spamservers
# bad_messageid.re + google_domeinen.re - voor de composite GOOGLE_AUTOGEN_VREEMD (composites.conf)
# Vervang jouwdomein.nl/contact door je eigen contactformulier. Let op: op DirectAdmin vervangt Exim deze
# weigertekst door een eigen tekst; daarom weigert de Exim-laag (zie het artikel) dezelfde lijsten nog eens zelf.
BAD_TLD_ENVFROM {
type = "from";
extract_from = "smtp";
filter = "email:domain";
map = "regexp;/etc/rspamd/local.d/maps/bad_tlds.re";
prefilter = true;
action = "reject";
message = "550 5.7.1 Rejected: sender domain %d is blocked due to abuse. If this is incorrect, contact https://jouwdomein.nl/contact";
}
BAD_TLD_FROMHDR {
type = "from";
extract_from = "mime";
filter = "email:domain";
map = "regexp;/etc/rspamd/local.d/maps/bad_tlds.re";
prefilter = true;
action = "reject";
message = "550 5.7.0 Rejected: sender domain %d violates local policy. If this is incorrect, contact https://jouwdomein.nl/contact";
}
BAD_REPLYTO_CAROPTIONS {
type = "header";
header = "Reply-To";
filter = "email:domain";
map = "regexp;/etc/rspamd/local.d/maps/bad_replyto_domains.re";
prefilter = true;
action = "reject";
message = "550 5.7.2 Rejected: sender blocked due to repeated unwanted correspondence. If this is incorrect, contact https://jouwdomein.nl/contact";
}
BAD_FROMHDR_CAROPTIONS_NAME {
type = "header";
header = "From";
map = "regexp;/etc/rspamd/local.d/maps/bad_from_headers.re";
prefilter = true;
action = "reject";
message = "550 5.7.3 Rejected: sender identity is blocked due to repeated unwanted correspondence. If this is incorrect, contact https://jouwdomein.nl/contact";
}
BLOCK_SENDER_DOMAIN_ENVFROM {
type = "from";
extract_from = "smtp";
filter = "email:domain";
map = "/etc/rspamd/local.d/maps/block_sender_domains.map";
prefilter = true;
action = "reject";
message = "550 5.7.1 Rejected: sender domain %d is blocked due to abuse. If this is incorrect, contact https://jouwdomein.nl/contact";
}
BLOCK_SENDER_DOMAIN_FROMHDR {
type = "from";
extract_from = "mime";
filter = "email:domain";
map = "/etc/rspamd/local.d/maps/block_sender_domains.map";
prefilter = true;
action = "reject";
message = "550 5.7.0 Rejected: sender domain %d violates local policy. If this is incorrect, contact https://jouwdomein.nl/contact";
}
BLOCK_SENDER_ADDR_ENVFROM {
type = "from";
extract_from = "smtp";
filter = "email";
map = "/etc/rspamd/local.d/maps/block_sender_addresses.map";
prefilter = true;
action = "reject";
message = "550 5.7.1 Rejected: sender address is blocked due to abuse. If this is incorrect, contact https://jouwdomein.nl/contact";
}
BLOCK_SENDER_ADDR_FROMHDR {
type = "from";
extract_from = "mime";
filter = "email";
map = "/etc/rspamd/local.d/maps/block_sender_addresses.map";
prefilter = true;
action = "reject";
message = "550 5.7.0 Rejected: sender address violates local policy. If this is incorrect, contact https://jouwdomein.nl/contact";
}
BLOCK_REPLYTO_DOMAIN {
type = "header";
header = "Reply-To";
filter = "email:domain";
map = "/etc/rspamd/local.d/maps/block_replyto_domains.map";
prefilter = true;
action = "reject";
message = "550 5.7.2 Rejected: reply-to domain %d is blocked due to abuse. If this is incorrect, contact https://jouwdomein.nl/contact";
}
BLOCK_SENDER_IP {
type = "ip";
map = "/etc/rspamd/local.d/maps/block_sender_ips.map";
prefilter = true;
action = "reject";
message = "550 5.7.1 Rejected: sending server is blocked due to abuse. If this is incorrect, contact https://jouwdomein.nl/contact";
}
BLOCK_SENDER_HOST {
type = "hostname";
map = "/etc/rspamd/local.d/maps/block_sender_hosts.map";
prefilter = true;
action = "reject";
message = "550 5.7.1 Rejected: sending host is blocked due to abuse. If this is incorrect, contact https://jouwdomein.nl/contact";
}
# 12 okt 2026: phishing via gekaapte Google Workspace-accounts (scholen wereldwijd) heeft een Message-Id
# <autogen-java-…@google.com>. Gecontroleerd op lodewijkict: 108 van de 108 zulke mails in vier weken waren spam.
# Geen weigering: de composite GOOGLE_AUTOGEN_VREEMD (composites.conf) zet ze in de spammap, tenzij het
# afzenderdomein van Google zelf is.
AUTOGEN_JAVA_MSGID {
type = "header";
header = "Message-Id";
map = "regexp;/etc/rspamd/local.d/maps/bad_messageid.re";
score = 0.0;
description = "Message-Id van een geautomatiseerde Google-verzending (autogen-java)";
}
FROM_GOOGLE_DOMEIN {
type = "from";
extract_from = "mime";
filter = "email:domain";
map = "regexp;/etc/rspamd/local.d/maps/google_domeinen.re";
score = 0.0;
description = "Afzenderdomein is van Google zelf";
}
Twee extra regels voor twijfelgevallen. Ze weigeren niet, maar verhogen de score, zodat de mail in de spammap belandt. De eerste raakt afzenders zonder SPF en zonder DMARC; de tweede raakt geautomatiseerde Google-mail (Message-Id autogen-java) met een afzender buiten Google. Op onze servers waren dat in vier weken 108 van de 108 keer phishing via een gekaapt account.
# Lodewijk.FRL, 11 okt 2026: afzenderdomein zonder SPF en zonder DMARC. Bij gekaapte Google
# Workspace-accounts klopt de DKIM wel (van gappssmtp.com) en blijft de score anders net onder 5.
GEEN_SPF_GEEN_DMARC {
expression = "R_SPF_NA & DMARC_NA";
score = 2.0;
description = "Afzenderdomein heeft geen SPF- en geen DMARC-record";
}
# 12 okt 2026: geautomatiseerde Google-verzending met een afzender buiten Google (zie multimap.conf).
GOOGLE_AUTOGEN_VREEMD {
expression = "AUTOGEN_JAVA_MSGID & !FROM_GOOGLE_DOMEIN";
score = 12.0;
description = "Geautomatiseerde Google-verzending met een afzender buiten Google: vrijwel altijd phishing via een gekaapt account";
}
nano /etc/rspamd/local.d/multimap.conf
nano /etc/rspamd/local.d/composites.conf
rspamadm configtest && systemctl reload rspamd
rspamadm configdump multimap | grep -cE "^[A-Z_]+ \{"
Stap 4: Exim, zelf weigeren met je eigen tekst
EximDit is het stuk dat de oude handleidingen misten. Vier bestanden en een cronregel. Zet de drie Exim-bestanden eerst in een proefmap (/etc/exim-proef/); live gaan ze pas in stap 6, als de proef helemaal goed is. Ze gelden voor elk domein op de server, en ze slaan uitgezonderd over: ingelogde gebruikers, de server zelf (eigen IP's en 127.0.0.1), afzenders op de DirectAdmin-whitelist en lege afzenders (bounces).
1. De tekst en de map met lijsten
# Lodewijk-blokkade (5 oktober 2026): gebruikt in /etc/exim.acl_check_recipient.pre.conf en # /etc/exim.acl_check_message.pre.conf. De tekst gaat terug naar een geblokkeerde afzender; de map bevat de # gecontroleerde kopie van /etc/rspamd/local.d/maps (zie /usr/local/sbin/lodewijk-blokkade-sync). LODEWIJK_BLOKKADE_MAPS = /etc/exim.lodewijk LODEWIJK_BLOKKADE_TEKST = 550 5.7.1 Uw bericht is geweigerd: deze afzender is bij ons geblokkeerd. Onterecht? Neem contact op via https://jouwdomein.nl/contact -- Message rejected: this sender is blocked here. If this is a mistake, please contact us via https://jouwdomein.nl/contact
Proefbestand: /etc/exim-proef/strings.conf.custom, live: /etc/exim.strings.conf.custom. Zet je eigen contactadres in de tekst. De namen beginnen met LODEWIJK_ omdat het zo op onze servers staat; je mag ze hernoemen, als je dat dan in alle bestanden doet.
2. Weigeren bij RCPT: IP, adres, domein, extensie en host
# -------------------------------------------------------------------------------------------------
# Lodewijk-blokkade, deel 1: bij RCPT TO (5 oktober 2026, besluit Theo).
# Weigert mail van afzenders op onze blokkadelijsten, voor elk domein op deze server en met onze eigen
# tekst (LODEWIJK_BLOKKADE_TEKST in /etc/exim.strings.conf.custom), zodat wie ten onrechte geblokkeerd is
# ons via het contactformulier kan bereiken. Rspamd gebruikt dezelfde lijsten, maar scant alleen voor
# gebruikers met spamfilterinstellingen en zijn tekst komt niet bij de afzender aan.
# Bron van de lijsten: /etc/rspamd/local.d/maps (alleen daar bewerken). Exim leest een gecontroleerde
# kopie in LODEWIJK_BLOKKADE_MAPS (/etc/exim.lodewijk), die /usr/local/sbin/lodewijk-blokkade-sync elke vijf
# minuten bijwerkt. Een lijst met een fout gaat niet door; dan blijft de vorige kopie gelden. Ontbreekt
# een kopie, dan slaat de regel zichzelf over: een kapotte lijst mag nooit alle mail stoppen.
# Niet voor: ingelogde gebruikers, de server zelf (lokaal verstuurd, eigen IP's, 127/8, ::1), afzenders
# op de DirectAdmin-whitelist (de nooduitgang) en mail zonder afzender (bounces).
# Deel 2 (From- en Reply-To-header) staat in /etc/exim.acl_check_message.pre.conf.
# -------------------------------------------------------------------------------------------------
warn set acl_m_lw_toets = 0
warn !authenticated = *
!hosts = <;; @[] ; 127.0.0.0/8 ; ::1
domains = +local_domains
condition = ${if !eq{$acl_m_is_whitelisted}{1}}
set acl_m_lw_toets = 1
deny condition = ${if eq{$acl_m_lw_toets}{1}}
hosts = ${if exists{LODEWIJK_BLOKKADE_MAPS/block_sender_ips.map}{LODEWIJK_BLOKKADE_MAPS/block_sender_ips.map}}
message = LODEWIJK_BLOKKADE_TEKST
log_message = LODEWIJK_BLOKKADE ip $sender_host_address
deny condition = ${if eq{$acl_m_lw_toets}{1}}
condition = ${if def:sender_address_domain}
condition = ${if exists{LODEWIJK_BLOKKADE_MAPS/block_sender_addresses.map}{${if or{{bool{${lookup{$sender_address}lsearch{LODEWIJK_BLOKKADE_MAPS/block_sender_addresses.map}{yes}{no}}}}{bool{${lookup{${sg{$sender_address}{\N"|\+[^@]*(?=@)\N}{}}}lsearch{LODEWIJK_BLOKKADE_MAPS/block_sender_addresses.map}{yes}{no}}}}}}}{no}}
message = LODEWIJK_BLOKKADE_TEKST
log_message = LODEWIJK_BLOKKADE adres $sender_address
deny condition = ${if eq{$acl_m_lw_toets}{1}}
condition = ${if def:sender_address_domain}
condition = ${if exists{LODEWIJK_BLOKKADE_MAPS/block_sender_domains.map}{${lookup{$sender_address_domain}lsearch{LODEWIJK_BLOKKADE_MAPS/block_sender_domains.map}{yes}{no}}}{no}}
message = LODEWIJK_BLOKKADE_TEKST
log_message = LODEWIJK_BLOKKADE domein $sender_address_domain
# Extensies (bad_tlds.re): niet voor onze eigen domeinen (zoals lodewijk.fr) als afzender.
deny condition = ${if eq{$acl_m_lw_toets}{1}}
condition = ${if def:sender_address_domain}
!sender_domains = +local_domains
condition = ${if exists{LODEWIJK_BLOKKADE_MAPS/bad_tlds.re}{${if forany{<\n ${readfile{LODEWIJK_BLOKKADE_MAPS/bad_tlds.re}}}{and{{!match{$item}{\N^\s*(#|$)\N}}{match{${lc:$sender_address_domain}}{${if match{$item}{\N^/(.*)/([a-tv-z]*)(u?)([a-z]*)$\N}{${if eq{$3}{u}{(*UTF)}{}}(?m$2$4)$1}{(?m)$item}}}}}}}}{no}}
message = LODEWIJK_BLOKKADE_TEKST
log_message = LODEWIJK_BLOKKADE extensie $sender_address_domain
deny condition = ${if eq{$acl_m_lw_toets}{1}}
hosts = ${if exists{LODEWIJK_BLOKKADE_MAPS/block_sender_hosts.map}{lsearch;LODEWIJK_BLOKKADE_MAPS/block_sender_hosts.map}}
message = LODEWIJK_BLOKKADE_TEKST
log_message = LODEWIJK_BLOKKADE host $sender_host_name
Proefbestand: /etc/exim-proef/exim.acl_check_recipient.pre.conf, live: /etc/exim.acl_check_recipient.pre.conf. Ontbreekt een lijst, dan slaat de regel zichzelf over.
3. Weigeren na DATA: From-kop, Reply-To en naam
# -------------------------------------------------------------------------------------------------
# Lodewijk-blokkade, deel 2: na DATA, op de From- en Reply-To-header (5 oktober 2026, besluit Theo).
# Zelfde lijsten, tekst, kopieën en uitzonderingen als deel 1 (/etc/exim.acl_check_recipient.pre.conf).
# Alle adressen in de headers tellen mee, ook als er meer dan één staat. Bounces (lege afzender) vallen
# alleen buiten de extensieregel: een foutmelding van een mailserver met een geblokkeerde extensie komt
# door, maar een bounce met een expliciet geblokkeerde afzender niet.
# Regexlijsten mogen in Rspamd-vorm (/patroon/i); die wordt hier (?mi)patroon.
# -------------------------------------------------------------------------------------------------
warn set acl_m_lw_toets = 0
set acl_m_lw_froms = ${lc:${addresses:>\n $bh_from:}}
set acl_m_lw_fromdoms = ${map{<\n $acl_m_lw_froms}{${domain:$item}}}
set acl_m_lw_rtdoms = ${map{<\n ${lc:${addresses:>\n $bh_reply-to:}}}{${domain:$item}}}
warn !authenticated = *
!hosts = <;; @[] ; 127.0.0.0/8 ; ::1
condition = ${if !eq{$acl_m_is_whitelisted}{1}}
set acl_m_lw_toets = 1
deny condition = ${if eq{$acl_m_lw_toets}{1}}
condition = ${if exists{LODEWIJK_BLOKKADE_MAPS/block_sender_addresses.map}{${if forany{<\n $acl_m_lw_froms}{or{{bool{${lookup{$item}lsearch{LODEWIJK_BLOKKADE_MAPS/block_sender_addresses.map}{yes}{no}}}}{bool{${lookup{${sg{$item}{\N"|\+[^@]*(?=@)\N}{}}}lsearch{LODEWIJK_BLOKKADE_MAPS/block_sender_addresses.map}{yes}{no}}}}}}}}{no}}
message = LODEWIJK_BLOKKADE_TEKST
log_message = LODEWIJK_BLOKKADE from-adres
deny condition = ${if eq{$acl_m_lw_toets}{1}}
condition = ${if exists{LODEWIJK_BLOKKADE_MAPS/block_sender_domains.map}{${if forany{<\n $acl_m_lw_fromdoms}{bool{${lookup{$item}lsearch{LODEWIJK_BLOKKADE_MAPS/block_sender_domains.map}{yes}{no}}}}}}{no}}
message = LODEWIJK_BLOKKADE_TEKST
log_message = LODEWIJK_BLOKKADE from-domein
# Extensies: niet voor bounces en niet voor onze eigen domeinen.
deny condition = ${if eq{$acl_m_lw_toets}{1}}
!senders = :
set acl_m_lw_vreemd = ${filter{<\n $acl_m_lw_fromdoms}{!match_domain{$item}{+local_domains}}}
condition = ${if def:acl_m_lw_vreemd}
condition = ${if exists{LODEWIJK_BLOKKADE_MAPS/bad_tlds.re}{${if forany{<\n ${readfile{LODEWIJK_BLOKKADE_MAPS/bad_tlds.re}}}{and{{!match{$item}{\N^\s*(#|$)\N}}{match{$acl_m_lw_vreemd}{${if match{$item}{\N^/(.*)/([a-tv-z]*)(u?)([a-z]*)$\N}{${if eq{$3}{u}{(*UTF)}{}}(?m$2$4)$1}{(?m)$item}}}}}}}}{no}}
message = LODEWIJK_BLOKKADE_TEKST
log_message = LODEWIJK_BLOKKADE from-extensie
deny condition = ${if eq{$acl_m_lw_toets}{1}}
condition = ${if exists{LODEWIJK_BLOKKADE_MAPS/block_replyto_domains.map}{${if forany{<\n $acl_m_lw_rtdoms}{bool{${lookup{$item}lsearch{LODEWIJK_BLOKKADE_MAPS/block_replyto_domains.map}{yes}{no}}}}}}{no}}
message = LODEWIJK_BLOKKADE_TEKST
log_message = LODEWIJK_BLOKKADE reply-to
deny condition = ${if eq{$acl_m_lw_toets}{1}}
condition = ${if def:acl_m_lw_rtdoms}
condition = ${if exists{LODEWIJK_BLOKKADE_MAPS/bad_replyto_domains.re}{${if forany{<\n ${readfile{LODEWIJK_BLOKKADE_MAPS/bad_replyto_domains.re}}}{and{{!match{$item}{\N^\s*(#|$)\N}}{match{$acl_m_lw_rtdoms}{${if match{$item}{\N^/(.*)/([a-tv-z]*)(u?)([a-z]*)$\N}{${if eq{$3}{u}{(*UTF)}{}}(?m$2$4)$1}{(?m)$item}}}}}}}}{no}}
message = LODEWIJK_BLOKKADE_TEKST
log_message = LODEWIJK_BLOKKADE reply-to-patroon
deny condition = ${if eq{$acl_m_lw_toets}{1}}
condition = ${if def:h_from:}
condition = ${if exists{LODEWIJK_BLOKKADE_MAPS/bad_from_headers.re}{${if forany{<\n ${readfile{LODEWIJK_BLOKKADE_MAPS/bad_from_headers.re}}}{and{{!match{$item}{\N^\s*(#|$)\N}}{match{$h_from:}{${if match{$item}{\N^/(.*)/([a-tv-z]*)(u?)([a-z]*)$\N}{${if eq{$3}{u}{(*UTF)}{}}(?m$2$4)$1}{(?m)$item}}}}}}}}{no}}
message = LODEWIJK_BLOKKADE_TEKST
log_message = LODEWIJK_BLOKKADE from-naam
Proefbestand: /etc/exim-proef/exim.acl_check_message.pre.conf, live: /etc/exim.acl_check_message.pre.conf. Alle adressen in de From-kop tellen mee, ook als er meer dan één staat. De Rspamd-vorm /patroon/i wordt hier omgezet naar wat Exim begrijpt, /u inbegrepen.
4. Het controlescript en de cronregel
Het script controleert elke lijst en zet hem pas daarna klaar in /etc/exim.lodewijk. Een regex die Exim niet kan lezen, een regel die ook gewone afzenders zoals gmail.com zou raken, of een regel die met een spatie begint: die lijst gaat niet door, de vorige kopie blijft gelden en je krijgt één mail. Log: /var/log/lodewijk-blokkade.log.
#!/bin/bash
# Lodewijk-blokkade (5 oktober 2026): zet de blokkadelijsten uit /etc/rspamd/local.d/maps na controle klaar
# voor Exim in /etc/exim.lodewijk (zie /etc/exim.acl_check_recipient.pre.conf). Bewerken gebeurt alleen in
# de Rspamd-map; dit script maakt er een kopie van die Exim veilig kan lezen.
# Een lijst met een fout (een regex die niet compileert, een regel die alles zou raken, een kapotte regel in
# een adres-, domein- of IP-lijst) gaat NIET door: dan blijft de vorige goede kopie gelden en komt er een
# melding. Zo kan een tikfout nooit alle mail laten weigeren.
# Draait elke vijf minuten via /etc/cron.d/lodewijk-blokkade; na het bewerken van een lijst kun je hem ook zelf
# draaien. Uitvoer: /var/log/lodewijk-blokkade.log. Bij een nieuwe fout (en bij herstel) een mail naar MELDEN.
set -u
BRON=${BRON:-/etc/rspamd/local.d/maps}
DOEL=${DOEL:-/etc/exim.lodewijk}
LOG=${LOG:-/var/log/lodewijk-blokkade.log}
MELDEN=${MELDEN:-root}
STAAT="$DOEL/.status"
EXIM=$(command -v exim || echo /usr/sbin/exim)
mkdir -p "$DOEL" && chmod 755 "$DOEL"
fouten=()
log() { printf '%s %s\n' "$(date '+%F %T')" "$*" >> "$LOG"; }
# Regels zonder commentaar en lege regels, inline commentaar (spatie + #) eraf.
regels() { sed -e 's/[[:space:]]#.*$//' -e 's/[[:space:]]*$//' "$1" | grep -v '^[[:space:]]*\(#\|$\)'; }
controleer_regex() { # bestand, onschuldige voorbeelden (newline-gescheiden)
local f=$1 goed=$2 uit
# Exact dezelfde omzetting als in de hooks; een regex die niet compileert geeft "Failed". De expressie gaat
# via stdin, want als argument staat Exim maar 256 tekens toe.
uit=$(printf '%s\n' "\${if forany{<\n \${readfile{$f}}}{and{{!match{\$item}{\N^\s*(#|\$)\N}}{match{$goed}{\${if match{\$item}{\N^/(.*)/([a-tv-z]*)(u?)([a-z]*)\$\N}{\${if eq{\$3}{u}{(*UTF)}{}}(?m\$2\$4)\$1}{(?m)\$item}}}}}}{RAAKT}{SCHOON}}" \
| "$EXIM" -be 2>&1 | sed 's/^> *//' | grep -v "cwd=\|^[0-9-]* [0-9:]* \|^$")
case "$uit" in
SCHOON) return 0 ;;
RAAKT) echo "een regel raakt ook gewone afzenders (zoals gmail.com): te breed"; return 1 ;;
*) echo "Exim kan een regel niet lezen: $(echo "$uit" | head -2 | tr '\n' ' ')"; return 1 ;;
esac
}
for f in block_sender_ips.map block_sender_addresses.map block_sender_domains.map block_sender_hosts.map block_replyto_domains.map bad_tlds.re bad_replyto_domains.re bad_from_headers.re; do
src="$BRON/$f"
if [ ! -r "$src" ]; then fouten+=("$f: ontbreekt of is niet leesbaar in $BRON"); continue; fi
if grep -q '^[[:space:]]\+[^[:space:]]' "$src"; then fouten+=("$f: een regel begint met een spatie"); continue; fi
probleem=""
case "$f" in
block_sender_ips.map)
probleem=$(regels "$src" | grep -Evx '([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]{1,2})?|[0-9a-fA-F:]*:[0-9a-fA-F:.]*(/[0-9]{1,3})?' | head -1) ;;
block_sender_addresses.map)
probleem=$(regels "$src" | grep -Evx '[^@[:space:]:]+@[a-zA-Z0-9.-]+\.[a-zA-Z0-9-]+' | head -1) ;;
block_sender_domains.map|block_sender_hosts.map|block_replyto_domains.map)
probleem=$(regels "$src" | grep -Evx '[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+' | head -1) ;;
bad_tlds.re|bad_replyto_domains.re)
r=$(controleer_regex "$src" "gmail.com\nlodewijk.frl\nhogerheide.nl\nziggo.nl\noutlook.com\nhollandskroon.nl") || probleem="$r" ;;
bad_from_headers.re)
r=$(controleer_regex "$src" "Jan Jansen <jan.jansen@gmail.com>\nGemeente Hollands Kroon <info@hollandskroon.nl>\ninfo@lodewijk.frl") || probleem="$r" ;;
esac
if [ -n "$probleem" ]; then fouten+=("$f: $probleem"); continue; fi
if ! cmp -s "$src" "$DOEL/$f"; then
tmp=$(mktemp "$DOEL/.$f.XXXXXX") && cp "$src" "$tmp" && chmod 644 "$tmp" && mv -f "$tmp" "$DOEL/$f" && log "bijgewerkt: $f"
fi
done
oud=$(cat "$STAAT" 2>/dev/null || echo ok)
if [ ${#fouten[@]} -gt 0 ]; then
tekst=$(printf '%s\n' "${fouten[@]}")
log "FOUT, vorige kopie blijft gelden: $(echo "$tekst" | tr '\n' ';')"
echo "$tekst" >&2
if [ "$oud" != "fout:$(echo "$tekst" | md5sum | cut -c1-12)" ]; then
{
printf 'To: %s
Subject: Spamblokkade %s: lijst met fout niet doorgezet
' "$MELDEN" "$(hostname)"
printf 'Op %s gaat een blokkadelijst niet door naar Exim. De vorige goede kopie blijft gelden, de mail loopt gewoon.
%s
Pas de lijst aan in %s; dit script controleert hem elke vijf minuten opnieuw.
' "$(hostname)" "$tekst" "$BRON"
} | "$EXIM" -odi -f "root@$(hostname)" -t 2>/dev/null
echo "fout:$(echo "$tekst" | md5sum | cut -c1-12)" > "$STAAT"
fi
exit 1
fi
if [ "$oud" != "ok" ]; then log "hersteld: alle lijsten weer in orde"; echo ok > "$STAAT"; fi
exit 0
mkdir -p /etc/exim-proef nano /etc/exim-proef/strings.conf.custom # blok 1 nano /etc/exim-proef/exim.acl_check_recipient.pre.conf # blok 2 nano /etc/exim-proef/exim.acl_check_message.pre.conf # blok 3 nano /usr/local/sbin/lodewijk-blokkade-sync # blok 4 chmod 755 /usr/local/sbin/lodewijk-blokkade-sync echo '*/5 * * * * root /usr/local/sbin/lodewijk-blokkade-sync >/dev/null 2>&1' > /etc/cron.d/lodewijk-blokkade /usr/local/sbin/lodewijk-blokkade-sync; tail -5 /var/log/lodewijk-blokkade.log
Stap 5: de nooduitgang
EximRaakt een regel toch een afzender die er doorheen moet? Zet het domein in de whitelist van DirectAdmin. Die geldt voor de Exim-laag én voor Rspamd, en werkt meteen. Bij ons staan er bijvoorbeeld een warmtepompfabrikant (meldingen vanaf een .se-domein) en een poortsysteem in.
echo "nibe.se" >> /etc/virtual/whitelist_domains cat /etc/virtual/whitelist_domains
Gebruik de whitelist alleen als nooduitgang. Beter is de regel zelf preciezer maken, zodat hij de echte afzender niet meer raakt.
Stap 6: testen zonder je echte mail te raken
TestenExim kan een SMTP-sessie naspelen met exim -bh <ip>: er wordt niets afgeleverd en niets verstuurd. Nog veiliger is een proefkopie van de configuratie die naar proefkopieën van de bestanden wijst. Dan raakt een fout nooit de echte mail.
P=/etc/exim-proef
mkdir -p $P/bron $P/lijsten
cp /etc/rspamd/local.d/maps/* $P/bron/
sed 's#^LODEWIJK_BLOKKADE_MAPS = .*#LODEWIJK_BLOKKADE_MAPS = /etc/exim-proef/lijsten#' $P/strings.conf.custom > $P/strings.proef.conf
sed -e 's#/etc/exim.strings.conf.custom#/etc/exim-proef/strings.proef.conf#' \
-e 's#/etc/exim.acl_check_recipient.pre.conf#/etc/exim-proef/exim.acl_check_recipient.pre.conf#' \
-e 's#/etc/exim.acl_check_message.pre.conf#/etc/exim-proef/exim.acl_check_message.pre.conf#' \
/etc/exim.conf > $P/exim.conf
chmod -R a+rX $P
BRON=$P/bron DOEL=$P/lijsten LOG=$P/sync.log /usr/local/sbin/lodewijk-blokkade-sync
exim -C $P/exim.conf -bV | tail -1
Zet de proefmatrix (download hieronder) in dezelfde map en draai hem met je eigen ontvanger, IP-adres en domein. Elke regel moet goed zeggen. De map moet leesbaar zijn voor de Exim-gebruiker: in /root krijg je "-C Permission denied".
cd /etc/exim-proef RCPT=info@jouwdomein.nl EIGEN_IP=203.0.113.10 EIGEN_DOMEIN=jouwdomein.nl bash proef.sh
goed gmail gewoon DOOR - goed ip geblokkeerd BLOK ip 550 5.7.1 ... goed domein envelope BLOK domein 550 5.7.1 ... goed from-naam CarOptions BLOK from-naam 550 5.7.1 ... goed bounce orange.fr (extensie) DOOR - goed from-naam ANWB Mobiliteit DOOR - ...
Live zetten
Zegt elke regel goed? Zet dan de drie Exim-bestanden op hun echte plek, controleer de configuratie en herstart Exim. Kijk daarna een paar minuten mee in het logboek. Bestaat een van de drie bestanden al met eigen regels erin, dan vervangt het script hieronder het niet: het bewaart een kopie en zegt welk blok je zelf moet toevoegen.
P=/etc/exim-proef
for paar in strings.conf.custom:exim.strings.conf.custom \
exim.acl_check_recipient.pre.conf:exim.acl_check_recipient.pre.conf \
exim.acl_check_message.pre.conf:exim.acl_check_message.pre.conf; do
bron=$P/${paar%%:*}; doel=/etc/${paar##*:}
if [ -s "$doel" ] && ! cmp -s "$bron" "$doel"; then
cp -a "$doel" "$doel.voor-blokkade"
echo "LET OP: $doel bestaat al (kopie: $doel.voor-blokkade). Voeg het blok uit $bron zelf toe."
else
cp "$bron" "$doel"
fi
done
exim -bV >/dev/null && systemctl restart exim
tail -f /var/log/exim/mainlog
Rspamd los testen
rspamadm configtest
rspamadm configdump multimap | grep -cE "^[A-Z_]+ \{"
cat > /tmp/proef.eml <<'EOF'
From: CarOptions B.V. <info-caroptions@deliver.moneybird.com>
Reply-To: CarOptions B.V. <info@caroptions.nl>
To: info@jouwdomein.nl
Subject: Proef blokkade
Proef.
EOF
rspamc -h localhost:11333 < /tmp/proef.eml | grep -E "Action|Symbol: (BAD|BLOCK)"
Op DirectAdmin luistert de controller van Rspamd vaak alleen op een socket. Geeft rspamc "Connection refused", gebruik dan -h localhost:11333 (de normale worker); ss -ltnp | grep rspamd laat zien waar hij luistert.
In het logboek
grep LODEWIJK_BLOKKADE /var/log/exim/mainlog | tail -20 grep -c LODEWIJK_BLOKKADE /var/log/exim/mainlog tail -f /var/log/lodewijk-blokkade.log
De eigen lijsten van DirectAdmin
TestenDirectAdmin heeft zelf ook blokkadelijsten. Exim leest die eerder dan onze lagen, en weigert dan met de standaardtekst van DirectAdmin.
/etc/virtual/blacklist_domains /etc/virtual/blacklist_senders /etc/virtual/bad_sender_hosts /etc/virtual/bad_sender_hosts_ip
Ons advies: zet nieuwe blokkades alleen nog in de Rspamd-lijsten, dan krijgt de afzender je eigen tekst. Wat al in de DirectAdmin-lijsten staat, mag blijven staan; het werkt, alleen met een kalere melding. bad_sender_hosts_ip blijft handig voor een server die je in één keer helemaal wilt weren, bijvoorbeeld bij een mailvloed.
wc -l /etc/virtual/blacklist_domains /etc/virtual/blacklist_senders /etc/virtual/bad_sender_hosts_ip grep -i "voorbeeld.nl" /etc/virtual/blacklist_* /etc/rspamd/local.d/maps/*
Dagelijks: een nieuwe afzender blokkeren
Testen- Kijk in de kop van de mail: envelope-afzender (Return-Path), From, Reply-To en de verzendende server (Received). Kies de smalste regel die werkt: een adres voor één gekaapt postvak, een domein als het hele domein troep is, een Reply-To als de partij via een platform mailt.
- Zet de waarde in de juiste lijst in
/etc/rspamd/local.d/maps/, met een korte reden erachter. - Draai het controlescript en kijk of het "bijgewerkt" zegt en geen FOUT. Herlaad Rspamd alleen bij een wijziging in
multimap.conf; de lijsten zelf leest Rspamd vanzelf opnieuw. - Twijfel je over een regex? Draai de proefmatrix.
echo "spamdomein.example # ongevraagde offertes, 3x in een week" >> /etc/rspamd/local.d/maps/block_sender_domains.map /usr/local/sbin/lodewijk-blokkade-sync; tail -3 /var/log/lodewijk-blokkade.log
Veelgemaakte fouten
Testen| Probleem | Gevolg | Oplossing |
|---|---|---|
| Alleen Rspamd laten weigeren | Afzender krijgt "High scoring spam message has been dropped" in plaats van jouw tekst | De Exim-laag uit stap 4 |
| Exim leest de lijst die je bewerkt rechtstreeks | Eén kapotte regex geeft 451 op alle inkomende mail | Laat Exim de gecontroleerde kopie lezen (controlescript) |
/patroon/u rechtstreeks in Exim | Exim kent geen inline u; de regel compileert niet | De hooks zetten /u om naar (*UTF); niet zelf aanpassen |
Regex in een .map-bestand | Regel raakt niets | .map voor exacte waarden, .re voor patronen |
@domein\.nl$ in een Reply-To-regex | Raakt niets, want de regel ziet alleen het domein | /(^|\.)domein\.nl$/i |
| Regel begint met een spatie | Exim ziet een vervolgregel en leest iets anders dan je denkt | Het controlescript weigert zo'n lijst; haal de spatie weg |
| Gedeeld platform op de IP-lijst | Je weigert ook alle nette afzenders op dat platform | IP-lijst alleen voor eigen spamservers; anders op adres, Reply-To of naam |
Proefconfig in /root | -C Permission denied | Gebruik een map die Exim mag lezen, zoals /etc/exim-proef |
Veelgestelde vragen
TestenWordt de mail nu twee keer gecontroleerd?
Ja, en dat is de bedoeling. Exim weigert met je eigen tekst en voor elk domein. Rspamd is het vangnet en geeft daarnaast scores voor twijfelgevallen. Het kost bijna niets: het zijn opzoekingen in kleine lijsten.
Overleeft dit een update van DirectAdmin?
Ja. De Exim-bestanden zijn de officiële inhaakpunten van DirectAdmin, /etc/rspamd/local.d/ wordt niet overschreven en het controlescript en de cronregel staan buiten custombuild. Bij ons kwamen ze ongeschonden door de update naar exim.conf 4.5.70.
Krijgt een geweigerde afzender echt mijn tekst te zien?
Ja. De verzendende server krijgt een 550-antwoord met jouw tekst en stuurt de afzender een bounce waarin die tekst staat. Daarin staat je contactformulier, zodat iemand die er onterecht op staat je toch kan bereiken.
Mag ik commentaar achter een regel zetten?
In een .map: ja, na een spatie en een #. In een .re: zet commentaar op een eigen regel die met # begint. Gebruik nooit /* ... */.
Werkt dit ook zonder Easy Spam Fighter?
De Exim-laag wel: die heeft Rspamd niet nodig, alleen de lijsten. Het controlescript en de hooks werken op elke DirectAdmin-server met Exim.
Downloads
Precies de bestanden die op deze pagina staan. De lijsten zijn onze ervaring, geen officiële blocklist: gekaapte studentenaccounts, adressen van personen en nieuwsbrieven halen we eruit voordat we ze delen.
Hulp nodig met Rspamd, DirectAdmin of mail filtering?
Komt een legitieme mail onterecht niet aan, of wil je hulp bij het instellen van spamfiltering op een DirectAdmin-server? Neem dan contact op.