Mein Jabber Server hat gerade neue Zertifikate bekommen. Die alten waren ausgelaufen…. Ich spare mit die Fingerprints, prüft bitte per TLSA.
So long…
Fragen? Einfach melden.
IT security, FreeBSD, Linux, mail server hardening, post-quantum crypto, DNS, retro computing & hands-on hardware hacks. Privater Tech-Blog seit 2003.
Mein Jabber Server hat gerade neue Zertifikate bekommen. Die alten waren ausgelaufen…. Ich spare mit die Fingerprints, prüft bitte per TLSA.
So long…
Fragen? Einfach melden.
Es gibt inzwischen viele Webtools die TLSA-Records prüfen. Aber wer es einmal von Hand gemacht hat, versteht was dabei passiert. Der Ablauf ist immer gleich: Zertifikat vom Server holen, Hash berechnen, mit dem DNS-Record vergleichen.
Verbindung zum Mailserver aufbauen und das Zertifikat per STARTTLS abholen:
openssl s_client -starttls smtp -connect smtp.kernel-error.de:25 \ -servername smtp.kernel-error.de 2>/dev/null | \ openssl x509 -outform PEM > /tmp/server.crt
Das Zertifikat liegt jetzt in /tmp/server.crt.
Welchen Hash man berechnen muss, hängt vom TLSA-Record ab. Die drei Felder im Record bestimmen das:
| Usage | 0 = CA, 1 = End-Entity (Kette muss gültig sein), 2 = Trust Anchor, 3 = End-Entity (keine Kettenprüfung) |
| Selector | 0 = ganzes Zertifikat, 1 = nur Public Key (SPKI) |
| Matching Type | 0 = exakter Vergleich, 1 = SHA-256, 2 = SHA-512 |
Am häufigsten sieht man 3 1 1 (End-Entity, nur Public Key, SHA-256) oder 3 0 1 (End-Entity, ganzes Zertifikat, SHA-256). Zuerst den TLSA-Record aus dem DNS holen um zu sehen was erwartet wird:
dig _25._tcp.smtp.kernel-error.de TLSA +short
Dann den passenden Hash berechnen. Bei Selector 0 (ganzes Zertifikat) und Matching Type 1 (SHA-256):
# Selector 0 (Full Certificate), SHA-256 openssl x509 -in /tmp/server.crt -outform DER | openssl sha256 # Ausgabe: SHA2-256(stdin)= 94c8e1bd...
Bei Selector 1 (nur SPKI, Public Key) und SHA-256:
# Selector 1 (SPKI), SHA-256 openssl x509 -in /tmp/server.crt -noout -pubkey | \ openssl pkey -pubin -outform DER | openssl sha256
Den berechneten Hash mit dem Wert aus dem TLSA-Record vergleichen. Stimmen sie überein, ist der Record korrekt.
Wer nicht alles von Hand machen will: posttls-finger (Teil von Postfix) prüft den kompletten DANE-Ablauf in einem Schritt:
posttls-finger -t30 -T180 -c -L verbose,summary kernel-error.de
In der Ausgabe steht am Ende entweder Verified TLS connection established (DANE-Prüfung bestanden) oder eine Fehlermeldung mit dem konkreten Problem. Das Tool löst die MX-Records auf, holt den TLSA-Record, baut die TLS-Verbindung auf und vergleicht alles automatisch.
Wer DANE für den eigenen Mailserver einrichten will, findet die Anleitung unter Postfix mit DANE und DNSSEC absichern. Die Grundlagen zu DANE und TLSA-Records erklärt der Beitrag DNSSEC und DANE: TLS-Zertifikate mit TLSA-Records absichern. Fragen? Einfach melden.
Veraltet: Das DNSSEC Validator Browser-Plugin wird nicht mehr gepflegt und ist in aktuellen Firefox-/Chrome-Versionen nicht mehr verfügbar. DNSSEC-Validierung sollte auf dem DNS-Resolver stattfinden, nicht im Browser.
Na schau mal einer an… Das Browser Plugin für den DNSSEC Validator kann nun auch DANE / TLSA und er zeigt es angenehm übersichtlich an.
https://www.dnssec-validator.cz/pages/download.html
Ist in jedem Fall noch mal einen Klick wert.
Bei mir ist die Frage aufgeschlagen, was man in seine BIND-Zone schreiben muss, wenn man Exchange Online, Lync/Skype for Business oder Office 365 nutzen will. Microsoft zeigt einem während der Einrichtung eine Tabelle mit DNS-Einträgen an. Für Webinterface-Hoster kein Problem, für BIND-Admins aber erstmal Übersetzungsarbeit.
Ich liebe es, wenn Microsoft solche Dinge ins Deutsche übersetzt. „Verweist auf die Adresse“ statt „Points to“. „Gültigkeitsdauer“ statt „TTL“. Aber gut.
Hinweis: Lync heißt inzwischen Teams, die SRV-Records für Lync Federation sind aber weiterhin nötig, solange Skype for Business im Tenant aktiv ist.
Für eine Domain (hier kernel-error.com als Beispiel) werden folgende Records benötigt:
# Exchange Online MX 0 kernel-error-com0i.mail.protection.outlook.com TXT "v=spf1 include:spf.protection.outlook.com -all" CNAME autodiscover → autodiscover.outlook.com # Lync / Skype for Business / Teams SRV _sip._tls 443 1 100 sipdir.online.lync.com SRV _sipfederationtls._tcp 5061 1 100 sipfed.online.lync.com CNAME sip → sipdir.online.lync.com CNAME lyncdiscover → webdir.online.lync.com # Office 365 allgemein TXT "MS=ms12345678" (Domain-Verifikation) CNAME msoid → clientconfig.microsoftonline-p.net
Und so sieht das dann als BIND-Zone aus:
$ORIGIN .
$TTL 86400
kernel-error.com IN SOA ns1.kernel-error.de. root.kernel-error.de. (
2014010101 ; serial
15000 ; refresh
1800 ; retry
604800 ; expire
86400 ; minimum
)
NS ns1.kernel-error.de.
NS ns2.kernel-error.org.
$TTL 1H
IN TXT "MS=ms12345678"
IN TXT "v=spf1 include:spf.protection.outlook.com -all"
IN MX 0 kernel-error-com0i.mail.protection.outlook.com.
_sip._tls.kernel-error.com. IN SRV 1 100 443 sipdir.online.lync.com.
_sipfederationtls._tcp.kernel-error.com. IN SRV 1 100 5061 sipfed.online.lync.com.
$ORIGIN kernel-error.com.
$TTL 1H
autodiscover IN CNAME autodiscover.outlook.com.
sip IN CNAME sipdir.online.lync.com.
lyncdiscover IN CNAME webdir.online.lync.com.
msoid IN CNAME clientconfig.microsoftonline-p.net.
Die gesetzten Records lassen sich mit dig prüfen:
dig +nocmd +noall +answer kernel-error.com IN MX @ns1.kernel-error.de dig +nocmd +noall +answer kernel-error.com IN TXT @ns1.kernel-error.de dig +nocmd +noall +answer _sip._tls.kernel-error.com IN SRV @ns1.kernel-error.de dig +nocmd +noall +answer autodiscover.kernel-error.com IN CNAME @ns1.kernel-error.de
Viel Spaß mit den Microsoft-Online-Produkten. Fragen? Einfach melden.
Google schaut in die E-Mails seiner gmail Nutzer, wertet den Inhalt aus und zeigt dem Nutzer dann maßgeschneiderte Werbung…. OK, ist jetzt nichts Neues! Nun hat Google aber bekanntgegeben dass sie zumindest keine Schüler mehr in dieser Form abschnorcheln will.

Zu lesen ist es hier: http://googleenterprise.blogspot.de/2014/04/protecting-students-with-google-apps.html
Toll, oder? Super… Alle freuen sich…. ALLE FREUEN SICH! Leute, schlaft ihr alle? Google durchsucht, nach eigenen Angaben, keine E-Mail Accounts von Schülern mehr und SCHÜTZT sie somit vor Werbung welche zu E-Mail Inhalten passt!!!!!
Das ist so als wenn der Postbote die Briefe liest und immer direkt das passende Werbeprospekt beilegt. Warum freuen sich alle? Ich verstehe es nicht, tut mir leid!
Fragen? Einfach melden.
Oh, da ging ja heute ein kleines Beben durchs Internet. Eine weitere RIR ist schon am letzten /8, die ARIN…. Ich bin ja fast vom Glauben abgefallen, das hat tatsächlich noch einige Leute überrascht.
Hallo!?!?! 2014 und so?!?!?
https://www.arin.net/resources/request/ipv4_countdown.html
Wann werden die Herren wohl mal wach?!? Jetzt hat also nur noch die AFRINIC etwas Luft.
Fragen? Einfach melden.
Fragen? Einfach melden.
Mailgraph von David Schweikert ist ein simples Perl-Script, das Postfix-Logdateien parst und daraus RRDtool-Graphen erzeugt. Empfangene, gesendete, abgelehnte Mails auf einen Blick. Für kleinere Mailserver oder Testsysteme reicht das völlig.
Was mailgraph nicht kann: SPF-, DMARC– und DKIM-Ergebnisse darstellen. Wie viele Mails bestehen den SPF-Check? Wie oft schlägt DMARC fehl? Gibt es einen Trend? Ich habe zwar einen existierenden SPF-Patch für mailgraph gefunden, wollte aber alle drei Protokolle in einem Aufwasch.
Die folgenden Patches erweitern mailgraph 1.14 um drei zusätzliche Graphen: SPF (pass/none/fail), DMARC (pass/none/fail) und DKIM (pass/none/fail). Ausgelegt für Postfix mit postfix-policyd-spf, opendkim und opendmarc.
Auf einem Debian-System müssen zwei Dateien ersetzt werden: /usr/sbin/mailgraph (der Daemon) und /usr/lib/cgi-bin/mailgraph.cgi (die Weboberfläche). Vorher sichern.
Die Patches als Download: mailgraph-dmarc-spf-dkim.patch.tar.gz
Der Daemon-Patch fügt neun RRD-Datasources hinzu (spfnone, spffail, spfpass, dmarcnone, dmarcfail, dmarcpass, dkimnone, dkimfail, dkimpass) und parst die Logzeilen von policy-spf, opendmarc und opendkim:
*** mailgraph 2012-06-17 00:00:00.000000000 +0200
--- mailgraph 2014-04-24 08:59:58.964977886 +0200
***************
*** 4,9 ****
--- 4,10 ----
# copyright (c) 2000-2007 ETH Zurich
# copyright (c) 2000-2007 David Schweikert <david@schweikert.ch>
# released under the GNU General Public License
+ # with dkim-, dmarc, spf-patch Sebastian van de Meer <kernel-error@kernel-error.de>
######## Parse::Syslog 1.09 (automatically embedded) ########
package Parse::Syslog;
***************
*** 382,388 ****
my $rrd_greylist = "mailgraph_greylist.rrd";
my $year;
my $this_minute;
! my %sum = ( sent => 0, received => 0, bounced => 0, rejected => 0, virus => 0, spam => 0, greylisted => 0, delayed => 0);
my $rrd_inited=0;
my %opt = ();
--- 383,389 ----
my $rrd_greylist = "mailgraph_greylist.rrd";
my $year;
my $this_minute;
! my %sum = ( sent => 0, received => 0, bounced => 0, rejected => 0, spfnone => 0, spffail => 0, spfpass => 0, dmarcnone => 0, dmarcfail => 0, dmarcpass => 0, dkimnone => 0, dkimfail => 0, dkimpass => 0, virus => 0, spam => 0, greylisted => 0, delayed => 0);
my $rrd_inited=0;
my %opt = ();
***************
*** 396,401 ****
--- 397,411 ----
sub event_rejected($);
sub event_virus($);
sub event_spam($);
+ sub event_spfnone($);
+ sub event_spffail($);
+ sub event_spfpass($);
+ sub event_dmarcnone($);
+ sub event_dmarcfail($);
+ sub event_dmarcpass($);
+ sub event_dkimnone($);
+ sub event_dkimfail($);
+ sub event_dkimpass($);
sub event_greylisted($);
sub event_delayed($);
sub init_rrd($);
***************
*** 533,538 ****
--- 543,557 ----
'DS:recv:ABSOLUTE:'.($rrdstep*2).':0:U',
'DS:bounced:ABSOLUTE:'.($rrdstep*2).':0:U',
'DS:rejected:ABSOLUTE:'.($rrdstep*2).':0:U',
+ 'DS:spfnone:ABSOLUTE:'.($rrdstep*2).':0:U',
+ 'DS:spffail:ABSOLUTE:'.($rrdstep*2).':0:U',
+ 'DS:spfpass:ABSOLUTE:'.($rrdstep*2).':0:U',
+ 'DS:dmarcnone:ABSOLUTE:'.($rrdstep*2).':0:U',
+ 'DS:dmarcfail:ABSOLUTE:'.($rrdstep*2).':0:U',
+ 'DS:dmarcpass:ABSOLUTE:'.($rrdstep*2).':0:U',
+ 'DS:dkimnone:ABSOLUTE:'.($rrdstep*2).':0:U',
+ 'DS:dkimfail:ABSOLUTE:'.($rrdstep*2).':0:U',
+ 'DS:dkimpass:ABSOLUTE:'.($rrdstep*2).':0:U',
"RRA:AVERAGE:0.5:$day_steps:$realrows", # day
"RRA:AVERAGE:0.5:$week_steps:$realrows", # week
"RRA:AVERAGE:0.5:$month_steps:$realrows", # month
***************
*** 614,619 ****
--- 633,649 ----
event($time, 'bounced');
}
}
+ elsif ($prog eq 'policy-spf') {
+ if ($text =~ /Received-SPF: none/) {
+ event($time, 'spfnone');
+ }
+ elsif($text =~ /Received-SPF: pass/) {
+ event($time, 'spfpass');
+ }
+ elsif($text =~ /Received-SPF:/) {
+ event($time, 'spffail');
+ }
+ }
elsif($prog eq 'local') {
if($text =~ /\bstatus=bounced\b/) {
event($time, 'bounced');
***************
*** 862,867 ****
--- 892,919 ----
event($time, 'virus');
}
}
+ elsif ($prog eq 'opendmarc') {
+ if ($text =~ /pass/) {
+ event($time, 'dmarcpass');
+ }
+ elsif($text =~ /none/) {
+ event($time, 'dmarcnone');
+ }
+ elsif($text =~ /fail/) {
+ event($time, 'dmarcfail');
+ }
+ }
+ elsif ($prog eq 'opendkim') {
+ if ($text =~ /DKIM verification successful/) {
+ event($time, 'dkimpass');
+ }
+ elsif($text =~ /no signature data/) {
+ event($time, 'dkimnone');
+ }
+ elsif($text =~ /bad signature data/) {
+ event($time, 'dkimfail');
+ }
+ }
elsif($prog eq 'avmilter') {
# AntiVir Milter
if($text =~ /^Alert!/) {
***************
*** 918,931 ****
return 1 if $m == $this_minute;
return 0 if $m < $this_minute;
! print "update $this_minute:$sum{sent}:$sum{received}:$sum{bounced}:$sum{rejected}:$sum{virus}:$sum{spam}:$sum{greylisted}:$sum{delayed}\n" if $opt{verbose};
! RRDs::update $rrd, "$this_minute:$sum{sent}:$sum{received}:$sum{bounced}:$sum{rejected}" unless $opt{'no-mail-rrd'};
RRDs::update $rrd_virus, "$this_minute:$sum{virus}:$sum{spam}" unless $opt{'no-virus-rrd'};
RRDs::update $rrd_greylist, "$this_minute:$sum{greylisted}:$sum{delayed}" unless $opt{'no-greylist-rrd'};
if($m > $this_minute+$rrdstep) {
for(my $sm=$this_minute+$rrdstep;$sm<$m;$sm+=$rrdstep) {
! print "update $sm:0:0:0:0:0:0:0:0 (SKIP)\n" if $opt{verbose};
! RRDs::update $rrd, "$sm:0:0:0:0" unless $opt{'no-mail-rrd'};
RRDs::update $rrd_virus, "$sm:0:0" unless $opt{'no-virus-rrd'};
RRDs::update $rrd_greylist, "$sm:0:0" unless $opt{'no-greylist-rrd'};
}
--- 970,983 ----
return 1 if $m == $this_minute;
return 0 if $m < $this_minute;
! print "update $this_minute:$sum{sent}:$sum{received}:$sum{bounced}:$sum{rejected}:$sum{spfnone}:$sum{spffail}:$sum{spfpass}:$sum{dmarcnone}:$sum{dmarcfail}:$sum{dmarcpass}:$sum{dkimnone}:$sum{dkimfail}:$sum{dkimpass}:$sum{virus}:$sum{spam}:$sum{greylisted}:$sum{delayed}\n" if $opt{verbose};
! RRDs::update $rrd, "$this_minute:$sum{sent}:$sum{received}:$sum{bounced}:$sum{rejected}:$sum{spfnone}:$sum{spffail}:$sum{spfpass}:$sum{dmarcnone}:$sum{dmarcfail}:$sum{dmarcpass}:$sum{dkimnone}:$sum{dkimfail}:$sum{dkimpass}" unless $opt{'no-mail-rrd'};
RRDs::update $rrd_virus, "$this_minute:$sum{virus}:$sum{spam}" unless $opt{'no-virus-rrd'};
RRDs::update $rrd_greylist, "$this_minute:$sum{greylisted}:$sum{delayed}" unless $opt{'no-greylist-rrd'};
if($m > $this_minute+$rrdstep) {
for(my $sm=$this_minute+$rrdstep;$sm<$m;$sm+=$rrdstep) {
! print "update $sm:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0 (SKIP)\n" if $opt{verbose};
! RRDs::update $rrd, "$sm:0:0:0:0:0:0:0:0:0:0:0:0:0" unless $opt{'no-mail-rrd'};
RRDs::update $rrd_virus, "$sm:0:0" unless $opt{'no-virus-rrd'};
RRDs::update $rrd_greylist, "$sm:0:0" unless $opt{'no-greylist-rrd'};
}
***************
*** 935,940 ****
--- 987,1001 ----
$sum{received}=0;
$sum{bounced}=0;
$sum{rejected}=0;
+ $sum{spfnone}=0;
+ $sum{spffail}=0;
+ $sum{spfpass}=0;
+ $sum{dmarcnone}=0;
+ $sum{dmarcfail}=0;
+ $sum{dmarcpass}=0;
+ $sum{dkimnone}=0;
+ $sum{dkimfail}=0;
+ $sum{dkimpass}=0;
$sum{virus}=0;
$sum{spam}=0;
$sum{greylisted}=0;
Der CGI-Patch erzeugt die drei neuen Graphen (SPF, DMARC, DKIM) und bindet sie in die HTML-Ausgabe ein. Jeder Graph zeigt pass als Fläche, none als Stack und fail als Linie:
*** mailgraph.cgi 2012-06-17 00:00:00.000000000 +0200
--- mailgraph.cgi 2014-04-24 09:03:11.917988368 +0200
***************
*** 4,9 ****
--- 4,10 ----
# copyright (c) 2000-2007 ETH Zurich
# copyright (c) 2000-2007 David Schweikert <david@schweikert.ch>
# released under the GNU General Public License
+ # with dkim-, dmarc, spf-patch Sebastian van de Meer <kernel-error@kernel-error.de>
use RRDs;
use POSIX qw(uname);
***************
*** 15,21 ****
--- 16,25 ----
my $xpoints = 540;
my $points_per_sample = 3;
my $ypoints = 160;
+ my $ypoints_spf = 96;
my $ypoints_err = 96;
+ my $ypoints_dmarc = 96;
+ my $ypoints_dkim = 96;
my $ypoints_grey = 96;
my $rrd = '/var/lib/mailgraph/mailgraph.rrd';
my $rrd_virus = '/var/lib/mailgraph/mailgraph_virus.rrd';
***************
*** 32,37 ****
--- 36,50 ----
sent => '000099',
received => '009900',
rejected => 'AA0000',
+ spfnone => '000AAA',
+ spffail => '12FF0A',
+ spfpass => 'D15400',
+ dmarcnone => 'FFFF00',
+ dmarcfail => 'FF00EA',
+ dmarcpass => '00FFD5',
+ dkimnone => '3013EC',
+ dkimfail => '006B3A',
+ dkimpass => '491503',
bounced => '000000',
virus => 'DDBB00',
spam => '999999',
***************
*** 154,159 ****
--- 167,292 ----
);
}
+ sub graph_spf($)
+ {
+ my ($range, $file) = @_;
+ my $step = $range*$points_per_sample/$xpoints;
+ rrd_graph($range, $file, $ypoints_spf,
+ "DEF:spfpass=$rrd:spfpass:AVERAGE",
+ "DEF:mspfpass=$rrd:spfpass:MAX",
+ "CDEF:rspfpass=spfpass,60,*",
+ "CDEF:dspfpass=spfpass,UN,0,spfpass,IF,$step,*",
+ "CDEF:sspfpass=PREV,UN,dspfpass,PREV,IF,dspfpass,+",
+ "CDEF:rmspfpass=mspfpass,60,*",
+ "AREA:rspfpass#$color{spfpass}:SPF pass",
+ 'GPRINT:sspfpass:MAX:total\: %8.0lf msgs',
+ 'GPRINT:rspfpass:AVERAGE:avg\: %5.2lf msgs/min',
+ 'GPRINT:rmspfpass:MAX:max\: %4.0lf msgs/min\l',
+ "DEF:spfnone=$rrd:spfnone:AVERAGE",
+ "DEF:mspfnone=$rrd:spfnone:MAX",
+ "CDEF:rspfnone=spfnone,60,*",
+ "CDEF:dspfnone=spfnone,UN,0,spfnone,IF,$step,*",
+ "CDEF:sspfnone=PREV,UN,dspfnone,PREV,IF,dspfnone,+",
+ "CDEF:rmspfnone=mspfnone,60,*",
+ "STACK:rspfnone#$color{spfnone}:SPF none",
+ 'GPRINT:sspfnone:MAX:total\: %8.0lf msgs',
+ 'GPRINT:rspfnone:AVERAGE:avg\: %5.2lf msgs/min',
+ 'GPRINT:rmsrspfnone:MAX:max\: %4.0lf msgs/min\l',
+ "DEF:spffail=$rrd:spffail:AVERAGE",
+ "DEF:mspffail=$rrd:spffail:MAX",
+ "CDEF:rspffail=spffail,60,*",
+ "CDEF:dspffail=spffail,UN,0,spffail,IF,$step,*",
+ "CDEF:sspffail=PREV,UN,dspffail,PREV,IF,dspffail,+",
+ "CDEF:rmspffail=mspffail,60,*",
+ "LINE2:rspffail#$color{spffail}:SPF fail",
+ 'GPRINT:sspffail:MAX:total\: %8.0lf msgs',
+ 'GPRINT:rspffail:AVERAGE:avg\: %5.2lf msgs/min',
+ 'GPRINT:rmspffail:MAX:max\: %4.0lf msgs/min\l',
+ );
+ }
+
+ sub graph_dmarc($)
+ {
+ my ($range, $file) = @_;
+ my $step = $range*$points_per_sample/$xpoints;
+ rrd_graph($range, $file, $ypoints_dmarc,
+ "DEF:dmarcpass=$rrd:dmarcpass:AVERAGE",
+ "DEF:mdmarcpass=$rrd:dmarcpass:MAX",
+ "CDEF:rdmarcpass=dmarcpass,60,*",
+ "CDEF:ddmarcpass=dmarcpass,UN,0,dmarcpass,IF,$step,*",
+ "CDEF:sdmarcpass=PREV,UN,ddmarcpass,PREV,IF,ddmarcpass,+",
+ "CDEF:rmdmarcpass=mdmarcpass,60,*",
+ "AREA:rdmarcpass#$color{dmarcpass}:DMARC pass",
+ 'GPRINT:sdmarcpass:MAX:total\: %8.0lf msgs',
+ 'GPRINT:rdmarcpass:AVERAGE:avg\: %5.2lf msgs/min',
+ 'GPRINT:rmdmarcpass:MAX:max\: %4.0lf msgs/min\l',
+ "DEF:dmarcnone=$rrd:dmarcnone:AVERAGE",
+ "DEF:mdmarcnone=$rrd:dmarcnone:MAX",
+ "CDEF:rdmarcnone=dmarcnone,60,*",
+ "CDEF:ddmarcnone=dmarcnone,UN,0,dmarcnone,IF,$step,*",
+ "CDEF:sdmarcnone=PREV,UN,ddmarcnone,PREV,IF,ddmarcnone,+",
+ "CDEF:rmdmarcnone=mdmarcnone,60,*",
+ "STACK:rdmarcnone#$color{dmarcnone}:DMARC none",
+ 'GPRINT:sdmarcnone:MAX:total\: %8.0lf msgs',
+ 'GPRINT:rdmarcnone:AVERAGE:avg\: %5.2lf msgs/min',
+ 'GPRINT:rmdmarcnone:MAX:max\: %4.0lf msgs/min\l',
+ "DEF:dmarcfail=$rrd:dmarcfail:AVERAGE",
+ "DEF:mdmarcfail=$rrd:dmarcfail:MAX",
+ "CDEF:rdmarcfail=dmarcfail,60,*",
+ "CDEF:ddmarcfail=dmarcfail,UN,0,dmarcfail,IF,$step,*",
+ "CDEF:sdmarcfail=PREV,UN,ddmarcfail,PREV,IF,ddmarcfail,+",
+ "CDEF:rmdmarcfail=mdmarcfail,60,*",
+ "LINE2:rdmarcfail#$color{dmarcfail}:DMARC fail",
+ 'GPRINT:sdmarcfail:MAX:total\: %8.0lf msgs',
+ 'GPRINT:rdmarcfail:AVERAGE:avg\: %5.2lf msgs/min',
+ 'GPRINT:rmdmarcfail:MAX:max\: %4.0lf msgs/min\l',
+ );
+ }
+
+ sub graph_dkim($)
+ {
+ my ($range, $file) = @_;
+ my $step = $range*$points_per_sample/$xpoints;
+ rrd_graph($range, $file, $ypoints_dkim,
+ "DEF:dkimpass=$rrd:dkimpass:AVERAGE",
+ "DEF:mdkimpass=$rrd:dkimpass:MAX",
+ "CDEF:rdkimpass=dkimpass,60,*",
+ "CDEF:ddkimpass=dkimpass,UN,0,dkimpass,IF,$step,*",
+ "CDEF:sdkimpass=PREV,UN,ddkimpass,PREV,IF,ddkimpass,+",
+ "CDEF:rmdkimpass=mdkimpass,60,*",
+ "AREA:rdkimpass#$color{dkimpass}:DKIM pass",
+ 'GPRINT:sdkimpass:MAX:total\: %8.0lf msgs',
+ 'GPRINT:rdkimpass:AVERAGE:avg\: %5.2lf msgs/min',
+ 'GPRINT:rmdkimpass:MAX:max\: %4.0lf msgs/min\l',
+ "DEF:dkimnone=$rrd:dkimnone:AVERAGE",
+ "DEF:mdkimnone=$rrd:dkimnone:MAX",
+ "CDEF:rdkimnone=dkimnone,60,*",
+ "CDEF:ddkimnone=dkimnone,UN,0,dkimnone,IF,$step,*",
+ "CDEF:sdkimnone=PREV,UN,ddkimnone,PREV,IF,ddkimnone,+",
+ "CDEF:rmdkimnone=mdkimnone,60,*",
+ "STACK:rdkimnone#$color{dkimnone}:DKIM none",
+ 'GPRINT:sdkimnone:MAX:total\: %8.0lf msgs',
+ 'GPRINT:rdkimnone:AVERAGE:avg\: %5.2lf msgs/min',
+ 'GPRINT:rmdkimnone:MAX:max\: %4.0lf msgs/min\l',
+ "DEF:dkimfail=$rrd:dkimfail:AVERAGE",
+ "DEF:mdkimfail=$rrd:dkimfail:MAX",
+ "CDEF:rdkimfail=dkimfail,60,*",
+ "CDEF:ddkimfail=dkimfail,UN,0,dkimfail,IF,$step,*",
+ "CDEF:sdkimfail=PREV,UN,ddkimfail,PREV,IF,ddkimfail,+",
+ "CDEF:rmdkimfail=mdkimfail,60,*",
+ "LINE2:rdkimfail#$color{dkimfail}:DKIM fail",
+ 'GPRINT:sdkimfail:MAX:total\: %8.0lf msgs',
+ 'GPRINT:rdkimfail:AVERAGE:avg\: %5.2lf msgs/min',
+ 'GPRINT:rmdkimfail:MAX:max\: %4.0lf msgs/min\l',
+ );
+ }
+
[...]
+ print "<img src=\"$scriptname?${n}-s\" alt=\"mailgraph\"/></p>\n";
+ print "<img src=\"$scriptname?${n}-d\" alt=\"mailgraph\"/></p>\n";
+ print "<img src=\"$scriptname?${n}-k\" alt=\"mailgraph\"/></p>\n";
[...]
+ elsif($img =~ /^(\d+)-s$/) {
+ my $file = "$tmp_dir/$uri/mailgraph_$1_spf.png";
+ graph_spf($graphs[$1]{seconds}, $file);
+ send_image($file);
+ }
+ elsif($img =~ /^(\d+)-d$/) {
+ my $file = "$tmp_dir/$uri/mailgraph_$1_dmarc.png";
+ graph_dmarc($graphs[$1]{seconds}, $file);
+ send_image($file);
+ }
+ elsif($img =~ /^(\d+)-k$/) {
+ my $file = "$tmp_dir/$uri/mailgraph_$1_dkim.png";
+ graph_dkim($graphs[$1]{seconds}, $file);
+ send_image($file);
+ }
Der Daemon-Patch erweitert die RRD-Datenbank um neun Datasources und erkennt im Syslog die Ausgaben von drei Diensten:
| Dienst | Log-Pattern | Ergebnis |
| policy-spf | Received-SPF: pass/none/* | spfpass, spfnone, spffail |
| opendmarc | pass/none/fail | dmarcpass, dmarcnone, dmarcfail |
| opendkim | DKIM verification successful / no signature data / bad signature data | dkimpass, dkimnone, dkimfail |
Der CGI-Patch erzeugt drei neue Graphen unterhalb der bestehenden. Jeder zeigt pass als Fläche, none als Stack darüber und fail als rote Linie. Die Zeiträume (Tag, Woche, Monat, Jahr) werden wie bei den Standard-Graphen automatisch erzeugt.
Die Patches sind für mailgraph 1.14 geschrieben. Weil mailgraph die RRD-Datasources beim ersten Start festlegt, muss nach dem Patchen die bestehende mailgraph.rrd gelöscht werden. Die alten Daten gehen dabei verloren, die neuen Graphen fangen bei Null an.
Es gibt auch einen separaten Patch für DANE-Graphen in mailgraph.
Wer heute mit dem Monitoring anfängt: rspamd bringt eine eigene Weboberfläche mit und prüft SPF, DKIM und DMARC in einem Durchgang. Zusammen mit Prometheus und Grafana bekommt man deutlich flexiblere Dashboards als mit RRDtool. Mailgraph bleibt trotzdem nützlich wenn man schnell was Schlankes braucht, das ohne externe Abhängigkeiten läuft.
Fragen oder Verbesserungsvorschläge? Einfach melden.
Man ist das hässlich… Da sitze ich hier an einem Sabayon System und bekomme keine saubere Uhrzeit. Zwar sollte ntpd.service beim Start die Uhrzeit abgleichen.. Der Dienst verkackt aber seinen Starten, weil er „noch“ kein Netzwerk hat. Dabei ist sogar hinterlegt dass es er erst nach dem Netzwerkmanager startet, denn noch scheint der Netzwerkmanager so schnell keine Verbindung zu bekommen. Der initiale Abgleich vom ntpd.service schlägt daher fehlt, der Dienst bleibt aus und es probiert es auch später nicht mehr. Grütze..
$ systemctl --failed UNIT LOAD ACTIVE SUB DESCRIPTION ntpdate.service loaded failed failed Set time via NTP using ntpdate LOAD = Reflects whether the unit definition was properly loaded. ACTIVE = The high-level unit activation state, i.e. generalization of SUB. SUB = The low-level unit activation state, values depend on unit type. 1 loaded units listed. Pass --all to see loaded but inactive units, too. To show all installed unit files use 'systemctl list-unit-files'.
Ich lasse jetzt mal das Bughunting und setzte einfach auf chrony. Nur für Zeitsync ist das ok für mich!
$ equo search chrony ╠ @@ Suche... ╠ @@ Paket: net-misc/chrony-1.29.1 Branch: 5, [sabayonlinux.org] ╠ Verfügbar: Version: 1.29.1 ~ tag: NoTag ~ Version: 0 ╠ Installiert: Version: Nicht installiert ~ tag: n/a ~ Version: n/a ╠ Slot: 0 ╠ Homepage: http://chrony.tuxfamily.org/ ╠ Beschreibung: NTP client and server programs ╠ Lizenz: GPL-2 ╠ Schlüsselwörter: chrony ╠ Gefunden: 1 Eintrag
Gefunde und dann installieren….
$ equo install chrony ╠ @@ Berechne Abhängigkeiten... ╠ ## [N] [sabayonlinux.org] dev-libs/libedit-20130712.3.1|0 ╠ ## [N] [sabayonlinux.org] net-misc/chrony-1.29.1|0 ╠ @@ Pakete die installiert/aktualisiert/entfernt werden müssen: 2 ╠ @@ Pakete die entfernt werden müssen: 0 ╠ @@ Download Größe: 413.1kB ╠ @@ Benutzter Festplattenspeicher: 715.6kB ╠ @@ Du brauchst zumindest: 1.5MB freien Speicherplatz ╠ ::: >>> (1/1) 2 Pakete ╠ ## downloaden: 2 Pakete ╠ ## ( mirror #1 ) [dev-libs:libedit-20130712.3.1.3556f679066ec7d787573f08ac74ef24922243b2~0.tbz2] @ http://na.mirror.garr.it ╠ ## ( mirror #1 ) [net-misc:chrony-1.29.1.98ee02f66d56c49e1fdc4d713b2cea5ff23d775e~0.tbz2] @ http://na.mirror.garr.it ╠ ## Sammeldownload: 2 Artikel ╠ # [1] na.mirror.garr.it => dev-libs:libedit-20130712.3.1.3556f679066ec7d787573f08ac74ef24922243b2~0.tbz2 ╠ # [2] na.mirror.garr.it => net-misc:chrony-1.29.1.98ee02f66d56c49e1fdc4d713b2cea5ff23d775e~0.tbz2 ╠ ## Überprüfe Paketprüfsumme... ╠ ## Überprüfe Paketprüfsumme... ╠ : [net-misc:chrony-1.29.1.98ee02f66d56c49e1fdc4d713b2cea5ff23d775e~0.tbz2] GPG validated ╠ : [dev-libs:libedit-20130712.3.1.3556f679066ec7d787573f08ac74ef24922243b2~0.tbz2] GPG validated ╠ : [net-misc:chrony-1.29.1.98ee02f66d56c49e1fdc4d713b2cea5ff23d775e~0.tbz2] SHA1 validated ╠ : SHA256 deaktiviert ╠ : [dev-libs:libedit-20130712.3.1.3556f679066ec7d787573f08ac74ef24922243b2~0.tbz2] SHA1 validated ╠ : SHA512 deaktiviert ╠ : SHA256 deaktiviert ╠ : SHA512 deaktiviert ╠ ## ( mirror #1 ) [dev-libs:libedit-20130712.3.1.3556f679066ec7d787573f08ac74ef24922243b2~0.tbz2] erfolgreich @ http://na.mirror.garr.it ╠ ## ( mirror #1 ) [net-misc:chrony-1.29.1.98ee02f66d56c49e1fdc4d713b2cea5ff23d775e~0.tbz2] erfolgreich @ http://na.mirror.garr.it ╠ ## angehäufte Transferrate: 658.3kB/Sekunde ╠ +++ >>> (1/2) dev-libs/libedit-20130712.3.1 ╠ ## Entpacke: dev-libs:libedit-20130712.3.1.3556f679066ec7d787573f08ac74ef24922243b2~0.tbz2 ╠ ## Installiere Paket: dev-libs/libedit-20130712.3.1 ╠ ## [BSD replacement for libreadline.] ╠ ## Updating installed packages repository: dev-libs/libedit-20130712.3.1 >>> Regenerating /etc/ld.so.cache... ╠ ## Aufräumen: dev-libs/libedit-20130712.3.1 ╠ +++ >>> (2/2) net-misc/chrony-1.29.1 ╠ ## Entpacke: net-misc:chrony-1.29.1.98ee02f66d56c49e1fdc4d713b2cea5ff23d775e~0.tbz2 ╠ ## Installiere Paket: net-misc/chrony-1.29.1 ╠ ## [NTP client and server programs] ╠ ## Updating installed packages repository: net-misc/chrony-1.29.1 ╠ ## Aufräumen: net-misc/chrony-1.29.1 ╠ @@ Installation vollständig. ╠ @@ No configuration files to update.
Beim Boot soll er in Zukunft gestartet werden!
$ systemctl enable chronyd.service ln -s '/usr/lib64/systemd/system/chronyd.service' '/etc/systemd/system/multi-user.target.wants/chronyd.service'
Och ja, am besten sollte der Dienst direkt gestartet werde…
$ systemctl start chronyd.service
Bevor ich es vergesse, den hässlichen ntpd.service will ich beim Start nicht mehr sehen!
$ systemctl disable ntpd.service
Fragen? Einfach melden.
Eine DMARC-Policy zu veröffentlichen ist die eine Seite. Die andere ist, eingehende Mails gegen die DMARC-Policy des Absenders zu prüfen. Postfix kann das nicht selbst. Dafür gibt es zwei Wege: OpenDMARC als eigenständigen Milter oder rspamd, der DMARC als eines von vielen Modulen mitbringt.
OpenDMARC ist ein dedizierter DMARC-Milter. Er liest die Ergebnisse von SPF und DKIM aus den Mailheadern und prüft ob die DMARC-Policy des Absenders erfüllt ist.
# Debian/Ubuntu apt install opendmarc # FreeBSD pkg install opendmarc
Die Konfiguration in /etc/opendmarc.conf:
AuthservID mail.example.de Socket inet:8893@localhost UserID opendmarc SoftwareHeader true Syslog true RejectFailures false HistoryFile /var/run/opendmarc/opendmarc.dat
RejectFailures false ist ein guter Startwert. Damit werden Mails die den DMARC-Check nicht bestehen nicht sofort abgelehnt, sondern nur markiert. So kann man erst beobachten bevor man scharf schaltet. AuthservID muss zum Hostnamen des Mailservers passen.
Wichtig: OpenDMARC verlässt sich darauf, dass SPF und DKIM bereits geprüft wurden und die Ergebnisse in den Authentication-Results-Headern stehen. Die Reihenfolge der Milter in Postfix ist daher entscheidend: SPF und DKIM müssen vor OpenDMARC laufen.
In der main.cf den Milter registrieren. OpenDMARC muss nach dem DKIM-Milter stehen:
# main.cf smtpd_milters = inet:localhost:8891, inet:localhost:8893 non_smtpd_milters = inet:localhost:8891, inet:localhost:8893
Port 8891 ist hier OpenDKIM, Port 8893 OpenDMARC. Nach einem postfix reload prüft OpenDMARC jede eingehende Mail und schreibt einen Authentication-Results-Header mit dem DMARC-Ergebnis.
Wer rspamd als Spamfilter einsetzt, braucht keinen separaten DMARC-Milter. rspamd prüft SPF, DKIM und DMARC in einem Durchgang und verrechnet das Ergebnis mit dem Gesamtscore. Das DMARC-Modul ist standardmäßig aktiv und unterstützt auch DMARC-Reporting (rua/ruf).
Der Vorteil von rspamd: Statt einer harten Ablehnung bei DMARC-Fail fließt das Ergebnis in die Gesamtbewertung ein. Eine Mail die DMARC nicht besteht aber sonst sauber aussieht, wird nicht sofort abgelehnt. Umgekehrt kann eine Mail die DMARC besteht trotzdem als Spam markiert werden wenn andere Indikatoren dagegen sprechen.
| OpenDMARC | rspamd | |
| Aufwand | Eigener Dienst, eigene Config | Bereits als Milter integriert |
| Verhalten | Harte Entscheidung (reject/accept) | Score-basiert, flexibel |
| Reporting | Eigenes Reporting-Tool nötig | rua-Reports eingebaut |
| Abhängigkeiten | Braucht SPF/DKIM in den Headern | Prüft SPF/DKIM selbst |
Für neue Setups ist rspamd meist die bessere Wahl, weil es SPF, DKIM, DMARC, ARC und Spam-Filterung in einem Paket liefert. OpenDMARC ist sinnvoll wenn man einen minimalen Stack ohne Content-Filter will oder bereits einen anderen Spamfilter einsetzt.
Übrigens: DMARC und Mailinglisten vertragen sich nicht ohne Weiteres. ARC löst das Problem. Fragen? Einfach melden.
© 2026 -=Kernel-Error=- — RSS
Theme von Anders Norén — Hoch ↑