Naar hoofdinhoud
Contact
Kennisbank · Rspamd en Exim

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.

bijgewerkt 24-10-2026 Theo Lodewijk draait op 3 servers 27 proefgevallen

Zo werkt het, in het kort

Onderzoek

Een 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. Verbinding2. Exim bij RCPT3. Exim na DATA4. Rspamd
IP-adres en hostnaam van de verzendende serverIP, afzenderadres, afzenderdomein, extensie, reverse DNSFrom-kop (alle adressen), Reply-To, afzendernaamdezelfde 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

Onderzoek

Onze 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:

  1. 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. Jouw message = uit multimap.conf komt nooit bij de afzender aan.
  2. 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.
  3. 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.conf en /etc/exim.acl_check_message.pre.conf) weigert Exim op dezelfde lijsten, met jouw tekst. Die bestanden overleven een update van DirectAdmin.
Wat wil jeAlleen DirectAdmin-lijstenAlleen RspamdRspamd + Exim (dit artikel)
Eigen melding met contactlinkneeneeja
Geldt voor elk domein op de serverjaalleen met spamfilter aanja
From-kop, Reply-To en afzendernaamneejaja
Patronen (regex), zoals extensiesneejaja
Eén plek om te beherenjajaja (de Rspamd-lijsten)
Beschermd tegen een tikfoutdeelsRspamd slaat een kapotte regel overja, 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

Lijsten

Elke 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/*
BestandWat erin staatVoorbeeld
block_sender_addresses.maplosse afzenderadressen, exact; voor een gekaapt postvak bij een verder nette partijrvo@kpnmail.nl # nep-RVO
block_sender_domains.maphele afzenderdomeinen, exact; alleen domeinen die uitsluitend troep sturenengine-shop.beauty # spam
block_replyto_domains.mapdomeinen in het Reply-To-adres, exactautoinkoopholland.com
block_sender_ips.mapIP-adressen van eigen spamservers; nooit een gedeeld platform zoals Gmail of Microsoft143.246.223.105
block_sender_hosts.maphostnaam (reverse DNS) van de verzendende serverwp.zapto.org
bad_tlds.reextensies en domeinpatronen van de afzender\.(ru|su|xyz|top)$
bad_replyto_domains.repatronen voor het Reply-To-domein/(^|\.)caroptions\.nl$/i
bad_from_headers.repatronen voor de hele From-kop, inclusief de naam/CarOptions\s+B\.V\./i

Stap 2: de lijsten vullen

Lijsten

Begin 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

Rspamd

Rspamd 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

Exim

Dit 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
Nog niet live. Het controlescript en de cronregel mogen al draaien: ze zetten alleen gecontroleerde kopieën klaar. De drie Exim-bestanden staan nog in de proefmap; live zet je ze in stap 6, na de proef. Een fout in een Exim-inhaakbestand raakt alle mail op de server.

Stap 5: de nooduitgang

Exim

Raakt 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

Testen

Exim 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

Testen

DirectAdmin 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
  1. 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.
  2. Zet de waarde in de juiste lijst in /etc/rspamd/local.d/maps/, met een korte reden erachter.
  3. 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.
  4. 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
ProbleemGevolgOplossing
Alleen Rspamd laten weigerenAfzender krijgt "High scoring spam message has been dropped" in plaats van jouw tekstDe Exim-laag uit stap 4
Exim leest de lijst die je bewerkt rechtstreeksEén kapotte regex geeft 451 op alle inkomende mailLaat Exim de gecontroleerde kopie lezen (controlescript)
/patroon/u rechtstreeks in EximExim kent geen inline u; de regel compileert nietDe hooks zetten /u om naar (*UTF); niet zelf aanpassen
Regex in een .map-bestandRegel raakt niets.map voor exacte waarden, .re voor patronen
@domein\.nl$ in een Reply-To-regexRaakt niets, want de regel ziet alleen het domein/(^|\.)domein\.nl$/i
Regel begint met een spatieExim ziet een vervolgregel en leest iets anders dan je denktHet controlescript weigert zo'n lijst; haal de spatie weg
Gedeeld platform op de IP-lijstJe weigert ook alle nette afzenders op dat platformIP-lijst alleen voor eigen spamservers; anders op adres, Reply-To of naam
Proefconfig in /root-C Permission deniedGebruik een map die Exim mag lezen, zoals /etc/exim-proef

Veelgestelde vragen

Testen

Wordt 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

getest 24-10-2026

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.

Delen