Vier Emailfilter mit procmail
Hallo Liste, um der täglichen Emailflut von verschiedenen Mailinglisten einigermaßen Herr zu werden, habe ich mir einige procmail-Regeln und Skripte geschrieben. Ich poste ich sie hier mal und würde mich über (fast) jeden Kommentar dazu freuen. Vielleicht nützt es ja dem einen oder anderen. Gleich vorweg: ich verwende fetchmail, procmail und mutt -- mit Sicherheit dürfte einiges auch für KMail und andere MUAs verwendbar sein, die ich aber nicht im Detail kenne. In meinem (SuSE 7.2-)System setze ich nacheinander die folgenden vier Filter ein: (1) Threadfilter (2) Domainfilter (3) Absenderfilter (4) Duplikatfilter Danach werden Mailinglisten aussortiert und mit dem Spambouncer www.spambouncer.org der Müll weggeräumt. (Großer `Sperrmüll' wird gar nicht erst abgeholt: vor dem fetchmail-Aufruf starte ich den mailfilter (mailfilter.sourceforge.net).) Beide Programme kann ich nur empfehlen. Soweit die grobe Beschreibung, nun zu den Details: 1. Threadfilter --------------- Das Ziel besteht im Ausfiltern von zukünftigen Beiträgen eines Diskussionsfadens (Threads), den man aufgrund der bereits eingetroffenen Beiträge als uninteressant bzw. unerwünscht eingestuft hat. Eine Filtrierung basierend auf den Betreffzeilen scheidet aus, weil sie nicht treffsicher genug ist. Selbst wenn man annimmt, dass unterschiedliche Threads mit identischen Betreffzeilen selten sind, kann es vorkommen dass einzelne Subthreads nicht korrekt ausgefiltert werden. Dies geschieht insbesondere dann, wenn die Betreffzeilen Umlaute oder andere Sonderzeichen enthalten -- die Filtrierung scheitert nämlich an dem für die Kodierung dieser Zeichen zuständigen `=?iso-8859-1?q?=...'! Die `Re:'-`Fwd:'-Problematik macht eine Betreffzeilen-Filtrierung nur noch zusätzlich fehleranfällig. Exakter lässt sich mit Hilfe der Message-IDs filtern. Verwendet werden dazu die Kopfzeilen `Message-ID:', `In-Reply-To:' und `References:'. Ich fasse hier mal kurz RFC 2822, Abschnitt 3.6.4 zusammen: Jede Email soll die Kopfzeile `Message-ID:' und -- falls es sich um eine Antwort handelt -- zusätzlich die Kopfzeilen `In-Reply-To:' und `References:' besitzen. Dabei gilt: - `Message-ID:' enthält genau eine ID. - In einer Antwort enthält `In-Reply-To:' die Message-ID jener Eltern-Email, auf die sich die Antwort bezieht. - `References:' enthält das gleichnamige Feld der Eltern-Email, ergänzt um deren Message-ID. Mit den IDs in 'References:' kann man also den Thread rekonstruieren. (Nähere Details im RFC.) Werden also von allen Beiträgen eines uninteressanten bzw. unerwünschten Threads die `Message-ID:'s gespeichert, dann können später ankommende Beiträge aufgrund deren Angaben in `References:' und `In-Reply-To:' zuverlässig ausgefiltert werden. In mutt erledigt das Speichern der Message-IDs folgendes Makro: ---------- in $HOME/.muttrc ---------- macro index <esc>Ft "<pipe-message>threadfilter.sh --add<enter>" \ "Filtere Diskussionsfaden (Thread) aus" -------------------------------------- (Das Skript threadfilter.sh berücksichtigt jede Einstellung von 'pipe-split'. D.h. es ist irrelevant, ob die ausgewählten Emails als Ganzes oder jede einzeln durch die Pipe geschickt werden.) Für KMail gibt es sicher auch eine entsprechende Möglichkeit, Emails auszuwählen und durch eine Pipe zu schicken. KMail kenne ich nicht so genau. 2. Der Domainfilter ------------------- Alle Emails, die aus bestimmten Domains kommen, werden ausgefiltert. Der Filtrierung zugrunde liegen die Kopfzeilen `From:', `Sender:' und `X-Sender:'. Die unerwünschten Domains sind in einer normalen Textdatei aufgelistet und ein awk-Skript besorgt die Umwandlung dieser Liste in eine procmail-Regel. 3. Der Absenderfilter --------------------- Ganz analog werden alle Emails ausgefiltert, die von bestimmten Personen kommen. Der Filter verwendet die gleichen Kopfzeilen wie der Domainfilter. Die Absender(adressen!), die in einer Textdatei enthalten sind, werden ebenfalls durch ein awk-Skript umgewandelt. Natürlich gibt es analog zum Threadfilter auch hier mutt-Makros, die die Extraktion von Domains bzw. Absenderadressen aus Emails in die `schwarzen Listen' vornehmen und (nach manueller Nachbearbeitung) die procmail-Regeln automatisch erstellen. Das lass' ich jetzt aber mal als Übung ... :-) 4. Der Duplikatfilter --------------------- Dieser Filter erwischt mehrfache identische Emails, der Vergleich beruht wieder auf den Message-IDs. Den Kern kann man aus `man 5 procmailex' entnehmen; ich habe den Filter nur etwas umgebaut (s.u.). Die Skripte: ---------- in $HOME/.procmailrc ---------- INCLUDERC = ${HOME}/.procmail/filterrc ------------------------------------------ ---------- $HOME/.procmail/filterrc ---------- # $HOME/.procmail/filterrc # Filterquartett für Procmail # # Hinweis: filterrc verwendet folgende *externe* Variablen: # - MAILDIR : Mailverzeichnis # - PMDIR : Procmail-Verzeichnis # - FORMAIL : /usr/bin/formail # Threadfilter FILTERTHREADS = yes THREADFOLDER = ${MAILDIR}/muell MSGIDTHREADCACHE = ${PMDIR}/msgid.thread.cache THREADFILTERSCRIPT = ${PMDIR}/threadfilter.sh # Domainfilter FILTERDOMAINS = yes DOMAINFOLDER = ${MAILDIR}/muell # Absenderfilter FILTERSENDERS = yes SENDERFOLDER = ${MAILDIR}/muell # Duplikatfilter FILTERDUPS = yes DUPFOLDER = ${MAILDIR}/muell MSGIDDUPCACHE = ${PMDIR}/msgid.dup.cache MSGIDDUPSIZE = 64000 ## ## Ende des Konfigurationsabschnitts ## # Interne Variablen NEWLINE=" " # Threadfilter # ============ :0 * FILTERTHREADS ?? yes * ! THREADFOLDER ?? ^^^^ * ! FORMAIL ?? ^^^^ * ! THREADFILTERSCRIPT ?? ^^^^ * ! MSGIDTHREADCACHE ?? ^^^^ { :0 Whc : ${MSGIDTHREADCACHE}.lock | ${THREADFILTERSCRIPT} --filter :0 a { :0 c | ${THREADFILTERSCRIPT} --add :0 fhw | ${FORMAIL} -A "X-Filter: Thread" LOG = " Filter: Thread${NEWLINE}" :0 : ${THREADFOLDER} } # Kriterien für den Beginn uninteressanter bzw. unerwünschter # Threads; d.h. im Prinzip ein Killfile für Autoren *und* von # *ihnen* begonnene Threads. :0 * 1^0 ^(From|(X-|)Sender):[ \t]+.*\? * 1^0 ^(From|(X-|)Sender):[ \t]+.*\? * 1^0 ^(From|(X-|)Sender):[ \t]+.*\? { :0 c | ${THREADFILTERSCRIPT} --add :0 fhw | ${FORMAIL} -A "X-Filter: Thread" LOG = " Filter: Thread${NEWLINE}" :0 : ${THREADFOLDER} } } # Domainfilter # ============ # # Regeln nach folgendem Muster: # * 1^0 ^(From|(X-|)Sender):[ \t]+.*@\/.*domain.com\>? :0 * FILTERDOMAINS ?? yes * ! DOMAINFOLDER ?? ^^^^ * ! FORMAIL ?? ^^^^ { INCLUDERC = ${PMDIR}/domainfilter } # Absenderfilter # ============== # # Regeln nach folgendem Muster: # * 1^0 ^(From|(X-|)Sender):[ \t]+\/.*\? :0 * FILTERSENDERS ?? yes * ! SENDERFOLDER ?? ^^^^ * ! FORMAIL ?? ^^^^ { INCLUDERC = ${PMDIR}/senderfilter } # Duplikatfilter # ============== :0 * FILTERDUPS ?? yes * ! DUPFOLDER ?? ^^^^ * ! FORMAIL ?? ^^^^ * ! MSGIDDUPCACHE ?? ^^^^ * ! MSGIDDUPSIZE ?? ^^^^ { :0 Whc : ${MSGIDDUPCACHE}.lock | ${FORMAIL} -D ${MSGIDDUPSIZE} ${MSGIDDUPCACHE} :0 a { :0 fhw | ${FORMAIL} -A "X-Filter: Duplikat" LOG = " Filter: Duplikat${NEWLINE}" :0 : ${DUPFOLDER} } } # Ende. ---------------------------------------------- ---------- $HOME/.procmail/threadfilter.sh ---------- #! /bin/sh # $HOME/.procmail/threadfilter.sh # Thread-Filter für Procmail # Konfiguration MSGIDCACHE="$HOME/.procmail/msgid.thread.cache" MAXIDCOUNT=2500 # Skript PROGNAME="${0##*/}" test -f "$MSGIDCACHE" || touch "$MSGIDCACHE" test $# -eq 0 && { echo "$PROGNAME: Keine Parameter." exit 1 } case "$1" in -a|--add) lockfile "$MSGIDCACHE".lock && { formail -s formail -czx message-id: >>"$MSGIDCACHE" rm -f "$MSGIDCACHE".lock } ;; -f|--filter) # Exitcode: 0 : Mail gehört zum Thread --> ausfiltern # 1 : Mail in Ordnung TMPFILE="$(mktemp -q /tmp/threadfilter-XXXXXX)" test $? -ne 0 && exit 1 formail -cz -x in-reply-to: -x references: | \ sed 's/\ />\ /g;' | \ sed -n '/^<[^[:blank:]]\+@[^[:blank:]]\+>$/p' | \ sort -u >"$TMPFILE" grep -qis -f "$TMPFILE" "$MSGIDCACHE" EXITCODE=$? rm -f "$TMPFILE" test "$EXITCODE" -ne 0 && exit 1 ;; -u|--update) lockfile "$MSGIDCACHE".lock && { COUNT="$(cat "$MSGIDCACHE" | wc -l)" test $COUNT -gt $MAXIDCOUNT && { tail -n $MAXIDCOUNT "$MSGIDCACHE" >"$MSGIDCACHE".new mv "$MSGIDCACHE".new "$MSGIDCACHE" } rm -f "$MSGIDCACHE".lock } ;; *) echo "$PROGNAME: Parameter '$1' unbekannt." exit 1 ;; esac exit 0 # Ende. ----------------------------------------------------- Zur Erzeugung der Domain- und Absenderfilter dienen die beiden folgenden awk-Skripte. Sie sind zwar nicht schön, dafür aber wenigstens platzsparend. Das Updaten der Filter kann man ja z.B. mit einem Makefile oder einem Cronjob lösen. ---------- $HOME/.procmail/domainfilter.awk ---------- # $HOME/.procmail/domainfilter.awk # Erstellt den Domainfilter für Procmail BEGIN { print "# Domainfilter für Procmail"; printf "# Automatisch erstellt am %s\n\n:0\n", strftime(); } END { printf "{\n\t:0 fhw\n\t| ${FORMAIL} -A \"X-Filter: Domain\"\n\n"; printf "\tLOG = \" Filter: Domain (${MATCH})${NEWLINE}\"\n\n"; printf "\t:0 :\n\t${DOMAINFOLDER}\n}\n\n# Ende.\n"; } ! /^(\#.*|[[:blank:]]*)$/ { domain = gensub("\\\.", "\\\\.", "g"); printf "* 1^0 ^(From|(X-|)Sender):"; printf "[ \\t]+.*@\\/.*%s\\>?\n", domain; } # Ende. ------------------------------------------------------ und beinahe analog ---------- $HOME/.procmail/senderfilter.awk ---------- # $HOME/.procmail/senderfilter.awk # Erstellt den Absenderfilter für Procmail BEGIN { print "# Absenderfilter für Procmail"; printf "# Automatisch erstellt am %s\n\n:0\n", strftime(); } END { printf "{\n\t:0 fhw\n\t| ${FORMAIL} -A \"X-Filter: Absender\"\n\n"; printf "\tLOG=\" Filter: Absender (${MATCH})${NEWLINE}\"\n\n"; printf "\t:0 :\n\t${SENDERFOLDER}\n}\n\n# Ende.\n"; } ! /^(\#.*|[[:blank:]]*)$/ { sender = gensub("\\\.", "\\\\.", "g"); printf "* 1^0 ^(From(X-|)|Sender):"; printf "[ \\t]+\\/.*\\%s\\>?\n", sender; } # Ende. ------------------------------------------------------ Hoffentlich war das jetzt nicht zu heavy zum Frühstück ... :-) Mit besten Grüßen, Mathias -- Das Briefgeheimnis sowie das Post- und Fernmeldegeheimnis sind unverletzlich. Grundgesetz, Artikel 10, Abs. 1 Infos rund um Email-Verschlüsselung --> www.gnupp.de Details meines GnuPG/PGP-Schlüssels --> in den Kopfzeilen
participants (1)
-
Mathias Bauer