Froxlor & Spam: SpamAssassin-Mails mit Sieve automatisch in den Junk-Ordner schieben

SpamAssassin markiert, aber verschiebt nicht — der eigentliche Schritt passiert in Dovecot. Wie ein globales Sieve-Skript Spam zuverlässig in den Junk-Ordner sortiert, egal ob X-Spam-Flag oder nur X-Spam-Level gesetzt ist.

Froxlor selbst ist ein Server-Verwaltungspanel; die eigentliche Spamfilterung passiert in den Komponenten dahinter. Typischerweise markiert SpamAssassin (über Amavis oder direkt aus Postfix aufgerufen) verdächtige Mails mit speziellen Headern, und Dovecot liefert sie über LMTP/LDA aus. Standardmäßig landet die Mail dabei trotzdem im Posteingang, denn das Setzen eines X-Spam-Flag-Headers verschiebt für sich genommen noch nichts. Den letzten Schritt übernimmt Sieve, die Filtersprache, mit der sich beim Zustellen entscheiden lässt, in welchen Ordner eine Mail wandert.

Voraussetzungen prüfen

Bevor das Sieve-Skript geschrieben wird, müssen drei Dinge stimmen. Wenn eines davon fehlt, läuft die Filterung ins Leere:

  1. SpamAssassin (oder Amavis mit SpamAssassin) markiert eingehende Mails. Bei einem typischen Amavis-Setup muss $sa_tag_level_deflt niedrig genug stehen, damit überhaupt X-Spam-*-Header eingefügt werden. Übliche Werte in /etc/amavis/conf.d/50-user oder /etc/amavis/conf.d/20-debian_defaults sind $sa_tag_level_deflt = -999; und $sa_tag2_level_deflt = 6.0; für den eigentlichen Spam-Flag.
  2. Dovecot stellt per LMTP oder LDA zu. Nur dann werden Sieve-Skripte überhaupt ausgeführt. Postfix sollte die finale Zustellung also an Dovecot übergeben (in der main.cf üblich: virtual_transport = lmtp:unix:private/dovecot-lmtp).
  3. Das Pigeonhole-Sieve-Plugin ist installiert. Unter Debian/Ubuntu liefert das Paket dovecot-sieve Plugin und Compiler (sievec), dovecot-managesieved bringt zusätzlich den Verwaltungsdienst auf Port 4190 mit, über den Roundcube & Co. Sieve-Skripte hochladen können.

Ob SpamAssassin wirklich Header setzt, lässt sich am schnellsten in einer betroffenen Mail prüfen, in Roundcube über „Quelltext anzeigen", in Thunderbird mit Strg+U. Welche Header gesetzt sind, hängt vom Setup ab. In der Praxis findet man eine von zwei Varianten:

  • Variante A: X-Spam-Flag: YES und X-Spam-Status: Yes, score=…. Das ist der „klassische" Fall, in dem SpamAssassin/Amavis ein eindeutiges Spam-Flag setzt. Voraussetzung dafür ist allerdings, dass in der SpamAssassin-Konfiguration die Zeile add_header spam Flag _YESNO_ (bzw. in Amavis die entsprechende Header-Aktion) aktiv ist.
  • Variante B: nur X-Spam-Level: ****** und X-Spam-Status: …, score=…, aber kein X-Spam-Flag. Das ist der häufigere Fall in puren SpamAssassin-Setups ohne Amavis-Header-Action: X-Spam-Level wird von SpamAssassin immer gesetzt, pro ganzzahligem Score-Punkt ein Asterisk (bei Score 4,3 also ****).

Beide Varianten lassen sich mit Sieve filtern, aber die Bedingung sieht anders aus. Deshalb steht der Header-Check zuerst.

Zwei Wege: globaler Filter oder pro Mailbox

Dovecot kennt zwei Stellen, an denen Sieve-Skripte greifen:

  • Globales Skript (sieve_before): wird vor dem persönlichen Skript des Users ausgeführt und gilt für alle Mailboxen. Ideal, um Spam einheitlich für alle Kunden eines Froxlor-Hostings in den Junk-Ordner zu verschieben, ohne dass jeder User selbst etwas anlegen muss.
  • Persönliches Skript pro User: wird typischerweise über ManageSieve verwaltet (Roundcube-Plugin „managesieve" oder externe Tools). Gibt den Endkunden die Möglichkeit, eigene Regeln zu pflegen, kann aber nicht garantieren, dass Spam wirklich aussortiert wird.

Für die Frage „Spam in den Spam-Ordner" ist der globale Weg der verlässlichere, denn er funktioniert auch für Benutzer, die nie ein Webmail öffnen.

Schritt 1: ManageSieve aktivieren (optional, aber empfohlen)

Damit Endkunden später eigene Regeln pflegen können (und damit Roundcube den Spam-Filter überhaupt anzeigt), muss in der Dovecot-Konfiguration das Sieve-Protokoll aktiviert sein. Laut offizieller Dovecot-Doku reicht dafür:

# /etc/dovecot/dovecot.conf
protocols = imap pop3 lmtp sieve

# /etc/dovecot/conf.d/20-managesieve.conf
service managesieve-login {
  inet_listener sieve {
    port = 4190
  }
}

service managesieve {
  process_limit = 1024
}

Der ManageSieve-Daemon lauscht damit auf TCP 4190, dem Standard-Port nach RFC 5804. In der Firewall sollte er nur lokal oder TLS-geschützt offen sein.

Schritt 2: Sieve als Plugin im LDA/LMTP aktivieren

Das ist der Schritt, der am häufigsten vergessen wird und ohne den nichts funktioniert: Sieve ist in Dovecot ein Plugin, das explizit pro Auslieferungsservice geladen werden muss. Taucht Sieve dort nicht in mail_plugins auf, ignoriert Dovecot sämtliche Sieve-Skripte stillschweigend, auch das gleich folgende globale Skript.

In /etc/dovecot/conf.d/15-lda.conf und /etc/dovecot/conf.d/20-lmtp.conf (Dateinamen variieren je nach Distribution; bei Froxlor-Standard-Setup auf Debian sind das die genannten) muss in den jeweiligen Protokoll-Blöcken das Plugin hinzugefügt werden:

protocol lda {
  mail_plugins = $mail_plugins sieve
}

protocol lmtp {
  mail_plugins = $mail_plugins sieve
}

Wichtig ist die Syntax $mail_plugins sieve: Sie hängt Sieve zusätzlich an die bereits geladenen Plugins an. Stünde stattdessen nur mail_plugins = sieve, wären andere Plugins (Quota, ACL, …) plötzlich deaktiviert.

Prüfen, ob Dovecot Sieve im LDA wirklich lädt, geht ohne Neustart per:

doveconf -f service=lda mail_plugins
doveconf -f service=lmtp mail_plugins

Beide Befehle sollten eine Zeile zurückgeben, in der sieve enthalten ist. Wenn nicht, läuft kein Sieve-Skript, egal wie sauber das Skript selbst geschrieben ist.

Schritt 3: Globales Spam-Sieve-Skript anlegen

Der Kern der Lösung ist ein winziges Skript, das auf den von SpamAssassin gesetzten Header reagiert und die Mail in einen Junk-Ordner verschiebt (und ihn anlegt, falls er noch nicht existiert). Welche Bedingung darin steht, hängt davon ab, welche Variante (siehe Header-Check oben) im eigenen Setup auftaucht.

Variante A, wenn X-Spam-Flag: YES gesetzt wird

# /etc/dovecot/sieve/spam-to-junk.sieve
require ["fileinto", "mailbox"];

if header :contains "X-Spam-Flag" "YES" {
    fileinto :create "Junk";
    stop;
}

Variante B, wenn nur X-Spam-Level mit Asterisken gesetzt wird

Der Trick: :contains prüft, ob ein Teilstring vorkommt. „Enthält fünf Asterisken in Folge" matcht damit automatisch jeden Wert mit fünf oder mehr Sternchen, ein sauberer Schwellenwert ohne Zahlenvergleich. Fünf Asterisken entsprechen dem SpamAssassin-Default-Schwellwert required_score = 5.0.

# /etc/dovecot/sieve/spam-to-junk.sieve
require ["fileinto", "mailbox"];

if header :contains "X-Spam-Level" "*****" {
    fileinto :create "Junk";
    stop;
}

Schwellwert ist frei justierbar, einfach mehr oder weniger Sterne:

  • "***": Score ≥ 3 (aggressiv, eher mehr False Positives)
  • "*****": Score ≥ 5 (SpamAssassin-Default, empfohlen)
  • "*******": Score ≥ 7 (nur ziemlich sicherer Spam)

In beiden Varianten sind die zwei require-Erweiterungen nötig: fileinto macht das Verschieben überhaupt möglich, mailbox erlaubt die :create-Option, damit der Ordner beim ersten Treffer automatisch entsteht. Das abschließende stop; verhindert, dass weitere Skripte die Mail nochmal anfassen.

Wer beide Header-Quellen abdecken will (z. B. weil mehrere Mailrouten existieren), kombiniert sie per anyof:

require ["fileinto", "mailbox"];

if anyof (
    header :contains "X-Spam-Flag"  "YES",
    header :contains "X-Spam-Level" "*****"
) {
    fileinto :create "Junk";
    stop;
}

Anschließend muss Dovecot wissen, dass dieses Skript als globaler Pre-Filter laufen soll. Dazu im Sieve-Plugin-Block in /etc/dovecot/conf.d/90-sieve.conf:

plugin {
  sieve            = file:~/sieve;active=~/.dovecot.sieve
  sieve_before     = /etc/dovecot/sieve/spam-to-junk.sieve
  sieve_extensions = +mailbox +fileinto
}

sieve_before bekommt hier den direkten Pfad zur Skript-Datei. Laut Dovecot-Doku akzeptiert die Option zwar auch ein Verzeichnis (in dem dann alle *.sieve-Dateien in alphabetischer Reihenfolge ausgeführt werden), aber für eine einzelne Spam-Regel ist der direkte Dateipfad eindeutiger und weniger fehleranfällig. So gibt es keine unbeabsichtigten Skript-Funde aus Restdateien.

Wichtig dabei: Pfade für Sieve-Skripte sollten nicht innerhalb des Maildir liegen. Das ist eine wiederkehrende Empfehlung in der Dovecot-Dokumentation und in den Froxlor-Foren, weil Sieve sonst zwischen IMAP-Ordnern und Skripten kollidiert.

Schritt 4: Junk-Ordner anlegen & Auto-Subscribe

Damit der Junk-Ordner in jedem Mail-Client (Thunderbird, Apple Mail, Roundcube) sauber als „Spam"/„Junk" erscheint und auch automatisch abonniert wird, hilft die Mailbox-Definition in /etc/dovecot/conf.d/15-mailboxes.conf innerhalb des Namespace-Blocks:

namespace inbox {
  # ... bestehende Mailboxen ...

  mailbox Junk {
    special_use = \Junk
    auto = subscribe
  }
}

Das special_use = \Junk teilt IMAP-Clients per RFC 6154 mit, dass dieser Ordner der Spam-Ordner ist. Sie zeigen ihn dann mit dem passenden Icon an und bieten oft direkt einen „Als Spam markieren"-Button.

Schritt 5: Sieve-Skript kompilieren und Dovecot neustarten

Pigeonhole kompiliert Sieve-Skripte zur Laufzeit selbst, aber bei einem globalen sieve_before-Skript empfiehlt sich das Vorabkompilieren, schon weil der LMTP-Prozess sonst nicht überall Schreibrechte auf /etc/dovecot/sieve/ hat:

sievec /etc/dovecot/sieve/spam-to-junk.sieve
systemctl reload dovecot

Anschließend lässt sich eine eingehende Test-Mail mit manuell gesetztem Header prüfen. Alternativ wartet man die nächste echte Spam-Mail ab und beobachtet das LMTP-Log:

tail -f /var/log/mail.log | grep -i sieve

Häufige Stolpersteine

1. Gar keine X-Spam-*-Header in der Mail. Dann arbeitet SpamAssassin/Amavis nicht oder wurde nicht so konfiguriert, dass es Header einfügt. In iredmail- und Froxlor-Setups ist das ein bekannter Fall: SpamAssassin läuft, aber Amavis fügt den Header schlicht nicht ein, wenn das $sa_tag_level_deflt nicht herabgesetzt ist.

2. X-Spam-Level da, aber kein X-Spam-Flag. Sehr häufig bei SpamAssassin ohne Amavis-Header-Action. X-Spam-Level wird immer gesetzt, das Flag nur, wenn add_header spam Flag _YESNO_ in der SpamAssassin-Konfiguration explizit aktiv ist. Lösung: Variante B aus Schritt 2 nehmen, dann läuft der Filter sofort.

3. „YES" matcht auch BAYES_00. Ein :contains auf den gesamten Header-Wert kann unbeabsichtigt zuschlagen, weil im X-Spam-Status-Header Teststring-Namen wie BAYES_00 oder UNPARSEABLE_RELAY stehen können. Deshalb konsequent auf X-Spam-Flag oder X-Spam-Level filtern, nicht auf X-Spam-Status.

4. Sieve läuft nur bei LMTP/LDA. Wer Mails per Procmail oder direkter Postfix-Auslieferung zustellt, umgeht den Filter komplett. Erst wenn virtual_transport = lmtp:unix:private/dovecot-lmtp in der main.cf steht (und der entsprechende Service in 10-master.conf konfiguriert ist), greift Sieve überhaupt.

5. Sieve-Verzeichnis im Maildir. In Froxlor-Setups landet die Mailbox üblicherweise unter /var/customers/mail/<domain>/<user>/. Sieve-Skripte sollten parallel dazu liegen, nicht innerhalb, sonst gibt es Fehler wie „Active sieve script file is no symlink nor a regular file."

6. Spam-Reporting nicht vergessen. Damit SpamAssassin sich verbessert, sollten Mails, die User aus dem Posteingang in den Junk-Ordner verschieben (und umgekehrt), an sa-learn übergeben werden. Pigeonhole liefert dafür das imapsieve-Modul, das eine eigene Folgegeschichte ist.

Pro User statt global: die Endkunden-Variante

Wer den Filter nicht für alle aufzwingen, sondern Endkunden die Wahl lassen will, lässt das globale Skript weg und liefert die Regel in Roundcube über das Managesieve-Plugin aus. Der Inhalt bleibt identisch, nur abgelegt wird er pro User in ~/.dovecot.sieve (bzw. dem in sieve_dir konfigurierten Pfad).

Im Froxlor-Forum hat sich für die Pfade unter Debian die Variante sieve_dir = /var/customers/mail/sieve/%d/%u als pragmatisch erwiesen. Sie hält Skripte sauber vom Maildir getrennt und macht das Backup einfacher.

Fazit

Das Verschieben von SpamAssassin-markierten Mails in einen Spam-Ordner ist in Froxlor-Setups keine Froxlor-Funktion, sondern eine Dovecot-Aufgabe. Drei Bausteine reichen aus, damit das Postfach für alle Mail-Clients sauber aussieht: ein aktives Amavis/SpamAssassin, ein globales Sieve-Skript via sieve_before und eine Junk-Mailbox mit special_use = \Junk. Wer ManageSieve dazu freischaltet, gibt seinen Kunden zusätzlich die Möglichkeit, ohne SSH-Zugang eigene Regeln zu pflegen.

Quellen

Mail-Server mit Froxlor sauber aufgesetzt?

Wenn Spam-Filter, Sieve oder Mail-Auslieferung nicht so wollen wie sie sollen: Ich unterstütze bei Konfiguration, Hardening und Migration von Froxlor-basierten Setups.

Anfrage senden