
##########################################################################
# $Id: exim,v 1.14 2005/05/25 23:09:28 bjorn Exp $
##########################################################################
# $Log: exim,v $
# Revision 1.14  2005/05/25 23:09:28  bjorn
# Added filters for malware/viruses, and protocol errors, by Gary Allen Vollink.
#
##########################################################################

########################################################
# Originally written by:
#    Dariusz Nierada <dnierada@kat.supermedia.pl>
########################################################
# Please send all comments, suggestions, bug reports,
#    etc, to logwatch-devel@logwatch.org
########################################################

########################################################
# Detail Levels:
#     0: Prints MisFormatted log lines (should never happen)
#        Prints server Stop/Start
#        Virus/Malware blocks (if AntiVirus configured)
#        Prints protocol violations (by category)
#
#     5: Prints Queue Run count
#        Prints Refused Relay count
#
#    10: Prints Refused Relay (individual lines)
#        Prints Per Message Tracking
########################################################

use Logwatch ':dates';

my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0;

# procedura sortujaca tak jak ja chce (bo tamta sotrowala po ASCII)
# procedure to compare numbers at the beginning of submitted strings.
#  .. Which in this case is the message count for a given message ID.
sub wedlug_liczb {
    ($aa) = ($a =~ /^(\d+).+/);
    ($bb) = ($b =~ /^(\d+).+/);
    $aa <=> $bb;
}

# START

my $SearchDate = TimeFilter("%Y-%m-%d %H:%M:%S");

while (defined($ThisLine = <STDIN>)) {
   chomp($ThisLine);
    # pobierz dzisiejsza date z 2002-03-31 22:13:48 ...
    # Collect this line's date, e.g. 2002-03-31 22:13:48 ...
   do {
      $BadFormat{$ThisLine}++;
      next;
   } unless ($year1,$month1,$day1,$h1,$m1,$s1) = ($ThisLine =~ /^(\d+)\-(\d+)\-(\d+)\s(\d+):(\d+):(\d+)\s.+/);

   next unless $ThisLine =~ /^$SearchDate /o;

   if ( $ThisLine =~ /End queue run\:/ ) {
      $EndQueue++;
   }
   elsif ( $ThisLine =~ /Start queue run\:/ ) {
      $StartQueue++;
   }
   elsif ( $ThisLine =~ /message contains malware/ ) {
      # Exim <= 4.44 with ExiScan-ACL Patch (Running AntiVirus Software)
      $Virus{$ThisLine}++;
   }
   elsif ( $ThisLine =~ /message contains a virus/ ) {
      # Exim >= 4.50 compiled with WITH_CONTENT_SCAN=yes (Running AntiVirus Software)
      $Virus{$ThisLine}++;
   }
   elsif ( $ThisLine =~ /unexpected disconnection while reading SMTP command/ ) {
      # Common error from SPAM hosts.
      $Proto{$ThisLine}++;
   }
   elsif ( $ThisLine =~ /SMTP protocol violation\:/ ) {
      # Common error from SPAM hosts.
      $Proto{$ThisLine}++;
   }
   elsif ( $ThisLine =~ /rejected [HE][EH]LO from\s/ ) {
      # Typically due to underscores _ in the HELO line
      #   (a common protocol violation)
      # Also can be due to odd escape sequences 
      #   (never seen from a valid MX)
      $Proto{$ThisLine}++;
   }
   elsif ( $ThisLine =~ /no host name found for IP address / ) {
      # This is common, and it's just noise... Ignore this
         # Could add to Proto - then detail it
   }
   elsif ( $ThisLine =~ /no IP address found for host/ ) {
      # This is common, and it's just noise... Ignore this
         # Could add to Proto - then detail it
   }
   elsif ( $ThisLine =~ /SIGHUP received\: re-exec/ ) {
      # I should probably 'push' this, but this seems cleaner.
      @Restart = (@Restart, "$year1-$month1-$day1 $h1:$m1:$s1 (stop)");
   }
   elsif ( $ThisLine =~ /daemon started\:/ ) {
      # I should probably 'push' this, but this seems cleaner.
      @Restart = (@Restart, "$year1-$month1-$day1 $h1:$m1:$s1 (start)");
   }
   elsif ( $ThisLine =~ /refused relay/ || $ThisLine =~ /rejected RCPT/ ) {
      $Relay++;
      @RelayH = (@RelayH, $ThisLine);
   }
   elsif ( $ThisLine =~ /^\d+\-\d+\-\d+\s\d+\:\d+\:\d+\s\w+\-\w+\-\w+\s/ ) { # inne wiadomosci przesylane przez EXIMA
    # Collect Message ID specific notes...
    ($mdate,$mtime,$mid,$mrest) = ($ThisLine =~ /^(\d+\-\d+\-\d+)\s(\d+\:\d+\:\d+)\s(\w+\-\w+\-\w+)(.+)/);
      # Count of individual Message Lines, used for sort
    $licze++;         # Dodaje taki licznik aby potem przy wypisaniu posortowac po nim, bo wypisywal nie po kolei
    $mmsg{$mid}{$licze.$mrest} = "$mdate $mtime";

   }
   else 
   {
      $OtherList{$ThisLine}++;
   }
} #end while

# Print MisFormatted log lines (should never happen)
if (%BadFormat) {
   print "\n***** BAD FORMAT (Possible data corruption or Exim bug) *****\n";
   foreach $ThisOne (keys %BadFormat) {
      print "$ThisOne\n";
   }
}

# Print server Stops/Starts
if (@Restart) {
   print "\n--- Exim Restarted ---\n";
   foreach $ThisOne (@Restart) {
      print "  $ThisOne\n";
   }
}


if ($Detail >= 5) {
   # Start Queue
   $StartQueue and print "\nStart queue run: $StartQueue Time(s)\n";
   # End Queue
   $EndQueue and print "End queue run: $EndQueue Time(s)\n";

   # Relaye!
   if (@RelayH) {
      print "\n--- Refused Relays \n";
      if ( $Detail >= 10 ) {
         print "--- \(eg. spam try\): $Relay Lines follow:\n\n";
   
         foreach $ThisOne (@RelayH) {
            print "$ThisOne\n";
         }
      }
      else {
         print "--- \(eg. spam try\): $Relay  time(s)\n\n";
      }
   }
}

# Print Blocked Viruses/Malware
if (%Virus) {
   my (%vir);
   print "\n--- VIRUS/MALWARE BLOCKED ---\n";
   foreach $ThisOne (sort(keys %Virus)) {
      # Need mid empty...
      $mid = "";
      # Virus name holder...
      $cc = "";
      # Extract exim date and time string...
      ($mdate, $mtime) = ($ThisOne =~ m/^(\d+-\d+-\d+)\s(\d+\:\d+\:\d+)\s/);
      # Link date and time (looks cleaner)...
      $aa = "$mdate $mtime";
      # Extract the REAL IP address...
      ($bb) = ($ThisOne =~ m/\s\[(\d+\.\d+\.\d+\.\d+)\]\s/);
         # Exim >= 4.50 compiled with, WITH_CONTENT_SCAN=yes
      # Default warning looks like this...
         # rejected after DATA: This message contains a virus (%s).
      if ($ThisOne =~ m/virus \((.*?)\)/) {
         $cc = $1;
      }
         # Exim <= 4.44 with ExiScan-ACL patch
         # rejected after DATA: This message contains malware (%s)
      elsif ($ThisOne =~ m/malware \((.*?)\)/) {
         $cc = $1;
      }
   # There is probably a more graceful way to do this...
   if (defined( $vir{$cc} )) { 
      # Assign current value to temporary (mid)
      $mid = $vir{$cc};
   }
   # Set current value to (old value)+new value+','
   $vir{$cc} = "$mid$aa : IP:$bb,";
   }
   # Print the results...
   foreach $ThisOne (sort(keys %vir)) {
      print "Virus: [$ThisOne]\n";
      foreach $aa ( sort( split /,/, $vir{$ThisOne} )) {
         print "   $aa\n";
      }
   }
}

# Print Protocol Violations
if (%Proto) {
   my (%spam);
   # Probable SPAM hosts...
   print "\n--- MALFUNCTIONING HOSTS ---\n";
   foreach $ThisOne (sort(keys %Proto)) {
      # We need this blank.
      $mid = "";
      # IP address/current issue holder.
      $bb = "";
      # Extract exim date and time string...
      ($mdate, $mtime) = ($ThisOne =~ m/^(\d+-\d+-\d+)\s(\d+\:\d+\:\d+)\s/);
      # Link date and time (looks cleaner)...
      $aa = "$mdate $mtime";
      if ( $ThisOne =~ m/SMTP protocol violation\:\s(.*?\(.*?\))\:/ ) {
         $cc = $1;
         ( $bb ) = ($ThisOne =~ m/\[(\d+\.\d+\.\d+\.\d+)\]/);
      }
      elsif ( $ThisOne =~ m/unexpected disconnection while reading SMTP command/ ) {
         $cc = "Sudden disconnect while expecting remote input";
         ( $bb ) = ($ThisOne =~ m/\[(\d+\.\d+\.\d+\.\d+)\]/);
      }
      elsif ( $ThisOne =~ m/rejected ([HE][EH])LO from \[(\d+\.\d+\.\d+\.\d+)\]\:\s(.*?):\s(.*?)$/ ) {
         $cc = "rejected HELO/EHLO: $3";
         $bb = "$2 ($1LO $4)";
      }
      else {
         # If we picked up a malfunction but didn't collect it here,
         # no need to make the user suffer with superfluous error 
         # messages.
         next;
      }
      if (defined( $spam{$cc} )) {
         $mid = $spam{$cc};
      }
      $spam{$cc} = "$mid$aa : IP:$bb,";
   }
   foreach $ThisOne (sort(keys %spam)) {
      print "Malfunction: [$ThisOne]\n";
      foreach $aa ( sort( split /,/, $spam{$ThisOne} )) {
         print "   $aa\n";
      }
   }
}


# Messages by ID
if (keys %mmsg and ($Detail >= 10)) {
   my $tmsgcount=0;
   my $tmsgrcpts=0;
   print "\n--- Messages history ---\n\n";
   # mmsg is hashed by message id, which is sorted by time
   foreach $tmsg (sort keys %mmsg) {
     my @tmsgkeys = sort {wedlug_liczb} keys %{$mmsg{$tmsg}};
     my $immed_deliv = 1;
     $immed_deliv = 0 unless $tmsgkeys[0] =~ /^\d+ <=/;
     foreach my $key (@tmsgkeys[1..$#tmsgkeys-1]) {
      $immed_deliv = 0 unless $key =~ /^\d+ [-=]>/;
     }
     $immed_deliv = 0 unless $tmsgkeys[$#tmsgkeys] =~ /^\d+ Completed/;
     my $qttmsgcount = 0;
     my $oldqttmsg = '';
     if (!$immed_deliv) {
      print "\-MsgID: $tmsg\: \n";
      foreach $ttmsg (@tmsgkeys) {
          $qttmsg = $ttmsg;
          $qttmsg =~ s/^\d+//; # wywal licznik na poczatku (te od sortowania)
          $qttmsg =~ s/P\=e*smtp S.+//; # wywal koncowki typu:  P=smtp S=372023 id=
          if ($oldqttmsg eq $qttmsg) {
      $qttmsgcount++;
          } else {
      $oldqttmsg = $qttmsg;
      if ($qttmsgcount > 0) {
         print "\tlast message repeated $qttmsgcount times\n";
         $qttmsgcount = 0;
      }
      print "\t$mmsg{$tmsg}{$ttmsg}$qttmsg\n";
          }
      }
      if ($qttmsgcount > 0) {
         print "\tlast message repeated $qttmsgcount times\n";
      }
     } else {
      $tmsgcount++;
      $tmsgrcpts+=$#tmsgkeys-1;
     }
   }
   print "$tmsgcount messages delivered immediately ";
   print "to $tmsgrcpts total recipients\n";
}

# INNE Badziewia
if (keys %OtherList) {
   print "\n**Unmatched Entries**\n";
   foreach $line (sort {$a cmp $b} keys %OtherList) {
      print "$line: $OtherList{$line} Time(s)\n";
   }
}

exit(0);

# vi: shiftwidth=3 tabstop=3 syntax=perl et

