use diagnostics;
use Sys::Hostname;
@logfiles = ("/var/log/maillog");
$junk = "/etc/mail/access";
$mailprog = "mailx";
$check_received = 0;
$check_from = 0;
$check_message_id = 0;
$check_rcpt = 0;
$check_mail = 0;
$check_relay = 0;
$found_in_junk = 0;
$other_reasons = 0;
$mail_errors = 0;
use Getopt::Long;
$top = 10;
GetOptions("j:s" => \$dojunk,
"m=s" => \$mailto,
"t=i" => \$top,
"d" => \$debug,
"h" => \$html,
"u" => \$unknown,
"s" => \$printdates,
);
$dojunk= 1 if (defined($dojunk) && !$dojunk);
@logfiles= @ARGV if @ARGV;
print "DEBUG!\n" if ($debug);
$host = hostname();
if ($mailto) {
if ($mailto =~ /^([-\@!\w,.]+)$/) {
$mailto = $1;
}
else {
die "Bad characters in mail address: $mailto";
}
$real_stdout = select();
open(MAILIT, "| $mailprog -s 'Sendmail Rejected Messages for $host' $mailto")
or die("Cannot fork $mailprog");
select(MAILIT);
}
if ($dojunk) {
%junk = (); open(JUNK, $junk) || die "Cannot access specified junk file [$junk].";
while(<JUNK>){
next if m/^\#/; next if (/\b(OK|RELAY|ALLOWED)$/); warn "bad entry on line $. of $junk:\n $_", next
unless m/^(\S+)\s+(\S+)/;
$junk{lc($1)} = $2;
$junkcnt++ if $debug;
}
close(JUNK);
print "Loaded $junkcnt entries from $junk\n" if $debug;
}
$id = 0;
$old_id = 1;
$arg1 = '';
$relay = '';
$reject = '';
$next = 0;
$earliest = 0; $latest = 50000; $mostrecent = 1;
foreach $logfile (@logfiles)
{
stat($logfile);
unless (-T _)
{ print "Skipping improper file reference <$logfile>.\n" if $debug;
next;
}
if (-M _ < $latest)
{ $latest= -M _;
$mostrecent= 1;
print "New latest file is $latest days old.\n" if $debug;
}
else
{ $mostrecent= 0; }
if (-M _ > $earliest)
{ $earliest= -M _;
$first= ""; print "New earliest file is $earliest days old.\n" if $debug;
}
unless (open(INPUT, "$logfile"))
{
print "Can't open logfile [$logfile]...skipping.\n";
next;
}
while (<INPUT>) {
$first = $_ if ($printdates && !$first);
$last = $_ if ($printdates && $mostrecent);
$id = $1 if (/sendmail\[\d+\]: (\D{3}\d{5})/);
if (/ruleset=/) {
print "\nRuleset found!\n" if ($debug);
$old_id = $id;
$ruleset = $1 if (/ruleset=(.+), arg1=/);
if (/relay=/) {
$arg1 = $1 if (/arg1=(.+), relay=/);
} else {
$arg1 = $1 if (/arg1=(.+),/);
}
if (/arg2=([^,]*)/) {
$arg2 = $1;
$next = 0;
} else {
$arg2 = "";
$next = 1;
}
$relay = $1 if (/relay=(.+), reject=/);
$reject = $1 if (/reject=(.+)/);
if ($debug) {
print "Ruleset:\t",$ruleset,"\n",
"Arg1:\t\t",$arg1,"\n",
"Arg2:\t\t",$arg2,"\n",
"Relay:\t\t",$relay,"\n",
"Reject:\t\t",$reject,"\n";
}
} elsif (($old_id eq $id) && $next) {
print "...continuing with previous ruleset!\n" if ($debug);
$next = 0;
$from = $1 if ( /from=(.*?), size=/ );
$relay = $1 if ( /relay=(.+ \[.*\])/ );
if ($debug) {
print "From:\t\t",$from,"\n";
print "Relay:\t\t",$relay,"\n";
}
$next = 1 if ( /lost input channel/);
$next = 1 if ( /gethostbyaddr/ );
} else {
$ruleset = "";
$old_id = "";
$relay = "";
$arg1 = "";
$arg2 = "";
$next = "";
$from = "";
next;
}
if (!$next) {
$$ruleset++;
if ($ruleset =~ /check_(received|message_id|from|mail|relay)/) {
$relevant = $arg1;
} elsif ($ruleset =~ /check_rcpt/) {
$relevant = $from;
} else {
print "Unknown ruleset $ruleset found.\n";
}
if ($ruleset =~ m/check_relay/) {
$reason = find_reason($relevant, $arg2, $reject);
} else {
$reason = find_reason($relevant, $relay, $reject);
}
if ($unknown and $reason =~ /^unknown$/) {
print "Unprogrammed Unknown Rejections:\n" if $mail_errors == 1;
print;
}
$name = "\$count_" . $ruleset;
if ($ruleset =~ /check_(rcpt|from|mail|relay)/) {
$$name{$relevant}++;
} else {
$$name{$relevant . "\n\tFrom: " . $from}++;
}
$name = "\$count_relay_" . $ruleset;
$$name{$relay}++;
}
}
}
close(INPUT);
$totreject = $check_from + $check_rcpt + $check_received +
$check_message_id + $check_relay + $check_mail;
print "\nTotal rejections: $totreject\n\n" if $debug;
if ( $totreject > 0 ) {
if ($printdates)
{
$from = "unknown"; $to = "unknown";
$from = $1 if $first =~ /^(.{7}\d\d:\d\d:\d\d )/;
$to = $1 if $last =~ /^(.{7}\d\d:\d\d:\d\d )/;
}
if ($html)
{
print "<HTML>\n<HEAD><TITLE>Sendmail Rejected Messages for $host";
print ": $from - $to" if $printdates;
print "</TITLE></HEAD>\n";
}
print "\n";
print "<H1>" if ($html);
print "Sendmail Rejected Messages for $host";
print " $from - $to" if $printdates;
print "</H1>" if ($html);
print "\n";
if ($html) {
print "<HR><P>\n";
} else {
print "-----------------------------------------------------------------\n";
}
print "<H2>" if ($html);
print "$totreject rejections processed";
print "</H2><P>" if ($html);
print "\n";
if (($dojunk) && ($found_in_junk > 0)) {
if ($html) {
print "<TABLE BORDER=1>\n<TR><TH COLSPAN=2>$found_in_junk Rejections due to $junk data file entry</TH></TR>\n";
} else {
print "\n*** $found_in_junk Rejections due to $junk data file entry ***\n\n";
}
foreach (keys %junk) {
&prjunk_entry($_);
}
print "</TABLE><P>\n" if ($html);
}
if ($other_reasons > 0) {
if ($html) {
print "<TABLE BORDER=1>\n<TR><TH COLSPAN=2>$other_reasons Rejections due to other reasons</TH></TR>\n" if ($html);
} else {
print "\n*** $other_reasons Rejections due to these reasons ***\n\n";
}
&prjunk_entry("Sender banned");
&prjunk_entry("Domain banned");
&prjunk_entry("Network banned");
&prjunk_entry("Invalid Account Specified");
&prjunk_entry("Poorly Specified Recipient");
&prjunk_entry("DNS failure. Host must resolve");
&prjunk_entry("Host must be in Domain format");
&prjunk_entry("Relaying Denied");
&prjunk_entry("Bad Message-Id");
&prjunk_entry("Message-Id must resolve");
&prjunk_entry("DNS failure. Client must resolve");
&prjunk_entry("Client must resolve");
&prjunk_entry("RBL Entry");
&prjunk_entry("ORBS Entry");
&prjunk_entry("DUL Entry");
&prjunk_entry("RSS Entry");
&prjunk_entry("Sender domain must resolve");
&prjunk_entry("Sender domain must exist");
&prjunk_entry("Sender not valid");
&prjunk_entry("List:; syntax illegal for recipient addresses");
&prjunk_entry("User address required");
&prjunk_entry("Colon illegal in host name part");
&prjunk_entry("Invalid host name");
&prjunk_entry("User address required");
&prjunk_entry("User has moved");
&prjunk_entry("Real domain name required");
&prjunk_entry("Domain name required");
&prjunk_entry("Relaying denied");
&prjunk_entry("unknown");
print "</TABLE><P>\n" if ($html);
}
print_stats("check_message_id");
print_stats("check_from");
print_stats("check_received");
print_stats("check_rcpt");
print_stats("check_mail");
print_stats("check_relay");
print "</HTML>\n" if ($html);
if ($mailto) {
select($real_stdout);
close(MAILIT);
}
}
sub sortbyvalues {
my(@data);
my(@datakeys);
while (($key, $value) = each(%tmp)) {
if ($html) {
$key =~ s/\</\<\;/g;
$key =~ s/\>/\>\;/g;
push(@data, sprintf("<TR><TD>%6d</TD><TD>%-s</TD></TR>\n", $value, $key));
} else {
push(@data, sprintf("%6d %-s\n", $value, $key));
}
push(@datakeys, sprintf("%6d", $value));
}
@data = @data[reverse sort { $datakeys[$a] <=> $datakeys[$b]; }
$[..$#data];
$#data = $top - 1 if ($#data >= $top);
return @data;
}
sub findinjunk {
my($keys) = shift;
my($key);
foreach $key (split(/[\[\]<> ]/, $keys))
{
next unless $key;
$key = lc($key);
return $key if exists($junk{$key});
if ($key=~ /.*@(.*)$/) {
$key = $1;
return $key if exists($junk{$key});
}
if ($key=~ /.*\.([0-9]+)$/) {
while ($key=~ /(.*)\.[^.]*$/) {
$key = $1;
return $key if exists($junk{$key});
}
} else {
while ($key=~ /[^.]*\.(.*)$/) {
$key = $1;
return $key if exists($junk{$key});
}
}
}
}
sub prjunk_entry {
my($name) = shift;
if ($count_junk{$name}) {
if ($html) {
printf("<TR><TD>%6d</TD><TD>%-s</TD></TR>\n", $count_junk{$name}, $name) ;
} else {
printf("%6d %-s\n", $count_junk{$name}, $name);
}
}
}
sub print_stats {
my($ruleset) = shift;
my($name);
local %tmp;
if ($$ruleset > 0) {
if ($html) {
print "<H2>$$ruleset rejections by ruleset $ruleset</H2>\n";
} else {
if ($ruleset =~ /check_(rcpt|from|relay)/) {
print "\n*** $$ruleset rejections by ruleset $ruleset ***\n" ;
} else {
print "\n*** $$ruleset rejections by ruleset $ruleset ***\n" ;
}
}
if ($top > 0) {
if ($html) {
if ($ruleset =~ /check_(rcpt|from|mail)/) {
print "<TABLE BORDER=1>\n<TR><TH COLSPAN=2>Top $top $ruleset rejections by relevant argument</TH></TR>\n";
} elsif ($ruleset !~ /check_relay/) {
print "<TABLE BORDER=1>\n<TR><TH COLSPAN=2>Top $top $ruleset rejections by relevant argument and From:</TH></TR>\n";
}
} else {
if ($ruleset =~ /check_(rcpt|from|mail)/) {
print "\nTop $top $ruleset rejections by relevant argument:\n\n";
} elsif ($ruleset !~ /check_relay/) {
print "\nTop $top $ruleset rejections by relevant argument and From: :\n\n";
}
}
$name = "\$count_" . $ruleset;
%tmp = %$name;
if ($ruleset !~ /check_relay/) {
print &sortbyvalues();
print "</TABLE><P>\n" if ($html);
}
if ($html) {
print "<TABLE BORDER=1>\n<TR><TH COLSPAN=2>Top $top $ruleset rejections by relay host</TH></TR>\n";
} else {
print "\nTop $top $ruleset rejections by relay host:\n\n";
}
$name = "\$count_relay_" . $ruleset;
%tmp = %$name;
print &sortbyvalues();
print "</TABLE><P>\n" if ($html);
}
print "\n";
}
}
sub find_reason {
my($key) = shift;
my($key2) = shift;
my($reject) = shift;
my($junk_entry);
$junk_entry = "Invalid Account Specified" if ($reject =~ /Invalid Account Specified/);
$junk_entry = "Poorly Specified Recipient" if ($reject =~ /user address required/);
$junk_entry = "DNS failure. Host must resolve" if ($reject =~ /unresolvable/);
$junk_entry = "Host must be in Domain format" if ($reject =~ / invalid host /);
$junk_entry = "Relaying Denied" if ($reject =~ /Relaying Denied/);
$junk_entry = "Relaying Denied" if ($reject =~ /Change your SMTP setting to your local SMTP server/);
$junk_entry = "Bad Message-Id" if ($reject =~ /Bad Message-Id/);
$junk_entry = "Message-Id must resolve" if ($reject =~ /Message-Id must resolve/);
$junk_entry = "DNS failure. Client must resolve" if ($reject =~ /DNS failure/);
$junk_entry = "Client must resolve" if ($reject =~ /Client must resolve/);
$junk_entry = "RBL Entry" if ($reject =~ /maps.vix.com/);
$junk_entry = "ORBS Entry" if ($reject =~ /www.orbs.org/);
$junk_entry = "DUL Entry" if ($reject =~ /www.orca.bc.ca/);
$junk_entry = "RSS Entry" if ($reject =~ /www.mail-abuse.org\/rss\//);
$junk_entry = "Sender domain must resolve" if ($reject =~ /Sender domain must resolve/);
$junk_entry = "Sender domain must exist" if ($reject =~ /Sender domain must exist/);
$junk_entry = "Sender not valid" if ($reject =~ /Sender not valid/);
$junk_entry = "List:; syntax illegal for recipient addresses" if ($reject =~ /recipient addresses/);
$junk_entry = "User address required" if ($reject =~ /User address required/);
$junk_entry = "Colon illegal in host name part" if ($reject =~ /Colon illegal in host name part/);
$junk_entry = "Invalid host name" if ($reject =~ /Invalid host name/);
$junk_entry = "User address required" if ($reject =~ /User address required/);
$junk_entry = "User has moved" if ($reject =~ /User has moved/);
$junk_entry = "Real domain name required" if ($reject =~ /Real domain name required/);
$junk_entry = "Domain name required" if ($reject =~ /Domain name required/);
$junk_entry = "Relaying denied" if ($reject =~ /Relaying denied/);
if ($junk_entry) {
$other_reasons++;
} else {
$junk_entry = &findinjunk($key);
$junk_entry = &findinjunk($key2) unless $junk_entry;
if ($junk_entry) {
$found_in_junk++;
} else {
$junk_entry = "unknown";
$junk_entry = "Domain banned"
if ($reject =~ /\sdomain\s/);
$junk_entry = "Host banned"
if ($reject =~ /\syou\s/);
$junk_entry = "Sender banned"
if ($reject =~ /\syou\s/ and
$key =~ /\@/);
$junk_entry = "Network banned"
if ($reject =~ /network/ and
$key =~ /^([0-9]+\.?){1,4}$/);
unless ($junk_entry) {
$junk_entry = "unknown";
$other_reasons++;
$mail_errors++;
}
if ($debug) {
print "Key: ",$key,"\n";
print "Reject: ", $reject,"\n";
}
}
}
$count_junk{$junk_entry}++;
if ($debug) {
print "Reason:\t\t", $junk_entry, " (",
$count_junk{$junk_entry}, ")\n\n";
}
$junk_entry;
}