#!/usr/bin/perl -w # This code was written by Felix Buenemann. # # It was posted to the spamdyke-users mailing list on Nov 4 2008. # # It has been modified by Brent Gardner: # - removed commented-out code # - fixed some indenting 'cause I have OCD :P # - fixed a bug that prevented processing of Spamdyke info in Qmail logs # my $build = "2010022601"; use diagnostics; use strict; use Getopt::Long; my $tldtop = 0; my $detailed = 1; my $syslog = 1; GetOptions ( "tld=i" => \$tldtop, "detail!" => \$detailed ) or exit 1; # Usage: # cat /var/log/qmail/smtpd/current | ./this_file my %status = (); # hash of status connections my %origin = (); # hash of tld per status code my %originsum = (); # hash of tld per status code sums my %rblstat = (); # hash of DNSBL lists matched my %rhsblstat = (); # hash of RHSBL lists matched my %rdnsblstat = (); # hash of patterns in IP_IN_RDNS_BLACKLIST matched my ($allow, $deny, $timeout, $error, $allowpercentage, $timeoutpercentage, $errorpercentage, $spampercentage, $sum, $rblsum, $rhsblsum, $rdnsblsum); sub percentage { my $num = pop; my $sum = pop; return " 0.00%" unless $sum; $num = $num/$sum*100.0; return sprintf("%3d.%02d%%", $num, ($num - int($num))*100); } #print "spamdyke-stats build $build\n\n"; while(<>){ if( substr($_,0,1) eq '@' ) { # this gets rid of the leading 25-char timestamp and a space # $_ = substr $_,26; # BG: this gets rid of all lines that don't start with "spamdyke[]: " # next unless ( m/(^spamdyke\[[0-9]+\]: )(.*)/i ); # BG: this in conjunction with the previous line gets rid of the leading "spamdyke[]: " # $_ = $2; } else { my ($hostname,$id,$line) = split / /, substr($_,16), 3; next unless substr($id,0,9) eq 'spamdyke['; my ($line2,$line3) = split /:/, substr($line,0), 2; next unless index($line2,"call]") > 0; $_ = $line3; } if( m/^(ALLOWED|ERROR|TIMEOUT|((DENIED|FILTER)_[^ ]+))/ ) { my $line = substr $_,length $1; $_ = $1; if( $detailed ) { if( m/FILTER_RBL_MATCH/ ){ $line =~ m/rbl: (\S+)/; $rblstat{$1}++; $rblsum++; } elsif( m/FILTER_RHSBL_MATCH/ ){ $line =~ m/rhsbl: (\S+)/; $rhsblstat{$1}++; $rhsblsum++; } elsif( m/FILTER_IP_IN_RDNS_BLACKLIST/ ){ $line =~ m/keyword: (\S+)/; $rdnsblstat{$1}++; $rdnsblsum++; } } next if m/^FILTER_/; $status{$_}++; if($tldtop and $line =~ m/ origin_rdns: ([^ ]+)/) { my $rdns = $1; $originsum{$_}++; if($rdns =~ m/^\(unknown\)$/){ next; } elsif($rdns =~ m/\.(com|net)$/){ $origin{$_}{$1}++; } elsif($rdns =~ m/\.([a-z]{2,2}\.[a-z]{2,2})$/){ # co.uk $origin{$_}{$1}++; } elsif($rdns =~ m/\.([a-z]{2,})$/){ # de, ru, ... $origin{$_}{$1}++ } else { next; } } } } $allow = 0; $deny = 0; $error = 0; $timeout = 0; foreach my $stat (sort keys %status){ if( $stat =~ m/ALLOWED/ ){ $allow = $status{$stat}; } elsif( $stat =~ m/TIMEOUT/ ){ $timeout += $status{$stat}; } elsif( $stat =~ m/ERROR/ ){ $error += $status{$stat}; } else{ $deny += $status{$stat}; } } $sum = ($deny + $error + $timeout + $allow); foreach my $key (sort { $status{$b} <=> $status{$a} || $a cmp $b; } keys %status){ printf "%8d %s $key\n", $status{$key}, percentage($sum, $status{$key}); if(length scalar(keys %rblstat) and $key eq "DENIED_RBL_MATCH" ){ print "--------------- Breakdown ---------------\n"; foreach my $key (sort { $rblstat{$b} <=> $rblstat{$a} || $a cmp $b; } keys %rblstat){ printf "%8d %s $key\n", $rblstat{$key}, percentage($rblsum,$rblstat{$key}); } print "-----------------------------------------\n"; } elsif(length scalar(keys %rhsblstat) and $key eq "DENIED_RHSBL_MATCH" ){ print "--------------- Breakdown ---------------\n"; foreach my $key (sort { $rhsblstat{$b} <=> $rblstat{$a} || $a cmp $b; } keys %rhsblstat){ printf "%8d %s $key\n", $rhsblstat{$key}, percentage($rhsblsum,$rhsblstat{$key}); } print "-----------------------------------------\n"; } elsif(length scalar(keys %rdnsblstat) and $key eq "DENIED_IP_IN_RDNS" ){ print "--------------- Breakdown ---------------\n"; foreach my $key (sort { $rdnsblstat{$b} <=> $rdnsblstat{$a} || $a cmp $b; } keys %rdnsblstat){ printf "%8d %s $key\n", $rdnsblstat{$key}, percentage($rdnsblsum,$rdnsblstat{$key}); } print "-----------------------------------------\n"; } if($tldtop && $origin{$key}) { my $top = $tldtop; print "--------------- Top $top TLD ---------------\n"; my $tldsum = 0; my $lastsum = 0; my @tldgroup = (); my %neworigin = (); foreach my $tld (sort { $origin{$key}{$a} <=> $origin{$key}{$b} } keys %{$origin{$key}}){ if(($origin{$key}{$tld}/$originsum{$key}*100) == $lastsum) { push(@tldgroup, $tld); } else { if(scalar @tldgroup) { $neworigin{join(', ', @tldgroup)} = $lastsum; @tldgroup = (); } push(@tldgroup, $tld); } $lastsum = $origin{$key}{$tld}/$originsum{$key}*100; $tldsum += $origin{$key}{$tld}; } if(scalar @tldgroup) { $neworigin{join(', ', scalar(@tldgroup))} = $lastsum * length scalar(@tldgroup); } foreach my $tld (sort { $neworigin{$b} <=> $neworigin{$a} } keys %neworigin){ printf "%s\t$tld\n", percentage($originsum{$key}, $neworigin{$tld}/100.0*$originsum{$key}); last unless --$top; } print "-----------------------------------------\n"; } } my $format_summary = "%8d %s"; print "\n"; print "---------------- Summary ----------------\n"; printf "Allowed: $format_summary\n", $allow, percentage($sum, $allow); printf "Timeout: $format_summary\n", $timeout, percentage($sum, $timeout); printf "Errors : $format_summary\n", $error, percentage($sum, $error); printf "Denied : $format_summary\n", $deny, percentage($sum, $deny); printf "Total : $format_summary\n", $sum, percentage($sum, $sum);