#!/usr/bin/perl
##########################################################################
# $Id$
##########################################################################
# $Log: spamassassin,v $
# Revision 1.2 2008/06/30 20:47:20 kirk
# fixed copyright holders for files where I know who they should be
#
# Revision 1.1 2008/05/11 22:00:41 mike
# Inital check in of spamassassin files -mgt
#
# Revision 1.3 2007/10/10 16:41:27 whb
# Yes, Win does get over 10000 spam messages PER DAY
#
# Revision 1.2 2007/01/20 17:13:56 whb
# Lump together child-related errors
# - We're experiencing periods of high loads, causing many fork errors
#
# Revision 1.1 2006/09/16 20:02:01 whb
# Initial revision
#
#
#######################################################
### Copyright (c) 2008 Win Bent <whb@haus.org>
### Covered under the included MIT/X-Consortium License:
### http://www.opensource.org/licenses/mit-license.php
### All modifications and contributions by other persons to
### this script are assumed to have been donated to the
### Logwatch project and thus assume the above copyright
### and licensing terms. If you want to make contributions
### under your own copyright or a different license this
### must be explicitly stated in the contribution an the
### Logwatch project reserves the right to not accept such
### contributions. If you have made significant
### contributions to this script and want to claim
### copyright please contact logwatch-devel@lists.sourceforge.net.
##########################################################
##########################################################################
use Logwatch ':sort';
my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0;
my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0;
my $Ignore_connections = $ENV{'ignore_connections'};
$StillRoot = 0;
$CleanTotal = 0;
$SpamTotal = 0;
$PyzorTerminated = 0;
my %Child;
my %Clean;
my %Spam;
my %Users;
my %Connections;
#Todo
# meta test DIGEST_MULTIPLE has undefined dependency 'DCC_CHECK' : 2 Time(s)
# server started on port 783/tcp (running version 3.1.9) : 2 Time(s)
# meta test DIGEST_MULTIPLE has undefined dependency 'RAZOR2_CHECK' : 2 Time(s)
# server hit by SIGHUP, restarting : 1 Time(s)
# server killed by SIGTERM, shutting down : 1 Time(s)
# meta test DIGEST_MULTIPLE has undefined dependency 'PYZOR_CHECK' : 1 Time(s)
# meta test SARE_SPEC_PROLEO_M2a has dependency 'MIME_QP_LONG_LINE' with a zero score : 1 Time(s)
while (defined($ThisLine = <STDIN>)) {
if ( # We don't care about these
# Note that we DO care about "connection from" non-localhost
( $ThisLine =~ m/connection from localhost / ) or
( $ThisLine =~ m/setuid to / ) or
( $ThisLine =~ m/processing message / ) or
( $ThisLine =~ m/^spamd: result: .*,mid=\(unknown\)/ ) or
( $ThisLine =~ m/^prefork: child states: / ) or
( $ThisLine =~ m/^spamd: alarm *$/ ) or
( $ThisLine =~ m/^spamd: handled cleanup of child / ) or
( $ThisLine =~ m/^spamd: server successfully spawned child process, / ) or
( $ThisLine =~ m/^logger: removing stderr method/ ) or
( $ThisLine =~ m/^spamd: server pid:/ ) or
( $ThisLine =~ m/^prefork: adjust: \d+ idle children (less|more) than \d+ (min|max)imum idle children/ ) or
# Sendmail messages to ignore
( $ThisLine =~ m/^AUTH=/ ) or
( $ThisLine =~ m/^STARTTLS/ ) or
( $ThisLine =~ m/^starting daemon \(/ ) or
( $ThisLine =~ m/^ruleset=trust_auth/ ) or
( $ThisLine =~ m/^ruleset=check_relay/ ) or
0 # Always last in the list, so all above can say "or" at the end
) {
; # We don't care about these
} elsif ( ($User) = ($ThisLine =~ m/clean message .* for (.+?):\d+ / )) {
$Clean{ $User}++;
$Users{ $User}++;
} elsif ( ($User) = ($ThisLine =~ m/identified spam .* for (.+?):\d+ / )) {
$Spam{ $User}++;
$Users{ $User}++;
} elsif ( $ThisLine =~ m/still running as root: / ) {
$StillRoot++;
} elsif ( $ThisLine =~ m/^spamd: connection from (.*) at port / ) {
$Connections{$1}++;
# These are caused by pyzor taking too long and being terminated
} elsif ( $ThisLine =~ m/^pyzor:.* error: TERMINATED, signal 15/ ) {
$PyzorTerminated++;
} elsif ( $ThisLine =~ m/\bchild\b/ ) {
chomp($ThisLine);
# Many perl errors report pseudo-line-numbers, e.g.
# ... at /usr/bin/spamd line 1085, <GEN5490> line 212
$ThisLine =~ s/\d+/___/g; # Make all numbers "generic"
$Child{ $ThisLine }++; # ...and count generic error types
} elsif ( ($spam, $score, $msgid) = ($ThisLine =~ m/^spamd: result: (.) (-?\d+).*,mid=<(.*)>/) ) {
# Only record the first scan
if (!defined($msg{$msgid}->{"score"}) and $spam eq "Y") {
$msg{$msgid}->{"score"} = $score;
$SpamRelay{$msg{$msgid}->{"relay"}}++;
}
} elsif ( $ThisLine =~ m/^\w+:/ ) {
# Sendmail lines for statistics
if ( ($from, $msgid, $relay) = ($ThisLine =~ m/^\w+: from=<(.*)>, .*, msgid=<(.*)>, .*, relay=(.*)/) ) {
$msg{$msgid}->{"from"} = $from;
$msg{$msgid}->{"relay"} = $relay;
}
# EVERYTHING ELSE, or, Incentive to identify all "We don't care" lines
# We on-purpose allow warnings about --max-children to go here
} else {
chomp($ThisLine);
# Report any unmatched entries...
$OtherList{$ThisLine}++;
}
}
#######################################################
#XX print "# Detail:${Detail}\n"; #XX debugging
if ( keys %Users ) {
my ($u, $cl, $sp);
print "\nMail Recipients:\n";
# Some might want to limit this output based on $Detail, but we want it all!
foreach $u (sort {$a cmp $b} keys %Users) {
$cl = 0 + $Clean{$u}; # Avoid "undefined" error
$sp = 0 + $Spam{$u}; # Avoid "undefined" error
$CleanTotal += $cl;
$SpamTotal += $sp;
#OLD: If one user gets over 9999 messages, you have our sympathies
#NOW: If one user gets over 99999 messages, you have our sympathies
printf " %-8s : %4d clean, %5d spam\n", $u, $cl, $sp;
}
}
if ( $CleanTotal || $SpamTotal ) {
my $ttotal = $CleanTotal + $SpamTotal;
print "\nSummary:\n";
printf "\tTotal Clean: %5d (%3d%%)\n", $CleanTotal,
int ((100.0 * $CleanTotal / $ttotal) + 0.5);
printf "\tTotal Spam: %5d (%3d%%)\n", $SpamTotal,
int ((100.0 * $SpamTotal / $ttotal) + 0.5);
}
if (keys %SpamRelay) {
print "\nTop 10 Spam Relays:\n";
$i = 0;
foreach $relay (sort {$SpamRelay{$b}<=>$SpamRelay{$a} } keys %SpamRelay) {
print " $relay: $SpamRelay{$relay} Time(s)\n";
last if ++$i == 10;
}
}
if ( $StillRoot ) {
print qq{\n"still running as root" error: $StillRoot time(s)\n};
}
if (keys %Child) {
print "\nChild-related errors\n";
foreach $line (sort {$Child{$b}<=>$Child{$a} } keys %Child) {
print " $line: $Child{$line} Time(s)\n";
}
}
if (keys %Connections) {
my $header_printed = 0;
foreach my $connection ( sort {$a cmp $b} keys %Connections ) {
if ($Ignore_connections && $connection =~ /$Ignore_connections/) { next; }
if (!$header_printed) {
print "\nConnections from:\n";
$header_printed = 1;
}
print " $connection : $Connections{$connection} Time(s)\n";
}
}
if ( $PyzorTerminated) {
print "\nPyzor TERMINATED errors: $PyzorTerminated time(s)\n";
}
if (keys %OtherList) {
print "\n**Unmatched Entries**\n";
foreach $line (sort {$OtherList{$b}<=>$OtherList{$a} } keys %OtherList) {
print " $line: $OtherList{$line} Time(s)\n";
}
}
exit(0);
# vi: shiftwidth=3 tabstop=3 syntax=perl et
# Local Variables:
# mode: perl
# perl-indent-level: 3
# indent-tabs-mode: nil
# End: