/ maildrop

Was man mit maildrop alles anstellen kann...

Seit knapp zwei Monaten bin ich jetzt bei Uberspace. Zeit, meinen Mailfilter aufzusetzen.

Wenn man IMAP nutzt, bietet es sich ja geradezu an, seine Mails in Ordner vorzusortieren. So kann man hinterher die ganzen Newsletter, die abzubestellen man zu faul ist, auf einen Schlag löschen. :-)

Für meine Experimente war die maildrop Dokumentation von Uberspace zusammen mit der man page für die maildrop Filter-Syntax ein guter Ausgangspunkt.


Mein Grundgerüst

Zunächst einmal habe ich das Grundgerüst ein wenig überarbeitet. Am Anfang sollte auf jeden Fall die Variable $MAILDIR gesetzt werden. Wir sind ja schließlich paranoid und wollen auf jeden Fall einen gültigen Wert haben, auch wenn dumpvuser aus irgendwelchen Gründen kein Verzeichnis zurückliefert:

# set default Maildir
MAILDIR="$HOME/Maildir"

# check if we're called from a .qmail-EXT  instead of .qmail
import EXT
if ( $EXT )
{
  # does a vmailmgr user named $EXT exist?
  # if yes, deliver mail to his Maildir instead
  CHECKMAILDIR = `dumpvuser $EXT | grep '^Directory' | awk '{ print $2 }'`
  if ( $CHECKMAILDIR )
  {
    MAILDIR=$CHECKMAILDIR
  }
}

Damit ist $MAILDIR festgelegt und zeigt auf das eigentliche Postfach. Ankommende Mails sollen nach $DESTDIR ausgeliefert werden. So können wir in aufeinanderfolgenden Filterregeln immer wieder auf $MAILDIR zurückgreifen. Daher ist vor allen weiteren Filterregeln folgende Zuweisung als Defaultwert notwendig:

# Set default destination Maildir
DESTDIR="$MAILDIR"

Danach kommen wie gesagt die Filterregeln. Und weil wir zu faul sind, alle Unterordner von Hand anzulegen, lassen wir das bei Bedarf auch den Computer machen, kurz bevor die Mail ausgeliefert wird:

# Hierarchically create DESTDIR if it does not already exist,
# and deliver mail

HIER="$DESTDIR"
CONTINUE=1
while ( $CONTINUE && $HIER =~ /^(.*\/(\.[^.]+)*)(\.[^.]+)$/ )
{
  if ( $MATCH3 ne ".INBOX" )
  {
    `test -d "$MATCH"`
    if ( $RETURNCODE == 1 )
    {
      `maildirmake "$MATCH"`
    }
    else
    {
      CONTINUE=0
    }
  }
  HIER=$MATCH1
}
to "$DESTDIR"

Das Skript hangelt sich im Verzeichnisbaum so lange nach oben, bis es ein bereits angelegtes Mailverzeichnis findet. Außerdem spart es den virtuellen Ordner .INBOX aus: Der muss zwar in der IMAP-Hierarchie angegeben werden, wenn ein Ordner unterhalb des Posteingangsordners liegen soll, aber die Mails an sich liegen ja in $MAILDIR.


Meine Filterregeln

Grundprinzip

Eigentlich muss eine Filterregel jetzt "nur" noch $DESTDIR in Abhängigkeit von irgendwelchen Eigenschaften der auszuliefernden Mail setzen:

DESTDIR="$MAILDIR/.INBOX.<Unterordner>"

Und da wir $MAILDIR nicht überschrieben haben, können wir es weiter unten wiederverwenden, wenn wir festgestellt haben, dass die Mail nicht nach .INBOX.<Unterordner> sondern anderswohin gehört:

DESTDIR="$MAILDIR/.INBOX.<Unterordner>.<Unter-Unterordner>"

So weit, so gut. Und wie filtern wir jetzt eigentlich? Momentan komme ich mit vier Typen von Filterregeln aus, nämlich: Local Part, Mailingliste, Empfänger und Absender.

Local Part

Falls mehrere Adressen per .qmail-... im selben Postfach landen, kann man ein Feature von qmail ausnutzen, das eigentlich Mail-Loops verhindern soll: den Delivered-To: Header:

# Filter by incoming local part
if ( /^Delivered-To:.*-adresse1@.*$/:h )
{
  DESTDIR="$MAILDIR/.INBOX.Adresse1"
}
elsif ( /^Delivered-To:.*-adresse2@.*$/:h )
{
  DESTDIR="$MAILDIR/.INBOX.Adresse2"
}
elsif ( /^Delivered-To:.*-adresse3@.*$/:h )
{
  DESTDIR="$MAILDIR/.INBOX.Unterordner.Adresse3"
}

Dabei ist zu beachten, dass vor der eigentlichen Adresse auf jeden Fall der Uberspace-Benutzername und ggf. noch der Namensraum stehen. Ein vollständiger Delivered-To: Header sieht auf Uberspace im Allgemeinen so aus:

Delivered-To: <User>-<Namespace>-<Address>@<domain>.<tld>

Richtig interessant wird das, wenn mehrere Umleitungen per .qmail-... ins Spiel kommen. Grundsätzlich fügt qmail nämlich für jede Umleitung eine Delivered-To: Zeile in den Header ein. Normalerweise sollte aber auch in so einem Fall ein einziger Patternvergleich reichen.

Mailingliste

Wenn ein Mailinglisten-Administrator so nett war, und seine Mailingliste mit einer List-ID: gemäß RFC 2919 ausgestattet hat, kann man solche Mails zum Beispiel folgendermaßen filtern:

# Filter by originating mailing list
if ( /^List-ID:.*<(.*)>/:h )
{
  LIST=tolower($MATCH1)
  if ( $LIST =~ /liste\.erstedomain\.tld/ )
  {
    DESTDIR="$MAILDIR/.INBOX.ErsteDomain"
  }
  elsif ( $LIST =~ /(.*)\.zweitedomain\.tld/ )
  {
    DESTDIR="$MAILDIR/.INBOX.MehrereListen"
    LIST=tolower($MATCH1)
    if ( $LIST =~ /erste-liste/ )
    {
      DESTDIR="$MAILDIR/.INBOX.MehrereListen.EineListe"
    }
    elsif ( $LIST =~ /zweite-liste/ )
    {
      DESTDIR="$MAILDIR/.INBOX.MehrereListen.AndereListe"
    }
  }
  elsif ( $LIST =~ /(.*)\.drittedomain\.tld/ )
  {
      DESTDIR="$MAILDIR/.INBOX.ListenVonDritterDomain"
  }
}

Empfänger

Manchmal gibt es aber auch nicht ganz so nette Listen-Admins. Dann fehlt der List-ID: Header. In dem Fall kann man sich aber möglicherweise den Umstand zu Nutze machen, dass die Mailingliste als Empfänger einer Mail eingetragen ist. maildrop stellt hier sogar die Funktion hasaddr() zur Verfügung, die einem das Parsen eines Headers nach RFC 822 abnimmt:

# Filter by destination
if ( hasaddr("liste1@listen-ohne-id.tld") || \
     hasaddr("liste2@listen-ohne-id.tld") )
{
  DESTDIR="$MAILDIR/.INBOX.ListenOhneID"
}

Aber Achtung: Auch Mails, die z.B. per CC: an die Liste gingen, werden auf diese Weise wegsortiert. hasaddr() sucht nämlich in den To:, Cc:, Resent-To: und Resent-Cc: Headern nach der angegebenen Adresse.

Absender

Man kann natürlich seine Mails auch ganz klassisch nach ihrem Absender sortieren:

# Filter by sender
if ( /^From: (.*)/:h )
{
  ADDR=getaddr($MATCH1)
  if ( $ADDR =~ /.*@example\.com/ )
  {
    DESTDIR="$MAILDIR/.INBOX.Example_com"
  }
  elsif ( $ADDR =~ /.*@example\.org/ )
  {
    DESTDIR="$MAILDIR/.INBOX.Example_org"
  }
}