##########################################################################
# $Id: http 305 2016-01-06 19:28:18Z stefjakobs $
##########################################################################

#####################################################
# Copyright (c) 2008 Michael Romeo <michaelromeo@mromeo.com>
# 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 diagnostics;
use strict;
use Logwatch ':sort';
# use re "debug";
#
#  parse httpd access_log
#
#  Get the detail level and
#  Build tables of the log format to parse it and determine whats what
#

my $detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0;
my $ignoreURLs = $ENV{'http_ignore_urls'};
my $ignoreIPs = $ENV{'http_ignore_ips'};
my $ignoreEval = $ENV{'http_ignore_eval'};
my $ignore_error_hacks = $ENV{'http_ignore_error_hacks'} || 0;
my $user_display = $ENV{'http_user_display'};
my $logformat = "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"|%h %l %u %t \"%r\" %>s %b|%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b";

if (defined $ENV{'logformat'}) {
   $logformat = $ENV{'logformat'};
}

my @log_fields = ();
my @log_format = ();
if ((defined $ENV{'http_fields'}) and (defined $ENV{'http_format'})) {
   @log_fields = split(" ", $ENV{'http_fields'});
   @log_format = split(" ", $ENV{'http_format'});
}

#
# Initialization etc.
#

my $byte_summary = 0;
my $failed_requests = 0;
my %field = ();
my %hacks =();
my %hack_success =();
my %needs_exam =();
my %users_logged =();
my %ban_ip =();
my %robots =();
my $pattern = "";
my $flag = 0;
my $isahack = 0;
my $a5xx_resp = 0;
my $a4xx_resp = 0;
my $a3xx_resp = 0;
my $a2xx_resp = 0;
my $a1xx_resp = 0;
my $image_count = 0;
my $image_bytes = 0;
my $docs_count = 0;
my $docs_bytes = 0;
my $archive_count = 0;
my $archive_bytes = 0;
my $sound_count = 0;
my $sound_bytes = 0;
my $movie_count = 0;
my $movie_bytes = 0;
my $winexec_count = 0;
my $winexec_bytes = 0;
my $content_count = 0;
my $content_bytes = 0;
my $redirect_count = 0;
my $redirect_bytes = 0;
my $other_count = 0;
my $other_bytes = 0;
my $total_hack_count = 0;
my $wpad_count =     0;
my $wpad_bytes =     0;
my $src_count =      0;
my $src_bytes =      0;
my $logs_count =     0;
my $logs_bytes =     0;
my $images_count =   0;
my $images_bytes =   0;
my $fonts_count =    0;
my $fonts_bytes =    0;
my $config_count =   0;
my $config_bytes =   0;
my $xpcomext_count = 0;
my $xpcomext_bytes = 0;
my $mozext_count =   0;
my $mozext_bytes =   0;
my $proxy_count =    0;
my $proxy_bytes =    0;
my %proxy_host =     ();
my $host =           "";
my $notparsed =      "";
my $notparsed_count =0;

######################
# file type comparisons are case-insensitive
my $image_types =    '(\.bmp|\.cdr|\.emz|\.gif|\.ico|\.jpe?g|\.png|\.svg|\.sxd|\.tiff?|\.wbmp|\.webp|\.wmf|\.wmz|\.xdm)';
my $content_types =  '(';
   $content_types =  $content_types.'\/server-status|\/server-info';
   $content_types =  $content_types.'|\.htm|\.html|\.jhtml|\.phtml|\.shtml|\/\.?';
   $content_types =  $content_types.'|\.html\.[a-z]{2,3}(_[A-Z]{2,3})?';
   $content_types =  $content_types.'|\.inc|\.php|\.php3|\.asmx|\.asp|\.pl|\.wml';
   $content_types =  $content_types.'|^\/mailman\/.*';
   $content_types =  $content_types.'|\/sqwebmail.*';
   $content_types =  $content_types.'|^\/announce|^\/scrape'; # BitTorrent tracker mod_bt
   $content_types =  $content_types.'|\.torrent';
   $content_types =  $content_types.'|\.css|\.js|\.cgi';
   $content_types =  $content_types.'|\.fla|\.swf|\.rdf';
   $content_types =  $content_types.'|\.class|\.jsp|\.jar|\.java';
   $content_types =  $content_types.'|COPYRIGHT|README|FAQ|INSTALL|\.txt)';
my $docs_types =     '(\.asc|\.bib|\.djvu|\.docx?|\.dot|\.dtd|\.dvi|\.gnumeric|\.mcd|\.mso|\.pdf|\.pps|\.pptx?|\.ps|\.rtf|\.sxi|\.tex|\.text|\.tm|\.xlsx?|\.xml)';
my $archive_types =  '(\.7z|\.ace|\.bz2|\.cab|\.deb|\.dsc|\.ed2k|\.gz|\.hqx|\.md5|\.rar|\.rpm|\.sig|\.sign|\.tar|\.tbz2|\.tgz|\.vl2|\.z|\.zip|\.hdr)';
my $sound_types =    '(\.aac|\.au|\.aud|\.m4a|\.mid|\.mp3|\.oga|\.pls|\.ram|\.raw|\.rm|\.wav|\.wma|\.xsm)';
my $movie_types =    '(\.asf|\.ass|\.avi|\.idx|\.flv|\.m2?ts|\.mkv|\.mp4|\.mpe?g|\.mov|\.ogg|\.ogv|\.qt|\.psb|\.srt|\.ssa|\.smi|\.sub|\.webm|\.wmv)';
my $winexec_types =  '(\.bat|\.com|\.exe|\.dll)';
my $wpad_files =     '(wpad\.dat|wspad\.dat|proxy\.pac)';
my $program_src =    '(';
   $program_src =    $program_src.'\.bas|\.cs?|\.cpp|\.diff|\.f|\.h|\.init|\.m|\.mo|\.pas|\.patch|\.po|\.pot|\.py|\.sh|\.spec';
   $program_src =    $program_src.'|Makefile|Makefile_c|Makefile_f77)';
my $images_types =   '(\.bin|\.cue|\.img|\.iso|\.run)';
my $logs_types =     '(\.log|_log|-log|\.logs|\.out|\.wyniki)';
my $fonts_types =    '(\.aft|\.otf|\.ttf|\.woff)';
my $config_types =   '(\.cfg|\.conf|\.config|\.ini|\.properties)';
my $xpcomext_types = '(\.xpt)';
my $mozext_types =   '(\.xul)';

# HTTP Status codes from HTTP/Status.pm, to avoid loading package
# that may or may not exist.  We only need those >=400, but all
# are included for potential future use.
my %StatusCode = (
    100 => 'Continue',
    101 => 'Switching Protocols',
    102 => 'Processing',                      # WebDAV
    200 => 'OK',
    201 => 'Created',
    202 => 'Accepted',
    203 => 'Non-Authoritative Information',
    204 => 'No Content',
    205 => 'Reset Content',
    206 => 'Partial Content',
    207 => 'Multi-Status',                    # WebDAV
    300 => 'Multiple Choices',
    301 => 'Moved Permanently',
    302 => 'Found',
    303 => 'See Other',
    304 => 'Not Modified',
    305 => 'Use Proxy',
    307 => 'Temporary Redirect',
    400 => 'Bad Request',
    401 => 'Unauthorized',
    402 => 'Payment Required',
    403 => 'Forbidden',
    404 => 'Not Found',
    405 => 'Method Not Allowed',
    406 => 'Not Acceptable',
    407 => 'Proxy Authentication Required',
    408 => 'Request Timeout',
    409 => 'Conflict',
    410 => 'Gone',
    411 => 'Length Required',
    412 => 'Precondition Failed',
    413 => 'Request Entity Too Large',
    414 => 'Request-URI Too Large',
    415 => 'Unsupported Media Type',
    416 => 'Request Range Not Satisfiable',
    417 => 'Expectation Failed',
    422 => 'Unprocessable Entity',            # WebDAV
    423 => 'Locked',                          # WebDAV
    424 => 'Failed Dependency',               # WebDAV
    500 => 'Internal Server Error',
    501 => 'Not Implemented',
    502 => 'Bad Gateway',
    503 => 'Service Unavailable',
    504 => 'Gateway Timeout',
    505 => 'HTTP Version Not Supported',
    507 => 'Insufficient Storage',            # WebDAV
);

#
#   what to look for as an attack  USE LOWER CASE!!!!!!
#
my @exploits = (
   '^null$',
   '/\.\./\.\./\.\./',
   '\.\./\.\./config\.sys',
   '/\.\./\.\./\.\./autoexec\.bat',
   '/\.\./\.\./windows/user\.dat',
   '\\\x02\\\xb1',
   '\\\x04\\\x01',
   '\\\x05\\\x01',
   '\\\x90\\\x02\\\xb1\\\x02\\\xb1',
   '\\\x90\\\x90\\\x90\\\x90',
   '\\\xff\\\xff\\\xff\\\xff',
   '\\\xe1\\\xcd\\\x80',
   '\\\xff\xe0\\\xe8\\\xf8\\\xff\\\xff\\\xff-m',
   '\\\xc7f\\\x0c',
   '\\\x84o\\\x01',
   '\\\x81',
   '\\\xff\\\xe0\\\xe8',
   '\/c\+dir',
   '\/c\+dir\+c',
   '\.htpasswd',
   'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
   'author\.exe',
   'boot\.ini',
   'cmd\.exe',
   'c%20dir%20c',
   'default\.ida',
   'fp30reg\.dll',
   'httpodbc\.dll',
   'nsiislog\.dll',
   'passwd$',
   'root\.exe',
   'shtml\.exe',
   'win\.ini',
   'xxxxxxxxxxxxxxxxxxxxxx',
);

#
#  Define some useful RE paterns
#

my %re_pattern = (
   space => '(.*)',
   brace => '\[(.*)\]',
   quote => '\"(.*)\"');

#
#  Build the regex to parse the line
#

for (my $i = 0; $i < @log_format; $i++) {
   $pattern = $pattern.$re_pattern{$log_format[$i]}.'\\s';
}

# this is easier than coding last element logic in the loop
chop($pattern);
chop($pattern);

# The following are used to build up pattern matching strings for
# the log format used in the access_log files.
my @parse_string = ();
my @parse_field = ();
my $parse_index = 0;
my $parse_subindex = 0;
$parse_string[$parse_index] = "";
$parse_field[$parse_index] = ();
if ($pattern) {
   # accommodate usage of HTTP_FIELDS and HTTP_FORMAT
   $parse_string[0] = $pattern;
   $parse_field[0] = [@log_fields];
   $parse_index++;
}

$parse_string[$parse_index] = "";
$parse_field[$parse_index] = ();
my $end_loop = 1;
$logformat =~ s/%[\d,!]*/%/g;
while ($end_loop) {

   if ($logformat =~ /\G%h/gc) {
      $parse_string[$parse_index] .= "(\\S*?)";
      $parse_field[$parse_index][$parse_subindex++] = "client_ip";
   } elsif ($logformat =~ /\G%l/gc) {
      $parse_string[$parse_index] .= "(\\S*?)";
      $parse_field[$parse_index][$parse_subindex++] = "ident";
   } elsif ($logformat =~ /\G%u/gc) {
      $parse_string[$parse_index] .= "(\\S*?)";
      $parse_field[$parse_index][$parse_subindex++] = "userid";
   } elsif ($logformat =~ /\G%t/gc) {
      $parse_string[$parse_index] .= "(\\[.*\\])";
      $parse_field[$parse_index][$parse_subindex++] = "timestamp";
   } elsif ($logformat =~ /\G%r/gc) {
      $parse_string[$parse_index] .= "(.*)";
      $parse_field[$parse_index][$parse_subindex++] = "request";
   } elsif ($logformat =~ /\G%>?s/gc) {
      $parse_string[$parse_index] .= "(\\d{3})";
      $parse_field[$parse_index][$parse_subindex++] = "http_rc";
   } elsif ($logformat =~ /\G%b/gc) {
      # "transfered" is misspelled, but not corrected because this string
      # comes from the configuration file, and would create a compatibility
      # issue
      $parse_field[$parse_index][$parse_subindex++] = "bytes_transfered";
      $parse_string[$parse_index] .= "(-|\\d*)";
   } elsif ($logformat =~ /\G%V/gc) {
      $parse_string[$parse_index] .= "(\\S*?)";
      $parse_field[$parse_index][$parse_subindex++] = "server_name";
   } elsif ($logformat =~ /\G%I/gc) {
      $parse_field[$parse_index][$parse_subindex++] = "bytes_in";
      $parse_string[$parse_index] .= "(-|\\d*)";
   } elsif ($logformat =~ /\G%O/gc) {
      $parse_field[$parse_index][$parse_subindex++] = "bytes_out";
      $parse_string[$parse_index] .= "(-|\\d*)";
   } elsif ($logformat =~ /\G%\{Referer}i/gci) {
      $parse_string[$parse_index] .= "(.*)";
      $parse_field[$parse_index][$parse_subindex++] = "referrer";
   } elsif ($logformat =~ /\G%\{User-Agent}i/gci) {
      $parse_string[$parse_index] .= "(.*)";
      $parse_field[$parse_index][$parse_subindex++] = "agent";
   } elsif ($logformat =~ /\G%({.*?})?./gc) {
      $parse_string[$parse_index] .= "(.*?)";
      $parse_field[$parse_index][$parse_subindex++] = "not_used";
   } elsif ($logformat =~ /\G\|/gc) {
      $parse_index++;
      $parse_subindex = 0;
      $parse_string[$parse_index] = "";
      $parse_field[$parse_index] = ();
   # perl 5.6 does not detect end of string properly in next elsif block,
   # so we test it explicitly here
   } elsif ($logformat =~ /\G$/gc) {
      $end_loop = 0;
   } elsif ((my $filler) = ($logformat =~ /\G([^%\|]*)/gc)) {
      $parse_string[$parse_index] .= $filler;
   # perl 5.6 loses track of match position, so we force it.  Perl 5.8
   # and later does it correctly, so it was fixed in 5.7 development.
      if ($] < 5.007) {pos($logformat) += length($filler);}
   } else {
      $end_loop = 0;
   }
}


#################   print "RE pattern     = $pattern \n";

#
#  Process log file on stdin
#

while (my $line = <STDIN>) {
   chomp($line);

   ##################  print "Line = $line \n";

   #
   # parse the line per the input spec
   #
   my @parsed_line;
   for $parse_index (0..$#parse_string) {
      if (@parsed_line = $line =~ /$parse_string[$parse_index]/) {
         @log_fields = @{$parse_field[$parse_index]};
         last;
      }
   }

   if (not @parsed_line) {
      $notparsed_count++;
      if ($notparsed_count <= 10) {
         $notparsed = $notparsed . "   " . $line . "\n";
      }
      next;
   }

   # hash the results so we can identify the fields
   #
   for my $i (0..$#log_fields) {
      #		print "$i $log_fields[$i] $parsed_line[$i] \n";
      $field{$log_fields[$i]} = $parsed_line[$i];
   }

   ##
   ## Do the default stuff
   ##

   #
   # Break up the request into method, url and protocol
   #

   ($field{method},$field{url},$field{protocol}) = split(/ +/,$field{"request"});
   if (! $field{url}) {
      $field{url}='null';
   }
   $field{lc_url} = lc $field{url};

   #
   # Bytes sent Summary
   # Apache uses "-" to represent 0 bytes transferred
   #

   if ($field{bytes_transfered} eq "-") {$field{bytes_transfered} = 0};
   $byte_summary += $field{bytes_transfered};

   #
   #  loop to check for typical exploit attempts
   #

   if (!$ignore_error_hacks) {
      for (my $i = 0; $i < @exploits; $i++) {
         # print "$i $exploits[$i] $field{lc_url} \n";
         if ( ($field{lc_url} =~ /$exploits[$i]/i) &&
              !((defined $ignoreURLs) && ($field{url} =~ /$ignoreURLs/i)) &&
              !((defined $ignoreIPs) && ($field{client_ip} =~ /$ignoreIPs/)) ) {
            $hacks{$field{client_ip}}{$exploits[$i]}++;
            $total_hack_count += 1;
            $ban_ip{$field{client_ip}} = " ";
            if ($field{http_rc} < 300) {
               $hack_success{$field{url}} = $field{http_rc};
            }
         }
      }
   }

   #
   #  Count types and bytes
   #
   #	this is only printed if detail > 4 but it also looks
   #	for 'strange' stuff so it needs to run always
   #

   ($field{base_url},$field{url_parms}) = split(/\?/,$field{"lc_url"});

   if ($field{base_url} =~ /$image_types$/oi) {
      $image_count += 1;
      $image_bytes += $field{bytes_transfered};
   } elsif ($field{base_url} =~ /$docs_types$/oi) {
      $docs_count += 1;
      $docs_bytes += $field{bytes_transfered};
   } elsif ($field{base_url} =~ /$archive_types$/oi) {
      $archive_count += 1;
      $archive_bytes += $field{bytes_transfered};
   } elsif ($field{base_url} =~ /$sound_types$/oi) {
      $sound_count += 1;
      $sound_bytes += $field{bytes_transfered};
   } elsif ($field{base_url} =~ /$movie_types$/oi) {
      $movie_count += 1;
      $movie_bytes += $field{bytes_transfered};
   } elsif ($field{base_url} =~ /$winexec_types$/oi) {
      $winexec_count += 1;
      $winexec_bytes += $field{bytes_transfered};
   } elsif ($field{base_url} =~ /$content_types$/oi) {
      $content_count += 1;
      $content_bytes += $field{bytes_transfered};
   } elsif ($field{base_url} =~ /$wpad_files$/oi) {
      $wpad_count += 1;
      $wpad_bytes += $field{bytes_transfered};
   } elsif ($field{base_url} =~ /$program_src$/oi) {
      $src_count += 1;
      $src_bytes += $field{bytes_transfered};
   } elsif ($field{base_url} =~ /$images_types$/oi) {
      $images_count += 1;
      $images_bytes += $field{bytes_transfered};
   } elsif ($field{base_url} =~ /$logs_types$/oi) {
      $logs_count += 1;
      $logs_bytes += $field{bytes_transfered};
   } elsif ($field{base_url} =~ /$fonts_types$/oi) {
      $fonts_count += 1;
      $fonts_bytes += $field{bytes_transfered};
   } elsif ($field{base_url} =~ /$config_types$/oi) {
      $config_count += 1;
      $config_bytes += $field{bytes_transfered};
   } elsif ($field{base_url} =~ /$xpcomext_types$/oi) {
      $xpcomext_count += 1;
      $xpcomext_bytes += $field{bytes_transfered};
   } elsif ($field{base_url} =~ /$mozext_types$/oi) {
      $mozext_count += 1;
      $mozext_bytes += $field{bytes_transfered};
   } elsif ($field{http_rc} =~ /3\d\d/) {
      $redirect_count += 1;
      $redirect_bytes += $field{bytes_transfered};
   } elsif ($field{method} =~ /CONNECT/) {
      $proxy_count += 1;
      $proxy_bytes += $field{bytes_transfered};
      $proxy_host{"$field{client_ip} -> $field{base_url}"}++;
   } else {
      $other_count += 1;
      $other_bytes += $field{bytes_transfered};
   }
   if ( ($field{http_rc} >= 400) && !shouldIgnore("needs_exam") ) {
      my $fmt_url = $field{url};
      if (length($field{url}) > 60) {
         $fmt_url = substr($field{url},0,42) . " ... " .
                    substr($field{url},-15,15);
      }
      $needs_exam{$field{http_rc}}{$fmt_url}++;
   }
   if (defined $field{userid} && $field{userid} ne "-" &&
         (eval $user_display) && !shouldIgnore("users_logged") ) {
       $users_logged{$field{userid}}{$field{client_ip}}++;
   }

   ##
   ## Do the > 4 stuff
   ##
   #
   #  Response Summary
   #

   if ($field{http_rc} > 499 ) {
      $a5xx_resp += 1;
   } elsif ($field{http_rc} > 399 ) {
      $a4xx_resp += 1;
   } elsif($field{http_rc} > 299 ) {
      $a3xx_resp += 1;
   } elsif($field{http_rc} > 199 ) {
      $a2xx_resp += 1;
   } else {
      $a1xx_resp += 1;
   }

   #
   #  Count the robots who actually ask for the robots.txt file
   #

   if ($field{lc_url} =~ /^\/robots.txt$/) {
      if (defined $field{agent}) {
         $robots{$field{agent}} +=1;
      }
   }

} ## End of while loop

#############################################
##   output the results
##

if ($detail >4) {
   printf "%.2f MB transferred " , $byte_summary/(1024*1024);
   print  "in ";
   print my $resp_total = ($a1xx_resp + $a2xx_resp + $a3xx_resp + $a4xx_resp + $a5xx_resp);
   print " responses ";
   print " (1xx $a1xx_resp, 2xx $a2xx_resp, 3xx $a3xx_resp,";
   print " 4xx $a4xx_resp, 5xx $a5xx_resp) \n";
   my $lr = length($resp_total);
   if ($image_count > 0)      { printf "   %*d Images (%.2f MB),\n" , $lr, $image_count, $image_bytes/(1024*1024); }
   if ($docs_count > 0)       { printf "   %*d Documents (%.2f MB),\n" , $lr, $docs_count, $docs_bytes/(1024*1024); }
   if ($archive_count > 0)    { printf "   %*d Archives (%.2f MB),\n" , $lr, $archive_count, $archive_bytes/(1024*1024); }
   if ($sound_count > 0)      { printf "   %*d Sound files (%.2f MB),\n" , $lr, $sound_count, $sound_bytes/(1024*1024); }
   if ($movie_count > 0)      { printf "   %*d Movies files (%.2f MB),\n" , $lr, $movie_count, $movie_bytes/(1024*1024); }
   if ($winexec_count > 0)    { printf "   %*d Windows executable files (%.2f MB),\n" , $lr, $winexec_count, $winexec_bytes/(1024*1024); }
   if ($content_count > 0)    { printf "   %*d Content pages (%.2f MB),\n" , $lr, $content_count, $content_bytes/(1024*1024); }
   if ($redirect_count > 0)   { printf "   %*d Redirects (%.2f MB),\n" , $lr, $redirect_count, $redirect_bytes/(1024*1024); }
   if ($wpad_count > 0)       { printf "   %*d Proxy Configuration Files (%.2f MB),\n" , $lr, $wpad_count, $wpad_bytes/(1024*1024); }
   if ($src_count > 0)        { printf "   %*d Program source files (%.2f MB),\n" , $lr, $src_count, $src_bytes/(1024*1024); }
   if ($images_count > 0)     { printf "   %*d CD Images (%.2f MB),\n" , $lr, $images_count, $images_bytes/(1024*1024); }
   if ($logs_count > 0)       { printf "   %*d Various Logs (%.2f MB),\n" , $lr, $logs_count, $logs_bytes/(1024*1024); }
   if ($fonts_count > 0)      { printf "   %*d Fonts (%.2f MB),\n" , $lr, $fonts_count, $fonts_bytes/(1024*1024); }
   if ($config_count > 0)     { printf "   %*d Configs (%.2f MB),\n" , $lr, $config_count, $config_bytes/(1024*1024); }
   if ($xpcomext_count > 0)   { printf "   %*d XPCOM Type Libraries (%.2f MB),\n" , $lr, $xpcomext_count, $xpcomext_bytes/(1024*1024); }
   if ($mozext_count > 0)     { printf "   %*d Mozilla extensions (%.2f MB),\n" , $lr, $mozext_count, $mozext_bytes/(1024*1024); }
   if ($proxy_count > 0)      { printf "   %*d mod_proxy requests (%.2f MB),\n" , $lr, $proxy_count, $proxy_bytes/(1024*1024); }
   if ($other_count > 0)      { printf "   %*d Other (%.2f MB) \n" , $lr, $other_count, $other_bytes/(1024*1024); }
}

#
#  List attempted exploits
#

if (($detail >4) and $total_hack_count) {
   print "\nAttempts to use known hacks by ".(keys %hacks).
         " hosts were logged $total_hack_count time(s) from:\n";
   my $order = TotalCountOrder(%hacks);
   foreach my $i (sort $order keys %hacks) {
      my $hacks_per_ip = 0;
      foreach my $j ( keys %{$hacks{$i}} ) {
         $hacks_per_ip += $hacks{$i}{$j};
      }
      print "   $i: $hacks_per_ip Time(s)\n";
      if ($detail > 9) {
         foreach my $j ( keys %{$hacks{$i}} ) {
            print "      $j $hacks{$i}{$j} Time(s) \n";
         }
      } else {
         print "\n";
      }
   }
}

if (keys %proxy_host) {
   print "\nConnection attempts using mod_proxy:\n";
   foreach $host (sort {$a cmp $b} keys %proxy_host) {
      print "   $host: $proxy_host{$host} Time(s)\n";
   }
}
#
#  List (wannabe) blackhat sites
#

$flag = 1;
foreach my $i (sort keys %ban_ip) {
   if ($flag) {
      print "\nA total of ".scalar(keys %ban_ip)." sites probed the server \n";
      $flag = 0;
   }
   #if ($detail > 4) {
      print "   $i\n";
   #}
}

#
#  List possible successful probes
#

$flag = 1;
if (keys %hack_success) {
   print "\nA total of " . scalar(keys %hack_success) . " possible successful probes were detected (the following URLs\n";
   print "contain strings that match one or more of a listing of strings that\n";
   print "indicate a possible exploit):\n\n";

   foreach my $i (keys %hack_success) {
      print "   $i HTTP Response $hack_success{$i} \n";
   }
}

#
#  List error response codes
#

if (keys %needs_exam) {
   print "\nRequests with error response codes\n";
   # my $count = TotalCountOrder(%needs_exam);
   for my $code (sort keys %needs_exam) {
      if (not defined $StatusCode{$code}) {
         $StatusCode{$code} = "\(undefined\)";
      }
      if( ($ENV{"http_rc_detail_rep_$code"} || $detail) > $detail ) {
      # only display summary for this code
         my $t = 0;
         my $u = 0;
         foreach my $k ( keys %{$needs_exam{$code}}) {
            $u += 1;
            $t += $needs_exam{$code}{$k};
         }
         print "   $code $StatusCode{$code} SUMMARY - $u URLs, total: $t Time(s)\n";
      } else {
         print "   $code $StatusCode{$code}\n";
         for my $url (sort { ($needs_exam{$code}{$b} <=> $needs_exam{$code}{$a}) or ($a cmp $b) } keys %{$needs_exam{$code}}) {
            print "      $url: $needs_exam{$code}{$url} Time(s)\n";
         }
      }
   }
}

if (keys %users_logged) {
   print "\nUsers logged successfully\n";
   for my $user (sort keys %users_logged) {
      my %userips = %{$users_logged{$user}};
      # If one user name logged from many IPs, don't print them all. 5 is arbitrary
      if (scalar(keys %userips) > 5) {
         my $count = 0;
         for my $ip (keys %userips) {
             $count += $userips{$ip};
         }
         print "   $user: $count Time(s) from ".scalar(keys %userips)." addresses\n";
      } else {
         print "   $user\n";
         for my $ip (sort keys %userips) {
            print "      $ip: $userips{$ip} Time(s)\n";
         }
      }
   }
}

#
#  List robots that identified themselves
#

if ($detail > 4) {
   $flag = 1;
   foreach my $i (keys %robots) {
      if ($flag) {
         print "\nA total of ".scalar(keys %robots)." ROBOTS were logged \n";
         $flag = 0;
      }
      if ($detail > 9) {
         print "   $i $robots{$i} Time(s) \n";
      }
   }
}

if ($notparsed) {
   print "\nThis is a listing of log lines that were not parsed correctly.\n";
   print "Perhaps the variable \$LogFormat in file conf/services/http.conf\n";
   print "is not correct?\n\n";
   if ($notparsed_count > 10) {
      print "(Only the first ten are printed; there were a total of $notparsed_count)\n";
   }
   print $notparsed;
}

exit (0);

sub shouldIgnore {
   my($context)=@_;

   if( ((defined $ignoreURLs) && ($field{url} =~ /$ignoreURLs/i)) ||
       ((defined $ignoreIPs) && ($field{client_ip} =~ /$ignoreIPs/)) ) {
      return 1;
   }
   return (eval $ignoreEval);
}

# vi: shiftwidth=3 tabstop=3 syntax=perl et
# Local Variables:
# mode: perl
# perl-indent-level: 3
# indent-tabs-mode: nil
# End:
