#!/usr/bin/perl
#
# PIX Logging Architecture v2.00
# Kris Philipsen [kris@logging-architecture.net]
#
# pla_parsed :
# - PIX Log Parsing
# - PIX Log Pushing to MySQL database
#
# PIX Logging Architecture
# [Kristof Philipsen]
#
# This file is part of PIX Logging Architecture
#
# PIX Logging Architecture is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# PIX Logging Architecture is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with PIX Logging Architecture; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

use DBI;
use File::Tail;
use Socket;
use POSIX qw(setsid);

##
## PLA Constants
##
$pla_version = "2.00";


##
## PLA REQUIRED Variables
##

$mysql_db_host = "<MYSQL_HOST>";		# MySQL Host
$mysql_db_port = "<MYSQL_PORT>";		# MySQL Port
$mysql_db_user = "<MYSQL_DB_USER>";		# MySQL User
$mysql_db_pass = "<MYSQL_DB_PASS>";		# MySQL Pass
$mysql_db_name = "<MYSQL_DB_NAME>";		# MySQL PIX Logging Architecture Database Name
$pix_log_file  = "<SYSLOG_MESSAGES_FILE>";	# PIX Log File


##
## PLA Optional Variables
##
$pla_suppress_unknown 		= "0"; # Defines whether to suppress unknown (not matching those in DB) Log Messages [ 0 - Do not suppress | 1 - Suppress ]
$pla_unknown_writetofile 	= "1"; # Only applies when setting $pla_suppress_unkwown to "0". Determines whether to write the output to a file
$pla_unknown_filename		= "/var/log/pla_unknown_msgs.log"; # Sets the filename to write the unknown messages to.
$pla_unknown_writetodb 		= "0"; # Only applies when setting $pla_suppress_unkwown to "0". Determines whether to write the output to the db (will be sent to the info_log table)


### 
### DEFINE YOUR SYSLOG LAYOUT HERE!
###

##
## The syslog format below is used when using PIX 6.x
##

$regex_log_begin = "(.*):(.*) (.*) (.*) (.*) (.*) (.*)";
$var_pixhost=3;
$var_pixmonth=4;
$var_pixdate=5;
$var_pixyear=6;
$var_pixtime=7;

##
## Temp Variables
##

$show_messages="0";	# Used to determine whether to print all PIX Parsing Messages from DB




##
##  Subroutine Definitions
##

## Establishes continuous database connection
##
sub db_connect {
    $dbh = DBI->connect("DBI:mysql:database=$mysql_db_name;host=$mysql_db_host;port=$mysql_db_port",$mysql_db_user,$mysql_db_pass)
           or die "Couldn't connect to database: $DBI::errstr\n";

}

## Writes PIX Logs to the database.
##
sub write_db {

    $writedb=$dbh->prepare($update_db);
    $rc=$writedb->execute();

}

## Calculates correct date
##

%months =   (
             "Jan","01",
             "Feb","02",
             "Mar","03",
             "Apr","04",
             "May","05",
             "Jun","06",
             "Jul","07",
             "Aug","08",
             "Sep","09",
             "Oct","10",
             "Nov","11",
             "Dec","12"
             );

sub calculatedate {

    if (length($pixdate) == "1") {
        $pixdate="0$pixdate";
    }
    $pix_full_date="$pixyear-$months{$pixmonth}-$pixdate $pixtime";

}

sub printhead {
    print "\n\n";
    print "-- [pla_parsed/$pla_version] - PIX Logging Architecture $pla_version\n";
    print "-- [pla_parsed/$pla_version] - [kris\@logging-architecture.net]\n";
    print "-- [pla_parsed/$pla_version] - \n";
    print "-- [pla_parsed/$pla_version] - Parse Daemon Started / Daemonized\n";
    print "\n";
}


%syslog_message = ();
%parse_filter = ();

    chdir '/' or die "cannot change to /:$!";
    defined(my $pid=fork) or die "cannot fork process:$!";
    exit if $pid;
    setsid;
    umask 0;
    while(1)
    {

&db_connect; ## set up the db connector
extract_log_messages(); ## extract log messages
extract_parse_filters(); ## extract parse filters
printhead(); ## print header
parse_log_messages(); ## parse the log messages
#close(PLAMSG);
    }



sub extract_log_messages() {

##
## PIX Log Message Extraction (from Database)
##

$syslog_table_dump = "SELECT * from syslog_message";
$get_syslog_messages = $dbh->prepare($syslog_table_dump) or die "Couldn't prepare query '$syslog_table_dump': $DBI::errstr\n";
$get_syslog_messages->execute();

while (($parse_field_id,$parse_message_id,$parse_message_type,$parse_message_regex,$parse_message_time,$parse_message_action,$parse_regex_src_ip,$parse_regex_dst_ip,$parse_regex_src_pt,$parse_regex_dst_pt,$parse_regex_xlate_src_ip,$parse_regex_xlate_dst_ip,$parse_regex_xlate_src_pt,$parse_regex_xlate_dst_pt,$parse_regex_idsmsg,$parse_regex_proto,$parse_regex_flags,$parse_regex_time,$parse_regex_sig,$parse_regex_msg,$parse_regex_iface) = $get_syslog_messages->fetchrow) {

	$syslog_message{$parse_field_id}->[0] = $parse_field_id;
	$syslog_message{$parse_field_id}->[1] = $parse_message_id;
	$syslog_message{$parse_field_id}->[2] = $parse_message_type;
	$syslog_message{$parse_field_id}->[3] = $parse_message_regex;
	$syslog_message{$parse_field_id}->[4] = $parse_message_time;
	$syslog_message{$parse_field_id}->[5] = $parse_message_action;
	$syslog_message{$parse_field_id}->[6] = $parse_regex_src_ip;
	$syslog_message{$parse_field_id}->[7] = $parse_regex_dst_ip;
	$syslog_message{$parse_field_id}->[8] = $parse_regex_src_pt;
	$syslog_message{$parse_field_id}->[9] = $parse_regex_dst_pt;
	$syslog_message{$parse_field_id}->[10] = $parse_regex_xlate_src_ip;
	$syslog_message{$parse_field_id}->[11] = $parse_regex_xlate_dst_ip;
	$syslog_message{$parse_field_id}->[12] = $parse_regex_xlate_src_pt;
	$syslog_message{$parse_field_id}->[13] = $parse_regex_xlate_dst_pt;
	$syslog_message{$parse_field_id}->[14] = $parse_regex_idsmsg;
	$syslog_message{$parse_field_id}->[15] = $parse_regex_proto;
	$syslog_message{$parse_field_id}->[16] = $parse_regex_flags;
	$syslog_message{$parse_field_id}->[17] = $parse_regex_time;
	$syslog_message{$parse_field_id}->[18] = $parse_regex_sig;
	$syslog_message{$parse_field_id}->[19] = $parse_regex_msg;
	$syslog_message{$parse_field_id}->[20] = $parse_regex_iface;
}

}


sub extract_parse_filters() {

##
## PLA Parse Filter Extraction (from Database)
##

$filter_table_dump = "SELECT * from parse_filter";
$get_parse_filters = $dbh->prepare($filter_table_dump) or die "Couldn't prepare query '$filter_table_dump': $DBI::errstr\n";
$get_parse_filters->execute();

while (($filter_field_id,$filter_state,$filter_add_time,$filter_update_time,$filter_log_resource,$filter_log_action,$filter_log_protocol,$filter_log_src_ip,$filter_log_src_pt,$filter_log_dst_ip,$filter_log_dst_pt,$filter_name,$filter_description) = $get_parse_filters->fetchrow) {

     if ($filter_state eq "active") {
        $parse_filter{$filter_field_id}->[0] = $filter_field_id;
        $parse_filter{$filter_field_id}->[1] = $filter_state;
        $parse_filter{$filter_field_id}->[2] = $filter_log_resource;
        $parse_filter{$filter_field_id}->[3] = $filter_log_action;
        $parse_filter{$filter_field_id}->[4] = $filter_log_protocol;
        $parse_filter{$filter_field_id}->[5] = $filter_log_src_ip;
        $parse_filter{$filter_field_id}->[6] = $filter_log_src_pt;
        $parse_filter{$filter_field_id}->[7] = $filter_log_dst_ip;
        $parse_filter{$filter_field_id}->[8] = $filter_log_dst_pt;
     }
}

}



##
## For DEBUG PURPOSES ONLY
## 
##

if ($show_messages eq "1") {

foreach $field_ident (sort keys %syslog_message) {
  
	print "\n";
	print "PIX Log Field ID: $syslog_message{$field_ident}->[0]\n";
	print "PIX Log Message ID: $syslog_message{$field_ident}->[1]\n";
	print "PIX Log Message Type: $syslog_message{$field_ident}->[2]\n";
	print "PIX Log Message Regex: $syslog_message{$field_ident}->[3]\n";
	print "PIX Log Message Time: $syslog_message{$field_ident}->[4]\n";
	print "PIX Log Message Action: $syslog_message{$field_ident}->[5]\n";
	print "PIX Log Regex SRC IP: $syslog_message{$field_ident}->[6]\n";
    print "PIX Log Regex DST IP: $syslog_message{$field_ident}->[7]\n";
    print "PIX Log Regex SRC PT: $syslog_message{$field_ident}->[8]\n";
    print "PIX Log Regex DST PT: $syslog_message{$field_ident}->[9]\n";
    print "PIX Log Regex XLATE SRC IP: $syslog_message{$field_ident}->[10]\n";
    print "PIX Log Regex XLATE DST IP: $syslog_message{$field_ident}->[11]\n";
    print "PIX Log Regex XLATE SRC PT: $syslog_message{$field_ident}->[12]\n";
    print "PIX Log Regex XLATE DST PT: $syslog_message{$field_ident}->[13]\n";
	print "\n";
}

exit();

}


##
##  PIX Log Parsing
##

sub parse_log_messages {

  $syslog_file=File::Tail->new(name=>$pix_log_file, maxinterval=>5, adjustafter=>7);
  
  ## Syslog File Reading Loop
  ##
  while (defined($line=$syslog_file->read)) {

	  $match_logmsg = "0";
	  $pla_log_format = "0";
	  $filterthismsg = "0";

          $line=~s/'/\\'/g; # Better be safe than sorry.
          $line=~s/"/\\"/g; # Better be safe than sorry.

	  ## Parse Syslog Message:
	  if ($line =~/(.*): %(.*): (.*)/) {
	      $res_a = $1;
		  $res_b = $2;
		  $res_c = $3;
		  
		  ## Fix Parsed Log Messages containing ":" multiple times in the log message.
		  if ($res_b =~ /(.*):(.*)/) {
			 ($res_b,$res_x) = split(":",$res_b);
			  $res_c = "$res_x $res_c";
		  }
		  
	  }
	 
      if ($res_a =~ /$regex_log_begin/) {
	      $pla_log_format = "1";
	      $pixhost = ${$var_pixhost};
	      $pixdate = ${$var_pixdate};
	      $pixmonth = ${$var_pixmonth};
	      $pixyear = ${$var_pixyear};
	      $pixtime = ${$var_pixtime};
	      &calculatedate;
      } else {
	  next;
      }
	  
	  ## PIX System Message matching (database stored parsing versus real-time log input)
	  ##
	  foreach $field_ident (sort keys %syslog_message) {
			# check to ensure log message is in the database
			next unless $res_b eq $syslog_message{$field_ident}->[1]; 
			$match_logmsg = "1"; # confirm log message match.

			    # non-parsed message to EXCLUDE from being logged into DB via info_log table

                           if ($syslog_message{$field_ident}->[2] eq "0" && $syslog_message{$field_ident}->[5] eq "1") {
			      #$res_c=~s/'/\\'/g;
			      #$res_c=~s/"/\\"/g;
                              # non-parsed message to INCLUDE to be logged into DB via info_log table
                              $update_db = "insert into info_log values(\'\',\'$pix_full_date\',\'$pixhost\',\'$res_b\',\'INFO\',\'$res_c\')";
			      &write_db;
                           }

                           next if ($syslog_message{$field_ident}->[2] eq "0" && $syslog_message{$field_ident}->[5] eq "0");
			
			if ($res_c =~ /$syslog_message{$field_ident}->[3]/) {
			
			   $pixmsg = $syslog_message{$field_ident}->[1];
			   $pixaction = $syslog_message{$field_ident}->[5];
			   $proto = ${$syslog_message{$field_ident}->[15]};
        	           $srcip = ${$syslog_message{$field_ident}->[6]};
			   $srcpt = ${$syslog_message{$field_ident}->[8]};
			   $dstip = ${$syslog_message{$field_ident}->[7]};
			   $dstpt = ${$syslog_message{$field_ident}->[9]};
			   $flag = ${$syslog_message{$field_ident}->[16]};
			   $src_xlate_ip = ${$syslog_message{$field_ident}->[10]};
			   $src_xlate_pt = ${$syslog_message{$field_ident}->[12]};
			   $dst_xlate_ip = ${$syslog_message{$field_ident}->[11]};
			   $dst_xlate_pt = ${$syslog_message{$field_ident}->[13]};
			   $signature = ${$syslog_message{$field_ident}->[18]};
			   $idsmsg = ${$syslog_message{$field_ident}->[14]};


			   ## Just in case the PIX returns the port name instead of the port number
			   ## 
			   $proto=~tr/A-Z/a-z/;		# rewrite protocol to lowercase
			

			   ## Check for SRC PT getservbyname();
			   ##
			   $servbyname_srcpt = $srcpt;
			   $servbyname_srcpt = getservbyname($srcpt,$proto) unless $srcpt =~ /^[0-9]+$/;
                           $srcpt = $servbyname_srcpt;   
			  
			   ## Check for DST PT getservbyname();
			   ##
			   $servbyname_dstpt = $dstpt;
			   $servbyname_dstpt = getservbyname($dstpt,$proto) unless $dstpt =~ /^[0-9]+$/;
                           $dstpt = $servbyname_dstpt;

		           ## Check for XLATE SRC PT getservbyname();
                           ##
                           $servbyname_xlatesrcpt = $src_xlate_pt;
                           $servbyname_xlatesrcpt = getservbyname($src_xlate_pt,$proto) unless $src_xlate_pt =~ /^[0-9]+$/;
                           $src_xlate_pt = $servbyname_xlatesrcpt;

                           ## Check for DST PT getservbyname();
                           ##
                           $servbyname_xlatedstpt = $dst_xlate_pt;
                           $servbyname_xlatedstpt = getservbyname($dst_xlate_pt,$proto) unless $dst_xlate_pt =~ /^[0-9]+$/;
                           $dst_xlate_pt = $servbyname_xlatedstpt;


			   $proto=~tr/a-z/A-Z/;		# rewrite protocol to uppercase


	       foreach $filter_field_id (sort keys %parse_filter) {
                        # check to ensure log message should not be filtered out

			$prs_pixhost = $parse_filter{$filter_field_id}->[2];
			$prs_pixaction = $parse_filter{$filter_field_id}->[3];
			$prs_proto = $parse_filter{$filter_field_id}->[4];
			$prs_srcip = $parse_filter{$filter_field_id}->[5];
			$prs_srcpt = $parse_filter{$filter_field_id}->[6];
			$prs_dstip = $parse_filter{$filter_field_id}->[7];
			$prs_dstpt = $parse_filter{$filter_field_id}->[8];

                        if ($prs_pixhost eq "any") {
                                $prs_pixhost = $pixhost;
                        }

                        if ($prs_pixaction eq "any") {
                                $prs_pixaction = $pixaction;
                        }

                        if ($prs_proto eq "any") {
                                $prs_proto = $proto;
                        }

                        if ($prs_srcip eq "any") {
                                $prs_srcip = $srcip;
                        }

                        if ($prs_srcpt eq "any") {
                                $prs_srcpt = $srcpt;
                        }

                        if ($prs_dstip eq "any") {
                                $prs_dstip = $dstip;
                        }

                        if ($prs_dstpt eq "any") {
                                $prs_dstpt = $dstpt;
                        }

                        $leftside = "$prs_pixhost|$prs_pixaction|$prs_proto|$prs_srcip|$prs_srcpt|$prs_dstip|$prs_dstpt";
                        $rightside = "$pixhost|$pixaction|$proto|$srcip|$srcpt|$dstip|$dstpt";

                        if ($leftside eq $rightside) {
                            $filterthismsg = "1";
                            #print "\nFILTER_MATCH: $rightside\n\n";
                        }
               }

			   if ($syslog_message{$field_ident}->[6] eq "0") {
				   $srcip = "";
		       }

		       if ($syslog_message{$field_ident}->[7] eq "0") {
				   $dstip = "";
               }

               if ($syslog_message{$field_ident}->[8] eq "0") {
				   $srcpt = "";
               }

                if ($syslog_message{$field_ident}->[9] eq "0") {
					$dstpt = "";
                }

                if ($syslog_message{$field_ident}->[10] eq "0") {
					$src_xlate_ip = "";
                }

                if ($syslog_message{$field_ident}->[11] eq "0") {
					$dst_xlate_ip = "";
                }

                if ($syslog_message{$field_ident}->[12] eq "0") {
					$src_xlate_pt = "";
                }

                if ($syslog_message{$field_ident}->[13] eq "0") {
					$dst_xlate_pt = "";
                }	

                if ($syslog_message{$field_ident}->[14] eq "0") {
					$idsmsg = "";
                }

                if ($syslog_message{$field_ident}->[15] eq "0") {
					$proto = "";
                }

                if ($syslog_message{$field_ident}->[16] eq "0") {
					$flag = "";
                }

                if ($syslog_message{$field_ident}->[17] eq "0") {
					$time = "";
                }

                if ($syslog_message{$field_ident}->[18] eq "0") {
                    $signature = "";
                }

                if ($syslog_message{$field_ident}->[19] eq "0") {
                    $message = "";
                }

                if ($syslog_message{$field_ident}->[20] eq "0") {
                    $interface = "";
                }



			   ##  Message_Type = 1 -> Traffic Logs
			   if (($syslog_message{$field_ident}->[2] eq "1") && ($filterthismsg eq "0")) { 
			       $update_db = "insert into traffic_log values(\'\',\'$pix_full_date\',\'$pixhost\',\'$pixmsg\',\'$pixaction\',\'$proto\',\'$srcip\',\'$srcpt\',\'$dstip\',\'$dstpt\',\'$flag\',\'$src_xlate_ip\',\'$src_xlate_pt\',\'$dst_xlate_ip\',\'$dst_xlate_pt\')";
			       &write_db;
			   }
			   
			   ##  Message_Type = 2 -> IDS Logs
			   if (($syslog_message{$field_ident}->[2] eq "2") && ($filterthismsg eq "0")) { 
			       
			       $update_db = "insert into ids_log values(\'\', \'$pix_full_date\', \'$pixhost\',\'$proto\',\'$srcip\',\'$dstip\',\'$signature $proto $idsmsg\')";
			       &write_db;
				#print "\n$update_db\n";
			   }

			}
		
		}

		   if ($pla_suppress_unknown eq "0") {
 
               	      if ($match_logmsg eq "0" && $pla_log_format eq "1") {
			  $update_db = "insert into info_log values(\'\',\'$pix_full_date\',\'$pixhost\',\'$res_b\',\'INFO\',\'$res_c\')";
			  if ($pla_unknown_writetofile eq "1") {
			      open(PLAMSG,">>$pla_unknown_filename") || die "can't open file: $!\n" if ($pla_unknown_writetofile eq "1");
                              print PLAMSG "[pla_parsed/$pla_version] - No PIX Log Match in Database for:\n$update_db\n\n";
			      close(PLAMSG);
			  }
			  &write_db if $pla_unknown_writetodb eq "1";
		      }

                   }
	  
  }
  
}


