#!/usr/bin/perl
#
# ModSecurity for Apache (http://www.modsecurity.org)
# Copyright (c) 2002-2006 Thinking Stone (http://www.thinkingstone.com)
#
# $Id: modsec-auditlog-collector,v 1.1.2.1 2006/02/21 09:43:59 ivanr Exp $
#
# This is a proof-of-concept script that listens to the
# audit log in real time and submits the entries to
# a remote HTTP server. This code is not suitable for
# non-trivial production use since it can only submit
# one audit log entry at a time, plus it does not handle
# errors gracefully.
#
# Usage:
#
# 1) Enter the correct parameters $CONSOLE_* below
#
# 2) Configure ModSecurity to use this script for
#    concurrent audit logging index:
#
#    SecAuditLog "|/path/to/modsec-auditlog-collector.pl \
#        /path/to/auditlog/data/ \
#        /path/to/auditlog/index"
#
# 3) Restart Apache.

use MIME::Base64();
use IO::Socket::INET;

my $CONSOLE_URI = "/rpc/auditLogReceiver";
my $CONSOLE_HOST = "192.168.2.11";
my $CONSOLE_PORT = "8886";
my $CONSOLE_USERNAME = "alpha";
my $CONSOLE_PASSWORD = "sensor";

# ---------------------------------------------------

my $logline_regex = "";

# hostname
$logline_regex .= "^(\\S+)";
# remote host, remote username, local username
$logline_regex .= "\\ (\\S+)\\ (\\S+)\\ (\\S+)";
# date, time, and gmt offset
$logline_regex .= "\\ \\[([^:]+):(\\d+:\\d+:\\d+)\\ ([^\\]]+)\\]";
# request method + request uri + protocol (as one field)
$logline_regex .= "\\ \"(.*)\"";
# status, bytes out
$logline_regex .= "\\ (\\d+)\\ (\\S+)";
# referer, user_agent
$logline_regex .= "\\ \"(.*)\"\\ \"(.*)\"";
# uniqueid, sessionid
$logline_regex .= "\\ (\\S+)\\ \"(.*)\"";
# filename, offset, size
$logline_regex .= "\\ (\\S+)\\ (\\d+)\\ (\\d+)";
# hash
$logline_regex .= "\\ (\\S+)";
# the rest (always keep this part of the regex)
$logline_regex .= "(.*)\$";

my $therequest_regex = "(\\S+)\\ (.*?)\\ (\\S+)";

sub send_entry {
  my ($file_name, $file_offset, $file_size, $hash, $summary) = @_;
  my $buffer;
  
  if (!open(F, $file_name)) {
    print LOG "> Could not open file $file_name.\n";
    return;
  }
  
  binmode F;

  $socket = IO::Socket::INET->new(Proto => 'tcp', PeerAddr => $CONSOLE_HOST, PeerPort => $CONSOLE_PORT, Timeout => 10);                      
  binmode $socket;
     
  if (!$socket) {
    print LOG "> Failed to open socket.\n";
    return;
  }
  
  $socket->autoflush(1);
  
  my $credentials = MIME::Base64::encode($CONSOLE_USERNAME . ":" . $CONSOLE_PASSWORD);
  chomp($credentials);
  
  print $socket "PUT $CONSOLE_URI HTTP/1.0\r\n";
  print $socket "Content-Length: " . $file_size . "\r\n";
  print $socket "Authorization: Basic " . $credentials . "\r\n";
  print $socket "X-ForensicLog-Summary: " . $summary . "\r\n";
  print $socket "X-Content-Hash: " . $hash . "\r\n";
  print $socket "\r\n";

  # send file contents
  while (
    read(F, $buffer, 8192)
    and print $socket $buffer
  ) {};
  close(F);
  
  my $status = 0;
  while(<$socket>) {
    # print "> $_";
    if (($status == 0) && (/^HTTP\/[0-9]\.[0-9] ([0-9]+).+$/)) {
      $status = $1;
    }                        
  }

  print LOG "> Status: " . $status . "\n";  
  close($socket);
}

# -- Main --------------------------------------------------------------------

if (@ARGV != 2) {
    print "Usage: modsec-auditlog-collector auditlog-folder auditlog-index\n";
    exit;
}

my($folder, $index) = @ARGV;        

open(LOG, ">>$index") || die("Failed to open: $index\n");
$| = 1, select $_ for select LOG;


while(<STDIN>) {
    # print LOG "Line: $_";    

    chomp();
    my $summary = $_;
    
    next if (/^$/);
    
    my @parsed_logline = /$logline_regex/x;
    if (@parsed_logline == 0) {
        print LOG "> Failed to parse line: " . $_ . "\n";
    } else {
      (
          $request{"hostname"},
          $request{"remote_ip"},
          $request{"remote_username"},
          $request{"username"},
          $request{"date"},
          $request{"time"},
          $request{"gmt_offset"},
          $request{"the_request"},
          $request{"status"},
          $request{"bytes_out"},
          $request{"referer"},
          $request{"user_agent"},
          $request{"unique_id"},
          $request{"session_id"},
          $request{"filename"},
          $request{"file_offset"},
          $request{"file_size"},
          $request{"hash"},
          $request{"the_rest"}
      ) = @parsed_logline;
    
      $_ = $request{"the_request"};
      my @parsed_therequest = /$therequest_regex/x;
      if (@parsed_therequest == 0) {
        $request{"invalid"} = "1";
        $request{"request_method"} = "";
        $request{"request_uri"} = "";
        $request{"protocol"} = "";
      } else {
        (
          $request{"request_method"},
          $request{"request_uri"},
          $request{"protocol"}
        ) = @parsed_therequest;
        }

      print LOG ($summary . "\n");
      send_entry($abs_file_name = $folder . "/" . $request{"filename"}, $request{"file_offset"}, $request{"file_size"}, $request{"hash"}, $summary);
    }
}

close(LOG);
