Rspamd hat ein Webinterface. Da kann man E-Mails reinkopieren und als Spam oder Ham markieren. Klingt erstmal praktisch. Ist es aber nicht. Niemand kopiert ernsthaft den Quellcode jeder fehlklassifizierten Mail in ein Webformular. Das macht man einmal zum Testen und dann nie wieder.
Was man eigentlich will: Wenn ein Benutzer eine Mail in den Junk-Ordner verschiebt, soll rspamd das automatisch als Spam lernen. Und wenn eine Mail aus dem Junk-Ordner rausgeholt wird, soll rspamd sie als Ham lernen. Kein Webinterface, kein manueller Eingriff. Der Benutzer sortiert einfach seine Mails — und rspamd lernt mit.
Genau das geht mit Dovecot und IMAPSieve. Hier beschreibe ich, wie ich das bei mir eingerichtet habe. Die Konfiguration läuft seit Mai 2020 unverändert — über sechs Jahre, ohne eine einzige Anpassung. Das darf man ruhig als stabil bezeichnen.
Was passiert da eigentlich
Der Datenfluss ist simpel:
- Benutzer verschiebt eine Mail in den Ordner „Junk“
- Dovecot erkennt die Verschiebung per IMAPSieve
- IMAPSieve startet ein Sieve-Script
- Das Sieve-Script ruft ein Shell-Script auf
- Das Shell-Script übergibt die Mail per
rspamc an rspamd
- Rspamd lernt die Mail als Spam (Bayes-Klassifikator)
In die andere Richtung genauso: Mail raus aus Junk, Dovecot erkennt es, rspamd lernt Ham. Egal ob der Benutzer über Thunderbird, Roundcube, ein Smartphone oder was auch immer sortiert — solange es IMAP ist, greift das.
Voraussetzungen
- Dovecot mit Sieve-Support (
dovecot-pigeonhole unter FreeBSD, dovecot-sieve unter Debian/Ubuntu)
- Rspamd mit laufendem Controller-Worker
- rspamc CLI-Tool (kommt mit rspamd mit)
Mein Setup läuft auf FreeBSD. Die Pfade beginnen daher mit /usr/local/. Unter Linux ist es /etc/dovecot/ statt /usr/local/etc/dovecot/ und /usr/lib/dovecot/ statt /usr/local/libexec/dovecot/. Ansonsten ist alles identisch.
Mein rspamd läuft in einer eigenen Jail und lauscht auf 127.0.0.3:11334. Wer rspamd lokal auf dem gleichen System hat, nimmt stattdessen 127.0.0.1:11334 oder den Unix-Socket.
Dovecot konfigurieren
Zuerst muss das Sieve-Plugin für IMAP aktiviert werden.
20-imap.conf:
protocol imap {
mail_plugins = $mail_plugins sieve
}
Dann die IMAPSieve-Konfiguration. Hier wird festgelegt, welche Ordner-Aktionen welches Sieve-Script auslösen.
90-plugin.conf:
plugin {
sieve_plugins = sieve_imapsieve sieve_extprograms
# Wenn eine Mail in den Junk-Ordner kopiert oder dort ein Flag geaendert wird
imapsieve_mailbox1_name = Junk
imapsieve_mailbox1_causes = COPY FLAG
imapsieve_mailbox1_before = file:/usr/local/etc/dovecot/sieve/report-spam.sieve
# Wenn eine Mail AUS dem Junk-Ordner woanders hin verschoben wird
imapsieve_mailbox2_name = *
imapsieve_mailbox2_from = Junk
imapsieve_mailbox2_causes = COPY
imapsieve_mailbox2_before = file:/usr/local/etc/dovecot/sieve/report-ham.sieve
sieve_pipe_bin_dir = /usr/local/libexec/dovecot
sieve_global_extensions = +vnd.dovecot.pipe
}
Zwei Trigger: Einer für „Mail landet im Junk“ (→ Spam lernen), einer für „Mail verlässt Junk“ (→ Ham lernen). COPY deckt Verschieben ab, FLAG fängt den Fall ab, dass ein Mail-Client den Junk-Status per Flag statt per Verschieben setzt.
Sieve-Scripts
Jetzt die beiden Sieve-Scripts, die von IMAPSieve aufgerufen werden.
report-spam.sieve — wird ausgelöst, wenn eine Mail im Junk-Ordner landet:
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "imap4flags"];
if environment :is "imap.cause" "COPY" {
pipe :copy "sa-learn-spam.sh";
}
# Beantworteten oder weitergeleiteten Spam ebenfalls lernen
elsif anyof (allof (hasflag "\\Answered",
environment :contains "imap.changedflags" "\\Answered"),
allof (hasflag "$Forwarded",
environment :contains "imap.changedflags" "$Forwarded")) {
pipe :copy "sa-learn-spam.sh";
}
Der erste Block fängt das normale Verschieben ab. Der zweite Block ist für einen Sonderfall: Wenn jemand auf eine Mail im Junk-Ordner antwortet oder sie weiterleitet, ändert sich das Flag — und auch das sollte als Spam gelernt werden.
report-ham.sieve — wird ausgelöst, wenn eine Mail den Junk-Ordner verlässt:
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
if environment :matches "imap.mailbox" "*" {
set "mailbox" "${1}";
}
if string "${mailbox}" [ "Trash", "train_ham", "train_prob", "train_spam" ] {
stop;
}
pipe :copy "sa-learn-ham.sh";
Hier passiert etwas Wichtiges: Bevor die Mail als Ham gelernt wird, prüfen wir wohin sie verschoben wurde. Wenn sie im Papierkorb landet, war das vermutlich kein „Das ist kein Spam“ sondern ein „Ich lösche den Spam“. Deshalb: stop; für Trash und die Trainingsordner. Nur wenn die Mail in einen echten Ordner verschoben wird, ist es ein Ham-Signal.
Beide Scripts müssen kompiliert werden:
sievec /usr/local/etc/dovecot/sieve/report-spam.sieve
sievec /usr/local/etc/dovecot/sieve/report-ham.sieve
Shell-Scripts für rspamc
Die Sieve-Scripts rufen Shell-Scripts auf, die die Mail per rspamc an rspamd übergeben. Simpel — jeweils ein Einzeiler.
/usr/local/libexec/dovecot/sa-learn-spam.sh:
#!/bin/sh
exec /usr/local/bin/rspamc -h 127.0.0.3:11334 learn_spam
/usr/local/libexec/dovecot/sa-learn-ham.sh:
#!/bin/sh
exec /usr/local/bin/rspamc -h 127.0.0.3:11334 learn_ham
Die Dateinamen sa-learn-* kommen historisch von SpamAssassin. Verwirrend, wenn man rspamd nutzt. Man könnte sie auch rspamd-learn-spam.sh nennen — funktional ist es egal. Ich habe sie so gelassen, weil man funktionierende Dinge nicht anfasst.
Beide ausführbar machen:
chmod +x /usr/local/libexec/dovecot/sa-learn-spam.sh /usr/local/libexec/dovecot/sa-learn-ham.sh
Wer rspamd lokal laufen hat, ersetzt 127.0.0.3 durch 127.0.0.1 oder nutzt den Unix-Socket (-h /var/run/rspamd/rspamd.sock). Unter Linux liegen die Scripts in /usr/lib/dovecot/ statt /usr/local/libexec/dovecot/. Der Pfad in sieve_pipe_bin_dir muss natürlich dazu passen.
Wichtig: Damit rspamc ohne Passwort trainieren darf, muss die IP im rspamd Controller-Worker als vertrauenswürdig eingetragen sein. In /usr/local/etc/rspamd/local.d/worker-controller.inc (FreeBSD) bzw. /etc/rspamd/local.d/worker-controller.inc (Linux):
secure_ip = "127.0.0.0/8";
secure_ip = "::1";
Ohne das schlägt rspamc learn_spam mit einem Authentifizierungsfehler fehl. Bei Jail-Setups wie meinem muss die Jail-IP (127.0.0.3) in der Liste stehen.
Testen
Dovecot neu laden:
service dovecot reload
Dann eine beliebige Mail in den Junk-Ordner verschieben und im rspamd-Log nachschauen:
rspamd_controller_learn_fin_task: <127.0.0.3> learned message as spam: MESSAGE-ID
Mail wieder raus aus Junk in den Posteingang:
rspamd_controller_learn_fin_task: <127.0.0.3> learned message as ham: MESSAGE-ID
Wenn das im Log steht, funktioniert alles. Kein Neustart nötig, kein Cache-Flush, kein Warten.
Wie viel Training braucht rspamd
Rspamd nutzt einen Bayes-Klassifikator. Der braucht eine Mindestmenge an gelernten Nachrichten, bevor er aktiv wird. Die Standardeinstellung ist 200 — also mindestens 200 Spam-Mails und 200 Ham-Mails. Vorher ignoriert rspamd die Bayes-Ergebnisse komplett.
Das klingt nach viel, geht aber schneller als man denkt. Wer ein paar Dutzend Benutzer auf dem Server hat, kommt da in wenigen Wochen hin. Und danach wird rspamd mit jeder sortierten Mail ein bisschen besser.
Den aktuellen Stand kann man jederzeit prüfen:
rspamc stat
Unter Statfile sieht man wie viele Nachrichten rspamd bereits gelernt hat.
Rspamd trainiert standardmäßig einen globalen Bayes-Klassifikator — alle Benutzer lernen in denselben Pool. Wer das pro Benutzer trennen will, setzt in der classifier-bayes.conf:
per_user = true;
Für die meisten Setups mit einer Handvoll Domains ist der globale Pool sinnvoller — mehr Trainingsdaten, schneller gute Ergebnisse.
Hinweise
Die Konfiguration ist stabil — Dovecot-Updates, rspamd-Updates, FreeBSD-Upgrades, alles durchgelaufen ohne Anpassung.
Wer rspamd danach noch eine Stufe weiter bringen will: Ich habe einen eigenen Beitrag geschrieben, wie man GPT-basierte Spam-Erkennung in rspamd integriert. Das läuft zusätzlich zum Bayes-Klassifikator und fängt die Mails ab, die durch das statistische Netz rutschen.
Fragen? Schreib mir über die Kontaktseite.