#!/usr/bin/perl 
##########################################################################
# $Id: sendmail,v 1.17 2003/02/18 15:42:01 kirk Exp $
##########################################################################

########################################################
# This was written and is maintained by:
#    Kenneth Porter <shiva@well.com>
#
# Please send all comments, suggestions, bug reports,
#    etc, to shiva@well.com.
########################################################

$MsgsSent = 0;
$BytesTransferred = 0;
$HourReturns = 0;
$DaysReturns = 0;

my %relay;
my %abuse;
my %largeHdrs;
my %notLocal;

while (defined($ThisLine = <STDIN>)) {
   ($QueueID) = ($ThisLine =~ m/^([a-zA-Z0-9]+): / );
   $ThisLine =~ s/^[a-zA-Z0-9]+: //;
   if ( ( $ThisLine =~ m/^to=.*stat=/ ) or
         ( $ThisLine =~ m/^alias database [^ ]* (auto)?rebuilt by/ ) or 
         ( $ThisLine =~ m/[0-9]* aliases, longest [0-9]* bytes, [0-9]* bytes total/ ) or 
         ( $ThisLine =~ m/^starting daemon (.*):/ ) or 
         ( $ThisLine =~ m/premature EOM/ ) or 
         ( $ThisLine =~ m/unexpected close on connection from/ ) or 
         ( $ThisLine =~ m/timeout waiting for input from/ ) or 
         ( $ThisLine =~ m/lost input channel from/ ) or 
         ( $ThisLine =~ m/DSN: Cannot send message for \d+ day/ ) or 
         ( $ThisLine =~ m/: Service unavailable$/) or 
         #( $ThisLine =~ m/did not issue MAIL\/EXPN\/VRFY\/ETRN during connection/ ) or 
         ( $ThisLine =~ m/Broken pipe|Connection (reset|timed out)/ ) or
         ( $ThisLine =~ m/X-Spam/ ) or
         ( $ThisLine =~ m/Milter message: body replaced/ ) or
         ( $ThisLine =~ m/Milter: data/ ) or
         ( $ThisLine =~ m/Milter change: header/ ) or
         ( $ThisLine =~ m/Milter delete: header/ ) or
         ( $ThisLine =~ m/^clone [a-zA-Z0-9]+, owner=/ ) ) {
      # We don't care about these
   } elsif ( ($Bytes, $NumRcpts, $RelayHost) = ($ThisLine =~ /^from=.*size=([0-9]+).*nrcpts=([0-9]+).*relay=(\[[0-9\.]+\]|[^ ]* \[[0-9\.]+\]|[^ ]+).*$/) ) {
      if ($NumRcpts > 0) {
         $MsgsSent++;
         $BytesTransferred += $Bytes;
      }
      chomp($Relays{$QueueID} = $RelayHost); 
   } elsif ( $ThisLine =~ m/X-Virus-Scanned: by amavis/) {
      $Amavis++;
   } elsif ( $ThisLine =~ m/X-Scanned-By: MIMEDefang/) {
      $Defang++;
   } elsif ( ($User) = ($ThisLine =~ /^<([^ ]*)>... User unknown$/) ) {
      $UnknownUsers{$User}{$QueueID}++;
   } elsif ( ($Host) = ($ThisLine =~ /\(Name server: ([^ ]+): host not found\)/)) {
      $UnknownHosts{$Host}++;
   } elsif ( ($Domain) = ($ThisLine =~ /Domain of sender address ([^ ]+) does not resolve/)) {
      $UnresolvedDomains{$Domain}++;
   } elsif ($ThisLine =~ /reject=550 5\.7\.1 <[^ ]*@([^ ]*)>\.\.\. Relaying Denied \(Spammer\)/) {
      # We block some particularly annoying spam domains with the
      # following in /etc/mail/access...
      # From:worduphosting.com	ERROR:550 5.7.1 Relaying Denied (Spammer)
      $KnownSpammer{$1}++;
   } elsif ( ($Dest,$Relay) = ($ThisLine =~ /^ruleset=check_rcpt, arg1=<([^ ]*)>, relay=([^,]*), reject=550\s*[\d.]*\s*<[^ ]*>\.\.\. Relaying denied/) ) {
      $Temp = "From " . $Relay . " to " . $Dest;
      $RelayDenied{$Temp}++;
   } elsif ( ($Relay,$BlSite) = ($ThisLine =~ /^ruleset=check_relay, arg1=[^,]*, arg2=[^,]*, relay=([^,]*), reject=550\s*[\d.]*\s*Mail from [^ ]* refused by blackhole site (.*)/) ) {
      $Temp = "From " . $Relay . " by " . $BlSite;
      $BlackHoled{$Temp}++;
   } elsif ( ($Relay,$BlSite) = ($ThisLine =~ /^ruleset=check_relay, arg1=[^,]*, arg2=[^,]*, relay=([^,]*), reject=553\s*[\d.]*\s*.*http:\/\/([^\/]*)\//) ) {
      $Temp = "From " . $Relay . " by " . $BlSite;
      $BlackHoled{$Temp}++;
     $BlackHoles{$BlSite}++;
   } elsif ( ($Relay,$BlSite) = ($ThisLine =~ /^ruleset=check_rcpt, arg1=[^,]*, relay=([^,]*), reject=550\s*[\d.]*\s*<[^ ]*>\.\.\. Mail from [^ ]* refused by blackhole site ([^ ]*)/) ) {
      $Temp = "From " . $Relay . " by " . $BlSite;
      $BlackHoled{$Temp}++;
     $BlackHoles{$BlSite}++;
   } elsif ( ($User) = ($ThisLine =~ /^ruleset=check_mail, arg1=<([^ ]*)>, relay=[^,]*, reject=451\s*[\d.]*\s*Domain of sender address [^ ]* does not resolve/) ) {
      $DomainErrors{$User . ": (does not resolve)"}++;
   } elsif ( ($User) = ($ThisLine =~ /^ruleset=check_mail, arg1=<([^ ]*)>, relay=[^,]*, reject=553\s*[\d.]*\s*<[^ ]*>\.\.\. Domain of sender address [^ ]* does not exist/) ) {
      $DomainErrors{$User . " (does not exist)"}++;
   } elsif ( ($User) = ($ThisLine =~ /^ruleset=check_mail, arg1=<([^ ]*)>, relay=[^,]*, reject=553\s*[\d.]*\s*<[^ ]*>\.\.\. Domain name required for sender address .*/) ) {
      $DomainErrors{$User . " (missing)"}++;
   } elsif ( ($Warning)  = ($ThisLine =~ /Authentication-Warning: [^ ]+: ([^ ]+ set sender to [^ ]+ using -f|.+ didn\'t use HELO protocol)/) ) {
      $AuthWarns{$Warning}++;
   } elsif ( ($Forward,$Error) = ($ThisLine =~ /^forward ([^ ]*): transient error: (.*)$/) ) {
      $Temp = $Forward . ": " . $Error;
      $ForwardErrors{$Temp}++;
   } elsif ( ($Forward,$Error) = ($ThisLine =~ /^forward ([^ ]*): (.*)/) ) {
      $Temp = $Forward . ": " . $Error;
      $ForwardErrors{$Temp}++;
   } elsif ( $ThisLine =~ m/^[a-zA-Z0-9]+: (return to sender|sender notify): Warning: could not send message for past (\d) hours/ ) {
      $NumHours = $2;
      $HourReturns++;
   } elsif ( $ThisLine =~ m/^[a-zA-Z0-9]+: (return to sender|sender notify): Cannot send message for (\d) days/ ) {
      $NumDays = $2;
      $DaysReturns++;
   } elsif ( ( $NOQUEUE ) = ($ThisLine =~ /\[([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\] (\(may be forged\) |)did not issue MAIL\/EXPN\/VRFY\/ETRN during connection to MTA/) ) {
      $Noqueuers{$NOQUEUE}++;
   } elsif ($ThisLine=~ /relay=(\S+)*.*\[(\d+.\d+.\d+.\d+)\], reject=444 4.4.4 \<([^\>]+)\>... Sorry (\S*)/) {
      chomp($host=$2." ". (defined($1) ? "(".$1.")" : "(unresolved)") );
      chomp($luser=$3);
      chomp($ruser=$4);
      $ruser="none" if (length($ruser)==0);
      $relay{$host}{$ruser}{$luser}++;
   } elsif ($ThisLine=~ /arg1=\<([^\>]+)\>, relay=(\S+)*.*\[([^\]]+)\], reject=444 4.4.4 Sorry (\S*)/) {
      chomp($host=$3." ". (defined($2) ? "(".$2.")" : "(unresolved)") );
      chomp($ruser=$1);
      $luser="none";
      $relay{$host}{$ruser}{$luser}++;
   } elsif ($ThisLine=~ /relay=(\S+)*.*\[(\d+.\d+.\d+.\d+)\], reject=441 4.4.1 \<([^\>]+)\>/) {
      chomp($host=$2." ". (defined($1) ? "(".$1.")" : "(unresolved)") );
      chomp($luser=$3);
      $notLocal{$host}{$luser}++;
   } elsif ($ThisLine=~ /headers too large .* from \[([^\]]+)/) {
      $largeHdrs{$1}++;
   } elsif ($ThisLine=~ /(\S+) \[([0-9\.]+)]: VRFY (\S+) \[rejected\]/) {
      chomp($host=$2." ". (defined($1) ? "(".$1.")" : "(unresolved)") );
      $luser=$3;
      $abuse{$host}{$luser}++;
   } elsif ( $ThisLine =~ m/[a-zA-Z0-9]+: (postmaster notify|return to sender): User unknown/ ) {
      $UserUnknown++;
   } elsif ( $ThisLine =~ m/timeout waiting for input from (\S+)/ ) {
      $Timeouts{$1}++;
   } elsif ( $ThisLine =~ m/timeout writing message to (\S+?)\.?:/ ) {
      $Timeouts{$1}++;
   } elsif ( $ThisLine =~ /\[([0-9\.]+)]: ETRN (\S+)/ ) {
      chomp($ETRN=$2." from ".$1);
      $ETRNs{$ETRN}++;
   } elsif ( $ThisLine =~ /rejecting connections on daemon MTA: load average: ([0-9]+)/ ) {
      $LoadAvg{$1}++;
   } else {
      $ThisLine =~ s/.*\: (DSN\: .*)/$1/;   
      $ThisLine =~ s/.*\: (postmaster notify\: .*)/$1/;
      chomp($ThisLine);
      # Report any unmatched entries...
      $OtherList{$ThisLine}++; 
   }
}

if (($MsgsSent > 0) and ($Detail >= 5)) {
   print "\n\n" . $BytesTransferred . " bytes transferred";
   print "\n" . $MsgsSent . " messages sent";
}

if ($Amavis > 0) {
   print "\n" . $Amavis . " messages scanned by Amavis";
}

if ($Defang > 0) {
   print "\n" . $Defang . " messages scanned by MIMEDefang";
}

if ($HourReturns > 0) {
   print "\n\n" . $HourReturns . " messages returned after " . $NumHours . " hours";
}

if ($DaysReturns > 0) {
   print "\n\n" . $DaysReturns . " messages returned after " . $NumDays . " days";
}

if ($UserUnknown > 0) {
   print "\n\n" . $UserUnknown . " unidentified unknown users";
}

if (keys %ETRNs) {
   print "\n\nETRNs Received:\n";
   foreach $ThisOne (sort keys %ETRNs) {
      print "    " . $ThisOne . ": " . $ETRNs{$ThisOne} . " Times(s)\n";
   }
}

if (keys %LoadAvg) {
   print "\n\nConnections Rejected due to load average::\n";
   foreach $ThisOne (sort keys %LoadAvg) {
      print "    Load Avg " . $ThisOne . ": " . $LoadAvg{$ThisOne} . " Times(s)\n";
   }
}

if (keys %UnknownUsers) {
   foreach $Usr (sort keys %UnknownUsers) {
      foreach $QueueID (sort keys %{ $UnknownUsers{$Usr} }) {
         $SortedUsers{$Usr}{$Relays{$QueueID}}++;
      }
   }
   print "\n\nUnknown users:\n";
   foreach $Usr (sort keys %SortedUsers) {
      print "\n    $Usr\n";
      foreach $RelayHost (sort keys %{ $SortedUsers{$Usr} }) {
         print "      from $RelayHost    $SortedUsers{$Usr}{$RelayHost} time(s).\n";
      }
   }
}

if (keys %KnownSpammer) {
   print "\n\nRelay attempts from known spammers:\n";
   foreach $ThisOne (sort keys %KnownSpammer) {
      print "    " . $ThisOne . ": " . $KnownSpammer{$ThisOne} . " Times(s)\n";
   }
}
if (keys %RelayDenied) {
   print "\n\nRelaying denied:\n";
   foreach $ThisOne (sort keys %RelayDenied) {
      print "    " . $ThisOne . ": " . $RelayDenied{$ThisOne} . " Times(s)\n";
   }
}

if (keys %BlackHoled) {
   print "\n\nBlackHole Totals:\n";
   foreach $ThisOne (sort keys %BlackHoles) {
      print "    " . $ThisOne . ": " . $BlackHoles{$ThisOne} . " Times(s)\n";
   }
   print "\nBlackholed:\n";
   foreach $ThisOne (sort keys %BlackHoled) {
      print "    " . $ThisOne . ": " . $BlackHoled{$ThisOne} . " Times(s)\n";
   }
}

if (keys %DomainErrors) {
   print "\n\nUnresolveable or non-existent domains:\n";
   foreach $ThisOne (sort keys %DomainErrors) {
      print "    " . $ThisOne . ": " . $DomainErrors{$ThisOne} . " Times(s)\n";
   }
}

if (keys %AuthWarns) {
   print "\n\nAuthentication warnings:\n";
   foreach $ThisOne (sort keys %AuthWarns) {
      print "    " . $ThisOne . ": " . $AuthWarns{$ThisOne} . " Times(s)\n";
   }
}

if (keys %UnknownHosts) {
   print "\n\nUnknown hosts:\n";
   foreach $ThisOne (keys %UnknownHosts) {
      print "    " . $ThisOne . ": " . $UnknownHosts{$ThisOne} . " Times(s)\n";
   }
}

if (keys %UnresolvedDomains) {
   print "\n\nUnresolved sender domains:\n";
   foreach $ThisOne (keys %UnresolvedDomains) {
      print "    " . $ThisOne . ": " . $UnresolvedDomains{$ThisOne} . " Times(s)\n";
   }
}

if (keys %Timeouts) {
   print "\n\nTimeouts:\n";
   foreach $ThisOne (keys %Timeouts) {
      print "    " . $ThisOne . ": " . $Timeouts{$ThisOne} . " Times(s)\n";
   }
}

if (keys %ForwardErrors) {
   print "\n\nForwarding errors:\n";
   foreach $ThisOne (sort keys %ForwardErrors) {
      print "    " . $ThisOne . ": " . $ForwardErrors{$ThisOne} . " Times(s)\n";
   }
}

if (keys %Noqueuers) {
   print "\n\nDid not issue MAIL/EXPN/VRFY/ETRN during connection to MTA:\n";
   foreach $ThisOne (sort {$Noqueuers{$b}<=>$Noqueuers{$a}} keys %Noqueuers) {
      printf "    %-17s : %4i Time(s)\n" , $ThisOne , $Noqueuers{$ThisOne};
   }
}

if (keys %relay) {
   print "\n\nWe do not relay for these (host,ruser,luser):\n";
   foreach $host (sort keys %relay) {
      print "\n    $host\n";
      foreach $ruser (sort keys %{ $relay{$host} }) {
         print "      $ruser\n";
         foreach $luser (sort keys %{$relay{$host}{$ruser}}) {
            printf "            %-30s %i \n",$luser,$relay{$host}{$ruser}{$luser};
         }
      }
   }
}

if (keys %notLocal) {
   print "\n\nAddress not local from these (host, user): \n";
   foreach $host (sort keys %notLocal ) {
      print "\n    $host\n";
      foreach $luser (sort keys %{ $notLocal{$host} }) {
         printf "     %-30s %i \n",$luser,$notLocal{$host}{$luser};
      }
   }
}

if (keys %abuse) {
   my $total;
   print "\n\nrejected VRFY (host,ruser):\n";
   foreach $host (sort keys %abuse) {
      print "\n    $host\n";
      $total = 0;
      foreach $luser (sort keys %{$abuse{$host}}) {
         print "       $luser\n";
         $total+=$abuse{$host}{$luser};
      }
      print " Total per host:$total\n";
   }
}

if (keys %largeHdrs) {
   print "\n\nToo large headers from: \n";
   foreach $host ( sort {$largeHdrs{$b}<=>$largeHdrs{$a}} keys %largeHdrs ) {
      printf "    %-17s   %-3i Time(s)\n",$host, $largeHdrs{$host};
   }
}

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);

