DMARC Milter und Postfix auf Debian 7 Wheezy

Über DMARC selbst habe ich ja schon etwas geschrieben. Selbst eine DMARC Policy zu veröffentlichen und sich um die eingehenden Reports zu kümmern ebenfalls. Heute soll es nun darum gehen, mit seinem Postfix auf einem Debian 7 die DMARC Policys der Absender zu Prüfen und zu berücksichtigen.

Es gibt bereits einen fertigen Milter für DMARC. Dieser findet sich leider nicht im aktuellen stable Zweig eines Debian 7.4 Wheezy. Nun könnte man auf einen Backport zurückgreifen.... So etwas kann einem aber schon mal das System etwas mehr "umdrehen" als man wirklich möchte. Nur für den openDMARC Milter ist das nicht nötig.

Die Installation ist sehr einfach und im folgenden beschrieben. Dabei gehe ich von einem funktionsfähigen Postfix, SPF und DKIM Filter aus. Für dieses Setup setzte ich auf postgrey sowie  postfix-policyd-spf-perl. Denn der openDMARC Milter "verlässt" sich darauf, das diese Programme bereits die richtigen Informationen in die Mailheader geschrieben haben.

ACHTUNG
Wer zum Beispiel AMaViS als smtpd_proxy_filter einsetzte, wird feststellen dass es nie zum openDMARC Milter kommt. Das liegt daran, dass die Milter an einer Stelle kommen, an welcher die E-Mail nie mehr vorbei kommt, wenn sie einmal durch einen smtpd_proxy_filter gelaufen sind. Hier kann man nun also abwarten bis DMARC nicht mehr als Milter arbeitet oder AMaViS als content_filter einsetzten tongue-out.

Wie immer ist es eine Beschreibung die den Einstieg erleichtern soll und keine "so ist es perfekt" Beschreibung. Nun also los...

Zuerst den openDMARC Milter direkt von der Projektseite herunterladen.

http://sourceforge.net/projects/opendmarc/

Für das spätere Kompilieren braucht man in jedem Fall die Entwicklerpakete für libmilter, daher installiere ich sie zuerst.

$ apt-get install libmilter-dev

Dann packen wir mal den Download aus..

$ tar xvzf opendmarc-1.2.0.tar.gz

Ins Verzeichnis wechseln und wie gewohnt kompilieren. Ich setzte dabei einfach mal das Prefix auf /usr, damit am Ende nicht alles unter /usr/local/ liegt. Bitte selbstdenkend nachmachen :-)

$ ./configure --prefix=/usr
$ make && make install

Der Milter sollte aus Sicherheitsgründen nicht als root laufen, daher lege ich dafür einen Benutzer opendmarc an und lege seine Heimat direkt ins spätere "Arbeitsverzeichnis". Als zweites werden die Rechte gesetzt, damit der Benutzer in seinem Zuhause arbeiten kann.

$ adduser --quiet --system --group --home /var/run/opendmarc opendmarc
$ chown opendmarc:opendmarc /var/run/opendmarc

Man sieht schon, ich versuche mich nahe am Debian Default zu orientieren. Das soll so weiter gehen um spätere Upgrades zu erleichtern und es muss nichts "Neue" erfunden werden.. Wie im testing Zweig und beim openDKIM Milter wird der zu verwendende Socket/Port... in der Konfigurationsdatei /etc/default/opendmarc gesetzt. Das spätere INIT-Script wird diese Konfigurationsdatei einfach beim Start des Milters, in die eigentliche Konfigurationsdatei, inkludieren. Also:

$ SOCKET="inet:8893@localhost"' > /etc/default/opendmarc

Das INIT-Script greife ich mir aus dem Testing Zweig des kommenden Debian 8...

$ vi /etc/init.d/opendmarc   
#! /bin/sh
#
### BEGIN INIT INFO
# Provides:             opendmarc
# Required-Start:       $syslog $time $local_fs $remote_fs $named $network
# Required-Stop:        $syslog $time $local_fs $remote_fs
# Default-Start:        2 3 4 5
# Default-Stop:         0 1 6
# Short-Description:    Start the OpenDMARC service
# Description:          Enable DMAR verification and reporting provided by OpenDMARC
### END INIT INFO

PATH=/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/sbin/opendmarc
NAME=opendmarc
DESC="OpenDMARC"
RUNDIR=/var/run/$NAME
USER=opendmarc
GROUP=opendmarc
SOCKET=local:$RUNDIR/$NAME.sock
PIDFILE=$RUNDIR/$NAME.pid

# How long to wait for the process to die on stop/restart
stoptimeout=5

test -x $DAEMON || exit 0

# Include LSB provided init functions
. /lib/lsb/init-functions

# Include opendkim defaults if available
if [ -f /etc/default/opendmarc ] ; then
        . /etc/default/opendmarc
fi

if [ -f /etc/opendmarc.conf ]; then
        CONFIG_SOCKET=`awk '$1 == "Socket" { print $2 }' /etc/opendmarc.conf`
fi

# This can be set via Socket option in config file, so it's not required
if [ -n "$SOCKET" -a -z "$CONFIG_SOCKET" ]; then
        DAEMON_OPTS="-p $SOCKET $DAEMON_OPTS"
fi

DAEMON_OPTS="-c /etc/opendmarc.conf -u $USER -P $PIDFILE $DAEMON_OPTS"

start() {
        # Create the run directory if it doesn't exist
        if [ ! -d "$RUNDIR" ]; then
                install -o "$USER" -g "$GROUP" -m 755 -d "$RUNDIR" || return 2
                [ -x /sbin/restorecon ] && /sbin/restorecon "$RUNDIR"
        fi
        # Clean up stale sockets
        if [ -f "$PIDFILE" ]; then
                pid=`cat $PIDFILE`
                if ! ps -C "$DAEMON" -s "$pid" >/dev/null; then
                        rm "$PIDFILE"
                        TMPSOCKET=""
                        if [ -n "$SOCKET" ]; then
                                TMPSOCKET="$SOCKET"
                        elif [ -n "$CONFIG_SOCKET" ]; then
                                TMPSOCKET="$CONFIG_SOCKET"
                        fi
                        if [ -n "$TMPSOCKET" ]; then
                                # UNIX sockets may be specified with or without the
                                # local: prefix; handle both
                                t=`echo $SOCKET | cut -d: -f1`
                                s=`echo $SOCKET | cut -d: -f2`
                                if [ -e "$s" -a -S "$s" ]; then
                                        if [ "$t" = "$s" -o "$t" = "local" ]; then
                                                rm "$s"
                                        fi
                                fi
                        fi
                fi
        fi
        start-stop-daemon --start --quiet --pidfile "$PIDFILE" --exec "$DAEMON" --test -- $DAEMON_OPTS || return 1
        start-stop-daemon --start --quiet --pidfile "$PIDFILE" --exec "$DAEMON" -- $DAEMON_OPTS || return 2
}

stop() {
        start-stop-daemon --stop --retry "$stoptimeout" --exec "$DAEMON"
        [ "$?" = 2 ] && return 2
}

reload() {
        start-stop-daemon --stop --signal USR1 --exec "$DAEMON"
}

status() {
    local pidfile daemon name status

    pidfile=
    OPTIND=1
    while getopts p: opt ; do
        case "$opt" in
            p)  pidfile="$OPTARG";;
        esac
    done
    shift $(($OPTIND - 1))

    if [ -n "$pidfile" ]; then
        pidfile="-p $pidfile"
    fi
    daemon="$1"
    name="$2"

    status="0"
    pidofproc $pidfile $daemon >/dev/null || status="$?"
    if [ "$status" = 0 ]; then
        log_success_msg "$name is running"
        return 0
    else
        log_failure_msg "$name is not running"
        return $status
    fi
}

case "$1" in
  start)
        echo -n "Starting $DESC: "
        start
        echo "$NAME."
        ;;
  stop)
        echo -n "Stopping $DESC: "
        stop
        echo "$NAME."
        ;;
  restart)
        echo -n "Restarting $DESC: "
        stop
        start
        echo "$NAME."
        ;;
  reload|force-reload)
        echo -n "Restarting $DESC: "
        reload
        echo "$NAME."
        ;;
  status)
        status $DAEMON $NAME
        ;;
  *)
        N=/etc/init.d/$NAME
        echo "Usage: $N {start|stop|restart|reload|force-reload|status}" >&2
        exit 1
        ;;
esac

exit 0 

Dieses nur noch an die richtige Stelle schieben, als ausführbar marken und bei jedem Booten mit starten lassen.

$ cp /pfad/opendmarc /etc/init.d/opendmarc
$ chmod +x /etc/init.d/opendmarc
$ update-rc.d opendmarc defaults

Das war schon fast das Schwerste... Jetzt kopiere ich die mitgelieferte Beispielkonfigurationsdatei für openDMARC nach /etc/.

$ cp /usr/share/doc/opendmarc/opendmarc.conf.sample /etc/

Diese passe ich nun wie folgt an:

$ vi /etc/opendmarc.conf
# This is a basic configuration that can easily be adapted to suit a standard
# installation. For more advanced options, see opendkim.conf(5) and/or
# /usr/share/doc/opendmarc/examples/opendmarc.conf.sample.

##  AuthservID (string)
##      defaults to MTA name
#
AuthservID smtp.kernel-error.de

##  BaseDirectory (string)
##      default (none)
##
##  If set, instructs the filter to change to the specified directory using
##  chdir(2) before doing anything else.  This means any files referenced
##  elsewhere in the configuration file can be specified relative to this
##  directory.  It's also useful for arranging that any crash dumps will be
##  saved to a specific location.
#
# BaseDirectory /var/run/opendmarc

##  ChangeRootDirectory (string)
##      default (none)
##
##  Requests that the operating system change the effective root directory of
##  the process to the one specified here prior to beginning execution.
##  chroot(2) requires superuser access.  A warning will be generated if
##  UserID is not also set.
# 
# ChangeRootDirectory /var/chroot/opendmarc

##  ForensicReports { true | false }
##      default "false"
##
# ForensicReports false

##  IgnoreHosts path
##      default (internal)
##
# IgnoreHosts /usr/local/etc/opendmarc/ignore.hosts

##  IgnoreMailFrom domain[,...]
##      default (none)
##
# IgnoreMailFrom example.com

##  PidFile path
##      default (none)
##
##  Specifies the path to a file that should be created at process start
##  containing the process ID.
##
#
PidFile /var/run/opendmarc.pid

##  RejectFailures { true | false }
##      default "false"
##
RejectFailures false

##  Socket socketspec
##      default (none)
##
##  Specifies the socket that should be established by the filter to receive
##  connections from sendmail(8) in order to provide service.  socketspec is
##  in one of two forms: local:path, which creates a UNIX domain socket at
##  the specified path, or inet:port[@host] or inet6:port[@host] which creates
##  a TCP socket on the specified port for the appropriate protocol family.
##  If the host is not given as either a hostname or an IP address, the
##  socket will be listening on all interfaces.  This option is mandatory
##  either in the configuration file or on the command line.  If an IP
##  address is used, it must be enclosed in square brackets.
#
#Socket inet:8893@localhost

##  SoftwareHeader { true | false }
##      default "false"
##
##  Causes the filter to add a "DMARC-Filter" header field indicating the
##  presence of this filter in the path of the message from injection to
##  delivery.  The product's name, version, and the job ID are included in
##  the header field's contents.
#
SoftwareHeader true

##  Syslog { true | false }
##      default "false"
##
##  Log via calls to syslog(3) any interesting activity.
#
Syslog true

##  SyslogFacility facility-name
##      default "mail"
##
##  Log via calls to syslog(3) using the named facility.  The facility names
##  are the same as the ones allowed in syslog.conf(5).
#
# SyslogFacility mail

##  TemporaryDirectory path
##      default /var/tmp
##
##  Specifies the directory in which temporary files should be written.
#
# TemporaryDirectory /var/tmp

##  TrustedAuthservIDs string
##      default HOSTNAME
##
##  Specifies one or more "authserv-id" values to trust as relaying true
##  upstream DKIM and SPF results.  The default is to use the name of
##  the MTA processing the message.  To specify a list, separate each entry
##  with a comma.  The key word "HOSTNAME" will be replaced by the name of
##  the host running the filter as reported by the gethostname(3) function.
#
# TrustedAuthservIDs d


##  UMask mask
##      default (none)
##
##  Requests a specific permissions mask to be used for file creation.  This
##  only really applies to creation of the socket when Socket specifies a
##  UNIX domain socket, and to the HistoryFile and PidFile (if any); temporary
##  files are normally created by the mkstemp(3) function that enforces a
##  specific file mode on creation regardless of the process umask.  See
##  umask(2) for more information.
#
UMask 0002

##  UserID user[:group]
##      default (none)
##
##  Attempts to become the specified userid before starting operations.
##  The process will be assigned all of the groups and primary group ID of
##  the named userid unless an alternate group is specified.
#
UserID opendmarc

CopyFailuresTo postmaster@kernel-error.de
ForensicReports true
ForensicReportsBcc postmaster@kernel-error.de
ForensicReportsSentBy postmaster@kernel-error.de
HistoryFile /var/run/opendmarc/opendmarc.dat
MilterDebug 0

Nach der Konfiguration sollte sich der Milter bereits starten lassen. Das mache ich nun mal und kontrolliere ob er läuft!

$ /etc/init.d/opendmarc start
$ netstat -ltnp | grep :8893
tcp        0      0 127.0.0.1:8893          0.0.0.0:*               LISTEN      15974/opendmarc 

Es muss nur noch Postfix darüber informiert werden, dass der Milter genutzt werden soll. Ich erweitere also einfach die folgenden Optionen. Dabei sollte der DMARC-Milter natürlich als letztes stehen:

$ vi /etc/postfix/main.cf
smtpd_milters = inet:127.0.0.1:54321, inet:localhost:8893
non_smtpd_milters = inet:127.0.0.1:54321, inet:localhost:8893

Ich sag es ja, die grobe Konfiguration ist erstmal extrem einfach. Nun geht es nach dem Testen an einen Feinschliff und eine saubere Konfiguration. Tja.... Noch Fragen? Dann fragen :-)