Sonntag, 11. Mai 2014

Email Archivierung - Teil 1

Eigentlich gehört Email Archivierung zur Pflicht jedes Unternehmens, sofern Umsatzsteuer-relevante Geschäftsanbahnungen auf diesem Wege stattfinden. Bisher habe ich immer für die relevanten Mailkonten ein entsprechendes Outlook Archiv-PST als Ausrede für Konformität benutzt.

Eine vernünftige Lösung wäre schon schön...



Nachdem alle Datenschutzbelange per Arbeitsvertrag geklärt sind, habe ich auf meinem Relay-Server begonnen, ein- und ausgehende Mails für ein Archiv auszuleiten.

In Postfix ist es relativ einfach, jede Mail per BCC an eine Archiv-Adresse zu senden, entweder per

always_bcc

oder per

recipient_bcc_maps

Wer diese Verfahren ausprobiert hat, wird schnell feststellen, dass der "Delivered-To:" Header immer die BCC-Adresse trägt - nicht gerade optimal, für eine Archivierung. An wen wurde die Mail denn nun ursprünglich zugestellt?

Dieses Dilemma zu umgehen, gibt es zwei Möglichkeiten:
  1. Benutzung von Sub-Adressierung
  2. Einfügen eines zusätzlichen Headers mit dem Originalempfänger, z.B. "X-Envelope-To:"

Sub-Addressing

Die Sub-Adressierung ist ein wenig bekanntes/genutzes Feature vieler Mailsysteme. Sie erlaubt es, zwischen Empfängername und Empfängerdomain nach einem Trennzeichen Zusatzinformationen  in einer Mailadresse unterzubringen (z.B. Zielordner). Das Trennzeichen ist meistens ein "+" (Pluszeichen).

"alex+TESTDATEN@example.org" ist eine Sub-Adresse von "alex@example.org" und was die MTAs angeht gleichbedeutend. Erst das Zielsystem (MDA/MUA) wird diese Information auswerten.

Diesen Sachverhalt mache ich mir zunutze, indem ich eine BCC-Map auf Basis einer Regular Expression anlege, die sämtliche Empfänger auf eine Archiv-Adresse mapt und als Unteradresse den Originalempfänger anführt:

# /etc/postfix/main.cf
#
recipient_delimiter = +
recipient_bcc_maps = pcre:/etc/postfix/bcc_map

# /etc/postfix/bcc_map
#
/^(.*)@(.*)$/      archiv+$1=$2@example.org

Da es logischer Weise nur ein "@"-Zeichen geben kann, ersetze ich das "@" aus dem ursprünglichen Empfänger durch ein "=" (Gleichheitszeichen).

Somit landet eine Mail an "alex@example.com" zusätzlich im Postfach von "archiv@example.org" mit der Unteradresse "alex=example.com" bzw. vollständig "archiv+alex=example.com@example.org".

Zusatz Header

Die ursprüngliche Empfängeradresse lässt sich auch erhalten, indem man über die smtpd_recipient_restrictions einen zusätlichen Header einfügt. Allerdings greift man so in die Integrität der Nachricht ein...


# /etc/postfix/main.cf
#
smtpd_recipient_restrictions =
   ...
   # Einfügen eines neuen Headers um die Envelope-Adresse für's Archiv zu erhalten
   check_recipient_access pcre:/etc/postfix/x-add-envelope-to
   ...


# /etc/postfix/x-add-envelope-to
#
# die Envelope Adresse wird in einen zusätzlichen Header geschrieben
/(.*)/  prepend X-Envelope-To: $1

Achtung: Bei BCC-Empfängern erscheint dieser ebenfalls im X-Envelope-To Header. D.h. die eigentliche Funktion des "Blind Carbon Copy" würde hier ausgehebelt. Die wenigsten Benutzer werden sich bei jeder Mail die Header anschauen, aber es wäre schon peinlich, wenn man auf diesem Wege herausfindet, dass das Best-Price Angebot ebenfalls an alle Konkurrenten gegangen ist!

Mails katalogisieren

So ausgerüstet kann es ans katalogisieren der Mails gehen. Dafür verwende ich dovecot mit dem sieve-Plugin. Das Mailverzeichnis liegt auf einer ausreichend großen Platte.

require
["reject","variables","date","relational","fileinto","mailbox","envelope","subaddress","regex","copy","include"];

set "name" "bounce";
set "dir" "in";

#
# aktuelles Datum
#
if currentdate :matches "year" "*" { set "year" "${1}"; }
if currentdate :matches "month" "*" { set "month" "${1}"; }
if currentdate :matches "day" "*" { set "day" "${1}"; }

#
# nach Möglichkeit das Datum aus der Mail
#
if date :matches "received" "year" "*" { set "year" "${1}"; }
if date :matches "received" "month" "*" { set "month" "${1}"; }
if date :matches "received" "day" "*" { set "day" "${1}"; }

if true {
        #
        # der Return-Path deutet auf eine ausgehende Mail hin,
        # sofern die Domain von uns stammt
        #
        if anyof(
                        header :contains "Return-Path" "@example.org",
                        header :contains "Return-Path" "@example.com"
                )
        {
                #
                # Name aus dem Return-Path extrahieren
                #
                if header :matches "Return-Path" "<*>" { set :lower "name" "${1}"; }
                if string :matches "${name}" "*@*" { set :lower "name" "${1}@${2}"; }

                set "dir" "out";
        }
        else
        {
                #
                # Bounces haben einen leeren Return-Pfad ...
                #
                if header :is "Return-Path" "<>"
                {
                        #
                        # Falls ein Delivered-To existiert lässt sich daraus
                        # Empfänger/Absender ermitteln
                        #
                        if header :matches "Delivered-To" "*+*=*@*" { set :lower "name" "${2}@${3}"; }
                        if envelope :matches :detail "to" "*" { set :lower "name" "${1}"; }
                        if string :matches "${name}" "*=*" { set :lower "name" "${1}@${2}"; }

                        if anyof(
                                string :contains "${name}" "@example.org",
                                string :contains "${name}" "@example.com"
                                )
                        {
                                set "dir" "in";
                        }
                        else
                        {

                                if header :matches "From" "*" { set :lower "name" "${1}"; }
                                if string :matches "${name}" "*<*>*" { set :lower "name" "${2}"; }
                                if string :matches "${name}" " *@*" { set :lower "name" "${1}@${2}"; }

                                set "dir" "out";
                        }
                }
                else
                {
                        #
                        # im schlimmsten Fall muss der "To:" Header herhalten ...
                        #
                        if header :matches "To" "*" { set :lower "name" "${1}"; }
                        if string :matches "${name}" "*<*>*" { set :lower "name" "${2}"; }
                        if string :matches "${name}" " *@*" { set :lower "name" "${1}@${2}"; }

                        #
                        # ... es sei denn der originale Empfänger wurde per Sub-Addressing
                        # (archiv+name=domain@example.org) erhalten
                        # in diesem Fall muss dass "="-Zeichen wieder durch "@" ersetzt
                        # werden
                        #
                        if header :matches "Delivered-To" "*+*=*@*" { set :lower "name" "${2}@${3}"; }
                        if envelope :matches :detail "to" "*" { set :lower "name" "${1}"; }
                        if string :matches "${name}" "*=*" { set :lower "name" "${1}@${2}"; }
                }
        }

        #
        # Verschieben der Mail
        #
        fileinto :create "user-archive/${name}/${dir}/${year}/${month}/${day}";

        stop;
}

Da dovecot standardmäßig einen Punkt (".") als Ordner-Trenner verwendet, musste ich noch folgende Anpassung vornehmen:

# /etc/dovecot/conf.d/10-mail.conf
#
mail_location = maildir:/home/vmail/%u:LAYOUT=fs

Diese Änderung sollte man nur auf dem Archivserver vornehmen, auf dem eigentlich keine weiteren Benutzerkonten existieren sollten!!!

Insgesamt ist es ratsam, die BCC-Adresse der Archivmail auf eine nicht geroutete Adresse (z.B. archiv@server.local) zu legen und dafür einen gesonderten Transport anzulegen.

Zwischenfazit


Nun liegen die Mails fein säuberlich sortiert nach ein- und ausgehend sowie Jahr, Monat und Tag im Dateisystem und können z.B. mit einem Installierten Roundcube Webmailer als Archiv-Benutzer angeschaut und durchsucht werden.

Allerdings ist die Unveränderbarkeit der Mails nicht sichergestellt - dafür bedarf es einer Art Verschlüsselung oder Signatur.
Ohne eine Art von Datenbank wird eine effiziente Suche nicht möglich sein.
Es kann nicht nachvollzogen werden, wer sich als Archiv-Benutzer welche Mail angeschaut hat.
Eine Einbindung von Benutzern ist nicht vorhanden - obwohl der Mechanismus auch dazu genutzt werden könnte, die Mails in einen Archiv-Ordner des Benutzers zu verlinken, sofern die Benutzer IMAP nutzen.