amavis MILTER HOWTO

Abstract

Postfix beherrscht seit langem schon das MILTER-Protokoll. Das Protokoll ist mächtiger und feiner zu steuern als der meist gepriesene Ansatz über den smtpd_proxy_filter. In diesem kleinen HOWTO möchte ich zeigen, wie das Content Filter Framework amavisd-new als MILTER in die Mailverarbeitung eingebunden werden kann.

amavisd-new MILTER HOWTO

Postfix beherrscht seit langem schon das MILTER-Protokoll. Das Protokoll ist mächtiger und feiner zu steuern als der meist gepriesene Ansatz über den smtpd_proxy_filter, über den gebetsmühlenartig behauptet wird, man müsse ihm folgen, weil man nur so E-Mail nach deutschem Recht gesetzeskonform verarbeiten könne.

Das kann ein MILTER auch – und er kann mehr! MILTER verarbeiten – per Spezifikation der Schnittstelle – Nachrichten pre-Queue (vor der Annahme) und in Memory. Damit sind sie nicht nur gesetzeskonform, sondern auch schnell.

In diesem kleinen HOWTO möchte ich zeigen, wie das Content Filter Framework amavisd-new als MILTER in die Mailverarbeitung eingebunden werden kann. Beginnen wir mit Postfix: Es soll Nachrichten an einen MILTER (amavis) abgeben.

Important

Eine wichtige Information vorweg: Wer mit MILTER Content filtern will, muss auf den Einsatz von smtpd_proxy_filter verzichten. MILTER und smtpd_proxy_filter vertragen sich nicht gut miteinander. Werden beide in derselben Postfix-Instanz eingesetzt, "sieht" der MILTER nur die SMTP-Kommandos, nicht aber die Header der Nachricht oder ihren Body. Das ist prinzipbedingt und wird sich nicht ändern.

MILTER in Postfix einbinden

Das MILTER-Protokoll stammt von Sendmail ab. Sendmail ist ein monolithisches Binary. Es sieht alles, was "in sich selbst" passiert. Postfix ist anders aufgebaut. Es setzt auf eine "compartmentalized architecture". Die einzelnen Teile wissen aus Sicherheitsgründen nicht voneinander.

Diese Trennung ist Ursache dafür, dass Postfix über zwei Parameter verfügt, über die MILTER eingebunden werden können: Ein Parameter listet MILTER auf, die für E-Mails zuständig sind, die über das (offline) sendmail-Kommando in das System gelangt sind. Der andere Parameter ist für alle E-Mails zuständig, die (von außen) über den smtpd-Server eingehen.

In diesem Beispiel konzentriere ich mich auf extern eingehende Nachrichten. Ich gehe davon aus, dass wir einen SMTP-Server bauen, der im MTA-zu-MTA-Verkehr steht und eingehende Nachrichten auf Port 25 mit amavis filtern soll.

Note

Falls der Server auch noch Submission machen soll, können Sie in der master.cf beim submission-Service von den folgenden globalen Settings abweichen.

Die Konfigurationsweisungen, E-Mails an den Content Filter amavis zu senden, sind in der main.cf schnell gemacht:

smtpd_milters = inet:localhost:10024

Note

Ich könnte statt einer TCP-Verbindung auch einen UNIX Domain Socket angeben. Der ist aber chroot-Beschränkungen und user/group Permissions unterworfen, und das funktioniert nicht auf allen Distributionen ohne größere Anstrengungen oder gar nicht. Also TCP-Verbindung, denn die ist unproblematisch.

Damit weiß Postfix, wo es amavis kontaktieren soll. Wir müssen amavis in die Verarbeitung einbinden. Dazu brauchen wir amavisd-milter.

amavisd-milter

amavisd-new beherrscht die Protokolle (E)SMTP, LMTP und AM.PDP (Amavis Policy Delegation Protocol) – das MILTER-Protokoll beherrscht amavis nicht. Trotzdem ist es möglich, amavis in eine MILTER-basierte Mailverarbeitung einzubinden.

Vermittler zwischen der MILTER-Welt und amavis ist das Hilfsprogramm amavisd-milter. Es übersetzt – in beiden Kommunikationsrichtungen – Kommandos und Informationen der MILTER-Schnittstelle in das amavis-eigene AM.PDP-Protokoll.

amavisd-milter kann in der Regel über das Repository der Linux-Distribution installiert werden. Wer es von Hand bauen muss oder möchte, findet die Quellen auf <http://amavisd-milter.sourceforge.net/>. Name und Speicherort der Datei, über die das Programm konfiguriert wird, variieren je nach Distribution.

Ein konfiguriertes amavisd-milter bietet dem MTA eine MILTER-Schnittstelle, auf die es connecten kann, und eine für die Kommunikation mit amavis. Zusätzliche Optionen legen ein Limit für Verarbeitungsanfragen fest, übergeben einen milter_macro_name, über den gezielt policy_banks in amavis angesprungen werden können, und (je nach Distribution und Patchlevel) user/group, mit denen UNIX Domain Sockets etabliert werden sollen.

/usr/sbin/amavisd-milter -s inet:10024@127.0.0.1 \
-p /var/amavis/amavisd-milter.pid -S /var/amavis/amavisd.sock \
-w /var/amavis/tmp -m 20

Hier läuft ein amavisd-milter, das auf 127.0.0.1:10024 auf eingehende Verbindungen von Postfix wartet und diese über den Socket /var/amavis/amavisd.sock an amavis weitergibt. Die Anzahl maximaler Verbindungen wurde auf 20 beschränkt.

Damit steht die Brücke zwischen Postfix und amavis, und wir können amavis konfigurieren.

amavisd-new

Im folgenden Code-Beispiel definieren wir mit @listen_sockets drei Listener in amavisd-new. Sie stehen für eingehende Verbindungen zur Verfügung und dienen z.B. dem Filtern von E-Mail oder der Freigabe von Nachrichten aus der Quarantäne.

#############################################################################
## SERVER
#

# Wie viele Instanzen soll amavis starten?
$max_servers = 16;

# Auf welchen Sockets sollen die Instanzen auf eingehende Verbindungen
# lauschen?
@listen_sockets = (
    # Quarantine Release
    '[::1]:9998',
    # Post-Queue, Submission
    '[::1]:10024',
    # Pre-Queue, MTA zu MTA
    "$MYHOME/amavisd.sock"
    );

Eingehende Verbindungen müssen wir nun bestimmten Policies ($policy_banks) zuführen. Wir mappen sie über die folgenden Mechanismen den $policy_banks zu:

#############################################################################
## POLICY MAPPING
#


# Hier mappen wir eingehende Verbindungen Policy Banks zu. Eine eingehende
# Verbindung können wir anhand verschiedener Eigenschaften erkennen:
#
# - TCP/UNIX-Socket einer eingehenden Verbindung
# - IP-Adresse/IP-Range einer eingehenden Verbindung
# - DKIM-authentifizierter Sender/Senderdomain


# In welche Policy routen wir die @listen_sockets?
$interface_policy{'SOCK'}   = 'AM.PDP-SOCK';
$interface_policy{'9998'}   = 'AM.PDP-INET';
$interface_policy{'10024'}  = 'SUBMISSION';


# In welche Policy routen wir bestimmte IPs/Netzwerke?
@client_ipaddr_policy = (
    [qw( 0.0.0.0/8 127.0.0.1/32 [::] [::1] )] => 'LOCALHOST',
    [qw( !172.16.1.0/24 172.16.0.0/12 192.168.0.0/16 )] => 'PRIVATENETS',
    [qw( 192.0.2.0/25 192.0.2.129 192.0.2.130 )] => 'PARTNER',
    \@mynetworks => 'MYNETS'
);


# In welche Policy routen wir DKIM-verifizierte Sender/Senderdomains?
@author_to_policy_bank_maps = ( {
    '.paypal.de'                => 'WHITELIST',
    'amazon.de'                 => 'WHITELIST',
} );

Jetzt, da amavis weiß, wohin es die Signale routen soll, müssen wir ihm noch beibringen, wie es diese verarbeiten soll. Für das AM.PDP habe ich diese beiden Policies vorbereitet:

#############################################################################
## POLICY BANKS: AM.PDP
#

# Quarantäne Release Policy
$policy_bank{'AM.PDP-INET'} = {
    protocol => 'AM.PDP',
    inet_acl => [qw( 127.0.0.1 )],
    auth_required_release => 0,
};

# MILTER Policy für MTA zu MTA Traffic
$policy_bank{'AM.PDP-SOCK'} = {
    protocol => 'AM.PDP',
    notify_method => 'smtp:127.0.0.1:10025',
    auth_required_release => 0,
};

Hervorzuheben in der MILTER-Policy Bank ist die dedizierte $notify_method. Über das MILTER-Protokoll gelangen Verarbeitungsanfragen von Postfix zu amavis. Sie können aber nur von Postfix und nicht von amavis initiiert werden. Das ist ein Problem, denn Benachrichtigungen, die amavis generiert, gelangen so nicht in den Mailtransport.

Die Antwort auf das Problem ist die dedizierte $notify_method. Sie weist amavis an, Benachrichtigungen an einen lokalen Postfix smtpd-Listener zu routen. Der nimmt sie dann an und transportiert sie näher zu ihrem Ziel.

Patrick Koetter, 31. July 2015