Spam-Reiz

Bisher hatte ich eigentlich nie größere Probleme mit Spam. Meine private Emailadresse kennen nur wenige, hoffte auf vernünftigen Umgang damit und verbreite keine Kettenmails. Mit geübten Blick war es bisher sehr leicht möglich die 5 wichtigen Mail von den 60 unwichtigen zu unterscheiden und zu löschen. Auch der im Outlook mitgelieferte Spamfilter schlug sich bei englischsprachigen Spammails wacker.

Doch irgendwann im Juli stieg die Anzahl an Spammails sprunghaft an und überschritt bis heute täglich die 100er Marke. Ewig langes Herunterladen der Mails war die Folge, der Outlookfilter versagte beim zunehmenden Anteil deutscher Spammails zusehends und auch ich hatte keine Motivation mehr mich täglich durch die Adressen und Betreffzeilen der Mails zu fräsen und zu löschen.

Von solchem Reiz gepeinigt, war ich genötigt einen richtigen, vorgeschalteten Spamfilter als Emailproxy zu installieren. Zwar ist es noch nicht mal so schwierig einen kostenlosen und guten Spamfilter zu finden, aber mit meinem Wunsch nach einem einfach zu installierenden und konfigurierenden Windows-Programm war ich kräftig auf dem Holzweg. Im Großen und Ganzen bleiben da nur noch Spam Assassin und ASSP übrig. Spam Assassin läuft nur mit Python und muß sogar noch mit fragwürdigen Tools selbst kompiliert werden -> alles zuviel Aufwand. Blieb also noch ASSP, das unbedingt Perl und ein halbes Dutzend Zusatzmodule installiert haben will.

Übrigens, hier eine schöne Erklärung aus dieser Quelle, was ASSP eigentlich ist, und der Beweis, daß Übersetzungsprogramme immer noch nicht perfekt arbeiten:

The Anti-Spam smtp Vollmacht-,zu unterstützen (ASSP) herstellt ist ein geöffnetes QuellPlattform-unabhängiges smtp proxy server, das die whitelists und bayesische Entstörung einführt, zum des Planeten von der Trockenfäule freiwilligen email (ECU) zu reinigen. ECU müssen am smtp Bediener gestoppt werden. Werkzeuge Anti-Spam müssen sein anpassungsfähig zu neuem Spam und besonders angefertigt werden für Postmuster jedes Aufstellungsortes. Diese freien, bedienungsfreundlichen Werkzeugarbeiten mit jeder möglicher Post transportieren und erzielen diese Ziele, die keine Operatorintervention erfordern, nachdem die Ausgangseinstellung phase.

Beim zweiten Versuch ASSP ans Laufen zu bekommen, klappte es dann endlich und ich wurde prompt mit einer Fülle an Konfigurationsoptionen erschlagen. Mühsam ging ich jede Option (sogar mehrfach) durch, nur um die Konfigurationen danach wieder zu verwerfen. Da gibt es dann so tolle „Features“ wie verzögerte Mailzustellung (Delaying). Alle eintreffenden Mails werden erst mal protokolliert und temporär abgelehnt. Wenn das Senderprogramm dann einen zweiten Zustellversuch unternimmt, wird die Mail zugelassen. Angeblich soll das die Wunderwaffe gegen Spam schlechthin sein, weil die Spamrobots und -würmer nie zwei mal hintereinander eine Mail an dieselbe Adresse schicken. Wers glaubt. Netter Nebeneffekt ist, dass auch alle Nicht-Spammails verzögert zugestellt werden. Kommt besonders klasse, wenn man dringend auf eine Mail mit einem vergessenen Passwort wartet. 🙁

Naja, nach vielen Tagen des Feintunings lief der Proxy endlich einigermaßen so, wie er sollte. Doch es gab noch eine letzte Hürde zu überwinden: ein täglicher Spamreport über die gefilterten Spammails sollte noch her. Schließlich will ich die Arbeit des Proxys ein wenig überwachen, ohne mühsam Logfiles lesen zu müssen oder mir alle Spammails wieder runterladen zu müssen. Im Grunde so etwas, wie GMX es bei seinen Mailaccounts jeden Tag zuschickt.

Es war natürlich naiv von mir zu glauben, daß bei einer verbreiteten, quelloffenen Software sich schon jemand Gedanken über dieses Problem gemacht hat, und ASSP bei jeder blockierten Mail deren Metadaten (Uhrzeit, Absender, Betreff) in eine Liste aufnimmt. Wäre ja auch zuviel verlangt. Und offenbar hat sich noch kein Depp gefunden, der sich getraut hat, solch ein Feature in den fies gefrickelten Perl-Sourcecode einzubauen (was ich in gewisser Weise nachvollziehen kann). Und eine Liste blockierter Mails gibt es recht nicht, wieso sollte auch jemand sowas überhaupt wissen wollen. Stattdessen muß man sich solche Infos aus ganz speziell formatierten Zeilen aus dem Logfile zusammenkratzen. Und nachdem ich das ganze Internet nach einer Lösung abgegrast hatte, habe ich tatsächlich eine (nochmals in Worten zu Verdeutlichung: EINE) einzige Lösung gefunden, indem jemand eben jenes Logfile nach bestimmen Zeichenfolgen, die bei einer Blockierung ausgegeben werden, geschrieben hat.

Aber der Reiz war noch nicht vorbei, denn die beiden Perl-Skripte, die das taten, funktionierten nicht mit der neusten ASSP Version, da diese andere Zeichenketten verwendet, und somit nie eine Zeile gefunden wurde. Also mußte ich mit meinen quasi nicht vorhandenen Perlkenntnissen die beiden Skripte umändern. Dies hat mich auch „nur“ 6 Stunden Arbeit gekostet. Als nächstes Schmankerl sollten diese Skripte alle blockierten Mail der letzten 24 Stunden nach Aufruf des Skriptes anzeigen. Nun hat ASSP aber die dumme Angewohnheit selbstständig und ohne Einflußmöglichkeit meinerseits, Punkt 24:00 Uhr das alte Logfile umzubenennen und ein neues, unter dem alten Namen anzulegen. Da ich nicht bis Mitternacht warten möchte, sondern die Spamübersicht gern dann hätte, wenn ich nach Hause komme, heißt das alle Logfileeinträge von aktuellen Logfile von heute 00:00 – 18:00 Uhr parsen, und das gestrige Logfile von 18:00 bis 24:00 Uhr. Ich muss ja nicht erwähnen, daß reizeshalber das aktuelle Logfile immer maillog.txt, das vorangegangene aber immer das Datum im Namen hat und sich damit der Dateiname jeden Tag ändert (spaßige Monats- und Jahreswechsel eingeschlossen).

Da ich aber ein 1337h4xx0r-|°r0gr4mm0r bin, habe ich mich auch dieses Reizes angenommen und nach „nur“ weiteren 6 Stunden habe ich auch diese (und alle anderen) Probleme gelöst. Und da ich das alles nicht am Stück, sondern noch schön abends daheim geproggt habe, hat mich der ganze Spaß eine Woche lange bis nachts um 1 um den Schlaf gebracht. Vielen Dank auch. Und weil ich so lieber Jedi bin, möchte ich allen anderen, die ein Bedürfnis nach täglichen Spamübersichtsmails beim ASSP haben, mein Skript hier zur Verfügung stellen. Hatte ich schon erwähnt, daß ich von Perl Null-Komma-Null Ahnung habe? Also keine Beschwerden, wenn das Skript einfach nur schlecht ist oder nicht sauber funktioniert.

(klicken um den Code zu sehen oder zu kopieren)

#!/usr/bin/perl

# Collects data for daily spam report
# (c) Marco Michelino 2006 under the terms of the GPL

use DBI;
use bytes;
use Mail::Sendmail qw(sendmail %mailcfg);

%monate=qw(Jan 01 Feb 02 Mar 03 Apr 04 May 05 Jun 06 Jul 07 Aug 08 Sep 09 Oct 10 Nov 11 Dec 12);
%monate2=qw(01 Jan 02 Feb 03 Mar 04 Apr 05 May 06 Jun 07 Jul 08 Aug 09 Sep 10 Oct 11 Nov 12 Dec);

open(F,"<assp.cfg");
while ($line = <F>) {
	($key, $value)= split(/:=|\n/, $line);

	# mysql Zugangsdaten besorgen
	if ($key =~ /myhost/) {
		$myhost= $value;
	} elsif ($key =~ /myuser/) {
		$myuser= $value;
	} elsif ($key =~ /mypassword/) {
		$mypassword= $value;
	} elsif ($key =~ /mydb/) {
		$mydb= $value;
	} elsif ($key =~ /subjectLogging/) {
		$subjectLogging= $value;
	}
}
close F;

# Datenbankverbindung aufbauen
$db=DBI->connect("DBI:mysql:$mydb:$myhost",$myuser,$mypassword) || die "cannot connect to database\n";

$create=$db->prepare('CREATE TABLE IF NOT EXISTS spam_report (
                      date timestamp NOT NULL,
                      sender varchar(100) NOT NULL,
                      recipient varchar(100) NOT NULL,
                      subject varchar(100) NOT NULL,
                      reason varchar(100) NOT NULL,
                      INDEX (date))'); 
$create->execute || die "no permissions on database\r\n";
$truncate=$db->prepare('TRUNCATE TABLE spam_report');
$truncate->execute || die "no permissions on database\r\n";

$write_spam_report= $db->prepare('INSERT INTO spam_report VALUES (?, ?, ?, ?, ?)');


# Tage berechnen
$d1=getToday();
$d2=getYesterday();
$length = @ARGV;
@files = ("logs\\maillog.txt");

if ($length != 0) {
	$d2=makeDate(@ARGV[0]);
	for (my $i=0; $i < $length; $i++) {
		unshift(@files, "logs\\@ARGV[$i].maillog.txt");
	}
} else {
	unshift(@files, getYesterdayMaillog());
}

foreach(@files) {
	open(F,"<".$_);
	my $counter = 0;
	while ($line = <F>){
		#print $line;
		if ($line !~ / passing if safe because testmode, otherwise /) {
			if ($line =~ / \[DNSBL\].* failed DNSBL: /) {
				$counter++;
				($vorne, $hinten)= split(/ failed DNSBL: /, $line);
				($date, $time, $test, $ip, $sender_angles, $w1, $recipient, $rest)= split(/ /, $vorne);
				($w2, $sender)= split(/<|>/, $sender_angles);
				($w3, $subject)= split(/ /, $hinten);
				$write_spam_report->execute(makeTimestamp($date, $time), $sender, $recipient, makeSubject($subject), 'DNSBL failed');
			} elsif ($line =~ / \[MessageLimit\].* Message Limit: /) {
				$counter++;
				($vorne, $hinten)= split(/ Message Limit /, $line);
				($date, $time, $test, $ip, $sender_angles, $w1, $recipient, $rest)= split(/ /, $vorne);
				($w2, $sender)= split(/<|>/, $sender_angles);
				($w3, $subject)= split(/ /, $hinten);
				$write_spam_report->execute(makeTimestamp($date, $time), $sender, $recipient, makeSubject($subject), 'Message Limit');
			} elsif ($line =~ / \[URIBL:max-uris\].* failed URIBL checks /) {
				$counter++;
				($vorne, $hinten)= split(/ URIBL checks \(maximum uris exceeded\) /, $line);
				($date, $time, $test, $ip, $sender_angles, $w1, $recipient, $rest)= split(/ /, $vorne);
				($w2, $sender)= split(/<|>/, $sender_angles);
				($w3, $subject)= split(/ /, $hinten);
				$write_spam_report->execute(makeTimestamp($date, $time), $sender, $recipient, makeSubject($subject), 'URIBL checks failed');
			}
		}
	}
	print "$counter Eintraege fuer $_\r\n";
}
close F;

# Mailversand starten
$read_spam_read = $db->prepare('SELECT date, sender, recipient, subject, reason FROM spam_report WHERE date between ? and ? ORDER BY date');
$read_spam_read->execute($d2, $d1);
$text="<html><body style=\"font-family: Arial; font-size: 10pt;\">";

if ($read_spam_read->rows) {
	$subj = $read_spam_read->rows." blockierte Spammails am ".getTodayString();
	$text.=$subj;
	$text.="<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\" style=\"font-family: Arial; font-size: 10pt;\">";
	
	while (@spam= $read_spam_read->fetchrow_array()) {
		$text.="<tr><td>&nbsp;</td><td>&nbsp;</td></tr>";
		#$text.="um: $spam[0]\r\n";
		$text.="<tr>";
		$text.="<td>von:&nbsp;</td>";
		$text.="<td>$spam[1]</td>";
		$text.="</tr>";
		#$text.=" an $spam[2]\r\n";
		$text.="<tr>";
		$text.="<td>Betreff:&nbsp;</td>";
		$text.="<td><b>$spam[3]</b></td>";
		$text.="</tr>";
		$text.="<tr>";
		$text.="<td><small>Grund:&nbsp;</small></td>";
		$text.="<td><small>$spam[4]</small></td>";
		$text.="</tr>";
	}
	$text.="</table>";
} else {
	$subj ="keine blockierte Spammails am ".getTodayString();
	$text.="<p>$subj</p>";
}
$text.="</body></html>";

%mail= (
	From	       => 'spam@domain.de',
	To	           => 'admin@domain.de',
	'Content-Type' => 'text/html; charset="iso-8859-1',
	Subject        => $subj,
	Message        => $text
);
sendmail(%mail) or print "domain.de $Mail::Sendmail::error\r\n";
$read_spam_read->finish;

sub makeDate {
	my ($y, $m, $d) = split(/-/,shift());
	my $y=sprintf("%02d",$y);
	my $m=sprintf("%02d",$m);
	my $d=sprintf("%02d",$d);
	return "20$y$m$d".getDateString(time(),"%H%M%S");;
}

sub makeTimestamp {
	my ($mon, $d, $y) = split(/-/,shift());
	my $d=sprintf("%02d",$d);
	
	my ($H, $M, $S) = split(/:/,shift());
	my $H=sprintf("%02d",$H);
	my $M=sprintf("%02d",$M);
	my $S=sprintf("%02d",$S);
	
	return "20$y$monate{$mon}$d$H$M$S";
}
 
sub makeSubject{
	$subject = shift();
	$subject .= '';
	$subject =~ s/_/ /g;
	return $subject;
}

sub getToday {
	return getDateString(time(),"%y%m%d%H%M%S");
}

sub getYesterday {
	return getDateString(time()-(24*60*60), "%y%m%d%H%M%S");
}

sub getTodayString {
	return getDateString(time(),"%d. %mon. %Y");
}

sub getYesterdayMaillog {
	return getDateString(time()-(24*60*60),"logs\\%y-%m-%d.maillog.txt");
}

sub getDateString {
	@t = localtime(shift());
	$format = shift();
	
	my $y=sprintf("%02d",@t[5]-100);
	my $Y=sprintf("%04d",@t[5]+1900);
	my $m=sprintf("%02d",@t[4]+1);
	my $mon=$monate2{$m};
	my $d=sprintf("%02d",@t[3]);
	my $H=sprintf("%02d",@t[2]);
	my $M=sprintf("%02d",@t[1]);
	my $S=sprintf("%02d",@t[0]);
	
	$format =~ s/%y/$y/;
	$format =~ s/%Y/$Y/;
	$format =~ s/%mon/$mon/;
	$format =~ s/%m/$m/;
	$format =~ s/%d/$d/;
	$format =~ s/%H/$H/;
	$format =~ s/%M/$M/;
	$format =~ s/%S/$S/;
	
	return $format;
}

 

Um im Gegensatz zu der Fundstelle des Originalskripts, leg ich sogar noch ne Anleitung obendrauf, Wahnsinn, was? Also, das Skript muß in den ASSP-Ordner kopieren werden. Es parst eine Menge von Logfiles, sucht nach Fundstellen für blockierte Mails und schreibt die Metadaten in eine MySQL-Datenbank-Tabelle. Anschließend wird aus dieser Tabelle eine chronologische Übersicht erzeugt und an eine Mailadresse verschickt. Dazu ist es nötig, daß in der Datei assp.cfg die Verbindungsdaten zur lokalen MySQL-Datenbank eingetragen sind.

Im ASSP-Ordner kann das Skript über eine Konsole oder auch als geplanter Task mit dem Kommando „perl spam-report.pl“ gestartet werden. Ohne weiteren Parameter liest das Skript standardmäßig die Logdatei logs\maillog.txt und die des Vortages aus (z.B. logs\07-08-16.maillog.txt). Als Kommandozeilenparameter kann man auch einzelne Tage angeben, deren Logfiles geparst werden sollen. Dies ist praktisch, wenn man mehr als 24 Stunden zurückgehen will, daher ist die aktuelle Datei unter logs\maillog.txt auch immer mit dabei. Ergo sähe der Aufruf vom 9. bis zum (fiktiv heutigen) 11. Juli so aus:

perl spam-report.pl 07-07-09 07-07-10

In Zeile 133 und 134 trägt man dann noch die Absender- und Empfangsadresse ein. Damit man das Skript die Mail auch versenden kann, muß man ASSP das Relaying von localhost aus erlauben. Dazu in der ASSP-Weboberfläche unter der Rubrik „Relaying“ beim Punkt „Accept All Mail“ den Wert „127.0.0.1“ hinzufügen. Außerdem muß die Absenderadresse in die Ausnahmeliste eingetragen werden, sonst erkennt ASSP dies Mail (korrekterweise) als Spam und blockiert sie vor uns selbst 🙂 Dazu in der Weboberfläche in der Kategorie „SPAM Lover/No Processing“ bei der Option „Unprocessed Addresses“ die From-Adresse im Skript eintragen. Hat man das alles erledigt, kann man einen geplanten Task oder cronjob auf täglicher Basis einrichten und das Skript mailt einem eine hübsch formatierte Spamübersicht in HTML-Mail zu. Wer meint das Skript in irgendeiner weise überarbeiten zu müssen, darf das hiermit gerne tun, solange er mir keine Programmierfragen stellt.