Es gibt so Tage, an denen man die Wände hoch gehen möchte. Beispielweise der, an dem ein Exchange-Server beim Kunden aus heiterem Himmel massiv Spam-Mails versendete. Variierende Absender alerts@XXXbank.de und Ziele in aller Welt. Dabei hat man schon Probleme, wenn der Kunde mal im Outlook oder Webmailer seine private Adresse als Absender verwenden will. Was war also passiert?

Daten sammeln

Zuerst einmal ist festzustellen, ob überhaupt gespammt wurde. Schließlich kann es ja sein, dass die auslösende Beschwerde eine Fehlinterpretation darstellt. In dem konkreten Fall sagen die relevanten Received-Zeilen zwar ganz koscher aus, aber man weiß ja nie.

Also zuerst mal ins Logverzeichnis des Mailserver, also der Komponente, die die E-Mails tatsächlich entgegen nimmt und dann verschickt.

C:\> cd C:\Program Files\Microsoft\Exchange Server\V15\TransportRoles\Logs\Hub\ProtocolLog\SmtpReceive

Dort finden sich Dateien der Form RECVyyyymmdd-ss.LOG. In denen steht, wie die Mailserver zu seinen E-Mails gekommen ist. Findet sich dort nichts auffälliges, schaut man nebenan im SmtpSend Folder nach, ob überhaupt vom Exchange gesendet wurde.

In diesem Fall stehen aber Einträge im Empfangsprotokoll:

...,[exchange-ip]:465,[exchange-ip]:38115,...
<,EHLO exch.local,
*,None,Set Session Permissions
>,250-exch.local Hello [2a01:...],
>,250-PIPELINING,
>,250-DSN,
>,250-ENHANCEDSTATUSCODES,
>,250-AUTH GSSAPI NTLM LOGIN,
>,250-X-EXPS EXCHANGEAUTH GSSAPI NTLM,
>,250-X-EXCHANGEAUTH SHA256,
>,250-8BITMIME,
>,250-BINARYMIME,
>,250-CHUNKING,
>,250-XEXCH50,
>,250-XRDST,
>,250-XSHADOWREQUEST,
>,250-XPROXY,
>,250-XPROXYFROM,
>,250-X-MESSAGECONTEXT ADRC-2.1.0.0 EPROP-1.2.0.0,
>,250-XSYSPROBE,
>,250 XORIGFROM,
<,X-EXPS EXCHANGEAUTH,
*,NT AUTHORITY\SYSTEM,authenticated
>,235 <authentication response>,
<,XPROXY SID=08D3C19C6F595AB4 IP=80.a.b.c PORT=51066 DOMAIN=xxx CAPABILITIES=0 SECID=Uy0xLTUtMjEtMDEyMzQ1Njc4OS0wMTIzNDU2Nzg5LTAxMjM0NTY3ODktMTIzNA+3D+3D,
*,SMTPSubmit SMTPSubmitForMLS SMTPAcceptAnyRecipient SMTPAcceptAuthenticationFlag SMTPAcceptAnySender SMTPAcceptAuthoritativeDomainSender BypassAntiSpam BypassMessageSizeLimit SMTPSendEXCH50 SMTPAcceptEXCH50 AcceptRoutingHeaders AcceptForestHeaders AcceptOrganizationHeaders SendRoutingHeaders SendForestHeaders SendOrganizationHeaders SendAs SMTPSendXShadow SMTPAcceptXShadow SMTPAcceptXProxyFrom SMTPAcceptXSessionParams SMTPAcceptXMessageContextADRecipientCache SMTPAcceptXMessageContextExtendedProperties SMTPAcceptXMessageContextFastIndex SMTPAcceptXAttr SMTPAcceptXSysProbe,Set Session Permissions
>,250 XProxy accepted and authenticated,
<,MAIL FROM:<alerts@1k8JzXXXbank.com>,
<,RCPT TO:<dest@ination.net>,
>,250 2.1.0 Sender OK,
>,250 2.1.5 Recipient OK,
<,BDAT 8192,
*,,receiving message with InternetMessageId <random@ination.net>
>,"250 2.6.0 CHUNK received OK, 8192 octets",
<,BDAT 8192,
>,"250 2.6.0 CHUNK received OK, 8192 octets",
<,BDAT 8192,
>,"250 2.6.0 CHUNK received OK, 8192 octets",
<,BDAT 5082 LAST,
>,"250 2.6.0 <random@ination.net> [InternalId=7013681596317, Hostname=exch.local] Queued mail for delivery",

Der überraschende Punkt ist, dass es tatsächlich ein Mailversand von einer fremden Adresse an eine fremde Adresse gibt. Und dafür hat sich der Client die Berechtigungen SMTPAcceptAnyRecipient, SMTPAcceptAnySender, BypassAntiSpam, BypassMessageSizeLimit gegeben.

Richtig heftig ist aber, dass die Mail von der lokalen Maschine kommt und dort mit Systemrechten authentisiert ist. Ganz offenbar versteckt sich da irgendwo im XPROXY eine nachträgliche Authenisierung, die die angegeben Rechte frei schaltet.

Verursacher finden

Schaut man sich die vielen XPROXY Zeilen an, variiert alles, bis auf die IP und SECID.

Die IP ist ganz offensichtlich die originäre Quelle der Einlieferung. Wie auch immer die Mail durch Outlook Web Access, POP, IMAP oder Submission Ports durchgelaufen ist, externe IP und externer Port sind bekannt. Das ist viel wert!

Die SECID dagegen schaut sehr nach BASE64 aus. Also mal dekodieren.

$ echo Uy0xLTUtMjEtMDEyMzQ1Njc4OS0wMTIzNDU2Nzg5LTAxMjM0NTY3ODktMTIzNA== | base64 -d
S-1-5-21-0123456789-0123456789-0123456789-1234

Oh, eine Security-ID von Windows. Das könnte ein Hinweis auf den realen Nutzer sein. Mal schauen:

PS C:\> $objSID = New-Object System.Security.Principal.SecurityIdentifier ("S-1-5-21-0123456789-0123456789-0123456789-1234")
PS C:\> $objUser = $objSID.Translate( [System.Security.Principal.NTAccount])
PS C:\> $objUser.value
KUNDE\backup

Huch? Ein gültiger Nutzername aus dem AD? Gleich mal beim Kunden anrufen.

Es stellt sich heraus, dass bei dem Namen das Passwort sehr leicht zu erraten war. Viel zu leicht. Angelegt hatte den Nutzer der Kunde selbst, um damit USB-Backup-Platten im Netz einbinden zu können. Und damit jeder damit arbeiten kann, hatte der Nutzer auch viel zu viele Rechte.

Der Nutzer wurde deaktiviert und sämtliche Passworte sind zu ändern. Es gibt auch verpflichtende Passwort-Richtlinien, die nun auch als Admin nicht mehr zu umgehen sind.

Aufräumen

Nachdem die Ursache des Spamversands gefunden, bleibt nun noch das Wegräumen der Spammails, die noch nicht versendet wurden. Erst dann kann die Blockade des Mailversands aufgehoben werden.

Erstmal die Mails raus suchen:

[PS] C:\>Get-Message -Filter {FromAddress -like "alerts@*"}

Identity         FromAddress             Status           Queue            Subject
--------         -----------             ------           -----            -------
exch\50755\69... alerts@QIsnlXXXXbank... Active           exch\50755       Important Account In...
exch\155681\7... alerts@HyThUXXXXbank... Active           exch\155681      Important Account In...
...

Und dann weg löschen:

[PS] C:\>Remove-Message -Filter {FromAddress -like "alerts@*"} -WithNDR $false

Bestätigung
Möchten Sie diese Aktion wirklich ausführen?
Die Nachrichten, die dem Filter 'FromAddress -like "alerts@*"' entsprechen, werden entfernt.
[J] Ja  [A] Ja, alle  [N] Nein  [K] Nein, keine  [?] Hilfe (Standard ist "J"):

Das ganze Spiel wiederholt sich solange, bis die Mailwarteschlange nur noch legitime E-Mails enthält.

Und dann kann man den Exchange wieder in die Freiheit entlassen.

Prävention

Im Nachgang sollte man überlegen, ob man solche Situationen nicht schneller erkennen kann.

Kann man: Symptomatisch für hohes Mailaufkommen ist die Anzahl der in einem kurzen Intervall versendeten E-Mails. Und genau dafür gibt es einen Leistungsindikator. Den trägt man mit einem sinnvollen Grenzwert im SystemCenter ein und bekommt einen Alarm, wenn der Grenzwert gerissen wird.

Gestern lief ein MySQL-Exploit durch meine Kommunikationskanäle durch. CVE-2016-6662 spricht von einer remote ausnutzbaren Lücke, die Code mit root-Rechten auf der Maschine ausführen kann. Betroffen sind alle Versionen von MySQL. Das ist ein GAU. Ein Super-GAU wäre es, wenn noch die Authentisierung wegfallen würde. Bei einer Überprüfung zeigten sich drei Gründe, warum mich das trotzdem nicht betrifft.

Funktionsweise

CVE-2016-6662 ist schnell erklärt.

Ein lokaler Benutzer legt eine shared Library mit dem gewünschten Schadcode im Dateisystem an einer Stelle ab, wo er Zugriff hat. I.d.R. wird das das /tmp-Verzeichnis oder sein Home-Verzeichnis sein.

Als nächstes meldet sich der Nutzer beim MySQL-Server an und lenkt die Ausgabe eines Logfiles auf den wohl bekannten Dateinamen einer Config-Datei des MySQL-Servers um. In dieses Logfile (also die Ini-Datei) schreibt er rein, dass die soeben abgelegte shared Library geladen werden soll. Alle Schreibzugriffe des MySQL-Servers erfolgen unter den Rechten des Daemon, nicht unter den Rechten des angemeldeten Nutzers. Der MySQL-Login muss nicht mal als Nutzer im Betriebssystem existieren.

Beim Restart des MySQL-Servers sorgt das mysqld_safe Startscript dafür, dass die Library mit root-Rechten geladen wird, bevor der Server die Rechte abgibt. Damit wird dann der vom Nutzer stammende Code unter root-Rechten ausgeführt.

Produktionseinsatz

Unser Setup ist produktiv, d.h. für Experimente durch die Nutzer besteht wenig Spielraum. Unser Setup stammt nicht aus einer Distribution, sondern ist im Laufe der Zeit organisch gewachsen. In diesem Fall ist das ein Vorteil.

Gegen das Ablegen von Dateien kann man nichts tun, denn genau das ist eine der Aufgaben, die der Server für den Nutzer erfüllen soll. In dem Fall z.B. ein Webhosting. Nehmen wir also an, der Nutzer hat den Schadcode auf dem Server abgelegt.

Das Überschreiben vorhandener INI-Dateien ist dem MySQL-Server nicht möglich, da bei uns diese Dateien dem administrierenden Nutzer root gehören. Damit scheidet das Überschreiben als Angriffsvektor schon mal aus.

Natürlich könnte der Angriff darüber erfolgen, dass eine andere INI-Datei erzeugt wird, die der Server beim Start parsen wird. Da gibt es einige Plätze, die in einen typischen MySQL-Server einkompiliert werden. Die Frage ist also, ob es klappen würde. Probieren wir das mal aus:

mysql> SELECT 'malicious config entry' INTO OUTFILE '/var/lib/mysql/my.cnf';
ERROR 1045 (28000): Access denied for user 'test'@'localhost' (using password: YES)

Ohja. Der MySQL-Nutzer hat gar nicht die Rechte für diesen Angriff! Aber es gab da ja noch andere Ideen:

mysql> set global general_log_file='/var/lib/mysql/my.cnf';
ERROR 1227 (42000): Access denied; you need (at least one of) the SUPER privilege(s) for this operation

Schon wieder nichts?! Einen hab ich noch:

mysql> CREATE DEFINER=`root`@`localhost` TRIGGER appendToConf
     > AFTER INSERT ON `active_table` FOR EACH ROW begin end;
ERROR 1227 (42000): Access denied; you need (at least one of) the SUPER privilege(s) for this operation

Für einen typischen shared-Hosting Kunden haben sich damit die Möglichkeiten des Angriffs erschöpft.

Aber nehmen wir mal an, es gäbe trotzdem eine Chance, eine INI-Datei zu beeinflussen. Dann würde bei uns nicht das Startscript mysqld_safe zum Zuge kommen, sondern ein eigenes Startscript, dass zuerst die Rechte wechselt und dann den MySQL-Server aufruft. Damit ist ausgeschlossen, dass der Schadcode mit Root-Rechten laufen könnte.

Fazit

Ende gut, alles gut? Nein, diese Art von Angriff zeigt, wie wichtig es ist, zwischen administrativen Aufgaben und Produktionsaufgaben zu unterscheiden. Das Problem wird erst dann relevant, wenn z.B. durch eine Distribution die Bequemlichkeit für den Anwender im Vordergrund steht. Oder wenn man den meist gelesenen Blog-Posts folgt und das Rechtesystem außer Kraft setzt.

Es kommt der Tag, da muss man aufräumen. Und das war nun bei IPTV der Fall. Einige tausend noch Karteileichen sollten nicht mehr versorgt werden. Die Idee dazu ist ganz einfach, nur noch authentisierte Clients bekommen eine IP. Ohne IP kein IGMP, ohne IGMP kein Multicast und ohne Multicast kein Fernsehen. Aber das hat fatale Nebenwirkungen.

Ausgangslage

Es sind knapp 12000 Nutzer im IPTV-Netz eingebucht, bekommen also eine IP Adresse. Die Verteilung der Sender erfolgt dezentral über Multicast-Replikation. Damit sind die Datenleitungen bis runter zum DSLAM nur minimal belastet. Das funktioniert sehr gut.

Die Liste der Kunden, die IPTV vertragsmäßig nutzen dürfen, liegt mittlerweile vor und kann auch von der Hotline/Vertrieb aktualisiert werden. Ein Abgleich der Datenbestände mit der Produktivumgebung erfolgt automatisch. Soweit so einfach.

Die Kunden bekommen aktuelle aus einem großen Pool privater IPs eine DHCP-Lease. Eine Authentisierung erfolgt nicht, weil das der bisherige gewünschte Operationsmodus ist.

Umsetzung

Zur Änderung des Operationsmodus sind einige Vorüberlegungen notwendig:

  • Es dürfen nicht alle Kunden mit einmal gesperrt werden, sondern nur ein Teil, damit die Hotline auf evtl. Fehlsperrungen reagieren kann.
  • Authentisierte und nicht authentisierte Kunden sollten technisch unterscheidbar bleiben, damit der Netzbetrieb Kenntnis von der Vertragslage hat.
  • Der Abgleich der Datenbanken zwischen Vertrieb und Produktion darf nicht angefasst werden, in den Datenbanken des Produktivsystem darf kein Kunde virtuell zugeteilte MAC-Adressen haben, die dem Vertriebssystem fremd sind. Andernfalls ist die Hotline nicht zur Störungsdiagnose und -behebung in der Lage und alle diese Fälle eskalieren in den Netzbetrieb.

Als Lösung werden zwei Pools eingerichtet:

  • ein großer Pool für die authentisierten Kunden und
  • ein kleinerer Pool für die nicht authentisierten Kunden.

Der unauthentisierte Pool kann dann schrittweise verkleinert werden, um für immer mehr Kunden den Zugang zu sperren. Bei Bedarf kann er auch schlagartig wieder vergrößert werden. Dies gestattet die Anzahl der ausgesperrten Kunden rein im operativen Betrieb einzustellen, die Datenbanken bleiben davon unberührt.

Da sich beide Pools hinsichtlich der verwendeten Adressbereiche unterscheiden ist die Diagnose, ob eine fehlende Authentisierung vorliegt auch der Hotline direkt möglich. Diese kann bei Bedarf die Daten auslesen und direkt zur Freischaltung verwenden.

Soweit der Plan.

Überraschungen

Ich müsste nicht darüber schreiben, wenn es so einfach gegangen wäre. Also zuerst einmal die Wirkung der Umschaltung.

poolusage-dhcp.iptv-limit

Nach einem Testlauf kurz vor Mitternacht (bei dem sich herausstellte, dass die Auswertung der Authentisierung gar nicht aktiviert wurde), erfolgte dann wie geplant die Umschaltung und die erste Limitierung der Kunden auf ca. 5000 unauthentisierte Teilnehmer.

Wie leicht zu sehen, hat das dann nach Ablauf der Lease-Zeiten auch funktioniert. Sobald der Pool voll war, sank die Anzahl der IPTV-Teilnehmer ab, bis alle authentisierten plus die glücklichen 5000 Pool-Teilnehmer. In Summe um die 6000 Kunden.

Auch zu sehen ist aber auch, dass diese Reglung wieder aufgehoben wurde. Und das kam so:

In bestimmten Gegenden fiel am nächsten Morgen regelmäßig der komplette Anschluss aus. Einige Switche stellte kurzzeitig, aber effektiv, die Arbeit ein. Zwar erholten sie sich wieder, jedoch blieb ein konstanter Betrieb aus. Stattdessen wiederholte sich der Ausfall in kurzen aber unregelmäßigen Abständen.

Es stellte sich heraus, dass diese Switche mit Bursts von Broadcasts nicht klar kamen, wenn in diesen Burst zu viele unbekannte Quell-MAC-Adressen auftauchten. Offenbar geraten diese Geräte dann an Limits hinsichtlich der Verwaltung von MAC-Adressen. Diese Bursts kommen aus dem DHCP:

poolrate-dhcp.iptv-limit

Als Notlösung wurde erst einmal wieder für alle der Zugang freigegeben. Damit hören die Bursts auf und das System funktioniert wieder.

Diese Bursts bestehen aus den DHCPDISOVER Anfragen der CPEs, für die keine Adressen mehr zur Verfügung stehen. Und die fragen eben alle paar Sekunden per Broadcast rum, ob irgendjemand evtl. doch eine IP für sie übrig hat.

Broadcast heißt aber auch, dass die Anfragen auch die hintersten Winkel des Netzes erreichen, insbesondere diese empfindlichen Switche. Im Normalbetrieb haben diese Switche kein Problem, aber so war's doch etwas zu heftig.

Die Lösung besteht nun darin, die betreffenden Segmente in getrennte Broadcast-Domains zu verfrachten und die Multicast-Streams in alle diese VLANs einzuspeisen. Aber dazu später mal mehr.

Quo usque tandem abutere, Mozilla, patientia nostra? Überaschenende Einblicke in das Zertifikatshandling von Firefox und Thunderbird. Einige wichtige abgelaufene Zertifikate werden defaultmäßig als in der Ausnahmeliste geführt. Und natürlich noch einiges mehr.

Überraschung

Mein Kollege schaute gerade etwas konsterniert, als er im ThunderBird einem Zertifikatsproblem nach jagte.

FireFox Advanced Options

Der Klick auf View Certificates und dann auf den Reiter Servers offenbart Erschreckendes:

FireFox freigeschaltete Zertifikate

Was zur Hölle?!

Was sollen diese Freischaltungen für abgelaufene Zertifikate? Was soll die Zertifikate für mögliche Schadcode-Server?

Usability-Problem

Zumindest die Suche nach dem ominösen MD5 Collisions Inc. Zertifikat bringt eine Erklärung: Allen diesen Zertifikaten wurde das Vertrauen entzogen.

Das Problem damit ist, dass man diese Eigenschaft nicht sofort sieht. Da in der Liste auf die individuellen Freischaltungen landen, die man selbst genehmigt hat, ist es nicht so leicht, den Überblick zu behalten. Denn schließlich dient die Liste ja ganz anderen Zwecken:

  • Mozilla stellt eine Liste von Zertifikatsausnahmen bereit, damit der Nutzer lokale Abweichungen der globalen Policy vornehmen kann.
  • Der Nutzer trägt in die Liste Zertifikate ein, denen er vertraut.
  • Mozilla behält sich vor, die Liste der Zertifikatsausnahmen auch selbst zu pflegen.
  • Bisher hat Mozilla zum Schutz der Nutzer dort Zertifikate eingetragen, denen nicht vertraut werden soll.
  • Es ist der Liste nicht anzusehen, ob vertraut oder nicht vertraut wird.

Damit bleibt dem Nutzer nichts anderes übrig, als regelmäßig alle Einträge in der Liste anzuklicken, um die Vertrauenseinstellungen zu überprüfen. Das ist inakzeptabel.

Vor einigen Tagen verloren nach einem Failover auf die Hot-Standby-Maschine ein Großteil dort aktiven Kunden ihre Kommunikationsmöglichkeit. Grund waren divergierende MySQL-Datenbanken, die eigentlich per Replikation in einem synchronen Zustand gehalten werden. Derartige Fehlerzustände werden überwacht und ggf. alarmiert. Aber genau das Monitoring hat versagt.

Was geschehen ist

Eine Appliance für einen klassischen Cloud-Dienst, steht als Hochverfügbarkeitslösung mit zwei Knoten da, von denen der eine den anderen in Sekundenbruchteilen übernehmen kann. Alle laufenden Sessions der Kunden werden dabei übernommen, so dass der Kunde nichts vom Umschalten mit bekommt.

Das hat jahrelang bestens geklappt. Nur eben diesmal nicht. Die Lösung des Problems war einfach: Zurückschalten und Beginn der Fehleranalyse.

Es stellte sich heraus, dass die MySQL-Datenbank auf dem Standby-System schon seit Tagen nicht mehr synchron lief. Sie verweigerte ab einem bestimmten Kommando (Löschen einer Tabelle) das Replay des Replikationslogs. Grund war, dass die Tabelle selbst schon nicht vorhanden war und deswegen nicht gelöscht werden konnte. Die Tabelle selbst war nur ein Überbleibsel eines Tests, also nicht wirklich relevant. Warum sie auf dem zweiten System schon gefehlt hat, ist nicht ganz nachvollziehbar. Vermutlich hat sich irgendwann der Replikations-Filter geändert, so dass die Tabelle erst später Bestandteil der Replikation wurde.

Fakt ist, dass die Replikation nicht mehr lief. Beim Failover traten dann die abstrusesten Effekte auf.

Warum es nicht auffiel

Wesentlich interessanter ist die Frage, warum die fehlerhafte Synchronisation nicht aufgefallen ist. Schließlich gibt es ein Monitoring auf Synchronisations-Diskrepanzen an die ein Alarm angeschlossen ist. Eigentlich hätte es also schon Tage vorher mächtig klingeln sollen.

Das Monitoring lieferte aber immer nur einen Wert von "0" für die Anzahl der Synchronisationsabweichungen. Warum?

Ein Blick in den Quellcode des Monitorings (ein collectd-Plugin) zeigt:

        if ($MK_REPLICATE_CHECK_TABLES && $MK_REPLICATE_CHECK_HOSTS) {
                my $now = time();
                my $fake;
                if ($MK_REPLICATE_CHECK_INTERVAL && $mk_last_run && defined($mk_last_result)) {
                        if ($now - $mk_last_run < $MK_REPLICATE_CHECK_INTERVAL) {
                                $fake = $mk_last_result;
                        }
                }

                if (!defined($fake)) {
                        my @tables = split(/[\s,;]+/, $MK_REPLICATE_CHECK_TABLES);

                        my @fails = do_replication_check(\@tables);
                        my @rechecks = do_replication_check(\@fails, '--wait=60');

                        $fake = scalar(@rechecks);
                        $mk_last_run = $now;
                        $mk_last_result = $fake;
                }

                defined($fake) and plugin_dispatch_values({
                        type => 'mysql_replication_discrepancies',
                        plugin => "XXX",
                        values => [ $fake ],
                        host => $ME,
                });
        } 

Collectd ruft eine read-Funktion regelmäßig auf, um dem Plugin die Möglichkeit zu geben, Daten zu sammeln. Hat das Plugin die Daten ermittelt, schickt es diese mit plugin_dispatch_values an den collectd zurück, der den Messwert an die verschiedenen write-Funktionen der Ausgabe-Plugins übermittelt. Letzteres ist u.a. der RRD-Writer.

In diesem Fall fällt auf, dass das Plugin nur alle $MK_REPLICATE_CHECK_INTERVAL Sekunden überhaupt eine Messung macht. In der Zwischenzeit liefert es den letzten Messwert immer wieder aus.

Weiterhin auffällig ist, dass der Messwert selbst die Anzahl der Tabellen enthält, die nicht synchron sind. Dazu übergibt er die zu überwachenden Tabellen der eigentlichen Messfunktion und diese liefert ihm die Liste der Tabellen, die nicht synchron sind.

In einer zweiten Runde werden – mit einer Wartezeit von bis zu einer Minute – die Tabellen angeschaut, die sich verändert haben. I.d.R. also die Tabellen, die häufigen Änderungen unterliegen. Diese werden nach einem deutlich aufwendigeren Verfahren auf Synchronität untersucht. Es ist offenbar sinnvoll, die statischen Tabellen in einer schnellen ersten Runde schon auszusortieren.

Was dann immer noch nicht synchron ist, bleibt übrig und erhöht den Rückgabewert um eins.

Dieser Teil der Funktion schaut auf den ersten Blick ganz normal aus.

Wie funktioniert aber nun die Messung?

Die eigentliche Messfunktion ist folgende:

        sub do_replication_check {
                my ($tables, $add_args) = @_;

                $add_args or $add_args = '';

                my %results;
                for my $t (@$tables) {
                        my $arg = ($t =~ /\./) ? "--tables=$t" : "--databases=$t";
                        my $out = `mk-table-checksum --algorithm=ACCUM $arg $add_args --tab $MK_REPLICATE_CHECK_HOSTS 2>&1`;
                        my @out = split(/[\n\r]+/, $out);
                        my @headers = split(/\t/, $out[0]);
                        for my $i (1 .. $#out) {
                                $out[$i] or next;
                                my @c = split(/\t/, $out[$i]);
                                scalar(@c) == scalar(@headers) or next;
                                my $h = { map {$headers[$_], $c[$_]} (0 .. $#headers) };
                                $results{$h->{DATABASE} . '.' . $h->{TABLE}}->{$h->{HOST}} = $h;
                        }
                }

                my @fails;
                for my $k (values(%results)) {
                        ref($k) eq 'HASH' or next;
                        my @r = values(%$k);
                        @r > 1 or next;
                        my $h = $r[0]->{CHECKSUM};
                        for my $j (1 .. $#r) {
                                my $i = $r[$j];
                                $h eq $i->{CHECKSUM} and next;
                                push(@fails, $i->{DATABASE} . '.' . $i->{TABLE});
                                last;
                        }
                }

                return @fails;
        }

Man kann sicher über den Stil der Perl-Programmierung lästern, aber das ist nicht das Thema. Interessanter ist die Frage, was der Code tut.

Er lässt sich vom Programm mt-table-checksum die Liste der Prüfsummen über die einzelnen Tabellen geben. Diese parst er und vergleicht anschließend, ob die Prüfsummen pro gefundener Tabelle auf beiden Systemen überein stimmt.

Hat er unterschiedliche Prüfsummen für die Tabellen gefunden, so wird die Tabelle als unsynchronisiert zurück gemeldet.

Was kann daran schon schief gehen?

Ich schaue mir die Ausgabe des Programms mal direkt an:

# mt-table-checksum ...
-bash: mt-table-checksum: command not found

WTF?!

Der Programmcode ignoriert die erste Zeile (die mit der Fehlermeldung) und liest dann die Liste der Tabellen mit ihren Prüfsummen ein (die ist leer). Anschließend vergleicht er, ob er für die gefundenen Tabellen (also keine) Unterschiede bei den Prüfsummen feststellen kann (was nicht möglich ist). Also kommt er zum Schluss, dass keine Unterschiede vorliegen.

Keine Unterschiede zwischen den beiden MySQL-Instanzen heißt also, nach Logik des darüber liegenden Monitorings, dass die Synchronisation in Ordnung ist.

Was wirklich schief gelaufen ist

Der eigentliche Fehler der Messfunktion besteht darin, dass sie keine Validitätsprüfung der Ausgabe gemacht hat.

Sie bekommt eine Liste von Tabellen und hat dann zumindest zu prüfen, ob diese Tabellen überhaupt Prüfsummen geliefert haben! Würde das geprüft, wäre das Fehlen des externen Programms instantan aufgefallen.

Man kann natürlich auch argumentieren, dass der Errorcode des Programmaufrufs zu prüfen gewesen wäre. Oder die Existenz des Programms selbst. Oder, oder ... Am Ende läuft es darauf hinaus, dass die erwartete Ausgabe vorliegt. Schließlich gibt es ja auch noch viele andere Möglichkeiten, warum das externe Programm seinen Job nicht erledigen konnte.

Es wäre nun leicht, diesen Test hinzu zufügen. Allerdings ist das eine fremde Appliance und da geht der Weg über den Hersteller-Support.

Was wir tun können, ist das fehlende Programm nachzuinstallieren. Es ist im Rahmen eines Updates verschwunden, als die Datenbank dieses Programm durch das wesentlich flexiblere und robustere pt-table-checksum ersetzt hat. Ja, das ist schon eine ganze Weile her.

Überraschende Nicht-Lösung

Nachdem nun das betreffende Programm mk-table-checksum installiert war (wobei der Alarm an sprang) und manuell die Datenbank wieder in Sync gebracht wurde (so dass der Alarm verstummte), schien alles soweit in Butter.

Allerdings zeigte sich in der darauf folgenden Nacht, dass da noch ein anderer Fehler drin steckt.

Über einige Stunden schwankte der Messwert, den collectd einsammelte, zwischen 0.1666, 0.25, 0.3333 und 0.5. Alle paar Minuten ergab sich ein anderer Wert.

Zur Erinnerung: Der Messwert ist die Anzahl der unsynchronisierten Tabellen!

Erst die Dokumentation von collectd brachte die Lösung: Wenn mehrere Messwerte in einem sehr kurzen Zeitfenster eingehen, werden sie von collectd gemittelt und dieser Mittelwert wird an die write-Funktionen übermittelt. Die Messwerte sind klare Brüche: Einer von zwei, drei, vier, ... Messungen hatte eine Eins, der Rest Null geliefert.

Wie können aber viele Messwerte in so kurzer Zeit eingehen?

Zur Erinnerung: collectd ruft die read-Funktion regelmäßig auf. Also auch dann, wenn die schon aufgerufene Funktion sich noch nicht beendet hat!

Es liefen also zwei oder mehr Prüfungen parallel. Und vermutlich war zu dem Zeitpunkt die Datenbank mächtig belastet. Es hat alles gedauert, auch die Tests. Als die Last nachließ, wurden alle Prüfungen zusammen fertig.

Logischerweise hinkt eine schwer belastete Datenbank auch in der Replikation hinterher. Es ist also nur natürlich, wenn einer der Messungen dieses feststellt.

Also Ticket Nummer zwei beim Hersteller: Verhindern der Doppelausführung.

Manchmal muss man Dinge tun, die nicht direkt in den eigentlichen Aufgabenbereich fallen. Aufgrund von Abwesenheit der üblichen Verdächtigen musste ich heute beim Updaten von Windows Servern aushelfen. Und das war mal ein spannendes Erlebnis, weil ein Update so richtig Ärger gemacht hat.

Gleich vorweg: Ja, man kann da viel automatisieren. Deswegen sieht man auch lokales Management der Updates. Unsere Spezialität im Cloud-Geschäft besteht aber darin, individuelle Lösungen mit dem Kunden zusammen zu erstellen und nicht ausschließlich identische Maschinen von der Stange zu nehmen oder den Kunden mit dem Updateproblem allein zu lassen. Also ist einiges an Handarbeit nach wie vor notwendig, um nach dem Update sicher zu stellen, dass die benötigten Dienste auch wirklich laufen.

Es hängt und hängt und hängt

Wenn besonders viele Systeme auf einmal angefasst werden müssen, ist es besonders nervig, wenn es nicht schnell genug vorwärts geht.

windows-update-stalled

An der Stelle kann man warten und warten. Manchmal geht's nach einer halben Stunde spontan weiter. Manchmal aber auch nicht.

Nein, natürlich sitzt da niemand davor und wartet, sondern die Updaterei erfolgt parallel. Man sieht halt nur, dass einige Kisten nicht weiter machen.

Also mal in den Taskmanager geschaut und festgestellt, dass ein Updateprozess nicht weiter macht. Den kann man manuell abschießen.

windows-update-kill

Ein beherzter Kill des Prozesses zeigt unmittelbare Wirkung.

windows-update-running

Beim späteren Reboot wird der Update dann korrekt installiert. Insofern ist das kein Problem.

Interessanter ist die Frage nach der Ursache dieses Phänomens. Und da hatte ich Glück.

windows-update-ursache

Wie man sehen kann, ist der aktiv laufende Prozess die alte Version des Tools, das aktualisiert werden soll.

Da das Tool zur Entfernung bösartiger Software sicherlich auch Signaturen solcher Software enthält, besteht eine gewisse Wahrscheinlichkeit, dass eine ältere Version diese neuen Signaturen als bösartig einstufen kann. Wenn das der Fall ist, wird sie die Ausführung der bösartigen Software verhindern.

Und damit steht der Update-Prozess.

Zur Gegenprobe kille ich den alten Prozess und siehe an: Das Update läuft sofort und ohne Fehler durch.

Heute flog mal wieder eine seltsame Fehlermeldung durchs Log bei der auch normales Googlen keine sinnvollen Ergebnisse brachte. Deswegen möchte ich sie hier dokumentieren.

bestmx_map_lookup: MX host 2001:4420:f501:0400::2. includes map delimiter character 0x3A 

Der Übeltäter war schnell gefunden: Eine E-Mail an eine taiwanische Adresse.

550 5.1.2 <xxx@ems.shiyeu.gov.tw>... Invalid MX record for recipient host ems.shiyeu.gov.tw 

Also schauen wir uns mal den MX genauer an:

$ host ems.shiyeu.gov.tw
ems.shiyeu.gov.tw has address 117.56.237.218
ems.shiyeu.gov.tw has address 192.168.5.252
ems.shiyeu.gov.tw has address 192.168.56.1
ems.shiyeu.gov.tw has IPv6 address 2001:4420:f501:400::2
ems.shiyeu.gov.tw mail is handled by 10 117.56.237.218.
ems.shiyeu.gov.tw mail is handled by 10 2001:4420:f501:0400::2.

Autsch! Da ist wohl jemanden aufgefallen, dass man keine IP-Adressen in den MX schreiben darf, weil dann Namen wie 117.56.237.218.ems.shiyeu.gov.tw. entstehen. Aber anstatt den Fehler zu verstehen und den Namen eines Servers hin zuschreiben, hat man einfach einen Punkt an die IP-Adresse angehängt und so den DNS-Namen verkürzt.

Der MTA interpretiert aber den Doppelpunkt (0x3A) als Trennzeichen in Maps (Hilfetabellen) und beschwert sich dann darüber.

Würde nur legacy IP(v4) benutzt, käme nicht mal eine so deutliche Fehlermeldung. Denn grundsätzlich könnte eine IP(v4)-Adresse auch ein gültiger DNS Name sein: Er hat alphanumerische Texte, die durch Punkte getrennt sind. Das zu der IP als Name interpretiert dann keine IP existiert, macht die Fehlersuche allerdings nicht einfacher.

Spielereien

Also spielen wir mal:

$ORIGIN donnerhacke.de.
broken-mx    MX  0  1.2.3.4
broken-mx2   MX  0  1.2.3.4.

Das ergibt dann bei der Abfrage im DNS:

$ host broken-mx.donnerhacke.de
broken-mx.donnerhacke.de mail is handled by 0 1.2.3.4.donnerhacke.de.

$ host broken-mx2.donnerhacke.de
broken-mx2.donnerhacke.de mail is handled by 0 1.2.3.4.

Und damit versuche ich jetzt mal E-Mails zu verschicken.

from=<test@broken-mx.donnerhacke.de>, size=40, nrcpts=1, proto=ESMTP, daemon=MTA, relay=... 

Das geht? Ja, natürlich. Ich habe ja einen Wildcard Eintrag. Der passt auch auf 1.2.3.4.donnerhacke.de. Und damit sieht das aus wie ein korrekter Absender.

550 5.1.2 <test@broken-mx2.donnerhacke.de>... Illegal MX record for recipient host broken-mx2.donnerhacke.de

Der andere Eintrag funktioniert aber nicht. Wie erwartet gibt es keine schwerwiegende Fehler-Meldung über irgendwelche Map-Zeichen, sondern nur eine blanke Ablehnung. Der Grund ist einfach, dass es für 1.2.3.4. keine IP Adresse im DNS gibt.

Beim Versenden der E-Mail an diesen Empfänger schaut es noch anders aus:

rcpt to: <test@broken-mx.donnerhacke.de>
250 2.1.5 <test@broken-mx.donnerhacke.de>... Recipient ok
rcpt to: <test@broken-mx2.donnerhacke.de>
250 2.1.5 <test@broken-mx2.donnerhacke.de>... Recipient ok
DATA
...
250 2.0.0 u4I8Kv6c010237 Message accepted for delivery 

Und was passiert genau?

to=<test@broken-mx.donnerhacke.de>, relay=1.2.3.4.donnerhacke.de. [217.17.193.188], stat=User unknown
to=<test@broken-mx2.donnerhacke.de>, relay=1.2.3.4., stat=Deferred: 1.2.3.4.: No route to host

In einem Fall greift der Wildcard-Eintrag, aber der Server nimmt keine E-Mail für solch einen Nutzer an.

In dem anderen Fall kann keine Adresse ermittelt und deswegen kann auch der Zielserver erreicht werden. Da eine solche Situation von externen Diensten abhängig ist und diese auch mal fehlerhaft sein können (die Namensauflösung kann schlicht auch mal scheitern), wir die E-Mail zurück gelegt und später nochmal versucht.

May 18 10:22:49 to=<test@broken-mx2.donnerhacke.de>, stat=Deferred: 1.2.3.4.: No route to host
May 18 10:23:49 to=<test@broken-mx2.donnerhacke.de>, stat=Deferred: 1.2.3.4.: No route to host
May 18 10:24:50 to=<test@broken-mx2.donnerhacke.de>, stat=Deferred: 1.2.3.4.: No route to host
May 18 10:25:50 to=<test@broken-mx2.donnerhacke.de>, stat=Deferred: 1.2.3.4.: No route to host
May 18 10:26:50 to=<test@broken-mx2.donnerhacke.de>, stat=Deferred: 1.2.3.4.: No route to host
May 18 10:27:50 to=<test@broken-mx2.donnerhacke.de>, stat=Deferred: 1.2.3.4.: No route to host
May 18 10:28:50 to=<test@broken-mx2.donnerhacke.de>, stat=Deferred: 1.2.3.4.: No route to host
...

Für den Admin ist die Meldung allerdings fehlerträchtig. Es ist leicht zu übersehen, das es sich um einen DNS-Namen und nicht um eine IP-Adresse handelt. Man kann Stunden damit zubringen, den Fehler überhaupt zu erkennen.

Die Seite https://ssl-tools.net/ ist eine Webseite, die vieles rund um Zertifikate prüft. Das macht sie gut, wenn auch Verbesserungsbedarf bei z.B. internen CAs besteht. Aber das ist Geschmackssache. Reale Fehler als valid auszuzeichnen ist allerdings keine Geschmacksfrage, sondern ein ernstes Problem. Akut führt die Seite es an sich selbst vor.

Zertifikate laufen aus

Im Gegensatz zu OpenPGP laufen die Zertifikate unter X.509 (aka SSL) nach einer festen Zeit aus. Bei DNSSEC ist das auch so, aber da ist der Erneuerungsprozess der Signaturen i.d.R. voll automatisiert, weil es alle Signaturen lokal erzeugt werden können. Bei X.509 muss man mit einem externen Partner regelmäßig zusammen arbeiten, um ein Zertifikat zu erneuern. https://letsencrypt.org schlägt deswegen ein Verfahren vor, diese Interaktion zu automatisieren und damit ähnlich leicht handhabbar zu machen, wie DNSSEC.

Aber bislang ist man meist immer noch mit den klassischen Zertifikaten unterwegs. Und hat sich um den regelmäßigen Austausch manuell zu kümmern. Das klappt nicht immer.

Heute schrie mein Webbrauser über fehlerhafte, weil abgelaufene Zertifikate von ssl-tools.net.

Reflexionen

Aber wie sieht sich die Seite selbst? Wird sie ebenfalls laut schreien und Warnungen anzeigen?

Oh, wirklich?

Das Zertifikatskästchen ist grün und vertrauenswürdig, aber in den Details steht was von seit 13 Stunden abgelaufen.

Ebenfalls witzig ist die Diskrepanz zwischen dem DANE-Kästchen (rot und böse) und den Details dort: valid.

Was ist denn nun bei DANE wirklich los? Alles rot oder alles grün? Was ist denn mit DNSSEC los?

Na das schaut ja gar nicht gut aus: Die gesamten DNSSEC-spezifischen Records (Signaturen) fehlen in der Zone!

Wie kann man da nur ein grünes valid hinschreiben?

Andere Sicht

Andere Testseiten wie https://www.ssllabs.com/ sind da wesentlich restriktiver:

In einem früheren Beitrag hatte ich anhand eines graphischen Beweises gezeigt, wie man sich den Satz des Pythagoras herleiten kann. Leider enthält der Beweis einige Ungereimtheiten, die ich geflissentlich ignoriert hatte. Welche Bedeutung diese Ungereimtheiten haben können, sei an einem konkreten Beispiel demonstriert.

Überraschung

In dem täglich durchfliegenden Datenstrom war heute ein Prachtstück des missratenen Beweises.

dreieck-fail

Wie man klar erkennen kann, liegen zwei verschiedene Zerlegungen ein und desselben Dreiecks in identische Teile vor. Aber irgendwie hat sich da eine Lücke aufgetan!

Ganz offensichtlich handelt es sich um einen Scherz, der deutlich macht, wie genau man auf seine Voraussetzungen achten muss.

Also rekapitulieren wir mal:

  • Handelt es sich um ein und dasselbe Dreieck?
  • Sind die Teilstücke identisch?
  • Haben die Stücken die Form, die sie vorgeben?
  • Ist die Zerlegung universell, oder handelt es sich um einen Spezialfall?

Beginnen wir mir der zweiten Frage: Sind die Teilstücken identisch?

Das ist leicht zu prüfen: Man zählt die Kästchen, die jedes Teilstück in jede Richtung einnimmt und schaut, ob alle Linien wirklich gerade sind. Das ist in beiden Bildern auf die gleiche Art der Fall.

Die zweite Frage ist also mit Ja zu beantworten.

Nun zu ersten Frage. Auch hier ist die Überprüfung genauso leicht: Man zählt wieder die Kästchen 13 breit und 5 hoch. Die Linien sind auch alle gerade, oder?

So ganz klar ist das nicht, denn das große Dreieck wird ja zusammen gestückelt. Es wäre also durchaus möglich, einer Täuschung zu unterliegen.

Also legen wir das rote und das türkisfarbene Dreieck mal direkt aufeinander und zeichnen die Linien dünn.

dreieck-overlap

Huch?! Da ist ja eine kleine Abweichung!

Wenn man die Dreiecke mal genau aufeinander malt, gibt es folgende Form:

dreieck-bereich

So richtig dreieckig sieht das nicht aus!

Damit ist die Frage drei mit einem eindeutigen Nein zu beantworten: Die Form ist nicht das, was sie zu sein vorgibt. Die dick gezeichneten Linien sind der Quell der Täuschung.

Und somit ist auch die fehlende Fläche gefunden: Der schmale, aber lange Bereich hat genau den Flächeninhalt des fehlenden Kästchens.

Hausaufgabe

Damit ist der Fall aber noch nicht beendet. Denn es bleibt Frage vier: Handelt es sich um einen Spezialfall?

Aber wo könnte denn der Spezialfall vorliegen? In der Zerlegung des Rechtecks.

Wer möchte, kann mal selbst knobeln, wann man das Rechteck nach der Vertauschung der Dreiecke auf die angegebene Weise zerlegen kann. Ist diese Zerlegung nämlich generell möglich, so müsste sie bei Verwendung eines richtigen Dreiecks keine Lücke lassen.

Ich freue mich über Eure Erfolgsmeldungen in den Kommentaren.

Heute morgen begann eine ClamAV Installation bei einem Kunden zu spinnen. Freshclam, der Updater von ClamAV, weigert sich die Aktualisierungen vorzunehmen. Wenn man den Grund dann kennt, ist alles wohl dokumentiert. Wenn auch die Ursache in einer schlechte Pflege der Produktionsumgebung von ClamAV liegt.

Der Morgen

Eine E-Mail vom cron berichtete Schlimmes vom Versuch die Datenbank des ClamAV zu aktualisieren.

ERROR: getpatch: Can't download daily-21465.cdiff from db.DE.clamav.net
ERROR: getfile: Download interrupted: Operation now in progress (IP: 62.27.56.14)
ERROR: Can't download daily.cvd from db.DE.clamav.net
ERROR: getpatch: Can't download daily-21465.cdiff from database.clamav.net
ERROR: getfile: Download interrupted: Operation now in progress (IP: 62.27.56.14)
ERROR: Can't download daily.cvd from database.clamav.net

Unglücklicherweise hörten diese Fehler nicht auf. Also muss man auf der Maschine schauen, was falsch läuft.

Ein händisch angeworfener Update produziert eine lange Liste von Fehlversuchen:

ClamAV update process started at Thu Mar 17 11:14:42 2016
main.cvd is up to date (version: 57, sigs: 4218790, f-level: 60, builder: amishhammer)
WARNING: getfile: daily-21465.cdiff not found on remote server (IP: 62.27.56.14)
WARNING: getpatch: Can't download daily-21465.cdiff from db.DE.clamav.net
Trying host db.DE.clamav.net (88.198.17.100)...
connect_error: getsockopt(SO_ERROR): fd=4 error=111: Connection refused
Can't connect to port 80 of host db.DE.clamav.net (IP: 88.198.17.100)
Trying host db.DE.clamav.net (178.63.73.246)...
nonblock_connect: connect timing out (30 secs)
Can't connect to port 80 of host db.DE.clamav.net (IP: 178.63.73.246)
Trying host db.DE.clamav.net (84.39.110.99)...
nonblock_connect: connect timing out (30 secs)
Can't connect to port 80 of host db.DE.clamav.net (IP: 84.39.110.99)
WARNING: getpatch: Can't download daily-21465.cdiff from db.DE.clamav.net
WARNING: getpatch: Can't download daily-21465.cdiff from db.DE.clamav.net
WARNING: getpatch: Can't download daily-21465.cdiff from db.DE.clamav.net
WARNING: getpatch: Can't download daily-21465.cdiff from db.DE.clamav.net
WARNING: Incremental update failed, trying to download daily.cvd
nonblock_recv: recv timing out (30 secs)
WARNING: getfile: Download interrupted: Operation now in progress (IP: 62.27.56.14)
WARNING: Can't download daily.cvd from db.DE.clamav.net
Trying again in 5 secs...

Bei jedem Versuch schaut das ähnlich aus. Egal, ob man es einige Minuten später oder gleich nochmal probiert.

Manchmal klappt aber auch eine Verbindung und der Download ist extrem langsam. Es dauert mehrere Minuten, bis 1% der Daten angekommen sind. Aber dann hat freshclam schon wieder aufgegeben.

Also einen Sniffer angeworfen und geschaut, was da eigentlich abgeht: Es wird z.B. die URL http://db.DE.clamav.net/daily.cvd abgerufen.

Mit einem wget auf der gleichen Maschine gibt es aber keinerlei Schwierigkeiten, diese Datei zu laden. Es geht richtig schnell.

Vielleicht hat ja die Maschine ein Durchsatzproblem in Richtung Festplatte? Nein, auch nicht. Selbst ein wget parallel zu einem langsamen dahin kriechenden freshclam ins das gleiche Verzeichnis, in das freshclam auch reinschreibt, ist rattenschnell.

Ein Blick ins DNS zeigt, dass das Problem auch auf Serverseite liegen kann. Also mal testen:

$ dig +short db.DE.clamav.net |
 while read a; do
   echo $a;
   wget -Y off -O /dev/null http://$a/daily.cvd;
 done

Ergebnis:

IP Datenrate
88.198.17.100 Connection refused
130.133.110.67 22.3 KB/s
144.76.28.11 404 Not Found
176.9.115.53 301 Moved Permanently
178.63.73.246 Connection timed out
193.27.49.165 781 KB/s
195.30.97.3 503 Service Temporarily Unavailable
212.227.138.145 Connection timed out
213.174.32.130 404 Not Found
62.27.56.14 600 Byte/s (3 KB/s mit langen Pausen)
62.201.161.84 2.41 MB/s
62.245.181.53 1.71 MB/s
84.39.110.99 Connection timed out

Das ist ja großes Kino! Die Qualität des Servernetzwerkes ist durchaus noch etwas ausbaufähig.

Ursache und Fehlerbehebung

Um mit solchen Ausfällen im Content Delivery Network umgehen zu können, hat freshclam einen Verfügbarkeitscache: Die Datei mirrors.dat.

Ein Blick in die Datei zeigt

$ freshclam --list-mirrors | fgrep Ignore | sort | uniq -c
      5 Ignore: No
     10 Ignore: Yes

Der größte Teil der Server wird also schon komplett ignoriert. Die paar Server, die noch zulässig sind, tauchen aber nicht mehr in der DNS-Auslösung mit auf. Im Endergebnis gibt es kaum noch zulässige Server, die freshclam benutzen darf und die sind extrem langsam oder funktionslos.

Nach dem Löschen der mirrors.dat fluscht der Update wieder.

Und jetzt fällt mir auch der Hinweis am Ende der freshclam Ausgabe wieder ein: Update failed. Your network may be down or none of the mirrors listed in /etc/freshclam.conf is working. Check http://www.clamav.net/doc/mirrors-faq.html for possible reasons.

Natürlich hatte ich schon am Anfang auf diesen Link geklickt. und war bei der Installationsanleitung gelandet. Jetzt habe ich mir die Mirrors-FAQ nochmal heraus gesucht und folgendes gefunden: It is also possible that you recently had a prolonged network outage and freshclam blacklisted all the mirrors: remove mirrors.dat from the DatabaseDirectory and try again.

Na prima. Könntet ihr bitte Eure Links und Eure Mirrors fixen? Danke.