#! /usr/bin/perl
#
# 2002-09-03 - Reported wrong month
# 2002-08-22 - try to get name of each host (in case of -r)
# 2002-08-20 - initial version
#
# WG-Dshield by Hans Sandsdalen, Kongsberg Spacetec
# hans@spacetec.no
#
# The script will read all logentries from today and produce output in
# Dshield-format.
# See http://www.dshield.org/specs.php#dshield_format
#
# It is intended to be run once a day, just before midnight. I have a
# cron-job like
#
#    59 23 * * * WG-Dshield | mail -s "FORMAT DSHIELD USERID 0 TZ +01:00" report@dshield.org
#
# (WG-Shield will run 23:59 every day, sending output to report@dshield.org)
#
# Syntax: WG-Dshield [-d] [-r]
#   -d - debug
#   -r - the script will write a list of hosts which are denied to stderr,
#        sorted on number of log entries. Since it is writen to stderr
#        there is no problem using it in a cron job (the list will not
#        be mailed to dshield in the example above).
#
#
#
# Dshield.awk written by Peter Feltham, Intelligent Organisations
# (Email: peter@intelligentorgs.com) is used as a base
#
# $logfile is the logfile on the unix host.
# Setup in Watchguard:
#    - Open Policy Manager
#    - Setup/Logging/syslog
#    - Enter your host
#
# ----------------------------------------------------------------------

use Socket;

# Values to be changed?
# UserID and TimeZone, see http://www.dshield.org/specs.php#dshield_format
$logfile  = "/var/log/firewall/firewall";
$UserID   = 23874654;
$TimeZone = "+01:00";

# No need to change below (?)
# ----------------------------------------------------------------------

require "getopts.pl";

sub basename {
   # returns only filename
   &ENTER("basename(@_)");

   local($basename) = @_;
   $basename =~ s#.*/##;
   $basename;
   &LEAVE("basename(), returns $basename\n");

   $basename;
}

sub settime {
   ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
   $mon = $mon + 1; 		# $mon is 0..11 
   $sec  = substr("0" . $sec,  length("0" . $sec )-2 ,2);
   $min  = substr("0" . $min,  length("0" . $min )-2 ,2);
   $hour = substr("0" . $hour, length("0" . $hour)-2 ,2);
   $mday = substr("0" . $mday, length("0" . $mday)-2 ,2);
   $mon  = substr("0" . $mon,  length("0" . $mon )-2 ,2);
   $year = substr("0" . $year, length("0" . $year)-2 ,2);
}

sub debug {
   if ( $opt_d ) {
      &settime;
      print "$hour:$min:$sec " . $Dindent . $myname . " DEBUG: @_\n";
   }
}

sub errmsg {
   die $myname . " ERROR: @_\n";
}

sub msg {
   print $myname . ": @_\n";
}

sub ENTER {
   &debug("ENTER: @_");
   $Dindent = $Dintdent . "   ";
}

sub LEAVE {
   $Dindent = substr($Dindent,3);
   &debug("LEAVE: @_");
}

sub setdate {
   &ENTER ("setdate @_");

   @month_names = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
                   "Sep", "Oct", "Nov", "Dec");

   &settime;

   $Oyear   = 2000 + $year;
   $Omon    = $mon;
   $Omday   = $mday;
   $monName = $month_names[$Omon - 1];
   $Odate   = "$Oyear-$Omon-$Omday";

   &LEAVE ("setdate $Odate $monName");
}

sub printline {
   # print one line. If lTime/lRepeats are given, use values from last line
   &ENTER("printline @_");

   local($lTime, $lRepeats) = @_;
   &debug("lTime=$lTime, lRepeats=$lRepeats");


   $line =~ s/ +/ /g;

   @words = split (/ /,$line);

   local($count) = 1;			# default count
   local($Time)  = $words[2];		# default time
   if ( $lTime != "" ) {
      $Time = $lTime;
      if ( $lRepeats != "") {
         $count = $lRepeats;
      }
   }

   &debug("words[16]=$words[16], words[17]=$words[17]");

   print "$Odate " .			# date
         "$Time " .			# hh:mm:ss
	 "$TimeZone " .			# timezone
	 "$UserID\t" .			# UserID
	 "$count\t" .			# count
	 "$words[12]\t" .		# Source IP address
	 "$words[14]\t" .		# Source port
	 "$words[13]\t" .		# Target IP address
	 "$words[15]\t" .		# Target port
	 "$words[9]\t" 			# protocol
	 ;

   # if ( $words[16] !~ /^\(.*\)$/ ) {
   if ( $words[16] !~ /^\(/ ) {
      # if $words[16] do not start with "("
      # this is a TCP flag
      print "$words[16]";
   }
   # end with a newline
   print "\n";

   &debug("Before: DeniedHost{$words[12]}=$DeniedHost{$words[12]}");
   if ( $DeniedHost{$words[12]} ne "") {
      $DeniedHost{$words[12]} += $count;
   } else {
      $DeniedHost{$words[12]} = $count;
   }
   &debug("After: DeniedHost{$words[12]}=$DeniedHost{$words[12]}");

   &LEAVE("printline");
}

sub by_reversed_count {
   if ( $DeniedHost{$a} < $DeniedHost{$b} ) {
      1;
   } elsif ( $DeniedHost{$a} == $DeniedHost{$b}) {
      0;
   } elsif ( $DeniedHost{$a} > $DeniedHost{$b} ) {
      -1;
   }
}

sub GetHostByAddr {
   &ENTER("GetHostByAddr(@_)");

   local($laddr) = @_;
   local($liaddr) = inet_aton($laddr);
   local($lhostname) = gethostbyaddr($liaddr,AF_INET);
   
   &LEAVE("GetHostByAddr, return $lhostname");
   $lhostname;
}


############################################################################

&Getopts("dr");

$myname = &basename(__FILE__);
undef %DeniedHost;

&setdate;

# remove leading 0 from $Omday
$O2mday = $Omday + 0;

$lineno = 0;
open (LOG, $logfile) || die ("Could not open $logfile");
while ($inpline = <LOG>) {
   $lineno += 1;
   chop ($inpline);
   # &debug ("Read line #$lineno:" . $inpline);

   if ( $inpline !~ /^$monName *$O2mday/ ) {
      &debug("$lineno next1: wrong date (" . substr($inpline,0,22) .
 "...)");
      next;
   }
   &debug("ok date");
   if ( $inpline !~ /last message repeated/ ) {
      # this is not a repeated line
      $line = $inpline;

      # assume that this line should not be printed
      $printline = 0;

      if ( $line !~ /deny in/ ) {
         &debug ("$lineno next2: no \"deny in\" ($line)");
         next;
      }
      &debug ("WITH \"deny in\"");

      # from Dshield.awk:
      # This first set of patterns look for miscellaneous crud coming
      # off the Firewall, which should not be reported to Dshield,
      # so if we find such a line, just ignore it. These little parsers
      # speed up processing - no need to parse crud after all...
      #
      if ( $line =~ /unexpextedly/                   ||
           $line =~ /are exiting/                    ||
   	   $line =~ /signal 2/                       ||
	   $line =~ /Can't receive data from server/ ||
	   $line =~ /exited with status 1/           ||
	   $line =~ /Connection terminated/          ||
	   $line =~ /proxy connect failed/
         ) {
         &debug ("$lineno next3: ignore ($line)");
         next;
      }

      # from Dshield.awk:
      # Here are some more throwaways, with lots of data fields
      # none of which is interesting for dShield
      # These are belts-and-braces backstops, in fact!
      # Protocol and Proxy dependent:
      #
      if ( $line =~ /process_rfds:/             ||
           $line =~ /Message has too much data/ ||
	   $line =~ /arp called for own IP/     ||
	   $line =~ /terminating session/       ||
	   $line =~ /proxy bind/                ||
	   $line =~ /server timed out/          ||
	   $line =~ /process_stop_request:/     ||
	   $line =~ /Exit./
         ) {
         &debug ("$lineno next4: ignore ($line)");
         next;
      }
      &debug("$lineno report!");
      $printline = 1;
      &printline;
   } elsif ( $printline ) {
      # last line was printed, and is repeated.
      &debug("Previous line: $line");
      &debug("REPEAT! $inpline");

      # find number of repeats and time
      $inpline =~ s/ +/ /g;
      ($w1, $w2, $inpTime, $w4, $w5, $w6, $w7, $number) = split (/
 /,$inpline);
      &debug("Time: $inpTime, # Repeats: $number");

      &printline($inpTime, $number);
   }
}

close (LOG);


if ( $opt_r ) {
   # print a sorted list of denied hosts to stderr
   @sortedkeys = sort by_reversed_count keys(%DeniedHost);
   foreach $host (@sortedkeys) {
      print stderr "$host\t$DeniedHost{$host}\t" . &GetHostByAddr($host) ."\n";
   }
}


