Benutzer-Werkzeuge

Webseiten-Werkzeuge


tachtler:postfix_centos_7_-_opendmarc_anbinden_opendmarc-milter

Postfix CentOS 7 - OpenDMARC anbinden (opendmarc-milter)

DMARC ist an sich kein eigenständiger Prozess in der e-Mail-Verarbeitung, vielmehr erweitert DMARC die Ergebnisprüfung der beiden Techniken SPF und OpenDKIM. DMARC ergänzt somit SPF und OpenDKIM, ohne die DMARC nicht funktionieren kann.

DMARC ergänzt SPF und OpenDKIM, indem festgelegt wird, was der empfangende e-Mail-Server mit einer e-Mail tun soll, welche die SPF oder OpenDKIM Prüfung nicht erfolgreich bestanden hat. Hierfür sind zwei Dinge notwendig:

  • Ein DNS TXT Record, der dem empfangenden e-Mail-Server sagt, was zu tun ist
  • OpenDMARC auf dem eigenem e-Mail-Server, damit dieser selbst die laut DMARC definierten Aktionen ausführen kann.

Ab hier werden zur Ausführung nachfolgender Befehle root-Rechte benötigt. Um der Benutzer root zu werden, melden Sie sich bitte als root-Benutzer am System an, oder wechseln mit nachfolgendem Befehl zum Benutzer root:

$ su -
Password:

Voraussetzungen

Die Installation und korrekt Konfiguration von nachfolgenden Diensten/Daemon bzw. MILTER ist erforderlich, da ohne diese OpenDMARC nicht funktionieren kann!

OpenDMARC führt seine Auswertungen nach den Einträgen im e-Mail-Header welche von SPF und OpenDKIM durch und trifft aufgrund dieser Einträge auch letztendlich die Entscheidung ob eine e-Mail angenommen oder abgelehnt wird.

Herunterladen

Nachfolgend sollen das Drittanbieter-Repository von EPEL, welches wie unter nachfolgendem internen Link dargestellt, eingebunden werden:

Installation

Nachfolgendes rpm-Paket ist zur Installation erforderlich:

  • opendmarc - ist im epel-Repository des Drittanbieters EPEL enthalten

Zusätzlich wird nachfolgendes rpm-Paket als Abhängigkeit zusätzlich installiert:

  • libopendmarc - ist im epel-Repository des Drittanbieters EPEL enthalten
  • perl-DBD-MySQL - ist im base-Repository von CentOS enthalten
  • perl-Switch - ist im base-Repository von CentOS enthalten

Die Installation von opendmarc, kann durch ausführen des nachfolgenden Befehls durchgeführt werden:

# yum install opendmarc
Loaded plugins: changelog, priorities
149 packages excluded due to repository priority protections
Resolving Dependencies
--> Running transaction check
---> Package opendmarc.x86_64 0:1.4.1-1.el7 will be installed
--> Processing Dependency: libopendmarc(x86-64) = 1.4.1-1.el7 for package: opendmarc-1.4.1-1.el7.x86_64
--> Processing Dependency: perl(Switch) for package: opendmarc-1.4.1-1.el7.x86_64
--> Processing Dependency: perl(DBD::mysql) for package: opendmarc-1.4.1-1.el7.x86_64
--> Processing Dependency: libopendmarc.so.2()(64bit) for package: opendmarc-1.4.1-1.el7.x86_64
--> Running transaction check
---> Package libopendmarc.x86_64 0:1.4.1-1.el7 will be installed
---> Package perl-DBD-MySQL.x86_64 0:4.023-5.el7 will be installed
---> Package perl-Switch.noarch 0:2.16-7.el7 will be installed
--> Finished Dependency Resolution

Changes in packages about to be updated:


Dependencies Resolved

================================================================================
 Package                Arch           Version               Repository    Size
================================================================================
Installing:
 opendmarc              x86_64         1.4.1-1.el7           epel         121 k
Installing for dependencies:
 libopendmarc           x86_64         1.4.1-1.el7           epel          27 k
 perl-DBD-MySQL         x86_64         4.023-5.el7           base         140 k
 perl-Switch            noarch         2.16-7.el7            base          22 k

Transaction Summary
================================================================================
Install  1 Package (+3 Dependent packages)

Total download size: 310 k
Installed size: 757 k
Is this ok [y/d/N]: y
Downloading packages:
(1/4): libopendmarc-1.4.1-1.el7.x86_64.rpm                 |  27 kB   00:00     
(2/4): opendmarc-1.4.1-1.el7.x86_64.rpm                    | 121 kB   00:00     
(3/4): perl-DBD-MySQL-4.023-5.el7.x86_64.rpm               | 140 kB   00:00     
(4/4): perl-Switch-2.16-7.el7.noarch.rpm                   |  22 kB   00:00     
--------------------------------------------------------------------------------
Total                                              712 kB/s | 310 kB  00:00     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : perl-Switch-2.16-7.el7.noarch                                1/4 
  Installing : libopendmarc-1.4.1-1.el7.x86_64                              2/4 
  Installing : perl-DBD-MySQL-4.023-5.el7.x86_64                            3/4 
  Installing : opendmarc-1.4.1-1.el7.x86_64                                 4/4 
  Verifying  : perl-DBD-MySQL-4.023-5.el7.x86_64                            1/4 
  Verifying  : opendmarc-1.4.1-1.el7.x86_64                                 2/4 
  Verifying  : libopendmarc-1.4.1-1.el7.x86_64                              3/4 
  Verifying  : perl-Switch-2.16-7.el7.noarch                                4/4 

Installed:
  opendmarc.x86_64 0:1.4.1-1.el7                                               

Dependency Installed:
  libopendmarc.x86_64 0:1.4.1-1.el7     perl-DBD-MySQL.x86_64 0:4.023-5.el7    
  perl-Switch.noarch 0:2.16-7.el7       

Complete!

Die Installation von opendmarc, kann durch ausführen des nachfolgenden Befehls durchgeführt werden:

# rpm -qil opendmarc
Name        : opendmarc
Version     : 1.4.1
Release     : 1.el7
Architecture: x86_64
Install Date: Mon 07 Jun 2021 03:08:31 PM CEST
Group       : Unspecified
Size        : 250395
License     : BSD and Sendmail
Signature   : RSA/SHA256, Sat 22 May 2021 08:49:51 PM CEST, Key ID 6a2faea2352c64e5
Source RPM  : opendmarc-1.4.1-1.el7.src.rpm
Build Date  : Sat 22 May 2021 08:44:46 PM CEST
Build Host  : buildhw-x86-06.iad2.fedoraproject.org
Relocations : (not relocatable)
Packager    : Fedora Project
Vendor      : Fedora Project
URL         : http://www.trusteddomain.org/opendmarc.html
Bug URL     : https://bugz.fedoraproject.org/opendmarc
Summary     : A Domain-based Message Authentication, Reporting & Conformance (DMARC) milter and library
Description :
OpenDMARC (Domain-based Message Authentication, Reporting & Conformance)
provides an open source library that implements the DMARC verification
service plus a milter-based filter application that can plug in to any
milter-aware MTA, including sendmail, Postfix, or any other MTA that supports
the milter protocol.

The DMARC sender authentication system is still a draft standard, working
towards RFC status.

The database schema required for some functions is provided in
/usr/share/opendmarc/db. The rddmarc tools are provided in
/usr/share/opendmarc/contrib/rddmarc.
/etc/opendmarc
/etc/opendmarc.conf
/etc/sysconfig/opendmarc
/etc/tmpfiles.d/opendmarc.conf
/run/opendmarc
/usr/lib/systemd/system/opendmarc.service
/usr/sbin/opendmarc
/usr/sbin/opendmarc-check
/usr/sbin/opendmarc-expire
/usr/sbin/opendmarc-import
/usr/sbin/opendmarc-importstats
/usr/sbin/opendmarc-params
/usr/sbin/opendmarc-reports
/usr/share/doc/opendmarc-1.4.1
/usr/share/doc/opendmarc-1.4.1/README
/usr/share/doc/opendmarc-1.4.1/RELEASE_NOTES
/usr/share/licenses/opendmarc-1.4.1
/usr/share/licenses/opendmarc-1.4.1/LICENSE
/usr/share/licenses/opendmarc-1.4.1/LICENSE.Sendmail
/usr/share/man/man5/opendmarc.conf.5.gz
/usr/share/man/man8/opendmarc-check.8.gz
/usr/share/man/man8/opendmarc-expire.8.gz
/usr/share/man/man8/opendmarc-import.8.gz
/usr/share/man/man8/opendmarc-importstats.8.gz
/usr/share/man/man8/opendmarc-params.8.gz
/usr/share/man/man8/opendmarc-reports.8.gz
/usr/share/man/man8/opendmarc.8.gz
/usr/share/opendmarc
/usr/share/opendmarc/contrib
/usr/share/opendmarc/contrib/rddmarc
/usr/share/opendmarc/contrib/rddmarc/README.rddmarc
/usr/share/opendmarc/contrib/rddmarc/dmarcfail.py
/usr/share/opendmarc/contrib/rddmarc/dmarcfail.pyc
/usr/share/opendmarc/contrib/rddmarc/dmarcfail.pyo
/usr/share/opendmarc/contrib/rddmarc/mkdmarc
/usr/share/opendmarc/contrib/rddmarc/mysql_ip6.c
/usr/share/opendmarc/contrib/rddmarc/rddmarc
/usr/share/opendmarc/db
/usr/share/opendmarc/db/README.schema
/usr/share/opendmarc/db/schema.mysql
/var/spool/opendmarc

iptables Regel

Damit der SPF auch über den Postfix - spf-milter erreichbar ist und nicht das Empfangen der IP-Paket vom Paketfilter iptables blockiert wird, muss nachfolgende Regel zum iptables-Regelwerk hinzugefügt werden.

Um die aktuellen iptables-Regeln erweitern zu können, sollten diese erst einmal aufgelistet werden, was mit nachfolgendem Befehl durchgeführt werden kann:

# iptables -L -nv --line-numbers
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination         
1        0     0 ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0           state RELATED,ESTABLISHED 
2        0     0 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0           
3        0     0 ACCEPT     all  --  lo     *       0.0.0.0/0            0.0.0.0/0           
4        0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0           state NEW tcp dpt:22  
5        0     0 REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0           reject-with icmp-host-prohibited 

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination         
1        0     0 REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0           reject-with icmp-host-prohibited 

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination

Nachfolgender Befehl, fügt folgende iptables-Regel dem iptables-Regelwerk nach der Position 4 hinzu, ohne das der Paketfilter angehalten werden muss:

  • -A INPUT -p tcp --dport 10013 -j ACCEPT

und hier der Befehl:

# iptables -I INPUT 5 -p tcp --dport 10013 -j ACCEPT

Ein erneute Abfrage des iptables-Regelwerts, sollte dann nachfolgend dargestellte Ausgabe ergeben, was mit folgendem Befehl durchgeführt werden kann:

# iptables -L -nv --line-numbers
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination         
1        0     0 ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0           state RELATED,ESTABLISHED 
2        0     0 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0           
3        0     0 ACCEPT     all  --  lo     *       0.0.0.0/0            0.0.0.0/0           
4        0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0           state NEW tcp dpt:22
5        0     0 ACCEPT     tcp  --  eth0   *       0.0.0.0/0            0.0.0.0/0           tcp dpt:10013 state NEW
6        0     0 REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0           reject-with icmp-host-prohibited 

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination         
1        0     0 REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0           reject-with icmp-host-prohibited 

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination

Die neue Zeile ist an Position 5 (INPUT) zu sehen, hier nachfolgend zur Verdeutlichung noch einmal dargestellt (nur relevanter Ausschnitt):

...
5        0     0 ACCEPT     tcp  --  eth0   *       0.0.0.0/0            0.0.0.0/0           tcp dpt:10013 state NEW
...

Um diese iptables-Regel dauerhaft, auch nach einem Neustart des Server, weiterhin im iptables-Regelwerk zu speichern, muss nachfolgend dargestellter Befehl abschließend noch ausgeführt werden:

# /usr/sbin/iptables-save > /etc/sysconfig/iptables 

Konfiguration: DNS

Damit bei ausgehenden e-Mails auch unsere DMARC-Übrprüfung erfolgreich ist, wir nachfolgender Eintrag im DNS benötigt, damit der Abfragende bestimmen kann, wie er mit Nachrichten von uns selbst umgehen soll, wenn die DMARC-Überprüfung seinerseits fehl schlägt.

Unter nachfolgendem externen Link, kann ein TXT-Record, welcher DMARC-Informationen enthalten soll, komfortabel erstellt werden:

Nach der Erstellung über oben genannten Assistenten, kann der TXT-RECORD zur DMARC-Überprüfung wie folgt aussehen:

v=DMARC1; p=none; rua=mailto:dmarc-aggregate@tachtler.net; ruf=mailto:dmarc-incorrect@tachtler.net; fo=0; adkim=r; aspf=r; pct=100; rf=afrf; ri=86400; sp=none
Parameter Wert Erklärung
v DMARC1 Identifiziert den Datensatz als DMARC-Record. Der Parameter muss als erster wert im DMARC-Record gesetzt sein. Fehlt dieser ist der gesamte DMARC-Record zu verwerfen!
p none Beschreibt, wie der Empfänger auf die Nachrichten des Domain-Inhabers reagieren soll, wenn die DMARC-Überprüfung fehl schlägt. Dies gilt auch für die Subdomains, sofern für diese mit dem Parameter sp keine separate Definition vorliegt. Mögliche Werte sind:

none : Der Domaininhaber hat keine Vorgaben zur Verabeitung/Zustellung der e-Mails gemacht.
quarantine : Der Domaininhaber wünscht, dass e-Mails, als verdächtig gewertet werden sollen. Abhängig vom Empfänger können diese als verdächtig markiert werden, mit weiteren/zusätzlichen SPAM Prüfungen zu belegen ist, oder in einen SPAM-Ordner verschoben werden soll.
reject : Der Domaininhaber wünscht, dass e-Mails abgewiesen, also nicht angenommen werden sollen. Dies sollte, wenn möglich, noch während des SMTP-Dialogs passieren.
rua mailto:dmarc-aggregate@tachtler.net Der Domaininhaber wünscht, per e-Mail mit aufbereiteten Statistikdaten der verarbeiteten e-Mails informiert zu werden. Ist dieser Parameter nicht gesetzt, braucht der Empfänger keine Statistikdaten aufzubereiten.
ruf mailto:dmarc-incorrect@tachtler.net Der Domaininhaber wünscht, per e-Mail mit detaillierten forensischen Statistikdaten der verarbeiteten e-Mails informiert zu werden. Ist dieser Parameter nicht gesetzt, braucht der Empfänger keine Statistikdaten aufzubereiten.
fo 0 Optionen zu den Fehlerreports, welche die gewünschte Detaillierung angeben.
adkim r Definiert, wie konservativ das Ergebnis der DKIM-Signaturüberprüfung bewertet werden soll. Mögliche Werte sind:

r=relaxed
s=strikt
aspf r Definiert, wie konservativ das Ergebnis der SPF-Üüberprüfung bewertet werden soll. Mögliche Werte sind:

r=relaxed
s=strikt
pct 100 Der Domaininhaber wünscht, dass der angegebene Prozentwert an Nachrichten von den DMARC-Überprüfungen benutzt werden soll. Der Wert beschreibt nicht das Verhältnis in den DMARC-Reports!
rf afrf Der Domaininhaber wünscht, dass die aufbereiteten forensischen Prüfberichte im Format AFRF oder IODEF zu erhalten, wenn sowohl die DKIM- wie auch der SPF-Überprüfung negativ ausfällt.
ri 86400 Der Domaininhaber wünscht, dass die aufbereiteten Statistik- und Forensikdaten spätestens alle 86400 Sekunden verschickt werden sollen (86400 Sekunden = 1 Tag)
sp none Beschreibt, wie der Empfänger die Nachrichten des Domain-Inhabers einer Subdomäne verwerten soll. Der Parameter beschreibt nur die abgefragte Subdomäne, nicht die Domäne an sich! Mögliche Werte sind:

none : Der Domaininhaber hat keine Vorgaben zur Verabeitung/Zustellung der e-Mails gemacht.
quarantine : Der Domaininhaber wünscht, dass e-Mails, als verdächtig gewertet werden sollen. Abhängig vom Empfänger können diese als verdächtig markiert werden, mit weiteren/zusätzlichen SPAM Prüfungen zu belegen ist, oder in einen SPAM-Ordner verschoben werden soll.
reject : Der Domaininhaber wünscht, dass e-Mails abgewiesen, also nicht angenommen werden sollen. Dies sollte, wenn möglich, noch während des SMTP-Dialogs passieren.

Dieser so erstelle Inhalt des TXT-RECORD zur DMARC-Überprüfung, muss nun im DNS veröffentlicht werden und zwar unter nachfolgendem Eintrag-Schema im DNS:

  • _dmarc.<DOMAIN>.<TLD>

Nachfolgend würde ein Eintrag für die Domäne tachtler.net, wie folgt aussehen:

# dig _dmarc.tachtler.net TX

; <<>> DiG 9.9.4-RedHat-9.9.4-18.el7_1.5 <<>> _dmarc.tachtler.net TXT
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1217
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;_dmarc.tachtler.net.		IN	TXT

;; ANSWER SECTION:
_dmarc.tachtler.net.	3591	IN	TXT	"v=DMARC1\; p=none\; rua=mailto:dmarc-aggregate@tachtler.net\; 
ruf=mailto:dmarc-incorrect@tachtler.net\; fo=0\; adkim=r\; aspf=r\; pct=100\; rf=afrf\; ri=86400\; sp=none"

;; Query time: 3 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Thu Oct 22 14:20:04 2015
;; MSG SIZE  rcvd: 208

:!: WICHTIG - Als absendende Domains für die DMARC-Reports an andere, sollte ein SUB-Domain verwendet werden, welche selbst keine

  • rua
  • ruf

Einträge besitzt, um so tägliche DMARC Ping-Pong Schleifen (loops) zu vermeiden.

v=DMARC1; p=none; fo=0; adkim=r; aspf=r; pct=100; rf=afrf; ri=86400; sp=none

Dieser so erstelle Inhalt des TXT-RECORD zur DMARC-Überprüfung, muss nun im DNS veröffentlicht werden und zwar unter nachfolgendem Eintrag-Schema im DNS:

  • _dmarc.<SUB>.<DOMAIN>.<TLD>
# dig _dmarc.dmarcreports.tachtler.net TXT

; <<>> DiG 9.9.4-RedHat-9.9.4-38.el7_3.3 <<>> @127.0.0.1 _dmarc.dmarcreports.tachtler.net TXT
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 14708
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;_dmarc.dmarcreports.tachtler.net. IN	TXT

;; ANSWER SECTION:
_dmarc.dmarcreports.tachtler.net. 10800	IN TXT	"v=DMARC1\; p=none\; fo=0\; adkim=r\; aspf=r\; pct=100\; 
rf=afrf\; ri=86400\; sp=none"

;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Mon May 01 09:43:27 CEST 2017
;; MSG SIZE  rcvd: 189

* Mein Dank für die Hinweise geht an Juri Haberland

Konfiguration: OpenDMARC

/etc/sysconfig/opendmarc

Nachfolgende Konfigurationsdatei von OpenDMARC setzt Standardparameter für den Start des OpenDMARC Dienst/Daemons.

:!: HINWEIS - Änderungen an dieser Konfigurationsdatei sind grundsätzlich nicht erforderlich!

# Set the necessary startup options
OPTIONS="-c /etc/opendmarc.conf -P /var/run/opendmarc/opendmarc.pid"

/etc/opendmarc.conf

Standardmäßig wird nach der Installation von OpenDMARC - opendmarc-milter in nachfolgendem Verzeichnis mit nachfolgendem Namen die Hauptkonfigurationsdatei für den OpenDMARC - opendmarc-milter hinterlegt:

  • /etc/opendmarc.conf

Nachfolgende Änderungen sind an der Konfigurationsdatei /etc/opendmarc.conf durchzuführen:

(Komplette Konfigurationsdatei)

# cat /etc/opendmarc.conf
##
## opendmarc.conf -- configuration file for OpenDMARC filter
##
## Copyright (c) 2012-2015, The Trusted Domain Project.  All rights reserved.
##
 
##  AuthservID (string)
##  	defaults to MTA name
##
##  Sets the "authserv-id" to use when generating the Authentication-Results:
##  header field after verifying a message.  If the string "HOSTNAME" is
##  provided, the name of the host running the filter (as returned by the
##  gethostname(3) function) will be used.  
#
# Tachtler
# default: # AuthservID name
AuthservID mx1.tachtler.net
 
##  AuthservIDWithJobID { true | false }
##  	default "false"
##
##  If "true", requests that the authserv-id portion of the added
##  Authentication-Results header fields contain the job ID of the message
##  being evaluated.
#
# Tachtler
# default: # AuthservIDWithJobID false
AuthservIDWithJobID true
 
##  AutoRestart { true | false }
##  	default "false"
##
##  Automatically re-start on failures. Use with caution; if the filter fails
##  instantly after it starts, this can cause a tight fork(2) loop.
#
# AutoRestart false
 
##  AutoRestartCount n
##  	default 0
##
##  Sets the maximum automatic restart count.  After this number of automatic
##  restarts, the filter will give up and terminate.  A value of 0 implies no
##  limit.
#
# AutoRestartCount 0
 
##  AutoRestartRate n/t[u]
##  	default (no limit)
##
##  Sets the maximum automatic restart rate.  If the filter begins restarting
##  faster than the rate defined here, it will give up and terminate.  This
##  is a string of the form n/t[u] where n is an integer limiting the count
##  of restarts in the given interval and t[u] defines the time interval
##  through which the rate is calculated; t is an integer and u defines the
##  units thus represented ("s" or "S" for seconds, the default; "m" or "M"
##  for minutes; "h" or "H" for hours; "d" or "D" for days). For example, a
##  value of "10/1h" limits the restarts to 10 in one hour. There is no
##  default, meaning restart rate is not limited.
#
# AutoRestartRate n/t[u]
 
##  Background { true | false }
##  	default "true"
##
##  Causes opendmarc to fork and exits immediately, leaving the service
##  running in the background.
#
# Background true
 
##  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
 
##  CopyFailuresTo (string)
##  	default (none)
##
##  Requests addition of the specified email address to the envelope of
##  any message that fails the DMARC evaluation.
#
# Tachtler
# default: # CopyFailuresTo postmaster@localhost
CopyFailuresTo postmaster@tachtler.net
 
##  DNSTimeout (integer)
##  	default 5
## 
##  Sets the DNS timeout in seconds.  A value of 0 causes an infinite wait.
##  (NOT YET IMPLEMENTED)
#
# DNSTimeout 5
 
##  EnableCoredumps { true | false }
##  	default "false"
##
##  On systems that have such support, make an explicit request to the kernel
##  to dump cores when the filter crashes for some reason.  Some modern UNIX
##  systems suppress core dumps during crashes for security reasons if the
##  user ID has changed during the lifetime of the process.  Currently only
##  supported on Linux.
#
# EnableCoreDumps false
 
##  FailureReports { true | false }
##  	default "false"
##
##  Enables generation of failure reports when the DMARC test fails and the
##  purported sender of the message has requested such reports.  Reports are
##  formatted per RFC6591.
# 
# Tachtler
# default: # FailureReports false
FailureReports true
 
##  FailureReportsBcc (string)
##  	default (none)
##
##  When failure reports are enabled and one is to be generated, always
##  send one to the address(es) specified here.  If a failure report is
##  requested by the domain owner, the address(es) are added in a Bcc: field.
##  If no request is made, they address(es) are used in a To: field.  There
##  is no default.
# 
# Tachtler
# default: # FailureReportsBcc postmaster@example.coom
FailureReportsBcc postmaster@tachtler.net
 
##  FailureReportsOnNone { true | false }
##  	default "false"
##
##  Supplements the "FailureReports" setting by generating reports for
##  domains that advertise "none" policies.  By default, reports are only
##  generated (when enabled) for sending domains advertising a "quarantine"
##  or "reject" policy.
# 
# FailureReportsOnNone false
 
##  FailureReportsSentBy string
##  	default "USER@HOSTNAME"
##
##  Specifies the email address to use in the From: field of failure
##  reports generated by the filter.  The default is to use the userid of
##  the user running the filter and the local hostname to construct an
##  email address.  "postmaster" is used in place of the userid if a name
##  could not be determined.
# 
# Tachtler
# default: # FailureReportsSentBy USER@HOSTNAME
FailureReportsSentBy postmaster@dmarcreports.tachtler.net
 
##  HistoryFile path
##  	default (none)
##
##  If set, specifies the location of a text file to which records are written
##  that can be used to generate DMARC aggregate reports.  Records are groups
##  of rows containing information about a single received message, and
##  include all relevant information needed to generate a DMARC aggregate
##  report.  It is expected that this will not be used in its raw form, but
##  rather periodically imported into a relational database from which the
##  aggregate reports can be extracted by a tool such as opendmarc-import(8).
#
# Tachtler
# default: # HistoryFile /var/spool/opendmarc/opendmarc.dat
HistoryFile /var/spool/opendmarc/opendmarc.dat
 
##  IgnoreAuthenticatedClients { true | false }
##  	default "false"
##
##  If set, causes mail from authenticated clients (i.e., those that used
##  SMTP AUTH) to be ignored by the filter.
#
# IgnoreAuthenticatedClients false
 
##  IgnoreHosts path
##  	default (internal)
##
##  Specifies the path to a file that contains a list of hostnames, IP
##  addresses, and/or CIDR expressions identifying hosts whose SMTP
##  connections are to be ignored by the filter.  If not specified, defaults
##  to "127.0.0.1" only.
#
# Tachtler
# default: # IgnoreHosts /etc/opendmarc/ignore.hosts
IgnoreHosts /etc/opendmarc/ignore.hosts
 
##  IgnoreMailFrom domain[,...]
##  	default (none)
##
##  Gives a list of domain names whose mail (based on the From: domain) is to
##  be ignored by the filter.  The list should be comma-separated.  Matching
##  against this list is case-insensitive.  The default is an empty list,
##  meaning no mail is ignored.
#
# IgnoreMailFrom example.com
 
##  MilterDebug (integer)
##  	default 0
##
##  Sets the debug level to be requested from the milter library.
#
# Tachtler
# default: # MilterDebug 0
MilterDebug 5
 
##  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
 
##  PublicSuffixList path
##  	default (none)
##
##  Specifies the path to a file that contains top-level domains (TLDs) that
##  will be used to compute the Organizational Domain for a given domain name,
##  as described in the DMARC specification.  If not provided, the filter will
##  not be able to determine the Organizational Domain and only the presented
##  domain will be evaluated.
#
# PublicSuffixList path
 
##  RecordAllMessages { true | false }
##  	default "false"
##
##  If set and "HistoryFile" is in use, all received messages are recorded
##  to the history file.  If not set (the default), only messages for which
##  the From: domain published a DMARC record will be recorded in the
##  history file.
#
# RecordAllMessages false
 
##  RejectFailures { true | false }
##  	default "false"
##
##  If set, messages will be rejected if they fail the DMARC evaluation, or
##  temp-failed if evaluation could not be completed.  By default, no message
##  will be rejected or temp-failed regardless of the outcome of the DMARC
##  evaluation of the message.  Instead, an Authentication-Results header
##  field will be added.
#
# RejectFailures false
 
##  ReportCommand string
##  	default "/usr/sbin/sendmail -t"
##
##  Indicates the shell command to which failure reports should be passed for
##  delivery when "FailureReports" is enabled.
#
# ReportCommand /usr/sbin/sendmail -t
 
##  RequiredHeaders { true | false }
##  	default "false"
##
##  If set, the filter will ensure the header of the message conforms to the
##  basic header field count restrictions laid out in RFC5322, Section 3.6.
##  Messages failing this test are rejected without further processing.  A
##  From: field from which no domain name could be extracted will also be
##  rejected.
#
# RequiredHeaders 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.
#
# Tachtler
# default: Socket inet:8893@localhost
Socket inet:10013@192.168.0.70
 
##  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
 
##  SPFIgnoreResults { true | false }
##	default "false"
##
##  Causes the filter to ignore any SPF results in the header of the
##  message.  This is useful if you want the filter to perfrom SPF checks
##  itself, or because you don't trust the arriving header.
#
# Tachtler
# default: SPFIgnoreResults true
SPFIgnoreResults false
 
##  SPFSelfValidate { true | false }
##	default false
##
##  Enable internal spf checking with --with-spf
##  To use libspf2 instead:  --with-spf --with-spf2-include=path --with-spf2-lib=path
##
##  Causes the filter to perform a fallback SPF check itself when
##  it can find no SPF results in the message header.  If SPFIgnoreResults
##  is also set, it never looks for SPF results in headers and
##  always performs the SPF check itself when this is set.
#
# Tachtler
# default: SPFSelfValidate true
SPFSelfValidate false
 
##  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
 
##  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 HOSTNAME
 
##  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 007
 
##  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:mail

Nachfolgende Änderungen sollten vorgenommen werden:

  • AuthservID mx1.tachtler.net

Explizites setzen des HOST-Namen, welcher bei Einträgen durch OpenDMARC verwendet werden soll. Standardmäßig würde hier der FQDN des Servers verwendet werden, auf dem der OpenDMARC ausgeführt wird.

  • AuthservIDWithJobID true

Die „Authserv-ID“ soll in den e-Mail-Header bei der Eintragung des Authentikations-Ergebnisses durchgeführt werden. Es wird die ID der e-Mail verwendet.

  • CopyFailuresTo postmaster@tachtler.net

Sendet eine Kopie der e-Mail an die angegeben e-Mail-Adresse, wenn eine DMARC-Überprüfung fehlgeschlagenen ist.

  • FailureReports true

Es soll eine Fehlerreport erstellt werden, falls die DMARC-Fehlerüberprüfung fehl schlägt und der Absender der e-Mail solch einen Report angefordert hat. Die Reports werden im so formatiert, wie dies im RFC6591 festgelegt wurde.

  • FailureReportsBcc postmaster@tachtler.net

Falls die Erstellung von Fehlerreports durchgeführt wird und ein Fehlerreport erstellt wird, soll an nachfolgende e-Mailadresse eine Blindkopie dieses Fehlerreports gesendet werden, wenn der Absender einen solchen Report angefordert hat. Falls der Absender keinen Report angefordert hat, wird eine e-Mail an die angegebene Adresse als Empfänger gesendet.

  • FailureReportsSentBy postmaster@dmarcreports.tachtler.net

Absender e-Mail-Adresse des Reports der per e-Mail an den Absender geht, bei dem die DMARC-Überprüfung fehlgeschlagen ist und der Absender einen solchen Report angefordert hat.

Es sollte eine SUB-Domain verwendet werden, da sonst die DMARC-Reports und -Failure-Messages von der Haupt-Domain verschickt werden, generiert jeder Report bei den Empfängern einen Report zurück. Das ist an sich nicht schlimm, aber generiert unter Umständen sinnlos Reports die nur sich selber als Report in sich tragen. Deshalb sollten die DMARC-Reports und -Failure-Messages von einer Subdomain aus zu verschickt werden, die eine eigenen DMARC-Record ohne rua- und ruf-Tags hat.

  • HistoryFile /var/spool/opendmarc/opendmarc.dat

Falls hier die Angabe einer Datei ggf. mit Pfadangabe durchgeführt wird, wird eine Textdatei erstellt, deren Inhalt ein DMARC Summenreport ist. Diese Datei kann dann mit dem opendmarc-import Programm extrahiert werden und in eine Datenbank geschrieben werden um daraus später weitere Reports erstellen zu können.

  • IgnoreHosts /etc/opendmarc/ignore.hosts

Bezeichnet den Pfad zu einer Datei, welche als Inhalt HOST-Namen, IP-Adressen oder auch Netzwerk-Angaben beinhaltet, welche von der DMARC-Überprüfung ausgeschlossen werden sollen. Falls keine Angaben hier erfolgt, wird Standardmäßig nur die IP-Adresse: 127.0.0.1 ausgeschlossen.

:!: HINWEIS - Diese Datei sollte für später vorbereitet werden, um ggf. Ausschlüsse von der Prüfung durchführen zu können!

  • MilterDebug 5

Setzen des Levels der Informationsausgabe, welche in das LOG geschrieben werden.

  • Socket inet:10013@192.168.0.70

Setzen des Sockets bzw. der IP-Adresse und des Ports, über den der MILTER angesprochen werden kann.

  • SPFIgnoreResults false

Die DMARC-Überprüfung von bereits vorhandenen e-Mail-Header Einträgen zur SPF-Überprüfung sollen nicht ignoriert werden, da diese bereits im Vorfeld in diesem Beispiel durch den SPF-MILTER gesetzt wurden. Hier könnte auch eine SPF-Überprüfung durch den DMARC-MILTER durchgeführt werden, was jedoch aus Stabilitätsgründen hier nicht erfolgen soll.

  • SPFSelfValidate false

Deaktiviert die SPF-Überprüfung, welche auch durch den DMARC-MILTER durchgeführt werden kann, wenn nicht bereits im e-Mail-Header SPF-Überprüfungsinformationen enhalten sind, was jedoch aus Stabilitätsgründen hier nicht erfolgen soll.

/etc/opendmarc/ignore.hosts

Nachfolgende Konfigurationsdatei in nachfolgendem Verzeichnis mit ebenfalls nachfolgendem Namen:

  • /etc/opendmarc/ignore.hosts

beinhaltet HOST-Namen, IP-Adressen oder ganze Netze, für die keine DMARC-Überprüfung durchgeführt werden soll.

Bevor die Angaben zu den HOST-Namen, IP-Adressen oder ganze Netze, für die keine DMARC-Überprüfung durchgeführt werden soll gemacht werden können, muss die Konfigurationsdatei mit nachfolgendem Befehl angelegt werden:

# touch /etc/opendmarc/ignore.hosts

Nachfolgende Einstellungen sind an der Konfigurationsdatei /etc/opendkim/TrustedHosts durchzuführen:

(Komplette Konfigurationsdatei)

##  File that contains a list of hostnames, IP addresses, and/or CIDR expressions
##  identifying hosts whose SMTP connections are to be ignored by the filter.
127.0.0.1

OpenDMARC Dienst/Daemon-Start einrichten

Um den OpenDMARC der als Dienst/Deamon als Hintergrundprozess läuft, auch nach einem Neustart des Servers zur Verfügung zu haben, soll der Dienst/Daemon mit dem Server mit gestartet werden, was mit nachfolgendem Befehl realisiert werden kann:

# systemctl enable opendmarc
ln -s '/usr/lib/systemd/system/opendmarc.service' '/etc/systemd/system/multi-user.target.wants/opendmarc.service'

Eine Überprüfung, ob beim Neustart des Server der opendmarc-Dienst/Deamon wirklich mit gestartet wird, kann mit nachfolgendem Befehl erfolgen und sollte eine Anzeige, wie ebenfalls nachfolgend dargestellt ausgeben:

# systemctl list-unit-files --type=service | grep -e opendmarc
opendmarc.service                      enabled

bzw.

# systemctl is-enabled opendmarc
enabled

Erster Start OpenDMARC

Um den OpenDMARC zu starten kann nachfolgender Befehl angewandt werden:

# systemctl start opendmarc

Eine Überprüfung ob der Start des OpenDMARC erfolgreich war kann mit nachfolgendem Befehl durchgeführt werden, welcher eine Ausgabe in etwa wie nachfolgende erzeugen sollte:

# systemctl status opendmarc
opendmarc.service - Domain-based Message Authentication, Reporting & Conformance (DMARC) Milter
   Loaded: loaded (/usr/lib/systemd/system/opendmarc.service; enabled)
   Active: active (running) since Thu 2015-10-22 09:23:26 CEST; 4s ago
     Docs: man:opendmarc(8)
           man:opendmarc.conf(5)
           man:opendmarc-import(8)
           man:opendmarc-reports(8)
           http://www.trusteddomain.org/opendmarc/
  Process: 2481 ExecStart=/usr/sbin/opendmarc $OPTIONS (code=exited, status=0/SUCCESS)
 Main PID: 2482 (opendmarc)
   CGroup: /system.slice/opendmarc.service
           └─2482 /usr/sbin/opendmarc -c /etc/opendmarc.conf -P /var/run/open...

Oct 22 09:23:26 server70.idmz.tachtler.net systemd[1]: Starting Domain-based ...
Oct 22 09:23:26 server70.idmz.tachtler.net opendmarc[2482]: OpenDMARC Filter ...
Oct 22 09:23:26 server70.idmz.tachtler.net opendmarc[2482]: additional truste...
Oct 22 09:23:26 server70.idmz.tachtler.net systemd[1]: Started Domain-based M...
Hint: Some lines were ellipsized, use -l to show in full.

bzw. mit nachfolgendem Befehl, ob der Dienst/Daemon in der Prozessliste erscheint:

# ps aux | grep opendmarc
opendma+  2482  0.0  0.0  39884   712 ?        Ssl  09:23   0:00 /usr/sbin/opendmarc -c /etc/opendmarc.conf -P /var/run/opendmarc/opendmarc.pid
root      2487  0.0  0.0 112640   928 pts/0    S+   09:24   0:00 grep --color=auto opendmarc

Eine weitere Möglichkeit ist die Überprüfung des journal, was mit nachfolgendem Befehl durchgeführt werden kann:

Oct 22 09:23:26 server70.idmz.tachtler.net systemd[1]: Starting Domain-based Mes
-- Subject: Unit opendmarc.service has begun with start-up
-- Defined-By: systemd
-- Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
-- 
-- Unit opendmarc.service has begun starting up.
Oct 22 09:23:26 server70.idmz.tachtler.net opendmarc[2481]: OpenDMARC Filter: Op
Oct 22 09:23:26 server70.idmz.tachtler.net opendmarc[2482]: OpenDMARC Filter v1.
Oct 22 09:23:26 server70.idmz.tachtler.net opendmarc[2482]: additional trusted a
Oct 22 09:23:26 server70.idmz.tachtler.net systemd[1]: Started Domain-based Mess
-- Subject: Unit opendmarc.service has finished start-up
-- Defined-By: systemd
-- Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
-- 
-- Unit opendmarc.service has finished starting up.
-- 
-- The start-up result is done.

Konfiguration: opendmarc-milter

Nachfolgende Änderungen werden an den Konfigurationsdateien

  • /etc/postfix/main.cf
  • /etc/postfix/master.cf

durchgeführt, um eine Anbindung des Postfix an den OpenDMARC zu realisieren.

Dabei soll die Anbindung von Postfix an den OpenDMARC mit dem Verfahren

  • opendmarc-milter erfolgen.

/etc/postfix/main.cf

Hier die Änderungen an der Konfigurationsdatei /etc/postfix/main.cf

(Nur relevanter Ausschnitt):

...
# OpenDMARC (opendmarc-milter)
opendmarc_milter = inet:192.168.0.70:10013
...

/etc/postfix/master.cf

Hier die Änderungen an der Konfigurationsdatei /etc/postfix/master.cf

(Nur relevanter Ausschnitt):

#
# Postfix master process configuration file.  For details on the format
# of the file, see the master(5) manual page (command: "man 5 master" or
# on-line: http://www.postfix.org/master.5.html).
#
# Do not forget to execute "postfix reload" after editing this file.
#
# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (yes)   (never) (100)
# ==========================================================================
# Tachtler - disabled - 
#smtp      inet  n       -       n       -       -       smtpd
# Tachtler - new - 
# Incoming traffic from untrust networks, with postscreen.
192.168.1.60:2525      inet  n       -       n       -       1       postscreen
# Tachtler - enabled - 
# Incoming traffic passed from untrust networks, with postscreen.
smtpd     pass  -       -       n       -       -       smtpd
  -o smtpd_milters=${opendmarc_milter}
...

Nachfolgend Erklärungen zu den WICHTIGSTEN Konfigurationen:

  •  -o smtpd_milters=${opendmarc_milter}

Die Option sorgt dafür, dass dem Parameter smtpd_milter der Inhalt des Parameters opendmarc_milter übergeben wird. Falls mehrere MILTER zum Einsatz kommen, wird hier die Reihenfolge festgelegt, in der diese aufgerufen werden!

Neustart

Falls vorstehende Änderungen (natürlich an die jeweiligen Bedürfnisse angepasst) durchgeführt wurden, muss ein Neustart von Postfix durchgeführt werden.

Danach kann der postfix-Server mit nachfolgendem Befehle neu gestartet werden:

# systemctl restart postfix

Mit nachfolgendem Befehl kann der Status des abgefragt werden:

# systemctl status postfix
postfix.service - Postfix Mail Transport Agent
   Loaded: loaded (/usr/lib/systemd/system/postfix.service; enabled)
   Active: active (running) since Thu 2015-10-15 11:11:26 CEST; 7s ago
  Process: 1128 ExecStop=/usr/sbin/postfix stop (code=exited, status=0/SUCCESS)
  Process: 1144 ExecStart=/usr/sbin/postfix start (code=exited, status=0/SUCCESS)
  Process: 1141 ExecStartPre=/usr/libexec/postfix/chroot-update (code=exited, status=0/SUCCESS)
  Process: 1138 ExecStartPre=/usr/libexec/postfix/aliasesdb (code=exited, status=0/SUCCESS)
 Main PID: 1216 (master)
   CGroup: /system.slice/postfix.service
           ├─1216 /usr/libexec/postfix/master -w
           ├─1217 pickup -l -t unix -u -o content_filter=lmtp:[192.168.0.70]...
           └─1218 qmgr -l -t unix -u

Oct 15 11:11:26 server60.idmz.tachtler.net systemd[1]: Starting Postfix Mail...
Oct 15 11:11:26 server60.idmz.tachtler.net postfix/postfix-script[1214]: sta...
Oct 15 11:11:26 server60.idmz.tachtler.net postfix/master[1216]: daemon star...
Oct 15 11:11:26 server60.idmz.tachtler.net systemd[1]: Started Postfix Mail ...
Hint: Some lines were ellipsized, use -l to show in full.

Test

Nachfolgend soll ein Test darin bestehen, dass eine e-Mail von einem externen Server an Postfix gesendet wird, und dieser dann die DKIM-Signatur des absendenden e-Mail-Servers prüft.

Wichtig sind zwei Einträge, in

  • den Header-Zeilen der eingehenden e-Mail
  • die LOG-Einträge im Server, auf dem OpenDMARC - opendmarc-milter läuft:

Überprüfung: Header-Zeilen

Nachfolgender Eintrag sollte in den Header-Zeilen einer eingehenden e-Mail zu finden sein, um das Ergebnis der OpenDMARC - opendmarc-milter Überprüfung zu zeigen:

DMARC-Filter: OpenDMARC Filter v1.4.1 mx1.tachtler.net EDCFE1800089
Authentication-Results: mx1.tachtler.net/EDCFE1800089; dmarc=pass header.from=nausch.org

* Das Ergbenis kann hier pass, fail und none sein.

Überprüfung: /var/log/maillog

Nachfolgender Eintrag sollte in den LOG-Einträgen des Servers auf dem der OpenDMARC - opendmarc-milter läuft bei einer eingehenden e-Mail zu finden sein, um das Ergebnis der OpenDMARC - opendmarc-milter Überprüfung zu dokumentieren:

Oct 22 09:39:23 server70 opendmarc[2482]: implicit authentication service: mx1.tachtler.net
Oct 22 09:39:23 server70 opendmarc[2482]: EDCFE1800089: nausch.org pass

Konfiguration: Reports ausgehend

Um Reports erstellen zu können, welche durch einliefernde e-Mail-Server im Rahmen von OpenDMARC angefordert werden, ist es vorgesehen, dass die Daten zuerst von OpenDMARC in eine Datei geschrieben werden, welche sich in nachfolgendem Verzeichnis mit nachfolgendem Namen befindet.

  • /var/spool/opendmarc/opendmarc.dat

Die in der oben genannten Datei befindlichen Daten müssen allerdings zuerst in eine Datenbank geschrieben werden, um daraus dann die gewünschten Reports erstellen zu können und anschließend auch versenden zu können. Nicht zu vergessen ist hierbei auch, das aus der Datenbank auch wieder entsprechend nach einer gewissen Zeit, ältere Daten gelöscht werden, damit diese nicht überlaufen kann.

Nachfolgende Konfiguration ist erforderlich, damit Daten durch das Skript in nachfolgendem Verzeichnis mit nachfolgendem Namen

  • /usr/sbin/opendmarc-import

in die Datenbank geschrieben werden können. Dazu ist die entsprechende Datenbank und die benötigten Tabellen und Felder anzulegen.

Dies kann durch ein, ebenfalls im rpm-Paket mitgeliefertes Skript, welches in nachfolgendem Verzeichnis mit nachfolgendem Namen zu finden ist:

  • /usr/share/opendmarc/db/schema.mysql

durchgeführt werden.

:!: HINWEIS - Falls von einem HOST anstelle von localhost Daten in die Datenbank geschrieben werden sollen, muss das Skript entsprechend erweitert bzw. angepasst werden!

/usr/share/opendmarc/db/schema.mysql

:!: HINWEIS - Nachfolgend sollen vom HOST mit der IP-Adresse 192.168.0.70 die Daten in die Datenbank geschrieben werden und nicht von localhost wie dies standardmäßig der Fall ist!

Die erforderlichen Anpassungen, befinden sich am Ende des Skripts, welches sonst nicht angepasst werden sollte!

-- OpenDMARC database schema
--
-- Copyright (c) 2012, 2016, 2018, 2021, The Trusted Domain Project.
-- 	All rights reserved.
 
CREATE DATABASE IF NOT EXISTS opendmarc;
USE opendmarc;
 
-- A table for mapping domain names and their DMARC policies to IDs
CREATE TABLE IF NOT EXISTS domains (
	id INT NOT NULL AUTO_INCREMENT,
	name VARCHAR(255) NOT NULL,
	firstseen TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
 
	PRIMARY KEY(id),
	UNIQUE KEY(name)
);
 
-- A table for logging encountered ARC selectors
CREATE TABLE IF NOT EXISTS selectors (
	id INT NOT NULL AUTO_INCREMENT,
	domain INT NOT NULL,
	name VARCHAR(255) NOT NULL,
	firstseen TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
 
	PRIMARY KEY(id),
	KEY(name),
	UNIQUE KEY(name, domain)
);
 
-- A table for logging ARC-Authentication-Results information
CREATE TABLE IF NOT EXISTS arcauthresults (
	id INT NOT NULL AUTO_INCREMENT,
	message INT UNSIGNED NOT NULL,
	instance INT UNSIGNED NOT NULL,
	arc_client_addr VARCHAR(64) NOT NULL DEFAULT '',
 
	PRIMARY KEY(id),
	KEY(message),
	UNIQUE KEY(message, instance)
);
 
-- A table for logging ARC-Seal information
CREATE TABLE IF NOT EXISTS arcseals (
	id INT NOT NULL AUTO_INCREMENT,
	message INT UNSIGNED NOT NULL,
	domain INT UNSIGNED NOT NULL,
	selector INT UNSIGNED NOT NULL,
	instance INT UNSIGNED NOT NULL,
	firstseen TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
 
	PRIMARY KEY(id),
	KEY(message),
	UNIQUE KEY(message, domain, selector, instance)
);
 
-- A table for logging reporting requests
CREATE TABLE IF NOT EXISTS requests (
	id INT NOT NULL AUTO_INCREMENT,
	domain INT NOT NULL,
	repuri VARCHAR(255) NOT NULL DEFAULT '',
	adkim TINYINT NOT NULL DEFAULT '0',
	aspf TINYINT NOT NULL DEFAULT '0',
	policy TINYINT NOT NULL DEFAULT '0',
	spolicy TINYINT NOT NULL DEFAULT '0',
	pct TINYINT NOT NULL DEFAULT '0',
	locked TINYINT NOT NULL DEFAULT '0',
	firstseen TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
	lastsent TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00',
 
	PRIMARY KEY(id),
	KEY(lastsent),
	UNIQUE KEY(domain)
);
 
-- A table for reporting hosts
CREATE TABLE IF NOT EXISTS reporters (
	id INT NOT NULL AUTO_INCREMENT,
	name VARCHAR(255) NOT NULL,
	firstseen TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
 
	PRIMARY KEY(id),
	UNIQUE KEY(name)
);
 
-- A table for connecting client IP addresses
CREATE TABLE IF NOT EXISTS ipaddr (
	id INT NOT NULL AUTO_INCREMENT,
	addr VARCHAR(64) NOT NULL,
	firstseen TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
 
	PRIMARY KEY(id),
	UNIQUE KEY(addr)
);
 
-- A table for messages
CREATE TABLE IF NOT EXISTS messages (
	id INT NOT NULL AUTO_INCREMENT,
	date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
	jobid VARCHAR(128) NOT NULL,
	reporter INT UNSIGNED NOT NULL,
	policy TINYINT UNSIGNED NOT NULL,
	disp TINYINT UNSIGNED NOT NULL,
	ip INT UNSIGNED NOT NULL,
	env_domain INT UNSIGNED NOT NULL,
	from_domain INT UNSIGNED NOT NULL,
	policy_domain INT UNSIGNED NOT NULL,
	spf TINYINT NOT NULL,
	align_dkim TINYINT UNSIGNED NOT NULL,
	align_spf TINYINT UNSIGNED NOT NULL,
	sigcount TINYINT UNSIGNED NOT NULL,
	arc TINYINT UNSIGNED NOT NULL,
	arc_policy TINYINT UNSIGNED NOT NULL,
 
	PRIMARY KEY(id),
	KEY(date),
	UNIQUE KEY(reporter, date, jobid)
);
 
-- A table for signatures
CREATE TABLE IF NOT EXISTS signatures (
	id INT NOT NULL AUTO_INCREMENT,
	message INT UNSIGNED NOT NULL,
	domain INT UNSIGNED NOT NULL,
	selector INT UNSIGNED NOT NULL,
	pass TINYINT UNSIGNED NOT NULL,
	error TINYINT UNSIGNED NOT NULL,
 
	PRIMARY KEY(id),
	KEY(message)
);
 
-- CREATE USER 'opendmarc'@'localhost' IDENTIFIED BY 'changeme';
-- GRANT ALL ON opendmarc.* to 'opendmarc'@'localhost';
 
-- # Tachtler - Grant ALL privileges to new users.
GRANT ALL PRIVILEGES ON *.* TO 'opendmarc_user'@'192.168.0.70' IDENTIFIED BY 'password' WITH GRANT OPTION;
GRANT ALL PRIVILEGES ON *.* TO 'opendmarc_user'@'server70.idmz.tachtler.net' IDENTIFIED BY 'password' WITH GRANT OPTION;
 
-- # Tachtler - Make sure that priviliges are reloaded.
FLUSH PRIVILEGES;

Nachfolgende Änderungen wurden am Skript durchgeführt:

Austausch des TIMESTAMP von '1970-01-01 00:00:00' zu '0000-00-00 00:00:00', da es sonst zu einer Fehlermeldung kommt, das '1970-01-01 00:00:00' kein gültiger DEFAULT-Wert ist.

  • -- # Tachtler - Create new users.
    CREATE USER 'opendmarc_user'@'192.168.0.70' IDENTIFIED BY 'password';
    CREATE USER 'opendmarc_user'@'server70.idmz.tachtler.net' IDENTIFIED BY 'password';

Erstellen von zwei neuen Benutzer, opendmarc_user'@'192.168.0.70 und opendmarc_user'@'server70.idmz.tachtler.net, welche via IP-Adresse oder FQDN vom Server 192.168.0.70 bzw. server70.idmz.tachtler.net agieren dürfen und ein Passwort besitzen

Den beiden Benutzern opendmarc_user'@'192.168.0.70 und opendmarc_user'@'server70.idmz.tachtler.net werden alle Privilegien in auf die Datenbank opendmarc gewährt.

  • -- # Tachtler - Make sure that priviliges are reloaded.
    FLUSH PRIVILEGES;

Die Benutzerrechte, inklusive der neu angelegten Benutzer mit den entsprechenden Rechten, sollen aktiviert werden, in dem die Rechte neu eingelesen werden sollen.

Die Ausführung des Skriptes und die damit verbunden Anlage der Datenbank, der Tabellen und Felder und der Nutzer, kann durch Ausführung des nachfolgenden Befehls durchgeführt werden, zudem jedoch das Kennwort zum Datenbankbenutzer root erforderlich ist!:

# /usr/bin/mysql -u root -p < /usr/share/opendmarc/db/schema.mysql
Enter password:

/etc/opendmarc/opendmarc_report_out.sh

Nachfolgend soll ein Skript erstellt werden, welches alle drei nachfolgende Aufgaben durchführen soll:

  1. Übernahme der Daten aus der Konfigurationsdatei /var/run/opendmarc/opendmarc.dat in die Datenbank
  2. Erstellen und versenden der Reports, zu den externen e-Mail-Servern, auf dessen Anforderung hin
  3. Aufräumen der Datenbank und löschen der älteren Datensätze um einem Überlauf der Datenbank vorzubeugen
#!/bin/bash
 
##############################################################################
# Script-Name : opendmarc_report_out.sh                                      # 
# Description : Add opendmarc report data from file                          #
#               - /var/spool/opendmarc/opendmarc.dat                         #
#               to a Database. Generate and sent reports to recipients who   #
#               request a report. Delete old data from Database to protect   #
#               the Database from overflow.
#                                                                            # 
#                                                                            # 
# Last update : 23.10.2015                                                   # 
# Version     : 1.00                                                         # 
##############################################################################
 
##############################################################################
#                                H I S T O R Y                               # 
##############################################################################
# Version     : x.xx                                                         # 
# Description : <Description>                                                #
# -------------------------------------------------------------------------- # 
# Version     : x.xx                                                         # 
# Description : <Description>                                                #
# -------------------------------------------------------------------------- # 
##############################################################################
 
# Source function library.
. /etc/init.d/functions
 
# Variable declarations.
 
##############################################################################
# >>> Please edit following lines for personal command and/or configuration! #
##############################################################################
 
# CUSTOM - Script-Name.
SCRIPT_NAME='opendmarc_report_out.sh'
 
# CUSTOM - Variables.
FILE_OPENDMARC_DAT='/var/spool/opendmarc/opendmarc.dat'
FILE_OPENDMARC_USER='opendmarc'
FILE_OPENDMARC_PERMISSION='660'
REPORT_INTERVAL='86400'
REPORT_SENDER='postmaster@dmarcreports.tachtler.net'
REPORT_ORG='tachtler.net'
REPORT_EXPIRE='90'
 
# CUSTOM - Database.
DB_HOST='mariadb.idmz.tachtler.net'
DB_PORT='3306'
DB_NAME='opendmarc'
DB_USER='opendmarc_user'
DB_PSWD='geheim'
 
# CUSTOM - Mail-Recipient.
MAIL_RECIPIENT='you@example.com'
 
# CUSTOM - Status-Mail [Y|N].
MAIL_STATUS='N'
 
##############################################################################
# >>> Normaly there is no need to change anything below this comment line. ! #
##############################################################################
 
# Binarys.
BIN_OPENDMARC_IMPORT=`command -v opendmarc-import`
BIN_OPENDMARC_REPORTS=`command -v opendmarc-reports`
BIN_OPENDMARC_EXPIRE=`command -v opendmarc-expire`
 
# Variables.
TOUCH_COMMAND=`command -v touch`
RM_COMMAND=`command -v rm`
CAT_COMMAND=`command -v cat`
DATE_COMMAND=`command -v date`
PROG_SENDMAIL=`command -v sendmail`
CHOWN_COMMAND=`command -v chown`
CHMOD_COMMAND=`command -v chmod`
FILE_LOCK='/tmp/'$SCRIPT_NAME'.lock'
FILE_LOG='/var/log/'$SCRIPT_NAME'.log'
FILE_LAST_LOG='/tmp/'$SCRIPT_NAME'.log'
FILE_MAIL='/tmp/'$SCRIPT_NAME'.mail'
VAR_HOSTNAME=`uname -n`
VAR_SENDER='root@'$VAR_HOSTNAME
VAR_EMAILDATE=`$DATE_COMMAND '+%a, %d %b %Y %H:%M:%S (%Z)'`
 
# Functions.
function log() {
        echo $1
        echo `$DATE_COMMAND '+%Y/%m/%d %H:%M:%S'` " INFO:" $1 >>${FILE_LAST_LOG}
}
 
function retval() {
if [ "$?" != "0" ]; then
        case "$?" in
        *)
                log "ERROR: Unknown error $?"
        ;;
        esac
fi
}
 
function movelog() {
        $CAT_COMMAND $FILE_LAST_LOG >> $FILE_LOG
        $RM_COMMAND -f $FILE_LAST_LOG
        $RM_COMMAND -f $FILE_LOCK
}
 
function sendmail() {
        case "$1" in
        'STATUS')
                MAIL_SUBJECT='Status execution '$SCRIPT_NAME' script.'
        ;;
        *)
                MAIL_SUBJECT='ERROR while execution '$SCRIPT_NAME' script !!!'
        ;;
        esac
 
$CAT_COMMAND <<MAIL >$FILE_MAIL
Subject: $MAIL_SUBJECT
Date: $VAR_EMAILDATE
From: $VAR_SENDER
To: $MAIL_RECIPIENT
 
MAIL
 
$CAT_COMMAND $FILE_LAST_LOG >> $FILE_MAIL
 
$PROG_SENDMAIL -f $VAR_SENDER -t $MAIL_RECIPIENT < $FILE_MAIL
 
$RM_COMMAND -f $FILE_MAIL
 
}
 
# Main.
log ""
log "+-----------------------------------------------------------------+"
log "| Start processing opendmarc database jobs and e-Mail-report gen. |"
log "+-----------------------------------------------------------------+"
log ""
log "Run script with following parameter:"
log ""
log "SCRIPT_NAME...........: $SCRIPT_NAME"
log ""
log "MAIL_RECIPIENT........: $MAIL_RECIPIENT"
log "MAIL_STATUS...........: $MAIL_STATUS"
log ""
log "FILE_OPENDMARC_DAT....: $FILE_OPENDMARC_DAT"
log ""
log ""
 
# Check if command (file) NOT exist OR IS empty.
if [ ! -s "$TOUCH_COMMAND" ]; then
        log "Check if command '$TOUCH_COMMAND' was found....................[FAILED]"
        sendmail ERROR
        movelog
        exit 10
else
        log "Check if command '$TOUCH_COMMAND' was found....................[  OK  ]"
fi
 
# Check if command (file) NOT exist OR IS empty.
if [ ! -s "$RM_COMMAND" ]; then
        log "Check if command '$RM_COMMAND' was found.......................[FAILED]"
        sendmail ERROR
        movelog
        exit 11
else
        log "Check if command '$RM_COMMAND' was found.......................[  OK  ]"
fi
 
# Check if command (file) NOT exist OR IS empty.
if [ ! -s "$CAT_COMMAND" ]; then
        log "Check if command '$CAT_COMMAND' was found......................[FAILED]"
        sendmail ERROR
        movelog
        exit 12
else
        log "Check if command '$CAT_COMMAND' was found......................[  OK  ]"
fi
 
# Check if command (file) NOT exist OR IS empty.
if [ ! -s "$DATE_COMMAND" ]; then
        log "Check if command '$DATE_COMMAND' was found.....................[FAILED]"
        sendmail ERROR
        movelog
        exit 13
else
        log "Check if command '$DATE_COMMAND' was found.....................[  OK  ]"
fi
 
# Check if command (file) NOT exist OR IS empty.
if [ ! -s "$PROG_SENDMAIL" ]; then
        log "Check if command '$PROG_SENDMAIL' was found................[FAILED]"
        sendmail ERROR
        movelog
        exit 14
else
        log "Check if command '$PROG_SENDMAIL' was found................[  OK  ]"
fi
 
# Check if command (file) NOT exist OR IS empty.
if [ ! -s "$CHOWN_COMMAND" ]; then
        log "Check if command '$CHOWN_COMMAND' was found....................[FAILED]"
        sendmail ERROR
        movelog
        exit 15
else
        log "Check if command '$CHOWN_COMMAND' was found....................[  OK  ]"
fi
 
# Check if command (file) NOT exist OR IS empty.
if [ ! -s "$CHMOD_COMMAND" ]; then
        log "Check if command '$CHMOD_COMMAND' was found....................[FAILED]"
        sendmail ERROR
        movelog
        exit 16
else
        log "Check if command '$CHMOD_COMMAND' was found....................[  OK  ]"
fi
 
# Check if LOCK file NOT exist.
if [ ! -e "$FILE_LOCK" ]; then
        log "Check if script is NOT already runnig .....................[  OK  ]"
 
        $TOUCH_COMMAND $FILE_LOCK
else
        log "Check if script is NOT already runnig .....................[FAILED]"
        log ""
        log "ERROR: The script was already running, or LOCK file already exists!"
        log ""
        sendmail ERROR
        movelog
        exit 20
fi
 
# Check if  NOT exist.
if [ ! -e "$FILE_OPENDMARC_DAT" ]; then
        log "Check if $FILE_OPENDMARC_DAT exists ........[FAILED]"
        log ""
        log "INFO : The file $FILE_OPENDMARC_DAT does NOT exists!!!"
        log "INFO : Nothing to do."
        log "INFO : Exit script."
        log ""
        $RM_COMMAND -f $FILE_LOCK
        sendmail ERROR
        movelog
        exit 50
else
        log "Check if $FILE_OPENDMARC_DAT exists ........[  OK  ]"
fi
 
# Start process.
log ""
log "+-----------------------------------------------------------------+"
log "| Run process from $SCRIPT_NAME ...................... |"
log "+-----------------------------------------------------------------+"
log ""
 
# Import DMARC data into the database from file.
$BIN_OPENDMARC_IMPORT --dbhost=$DB_HOST --dbport=$DB_PORT --dbname=$DB_NAME --dbuser=$DB_USER --dbpasswd=$DB_PSWD --verbose < $FILE_OPENDMARC_DAT
 
if [ "$?" != 0 ]; then
        retval $?
        log "Import DMARC data into the database from file .............[FAILED]"
        $RM_COMMAND -f $FILE_LOCK
        sendmail ERROR
        movelog
        exit 51
else
        log "Import DMARC data into the database from file .............[  OK  ]"
fi
 
log ""
 
# Generate reports and sent them.
$BIN_OPENDMARC_REPORTS --dbhost=$DB_HOST --dbport=$DB_PORT --dbname=$DB_NAME --dbuser=$DB_USER --dbpasswd=$DB_PSWD --verbose --interval=$REPORT_INTERVAL --report-email=$REPORT_SENDER --report-org=$REPORT_ORG
 
if [ "$?" != 0 ]; then
        retval $?
        log "Generate reports and sent them ............................[FAILED]"
        $RM_COMMAND -f $FILE_LOCK
        sendmail ERROR
        movelog
        exit 52
else
        log "Generate reports and sent them ............................[  OK  ]"
fi
 
log ""
 
# Clean the database.
$BIN_OPENDMARC_EXPIRE --alltables --dbhost=$DB_HOST --dbport=$DB_PORT --dbname=$DB_NAME --dbuser=$DB_USER --dbpasswd=$DB_PSWD --verbose --expire=$REPORT_EXPIRE
 
if [ "$?" != 0 ]; then
        retval $?
        log "Clean the database ........................................[FAILED]"
        $RM_COMMAND -f $FILE_LOCK
        sendmail ERROR
        movelog
        exit 53
else
        log "Clean the database ........................................[  OK  ]"
fi
 
log ""
 
# Remove OpenDMARC file.
$RM_COMMAND $FILE_OPENDMARC_DAT
 
if [ "$?" != 0 ]; then
        retval $?
        log "Remove OpenDMARC HistoryFile ..............................[FAILED]"
        $RM_COMMAND -f $FILE_LOCK
        sendmail ERROR
        movelog
        exit 54
else
        log "Remove OpenDMARC HistoryFile ..............................[  OK  ]"
fi
 
# Create OpenDMARC file.
$TOUCH_COMMAND $FILE_OPENDMARC_DAT
 
if [ "$?" != 0 ]; then
        retval $?
        log "Create OpenDMARC HistoryFile ..............................[FAILED]"
        $RM_COMMAND -f $FILE_LOCK
        sendmail ERROR
        movelog
        exit 55
else
        log "Create OpenDMARC HistoryFile ..............................[  OK  ]"
fi
 
# Set owner to OpenDMARC file.
$CHOWN_COMMAND $FILE_OPENDMARC_USER:$FILE_OPENDMARC_USER $FILE_OPENDMARC_DAT
 
if [ "$?" != 0 ]; then
        retval $?
        log "Set owner to OpenDMARC HistoryFile ........................[FAILED]"
        $RM_COMMAND -f $FILE_LOCK
        sendmail ERROR
        movelog
        exit 56
else
        log "Set owner to OpenDMARC HistoryFile ........................[  OK  ]"
fi
 
# Set rights to OpenDMARC file.
$CHMOD_COMMAND $FILE_OPENDMARC_PERMISSION $FILE_OPENDMARC_DAT
 
if [ "$?" != 0 ]; then
        retval $?
        log "Set rights to OpenDMARC HistoryFile .......................[FAILED]"
        $RM_COMMAND -f $FILE_LOCK
        sendmail ERROR
        movelog
        exit 57
else
        log "Set rights to OpenDMARC HistoryFile .......................[  OK  ]"
fi
 
# Finish process.
log ""
log "+-----------------------------------------------------------------+"
log "| End process from $SCRIPT_NAME ...................... |"
log "+-----------------------------------------------------------------+"
log ""
 
# Status e-mail.
if [ $MAIL_STATUS = 'Y' ]; then
        sendmail STATUS
fi
# Move temporary log to permanent log
movelog
 
exit 0

Nachfolgender Befehl setzte die Datei- und Besitzrechte für das Skript wie folgt:

# chmod 750 /etc/opendmarc/opendmarc_report_out.sh

und

# chown root:root /etc/opendmarc/opendmarc_report_out.sh

Nach der erfolgreichen Ausführung des Skripts, wie nachfolgender manueller Aufruf zeigt, sind die Daten in der Datenbank und eine e-Mail an den einliefernden e-Mail-Server, welcher einen Report angefordert hat wurde ebenfalls generiert. Zum Abschluss ist dann noch eine Datenbankbereinigung angestoßen worden:

+-----------------------------------------------------------------+
| Start processing opendmarc database jobs and e-Mail-report gen. |
+-----------------------------------------------------------------+

Run script with following parameter:

SCRIPT_NAME...........: opendmarc_report_out.sh

MAIL_RECIPIENT........: root@tachtler.net
MAIL_STATUS...........: N

FILE_OPENDMARC_DAT....: /var/spool/opendmarc/opendmarc.dat


Check if command '/bin/touch' was found....................[ OK ]
Check if command '/bin/rm' was found.......................[ OK ]
Check if command '/bin/cat' was found......................[ OK ]
Check if command '/bin/date' was found.....................[ OK ]
Check if command '/sbin/sendmail' was found................[ OK ]
Check if script is NOT already runnig .....................[ OK ]
Check if /var/spool/opendmarc/opendmarc.dat exists ........[ OK ]

+-----------------------------------------------------------------+
| Run process from opendmarc_report_out.sh ...................... |
+-----------------------------------------------------------------+

Import DMARC data into the database from file .............[ OK ]

opendmarc-reports: started at Fri Oct 23 11:10:33 2015
opendmarc-reports: selected 1 domain(s)
opendmarc-reports: sent report for nausch.org to dmarc-reports@nausch.org (2.0.0 Ok: queued as 27894805BB4)
opendmarc-reports: terminating at Fri Oct 23 11:10:34 2015
Generate reports and sent them ............................[ OK ]

opendmarc-expire: started at Fri Oct 23 11:10:34 2015
opendmarc-expire: connected to database
opendmarc-expire: expiring messages older than 90 day(s)
opendmarc-expire: no rows deleted
opendmarc-expire: expiring signatures on expired messages (id < 1)
opendmarc-expire: no rows deleted
opendmarc-expire: expiring request data older than 90 days
opendmarc-expire: no rows deleted
opendmarc-expire: terminating at Fri Oct 23 11:10:34 2015
Clean the database ........................................[ OK ]

Remove OpenDMARC HistoryFile ..............................[ OK ]
Create OpenDMARC HistoryFile ..............................[ OK ]
Set owner to OpenDMARC HistoryFile ........................[ OK ]
Set rights to OpenDMARC HistoryFile .......................[ OK ]

+-----------------------------------------------------------------+
| End process from opendmarc_report_out.sh ...................... |
+-----------------------------------------------------------------+

/etc/crontab

Abschließend soll das Skript nun ein mal pro Tag ausgeführt werden. Die soll nicht durch den anacron-job-Lauf erfolgen, sondern durch einen klassiscen cron-job.

Dazu soll nachfolgender Eintrag in der Konfigurationsdatei:

  • /etc/crontab

eingefügt werden:

(Komplette Konfigurationsdatei:)

SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
 
# For details see man 4 crontabs
 
# Example of job definition:
# .---------------- minute (0 - 59)
# |  .------------- hour (0 - 23)
# |  |  .---------- day of month (1 - 31)
# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# |  |  |  |  |
# *  *  *  *  * user-name  command to be executed
 
# Tachtler
# OpenDMARC data import, report generation and e-Mail sent process and database cleaning.
0  4  *  *  * root	/root/bin/opendmarc_report_out.sh 	1>/dev/null 2>&1

* Das Skript soll jeden Tag um 4:00 Uhr ausgeführt werden!

Konfiguration: Reports eingehend

Um eingehende OpenDMARC-Reports, welche als e-Mail mit einem ZIP-Anhang versandt werden, ebenfalls verarbeiten zu können und in einer Datenbank speichern zu können, sind nachfolgende Schritte notwendig.

Die original Anleitung zu diesem Thema ist unter nachfolgendem externen Link zu finden:

Dank der Vorarbeiten und den Skripten von John Levine, ist die Einbindung einfacher möglich geworden, da hier sonst keine Unterstützung von Seiten OpenDMARC geboten wird.

Installation: Skriptabhängigkeiten

Nachfolgende rpm-Pakete müssen als Abhängigkeiten zum Skript readdmarc.pl zusätzlich installiert werden:

  • perl-DBI - ist im base-Repository von CentOS enthalten
  • perl-Getopt-Simple - ist im base-Repository von CentOS enthalten
  • perl-IO-Socket-INET6 - ist im base-Repository von CentOS enthalten
  • perl-MIME-tools - ist im epel-Repository des Drittanbieters EPEL enthalten
  • perl-NetAddr-IP - ist im base-Repository von CentOS enthalten
  • perl-PerlIO-gzip - ist im epel-Repository des Drittanbieters EPEL enthalten
  • perl-XML-Parser - ist im base-Repository von CentOS enthalten
  • perl-XML-SAX - ist im base-Repository von CentOS enthalten
  • perl-XML-Simple - ist im base-Repository von CentOS enthalten

Die Installation der Skriptabhängigkeiten, kann durch ausführen des nachfolgenden Befehls durchgeführt werden:

# yum install perl-DBI perl-Getopt-Simple perl-IO-Socket-INET6 perl-MIME-tools perl-NetAddr-IP perl-PerlIO-gzip perl-XML-Parser 
perl-XML-SAX perl-XML-Simple
Loaded plugins: changelog, priorities
149 packages excluded due to repository priority protections
Package perl-DBI-1.627-4.el7.x86_64 already installed and latest version
Package perl-IO-Socket-INET6-2.69-5.el7.noarch already installed and latest version
Package perl-MIME-tools-5.505-1.el7.noarch already installed and latest version
Package perl-NetAddr-IP-4.069-3.el7.x86_64 already installed and latest version
Resolving Dependencies
--> Running transaction check
---> Package perl-PerlIO-gzip.x86_64 0:0.19-1.el7 will be installed
---> Package perl-XML-Parser.x86_64 0:2.41-10.el7 will be installed
---> Package perl-XML-SAX.noarch 0:0.99-9.el7 will be installed
---> Package perl-XML-Simple.noarch 0:2.20-5.el7 will be installed
--> Finished Dependency Resolution

Changes in packages about to be updated:


Dependencies Resolved

===============================================================================
 Package                 Arch          Version               Repository   Size
===============================================================================
Installing:
 perl-PerlIO-gzip        x86_64        0.19-1.el7            epel         23 k
 perl-XML-Parser         x86_64        2.41-10.el7           base        223 k
 perl-XML-SAX            noarch        0.99-9.el7            base         63 k
 perl-XML-Simple         noarch        2.20-5.el7            base         74 k

Transaction Summary
===============================================================================
Install  4 Packages

Total download size: 384 k
Installed size: 943 k
Is this ok [y/d/N]: y
Downloading packages:
(1/4): perl-PerlIO-gzip-0.19-1.el7.x86_64.rpm             |  23 kB   00:00     
(2/4): perl-XML-Parser-2.41-10.el7.x86_64.rpm             | 223 kB   00:00     
(3/4): perl-XML-Simple-2.20-5.el7.noarch.rpm              |  74 kB   00:00     
(4/4): perl-XML-SAX-0.99-9.el7.noarch.rpm                 |  63 kB   00:00     
-------------------------------------------------------------------------------
Total                                             1.1 MB/s | 384 kB  00:00     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : perl-XML-Parser-2.41-10.el7.x86_64                          1/4 
  Installing : perl-XML-SAX-0.99-9.el7.noarch                              2/4 
  Installing : perl-XML-Simple-2.20-5.el7.noarch                           3/4 
  Installing : perl-PerlIO-gzip-0.19-1.el7.x86_64                          4/4 
  Verifying  : perl-XML-Simple-2.20-5.el7.noarch                           1/4 
  Verifying  : perl-PerlIO-gzip-0.19-1.el7.x86_64                          2/4 
  Verifying  : perl-XML-SAX-0.99-9.el7.noarch                              3/4 
  Verifying  : perl-XML-Parser-2.41-10.el7.x86_64                          4/4 

Installed:
  perl-PerlIO-gzip.x86_64 0:0.19-1.el7   perl-XML-Parser.x86_64 0:2.41-10.el7  
  perl-XML-SAX.noarch 0:0.99-9.el7       perl-XML-Simple.noarch 0:2.20-5.el7   

Complete!

Nachfolgende rpm-Pakete müssen als Abhängigkeiten zum Skript readdmarcfailure.py zusätzlich installiert werden:

  • MySQL-python - ist im base-Repository von CentOS enthalten

Die Installation der Skriptabhängigkeiten, kann durch ausführen des nachfolgenden Befehls durchgeführt werden:

# yum install MySQL-python
Loaded plugins: changelog, priorities
262 packages excluded due to repository priority protections
Resolving Dependencies
--> Running transaction check
---> Package MySQL-python.x86_64 0:1.2.5-1.el7 will be installed
--> Finished Dependency Resolution

Changes in packages about to be updated:


Dependencies Resolved

================================================================================
 Package               Arch            Version              Repository     Size
================================================================================
Installing:
 MySQL-python          x86_64          1.2.5-1.el7          base           90 k

Transaction Summary
================================================================================
Install  1 Package

Total download size: 90 k
Installed size: 284 k
Is this ok [y/d/N]: y
Downloading packages:
MySQL-python-1.2.5-1.el7.x86_64.rpm                        |  90 kB   00:00     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : MySQL-python-1.2.5-1.el7.x86_64                              1/1 
  Verifying  : MySQL-python-1.2.5-1.el7.x86_64                              1/1 

Installed:
  MySQL-python.x86_64 0:1.2.5-1.el7                                             

Complete!

/etc/opendmarc/mkdmarc

Das Skript mkdmarc erstellt eine Datenbank, Tabellen und die dazugehörigen Felder um DMARC-Reports, die per e-Mail eintreffen in die Datenbank zu transferieren.

:!: HINWEIS - Nachfolgende Änderungen sollten am Skript vorgenommen werden, um die Datenbank, Tabellen und Felder möglichst einfach anlegen zu können

Alle Anpassungen am Skript sind mit

-- # Tachtler

gekennzeichnet.

-- Create database for DMARC data
 
-- Copyright 2012, 2016, Taughannock Networks. All rights reserved.
 
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions
-- are met:
 
-- Redistributions of source code must retain the above copyright
-- notice, this list of conditions and the following disclaimer.
 
-- Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
 
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-- HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-- INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-- BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-- OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-- AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
-- WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
 
-- # Tachtler
CREATE DATABASE IF NOT EXISTS dmarc;
USE dmarc;
 
-- # Tachtler
CREATE TABLE IF NOT EXISTS report (
  serial int(10) unsigned NOT NULL AUTO_INCREMENT,
  mindate timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  maxdate timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  domain varchar(255) NOT NULL,
  org varchar(255) NOT NULL,
  reportid varchar(255) NOT NULL,
  email varchar(255),
  extra_contact_info varchar(255),
  policy_adkim varchar(20),
  policy_aspf varchar(20),
  policy_p varchar(20),
  policy_sp varchar(20),
  policy_pct tinyint unsigned,
  PRIMARY KEY (serial),
  UNIQUE KEY domain (domain,reportid)
);
 
-- Use these commands to change the old IPv4 only DMARC table to the new one
/***
alter table rptrecord modify ip int(10) unsigned;
alter table rptrecord add column ip6 binary(16) after ip;
alter table rptrecord add key serial6(serial,ip6);
***/
 
-- Use these commands to load in the optional IPv6 formatting functions
/***
CREATE FUNCTION inet_6top RETURNS STRING SONAME 'mysql_ip6.so';
CREATE FUNCTION inet_pto6 RETURNS STRING SONAME 'mysql_ip6.so';
***/
 
-- # Tachtler
CREATE TABLE IF NOT EXISTS rptrecord (
  serial int(10) unsigned NOT NULL,
  ip int(10) unsigned,
  ip6 binary(16),
  rcount int(10) unsigned NOT NULL,
  disposition enum('none','quarantine','reject'),
  reason varchar(255),
  dkimdomain varchar(255),
  dkimresult enum('none','pass','fail','neutral','policy','temperror','permerror'),
  spfdomain varchar(255),
  spfresult enum('none','neutral','pass','fail','softfail','temperror','permerror'),
  spf_align enum('fail', 'pass'),
  dkim_align enum('fail', 'pass'),
  identifier_hfrom varchar(255),
  KEY serial (serial,ip),
  KEY serial6 (serial,ip6)
) ENGINE=MyISAM;
 
-- # Tachtler
CREATE TABLE IF NOT EXISTS failure (
  serial int(10) unsigned NOT NULL AUTO_INCREMENT,
  org varchar(255) NOT NULL, -- reported-domain
  bouncedomain varchar(255), -- MAIL FROM bouncebox@bouncedomain
  bouncebox varchar(255),
  fromdomain varchar(255), -- From: frombox@fromdomain
  frombox varchar(255),
  arrival TIMESTAMP,
  sourceip int unsigned, -- inet_aton(source-ip)
  sourceip6 BINARY(16), -- inet_6top(source-ip)
  headers TEXT,
  authres TEXT,
  PRIMARY KEY(serial),
  KEY(sourceip),
  KEY(fromdomain),
  KEY(bouncedomain)
) charset=utf8;
 
-- # Tachtler
-- GRANT all on dmarc.* to dmarc identified by 'xxx';
-- GRANT all on dmarc.* to dmarc@localhost identified by 'xxx';
 
-- # Tachtler - Create new users.
CREATE USER 'dmarc_user'@'192.168.0.70' IDENTIFIED BY 'password';
CREATE USER 'dmarc_user'@'server70.idmz.tachtler.net' IDENTIFIED BY 'password';
 
-- # Tachtler - Grant ALL privileges to new users.
GRANT ALL PRIVILEGES ON *.* TO 'dmarc_user'@'192.168.0.70' IDENTIFIED BY 'password' WITH GRANT OPTION;
GRANT ALL PRIVILEGES ON *.* TO 'dmarc_user'@'server70.idmz.tachtler.net' IDENTIFIED BY 'password' WITH GRANT OPTION;
 
-- # Tachtler - Make sure that priviliges are reloaded.
FLUSH PRIVILEGES;

Nachfolgende Änderungen wurden am Skript durchgeführt:

Erstellen der Datenbank dmarc, wenn diese nocht nicht existieren sollte und anbringen eines ; (Semikolon) hinter USE dmarc.

Austausch des Syntax CREATE TABLE <Tabellenname> gegen CREATE TABLE IF NOT EXISTS <Tabellenname>.

  • -- # Tachtler - Create new users.
    CREATE USER 'dmarc_user'@'192.168.0.70' IDENTIFIED BY 'password';
    CREATE USER 'dmarc_user'@'server70.idmz.tachtler.net' IDENTIFIED BY 'password';

Erstellen von zwei neuen Benutzer, dmarc_user'@'192.168.0.70 und dmarc_user'@'server70.idmz.tachtler.net, welche via IP-Adresse oder FQDN vom Server 192.168.0.70 bzw. server70.idmz.tachtler.net agieren dürfen und ein Passwort besitzen

Den beiden Benutzern dmarc_user'@'192.168.0.70 und dmarc_user'@'server70.idmz.tachtler.net werden alle Privilegien in auf die Datenbank dmarc gewährt.

  • -- # Tachtler - Make sure that priviliges are reloaded.
    FLUSH PRIVILEGES;

Die Benutzerrechte, inklusive der neu angelegten Benutzer mit den entsprechenden Rechten, sollen aktiviert werden, in dem die Rechte neu eingelesen werden sollen.

Die Ausführung des Skriptes und die damit verbunden Anlage der Datenbank, der Tabellen und Felder und der Nutzer, kann durch Ausführung des nachfolgenden Befehls durchgeführt werden, zudem jedoch das Kennwort zum Datenbankbenutzer root erforderlich ist!:

# /usr/bin/mysql -u root -p < /etc/opendmarc/mkdmarc
Enter password:

/usr/local/bin/readdmarc.pl

Das Skript readdmarc.pl liest eine eingehende e-Mail, welche einen Report in einer ZIP-Datei enthält direkt vom Postfix dadurch ein, das Postfix die e-Mail direkt an das Skript übergibt und dieses dann alle Informationen extrahiert und in die Datenbank schreibt.

:!: HINWIES - Anpassungen müssen beim Datenbankzugriff durchgeführt werden!

#!/usr/bin/perl
 
# Script zum automatischen Verarbeiten der DMARC-Reportmails in die mySQL-Datenbank dmarc
# basierend auf den DMARC Reporting scripts (http://www.taugh.com/rddmarc) von John Levine (http://www.johnlevine.com/)
# Über STDIN wird dem Script readdmarc die eMail übergeben, also z.B.: $ readdmarc < mailtext
# 2014-05-15 : V.01 by Django (django@mailserver.guru) - mit freundlicher Unterstützung von Klaus (klaus@tachtler.net)
 
use strict;
use MIME::Parser;
use MIME::Words qw(:all);
use XML::Simple;
use DBI;
use IO::Socket::INET6;
use Socket6;
use Socket qw(:DEFAULT :crlf);
use NetAddr::IP;
use PerlIO::gzip;
#use IO::Compress;
 
my $buffer = '';
my $input = '';
# Debugmeldungen ausgeben? Wenn ja, dann $opt_d = 1
my $opt_d = 0;
my $db_host = "mariadb.idmz.tachtler.net";
my $db_port = "3306";
my $db_name = "dmarc";
my $db_user = "dmarc_user";
my $db_pass = "";
my $dbh = DBI -> connect ("DBI:mysql:database=$db_name;host=$db_host;port=$db_port","$db_user",'geheim') or die "Cannot connect to database\n";
 
while (sysread(STDIN, $input, 1024) > 0) {
    $buffer .= $input;
}
        print "parsing \n";
        my ($zip, $ent, $isgzip);
        my $parser = new MIME::Parser;
        $parser->output_dir("/tmp");
        $ent = $parser->parse_data($buffer);
        my $body = $ent->bodyhandle;
        $zip = $body;
        my $mtype = $ent->mime_type;
        my $subj = decode_mimewords($ent->get('subject'));
        print " $subj";
        # if multipart/whatever, look through the parts to find a ZIP
        if(lc $mtype =~ "multipart/") {
            print "Look through $mtype\n";
            $zip = undef;
            my $npart = $ent->parts;
            for my $n (0..($npart-1)) {
                my $part = $ent->parts($n);
                if(lc $part->mime_type eq "application/gzip") {
                    $zip = $part->bodyhandle;
                    $isgzip = 1;
                    last;
                } elsif(lc $part->mime_type eq "application/zip"
                   or lc $part->mime_type eq "application/x-zip-compressed"
                   or lc $part->mime_type eq "application/octet-stream") {
                    $zip = $part->bodyhandle;
                    last;
                } else {
                    $part->bodyhandle->purge; # not useful
                }
            }
            die "no zip" unless $zip;
        } elsif(lc $mtype ne "application/zip") {
            print "don't understand $mtype\n";
            next;
        }
        if(defined($zip->path)) {
            print "body is in " . $zip->path . "\n" if $opt_d;
        } else {
            print "body is nowhere\n";
            next;
        }
        if($isgzip) {
            open(XML, "<:gzip", $zip->path)
                or die "cannot ungzip $zip->path";
        } else {
            open(XML,"unzip -p " . $zip->path . " |")
                or die "cannot unzip $zip->path";
        }
    my $xml = "";
    $xml .= $_ while <XML>;
    $ent->purge if $ent;
    $zip->purge if $zip;
 
    my $xs = XML::Simple->new();
 
    print "XML is ======\n$xml\n=====\n" if $opt_d;
 
    my $ref = $xs->XMLin($xml, SuppressEmpty => '');
    my %xml = %{$ref};
    my $from = $xml{'report_metadata'}->{'date_range'}->{'begin'};
    my $to = $xml{'report_metadata'}->{'date_range'}->{'end'};
    my $org = $xml{'report_metadata'}->{'org_name'};
    my $id = $xml{'report_metadata'}->{'report_id'};
    my $email = $xml{'report_metadata'}->{'email'};
    my $extra = $xml{'report_metadata'}->{'extra_contact_info'};
    my $domain =  $xml{'policy_published'}->{'domain'};
    my $policy_adkim = $xml{'policy_published'}->{'adkim'};
    my $policy_aspf = $xml{'policy_published'}->{'aspf'};
    my $policy_p = $xml{'policy_published'}->{'p'};
    my $policy_sp = $xml{'policy_published'}->{'sp'};
    my $policy_pct = $xml{'policy_published'}->{'pct'};
    print "report $org ($id) $from to $to for $domain\n" if $opt_d;
    # see if already stored
    my ($xorg, $xid, $serial) = $dbh->selectrow_array(qq{SELECT org,reportid,serial FROM report WHERE reportid=?}, undef, $id);
    if($xorg) {
            print "Already have $xorg $xid, skipped\n";
    }
 
    my $sql = qq{INSERT INTO report(serial,mindate,maxdate,domain,org,reportid,email,extra_contact_info,policy_adkim,policy_aspf,policy_p,policy_sp,policy_pct)
                VALUES(NULL,FROM_UNIXTIME(?),FROM_UNIXTIME(?),?,?,?,?,?,?,?,?,?,?)};
    $sql = qq{REPLACE INTO report(serial,mindate,maxdate,domain,org,reportid,email,extra_contact_info,policy_adkim,policy_aspf,policy_p,policy_sp,policy_pct)
                VALUES('$serial',FROM_UNIXTIME(?),FROM_UNIXTIME(?),?,?,?,?,?,?,?,?,?,?)} if $xorg;
 
    $dbh->do($sql, undef, $from, $to, $domain, $org, $id, $email, $extra, $policy_adkim, $policy_aspf, $policy_p, $policy_sp, $policy_pct)
            or die "cannot make report" . $dbh->errstr;
 
    $serial = $dbh->{'mysql_insertid'} ||  $dbh->{'insertid'} unless $xorg;
    print " serial $serial ";
    my $record = $xml{'record'};
 
 
    sub dorow($$) {
        my ($serial,$recp) = @_;
        my %r = %$recp;
 
        my $ip = $r{'row'}->{'source_ip'};
        my $count = $r{'row'}->{'count'};
        my $disp = $r{'row'}->{'policy_evaluated'}->{'disposition'};
        my $dkim_align = $r{'row'}->{'policy_evaluated'}->{'dkim'};
        my $spf_align = $r{'row'}->{'policy_evaluated'}->{'spf'};
 
        my $identifier_hfrom = $r{'identifiers'}->{'header_from'};
 
        print "\nip $ip, count $count, disp $disp" if $opt_d;
        my ($dkim, $dkimresult, $spf, $spfresult, $reason);
        my $rp = $r{'auth_results'}->{'dkim'};
        printf " rp $rp\n" if $opt_d;
        if(ref $rp eq "HASH") {
            $dkim = $rp->{'domain'};
            $dkim = undef if ref $dkim eq "HASH";
            $dkimresult = $rp->{'result'};
        } else { # array
        # glom sigs together, report first result
            $dkim = join '/',map { my $d = $_->{'domain'}; ref $d eq "HASH"?"": $d } @$rp;
            $dkimresult = $rp->[0]->{'result'};
        }
        $rp = $r{'auth_results'}->{'spf'};
        if(ref $rp eq "HASH") {
            $spf = $rp->{'domain'};
            $spfresult = $rp->{'result'};
        } else { # array
        # glom domains together, report first result
            $spf = join '/',map { my $d = $_->{'domain'}; ref $d eq "HASH"? "": $d } @$rp;
            $spfresult = $rp->[0]->{'result'};
        }
 
        $rp = $r{'row'}->{'policy_evaluated'}->{'reason'};
        if(ref $rp eq "HASH") {
            $reason = $rp->{'type'};
        } else {
            $reason = join '/',map { $_->{'type'} } @$rp;
        }
        #print "ip=$ip, count=$count, disp=$disp, r=$reason,";
        #print "dkim=$dkim/$dkimresult, spf=$spf/$spfresult\n";
        # figure out if it's IPv4 or IPv6
        my ($nip, $iptype, $ipval);
        if($nip = inet_pton(AF_INET, $ip)) {
            $ipval = unpack "N", $nip;
            $iptype = "ip";
        } elsif($nip = inet_pton(AF_INET6, $ip)) {
            $ipval = "X'" . unpack("H*",$nip) . "'";
            $iptype = "ip6";
        } else {
            print "??? mystery ip $ip\n";
            next;
        }
        print "$iptype = $ipval\n" if $opt_d;
 
        print "$iptype = $ipval, spf $spf_align, dkim $dkim_align\n" if $opt_d;
        $dbh->do(qq{INSERT INTO rptrecord(serial,$iptype,rcount,disposition,spf_align,dkim_align,reason,dkimdomain,dkimresult,spfdomain,spfresult,identifier_hfrom)
                  VALUES(?,$ipval,?,?,?,?,?,?,?,?,?,?)},undef, $serial,$count,$disp,$spf_align,$dkim_align,$reason,$dkim,$dkimresult,$spf,$spfresult,$identifier_hfrom)
                or die "cannot insert record " . $dbh->{'mysql_error'};
    } # dorow
 
    if(ref $record eq "HASH") {
        print "single record\n";
        dorow($serial,$record);
    } elsif(ref $record eq "ARRAY") {
        print "multi record\n";
        foreach my $row (@$record) {
            dorow($serial,$row);
        }
    } else {
        print "mystery type " . ref($record) . "\n";
    }

Nachfolgende Änderungen wurden am Skript durchgeführt:

  • my $db_host = "mariadb.idmz.tachtler.net";
    my $db_port = "3306";
    my $db_name = "dmarc";
    my $db_user = "dmarc_user";
    my $db_pass = "";
    my $dbh = DBI -> connect ("DBI:mysql:database=$db_name;host=$db_host;port=$db_port","$db_user",'geheim') or die "Cannot connect to database\n";

Anpassen des Datenbankzugriffs in Bezug auf den Datenbank-Server, Datenbank-Port, den Benutzernamen und das Passwort.

:!: HINWEIS - Die Variable $db_pass wird hier nicht genutzt, sondern das Passwort wird direkt in den Verbindungsaufbau eingetragen, da bei komplexen Passwörtern mit Sonderzeichenkombinationen sonst Probleme auftreten könnten!

Nachfolgender Befehl setzte die Datei- und Besitzrechte für das Skript wie folgt:

# chmod 755 /usr/local/bin/readdmarc.pl

und

# chown root:root /usr/local/bin/readdmarc.pl

/usr/local/bin/readdmarcfailure.py

Das Skript readdmarcfailure.py liest eine eingehende e-Mail, welche einen Fehlerreport in einer ZIP-Datei enthält direkt vom Postfix dadurch ein, das Postfix die e-Mail direkt an das Skript übergibt und dieses dann alle Informationen extrahiert und in die Datenbank schreibt.

:!: HINWIES - Anpassungen müssen beim Datenbankzugriff durchgeführt werden!

#!/usr/bin/python
# $Header: /home/johnl/hack/dmarc/RCS/dmarcfail.py,v 1.2 2015/05/02 20:15:02 johnl Exp $
# parse DMARC failure reports, add it to the mysql database
# optional arguments are names of files containing ARF messages,
# otherwise it reads stdin
 
# Copyright 2012, Taughannock Networks. All rights reserved.
 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
 
# Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
 
# Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
# WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
 
#!/usr/local/bin/python
# parse a DMARC failure report, add it to the mysql database
 
import re
import email
import time
import MySQLdb
 
# Tachtler
# default: db = MySQLdb.connect(user='dmarc',passwd='x',db='dmarc', use_unicode=True)
db = MySQLdb.connect(host='mariadb.idmz.tachtler.net',port=3306,user='dmarc_user',passwd='geheim',db='dmarc',
MySQLdb.paramstyle='format'
 
def dmfail(h,f):
    e = email.message_from_file(h)
    if(e.get_content_type() != "multipart/report"):
        print f,"is not a report"
        return
 
    for p in e.get_payload():
        if(p.get_content_type() == "message/feedback-report"):
            r = email.parser.Parser()
            fr = r.parsestr(p.get_payload()[0].as_string(), True)
            fx = re.search(r'<(.+?)@(.+?)>', fr['original-mail-from'])
            if fx: origbox,origdom = fx.group(1,2)
            else: origbox,origdom = (None,None)
            if 'arrival-date' in fr:
                arr = int(email.utils.mktime_tz(email.utils.parsedate_tz(fr['arrival-date'])))
            else:                       # fake it with message date
                arr = int(email.utils.mktime_tz(email.utils.parsedate_tz(e['date'])))
            authres = fr['authentication-results'] if 'authentication-results' in fr else None
 
        elif(p.get_content_type() == "message/rfc822" or
            p.get_content_type() == "text/rfc822-headers"):
            if p.is_multipart():        # library thinks message/rfc822 is multipart
                m = email.message_from_string(p.get_payload(0).as_string())
            else:    
                m = email.message_from_string(p.get_payload())
            frombox = fromdom = None
            if 'from' in m:
                fx = re.search(r'<(.+)@(.+)>', m['from'])
                if(fx):
                    frombox,fromdom = fx.group(1,2)
                else:
                    t = re.sub(m['from'],r'\s+|\([^)]*\)',"")
                    fx = re.match(r'(.+)@(.+)', t)
                    if(fx):
                        frombox,fromdom = fx.group(1,2)
 
    # OK, parsed it, now add an entry to the database
    #print fr['reported-domain'],origdom,origbox,fromdom,frombox,arr,fr['source-ip'],"==="
    #print m.as_string()
    #print "==="
    c = db.cursor()
    c.execute("""INSERT INTO failure(serial,org,bouncedomain,bouncebox,fromdomain,
        frombox,arrival,sourceip,headers,authres)
        VALUES(NULL,%s,%s,%s,%s,%s,FROM_UNIXTIME(%s),INET_ATON(%s),%s,%s)""",
        (fr['reported-domain'],origdom,origbox,fromdom,frombox,arr,fr['source-ip'],m.as_string(),authres))
    print "Inserted failure report %s" % c.lastrowid
    c.close()
    db.commit()
 
if __name__ == "__main__":
    import sys
 
    if(len(sys.argv) < 2):
        dmfail(sys.stdin,"stdin");
    else:
        for f in sys.argv[1:]:
            h = open(f)
            dmfail(h, f)
            h.close()

Nachfolgende Änderungen wurden am Skript durchgeführt:

  • db = MySQLdb.connect(host='mariadb.idmz.tachtler.net',port=3306,user='dmarc_user',passwd='geheim',db='dmarc', use_unicode=True)

Anpassen des Datenbankzugriffs in Bezug auf den Datenbank-Server, Datenbank-Port, den Benutzernamen und das Passwort.

Nachfolgender Befehl setzte die Datei- und Besitzrechte für das Skript wie folgt:

# chmod 755 /usr/local/bin/readdmarcfailure.py

und

# chown root:root /usr/local/bin/readdmarcfailure.py

/etc/postfix/master.cf

Nachfolgende Definitionen sind erforderlich, damit Postfix das Skript bekannt ist und nutzen kann:

(Nur relevanter Ausschnitt:)

...
# Tachtler
readdmarc      unix  -       n       n       -       1       pipe
  flags=DRhu user=nobody argv=/usr/local/bin/readdmarc.pl
# Tachtler
readdmarcfailure      unix  -       n       n       -       1       pipe
  flags=DRhu user=nobody argv=/usr/local/bin/readdmarcfailure.py      
...

/etc/postfix/main.cf

Nachfolgende Änderungen sind erforderlich, falls noch keine transport_maps verwendet werden:

(Nur relevanter Ausschnitt):

...
# TRANSPORT MAP
#
# See the discussion in the ADDRESS_REWRITING_README document.
 
# Tachtler - new - 
transport_maps = btree:/etc/postfix/transport_maps, $relay_domains
...

/etc/postfix/transport_maps

Zur Weiterleitung von Postfix an die Skripte /usr/local/bin/readddmarc.pl und /usr/local/bin/readddmarcfailure.py, ist es erforderlich diese in der /etc/postfix/transport_maps entsprechend zu definieren, wie nachfolgend gezeigt:

(Komplette Konfigurationsdatei:)

dmarc-aggregate@tachtler.net	readdmarc:	
dmarc-incorrect@tachtler.net	readdmarcfailure:

:!: HINWEIS - Als Trennzeichen zwischen den beiden Einträgen, sollte am besten die [TAB]-Taste verwendet werden, es ist aber auch die Trennung durch [Leerzeichen] möglich!

:!: WICHTIG - Zum Abschluss muss die Konfigurationsdatei in ein Datenbank-Format, z.B. das Format btree mithilfe des Postfix eigenen Befehls postmap umgewandelt werden, wie nachfolgender Befehl zeigt:

# postmap btree:/etc/postfix/transport_maps

Anschließend sollte eine neue Datei mit dem Namen /etc/postfix/transport_maps.db entstanden sein, was mit nachfolgendem Befehl überprüft werden:

# ls -l /etc/postfix/transport_maps*
-rw-r--r-- 1 root root   24 Aug 19 09:51 /etc/postfix/transport_maps
-rw-r--r-- 1 root root 8192 Aug 19 09:51 /etc/postfix/transport_maps.db

**ODER BESSER** /etc/aliases

:!: HINWEIS - Als alternative Weiterleitung von Postfix an die Skripte /usr/local/bin/readddmarc.pl und /usr/local/bin/readddmarcfailure.py, ist es ebenfalls möglich anstelle von /etc/postfix/transport_maps, /etc/postfix/main.cf und /etc/postfix/master.cf diese in der /etc/aliases entsprechend zu definieren, wie nachfolgend gezeigt:

(Nur relevanter Ausschnitt:)

...
dmarc-aggregate:        "|/usr/local/bin/readdmarc.pl"
dmarc-aggregate+badhd:  "|/usr/local/bin/readdmarc.pl"
dmarc-incorrect:        "|/usr/local/bin/readdmarcfailure.py"
dmarc-incorrect+badhd:  "|/usr/local/bin/readdmarcfailure.py"

Anschließend ist sind die Änderungen noch in ein für Postfix besser lesbares Datenbankformat umzuwandeln, was mit nachfolgendem Befehl durchgeführt werden kann:

# newaliases

Konfiguration: DMARC Reports Web GUI

Unter nachfolgenden externen Link

kann eine Web GUI heruntergeladen werden, welche zur Anzeige der Daten, die innerhalb der Datenbank gespeichert sind, genutzt werden kann.

Nachfolgend soll die Konfiguration eines Apache HTTP Servers durchgeführt werden, um die in PHP Net geschrieben DMARC Reports Web GUI zur anzeige zu bringen.

Voraussetzungen

Als Voraussetzung für die Installation von DMARC Reports sind folgende Komponenten erforderlich:

Herunterladen

Bevor die in PHP Net geschriebene DMARC Reports Web GUI DMARC Reports heruntergeladen werden soll, muss zuerst auf dem Server auf dem der Apache HTTP Server läuft ein neues Verzeichnis angelegt werden, was mit nachfolgendem Befehl durchgeführt werden kann:

# mkdir /var/www/dmarcreports

Anschließend kann mit nachfolgendem Befehl die DMARC Reports Web GUI DMARC Reports direkt in das neu erstellte Verzeichnis heruntergeladen und umbenannt werden:

# wget https://github.com/techsneeze/dmarcts-report-viewer/archive/master.zip -P /tmp/
--2017-10-20 09:15:54--  https://github.com/techsneeze/dmarcts-report-viewer/archive/master.zip
Resolving github.com (github.com)... 192.30.253.112, 192.30.253.113
Connecting to github.com (github.com)|192.30.253.112|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://codeload.github.com/techsneeze/dmarcts-report-viewer/zip/master [following]
--2017-10-20 09:15:54--  https://codeload.github.com/techsneeze/dmarcts-report-viewer/zip/master
Resolving codeload.github.com (codeload.github.com)... 192.30.253.121, 192.30.253.120
Connecting to codeload.github.com (codeload.github.com)|192.30.253.121|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 18293 (18K) [application/zip]
Saving to: ‘/tmp/master.zip’

100%[======================================>] 18,293      --.-K/s   in 0.1s    

2017-10-20 09:15:55 (133 KB/s) - ‘/tmp/master.zip’ saved [18293/18293

Anschließend soll das soeben heruntergeladene ZIP-Archiv mit nachfolgendem Befehl entpackt werden:

# unzip /tmp/master.zip -d /var/www/
Archive:  /tmp/master.zip
5d97612e00bcce48d446fa85e19b392d8433376f
   creating: /var/www/dmarcts-report-viewer-master/
 extracting: /var/www/dmarcts-report-viewer-master/.gitignore  
  inflating: /var/www/dmarcts-report-viewer-master/LICENSE  
  inflating: /var/www/dmarcts-report-viewer-master/README.md  
  inflating: /var/www/dmarcts-report-viewer-master/default.css  
  inflating: /var/www/dmarcts-report-viewer-master/dmarcts-report-viewer-config.php.sample  
  inflating: /var/www/dmarcts-report-viewer-master/dmarcts-report-viewer.php

Das so entstandene Verzeichnis

  • /var/www/dmarcts-report-viewer-master/

kann nun mit nachfolgendem Befehl umbenannt werden:

# mv /var/www/dmarcts-report-viewer-master /var/www/dmarcreports

Nachfolgender Befehl zeigt nun den Inhalt des Verzeichnisses /var/www/dmarcreports:

# ls -la /var/www/dmarcreports
total 68
drwxr-xr-x   2 root root   149 Oct  2 20:34 .
drwxr-xr-x. 10 root root  4096 Oct 20 09:21 ..
-rw-r--r--   1 root root  1307 Oct  2 20:34 default.css
-rw-r--r--   1 root root   358 Oct  2 20:34 dmarcts-report-viewer-config.php.sample
-rw-r--r--   1 root root 11254 Oct  2 20:34 dmarcts-report-viewer.php
-rw-r--r--   1 root root    47 Oct  2 20:34 .gitignore
-rw-r--r--   1 root root 35141 Oct  2 20:34 LICENSE
-rw-r--r--   1 root root  2132 Oct  2 20:34 README.md

/var/www/dmarcreports/dmarcts-report-viewer-config.php.

Bevor die in PHP Net geschriebene DMARC Reports Web GUI DMARC Reports genutzt werden kann, ist es noch erforderlich die Beispielkonfigurationsdatei

  • /var/www/dmarcreports/dmarcts-report-viewer-config.php.sample

zu kopieren und die darin befindlichen Variablen anzupassen, damit ein Zugriff auf die Datenbank durch das Skript durchgeführt werden kann, was mit nachfolgendem Befehl durchgeführt werden kann:

# cp -a /var/www/dmarcreports/dmarcts-report-viewer-config.php.sample /var/www/dmarcreports/dmarcts-report-viewer-config.php

(Komplettes Skript)

<?php
 
// ####################################################################
// ### configuration ##################################################
// ####################################################################
 
$dbhost="mariadb.idmz.tachtler.net";
$dbname="dmarc";
$dbuser="dmarc_user";
$dbpass="geheim";
 
$default_lookup = 0;  # 1= on 0=off (on is old behaviour )

?>

Nachfolgende Änderungen wurden am Skript durchgeführt:

  • $dbhost="mariadb.idmz.tachtler.net";
    $dbname="dmarc";
    $dbuser="dmarc_user";
    $dbpass="geheim";

Anpassung der Zugriffsdaten für die Datenbank, wie Datenbank-Server, Benutzername und das Passwort.

  • $default_lookup = 0;  # 1= on 0=off (on is old behaviour )

Standardmäßig keine DNS-Anfragen (lookups) zur Ermittelung der Host-Namen (DNS-Namensauflösung) basierend auf den IP-Adressen durchführen.

:!: HINWEIS - Dies sollte deaktiviert werden, da sonst viele DSN-Anfrage den Seitenaufbau extrem verlangsamen können!

/var/www/dmarcreports/index.php

Abschließend soll noch ein symbolischer Link wie folgt gesetzt werden, damit der Aufruf des PGP-Scripts/Seite /var/www/dmarcreports/dmarcts-report-viewer.php via /var/www/dmarcreports/index.php erfolgen kann. Dies ist nicht unbedingt notwendig, aber schöner und ggf. auch bei Standard Webserver-Konfigurationen besser anzuwenden:

# ln -s /var/www/dmarcreports/dmarcts-report-viewer.php index.php

Abschließend zeigt nachfolgender Befehl nun den aktuellen Inhalt des Verzeichnisses /var/www/dmarcreports:

# ls -la /var/www/dmarcreports
total 76
drwxr-xr-x   2 root root  4096 Oct 20 09:32 .
drwxr-xr-x. 10 root root  4096 Oct 20 09:21 ..
-rw-r--r--   1 root root  1307 Oct  2 20:34 default.css
-rw-r--r--   1 root root   378 Oct 20 07:58 dmarcts-report-viewer-config.php
-rw-r--r--   1 root root   358 Oct  2 20:34 dmarcts-report-viewer-config.php.sample
-rw-r--r--   1 root root 11254 Oct  2 20:34 dmarcts-report-viewer.php
-rw-r--r--   1 root root    47 Oct  2 20:34 .gitignore
lrwxrwxrwx   1 root root    47 Oct 20 09:32 index.php -> /var/www/dmarcreports/dmarcts-report-viewer.php
-rw-r--r--   1 root root 35141 Oct  2 20:34 LICENSE
-rw-r--r--   1 root root  2132 Oct  2 20:34 README.md

/etc/httpd/conf.d/vhost.conf

Es soll ein virtueller Host im Apache HTTP Server eingerichtet werden.

Siehe dazu auch nachfolgende interne Links:

Dazu kann der Inhalt einer möglichen Konfigurationsdatei

  • /etc/httpd/conf.d/dmarcreports.conf

wie nachfolgend dargestellt entsprechend erstellt werden:

(Komplette Konfigurationsdatei)

#
# dmarcreports.tachtler.net (DMARC-Reports for OpenDMARC)
#
<VirtualHost *:80>
        ServerAdmin webmaster@tachtler.net
        ServerName dmarcreports.tachtler.net
        ServerAlias www.dmarcreports.tachtler.net
        ServerPath /
 
        DocumentRoot "/var/www/dmarcreports"
        <Directory "/var/www/dmarcreports">
                Options -Indexes +FollowSymLinks
                # Tachtler (enable for .htaccess file support)
                # AllowOverride AuthConfig
                AllowOverride None
                # Tachtler (enable for unlimited access)
		Require all granted 
        </Directory>
 
        DirectoryIndex index.php
 
        ErrorLog logs/dmarcreports_error.log
        SetEnvIf X-Forwarded-For "^.*\..*\..*\..*" forwarded
        CustomLog logs/dmarcreports_access.log combined env=!forwarded
        CustomLog logs/dmarcreports_access.log combined_proxypass env=forwarded
</VirtualHost>

:!: HINWEIS - Zu beachten sind hier, die Zeilen mit dem Inhalten

  • DirectoryIndex index.php - da hier die erste Seite der Anwendung ein PHP-Script ist!

Nach Durchführung der vorhergehenden Konfigurationsschritte, sollte einem Neustart nichts im Wege stehen und die Apache VHOST-Konfiguration angezogen werden:

# systemctl restart httpd.service

:!: HINWEIS - Es erfolgen keine weiteren Ausgaben, wenn der Start erfolgreich war !

Aufruf DMARC Reports Web GUI

Anschließend kann die DMARC Reports Web GUI DMARC Reports über den Browser aufgerufen werden. Falls bereits Daten in der Datenbank stehen, könnte die Bildschirmausgabe wie folgt aussehen:

DMARC Reports Web GUI

Test Werkzeuge

Nachfolgende externe Links führen zu verschiedenen Test Werkzeugen:

Diese Website verwendet Cookies. Durch die Nutzung der Website stimmen Sie dem Speichern von Cookies auf Ihrem Computer zu. Außerdem bestätigen Sie, dass Sie unsere Datenschutzbestimmungen gelesen und verstanden haben. Wenn Sie nicht einverstanden sind, verlassen Sie die Website.Weitere Information
tachtler/postfix_centos_7_-_opendmarc_anbinden_opendmarc-milter.txt · Zuletzt geändert: 2021/06/12 08:21 von klaus