#!/usr/bin/perl
#==============================================================================
#
# NAME:		fw1logsearch.pl
#
# AUTHOR:	Allen Pomeroy (apomeroy@networkforensics.org)
#
#		Copyright (c)2008 Allen Pomeroy
#
# PURPOSE:	Search Firewall-1 export logs for criteria specified on the 
#   command line.  Reads the export logfile header to determine the 
#   column(s) to search.  Ignores multiple header lines, allowing multiple
#   concatenated input files (as long as the column positions are the same).
#   For input files with differing column positions, simply run the input
#   files through fw1logsearch.pl individually (prehaps via shell script).
#
#		Inspired (and based off) fwlogsum by Peter Sundstrom (ginini.com)
# 
# SOURCE:	http://www.networkforensics.org/software/checkpoint/fw1logsearch.pl
#
# This program 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.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
# 
#==============================================================================

#
# Keywords used in the Include and Exclude arrays MUST match the column
# labels used by Checkpoint when generating the fwm logexport files. See
# logexport_default.C for column names
#

#==============================================================================
# Revision History
# -----------------------------------------------------------------------------
# v0.4 2009/02/06 - AP
# Added source port column processing
#
# v0.2 2008/07/25 - AP
# Added action column processing - to support additional columns, three 
# sections must be updated:
#  - add command line arguments which reference the column name as the key
#  - add cmd line arg processing
#  - add column header index parsing
#
# TODOs
# - hacked out PrintLine and RejectLine subroutines just to get this running
#   changed @p to a global to make it work
# - code optimization
# -----------------------------------------------------------------------------
# Declares, includes, requires, constants
require 5.000;
use Getopt::Long;			# For Processing options
use AnyDBM_File;      # For DBM functions
use Fcntl;				    # For DBM open permissions
use Socket;           # For name resolution
my $version='0.4';		# Script Version

# declare and set defaults
#my %Includes = ("service"	=> "53|domain");

# associative array to hold regex patterns to be run against these respective 
# columns in the fw log data
my %Include;
my %Exclude;

# associative array to hold the status of any searches performed
my %IncludeStatus;
my %ExcludeStatus;

my %ColumnIndex;
my %dnscache;
                
my $debug=0;
my $HeaderFound=0;
my $HeaderPrinted=0;
my $RejectHeaderPrinted=0;
my $Header;
my $LineNumber=0;
my $RejectLineNumber=0;
my $FullMatch=0;
my $DNSexpire=10;  # days
my $CacheDNS=1;
my $ResolveIP=0;
my $DNScachefile="./dnscache-fw1logsearch.db";
#my $DNScachefile="/work/dnscache-fw1logsearch.db";
my $StatusFile="./fw1logsearch.status";
my $ProcessStatus=0;
my $IncludeAll=0;   # 1=all includes must match to get a hit
my $ExcludeAll=0;   # 1=all excludes must match to reject a line

#$Date=localtime();
#$| = 1;

# Save options so they can be used as a META tag
my $options=@ARGV; 

# -----------------------------------------------------------------------------
# Process arguments
#
Getopt::Long::config("bundling");
GetOptions( \%opt,
  # include options - operates on log file columns
	'a=s', 'incaction=s',   # action (accept|drop|...)
	'p=s', 'incservice=s',  # dest port (service)
	'b=s', 'incs_port=s',   # source port (s_port)
	's=s', 'incsrc=s',      # source ip|hostname
	'd=s', 'incdst=s',      # destination ip|hostname
	'o=s', 'incorig=s',     # reporting fw ip|hostname
	'r=s', 'incrule=s',     # rule number !! policy changes can = rule # change
	't=s', 'incproto=s',    # packet protocol (tcp,udp,icmp)
  # exclude options - operates on log file columns
	'A=s', 'excaction=s',   # action (accept|drop|...)
	'P=s', 'excservice=s',  # dest port (service)
	'B=s', 'excs_port=s',   # source port (s_port)
	'S=s', 'excsrc=s',      # source ip|hostname
	'D=s', 'excdst=s',      # destination ip|hostname
	'O=s', 'excorig=s',     # reporting fw ip|hostname
	'R=s', 'excrule=s',     # rule number !! policy changes can = rule # change
	'T=s', 'excproto=s',    # packet protocol (tcp,udp,icmp)
  # log file handling options
	'inputfile=s',          # read from file
	'dnscache=s',           # dns cache file to use
	'resolveip',            # post resolve src and dst IP addresses
	'statuslog',            # enable updates to statuslog
	'rejectfile=s',         # dump non-match lines to output file
	'allinclude',           # all includes must match for -> stdout
	'allexclude',           # all excludes must match for -> reject
	'debug=s',              # set debug level
	) or Usage();

#
# process include regexs
#

# include action (accept|drop|...) in search
if ($opt{a}) {
  # add to search list
  $Include{action}=$opt{a};
} elsif ($opt{incaction}) {
  # add to search list
  $Include{action}=$opt{incaction};
}

# include service (destination port) in search
if ($opt{p}) {
  # add to search list
  $Include{service}=$opt{p};
} elsif ($opt{incservice}) {
  # add to search list
  $Include{service}=$opt{incservice};
}

# include source port (s_port) in search
if ($opt{b}) {
  # add to search list
  $Include{s_port}=$opt{b};
} elsif ($opt{incs_port}) {
  # add to search list
  $Include{s_port}=$opt{incs_port};
}

# include source (source IP/addr) in search
if ($opt{s}) {
  # add to search list
  $Include{src}=$opt{s};
} elsif ($opt{incsrc}) {
  # add to search list
  $Include{src}=$opt{incsrc};
}

# include destination (destination IP/addr) in search
if ($opt{d}) {
  # add to search list
  $Include{dst}=$opt{d};
} elsif ($opt{incdst}) {
  # add to search list
  $Include{dst}=$opt{incdst};
}

# include event origin (fw IP/addr) in search
if ($opt{o}) {
  # add to search list
  $Include{orig}=$opt{o};
} elsif ($opt{incorig}) {
  # add to search list
  $Include{orig}=$opt{incorig};
}

# include rule number in search
if ($opt{r}) {
  # add to search list
  $Include{rule}=$opt{r};
} elsif ($opt{incrule}) {
  # add to search list
  $Include{rule}=$opt{incrule};
}

# include packet protocol in search
if ($opt{t}) {
  # add to search list
  $Include{proto}=$opt{t};
} elsif ($opt{incproto}) {
  # add to search list
  $Include{proto}=$opt{incproto};
}

#
# process exclude regexs
#

# exclude action (accept|drop|...) in search
if ($opt{A}) {
  # add to search list
  $Exclude{action}=$opt{A};
} elsif ($opt{excaction}) {
  # add to search list
  $Exclude{action}=$opt{excaction};
}

# exclude service (destination port) in search
if ($opt{P}) {
  # add to search list
  $Exclude{service}=$opt{P};
} elsif ($opt{excservice}) {
  #$ExcludeService=$opt{incsvc};
  # add to search list
  $Exclude{service}=$opt{excservice};
}

# exclude s_port (source port) in search
if ($opt{B}) {
  # add to search list
  $Exclude{s_port}=$opt{B};
} elsif ($opt{excs_port}) {
  # add to search list
  $Exclude{s_port}=$opt{excs_port};
}

# exclude source (source IP/addr) in search
if ($opt{S}) {
  # add to search list
  $Exclude{src}=$opt{S};
} elsif ($opt{excsrc}) {
  # add to search list
  $Exclude{src}=$opt{excsrc};
}

# exclude destination (destination IP/addr) in search
if ($opt{D}) {
  # add to search list
  $Exclude{dst}=$opt{D};
} elsif ($opt{excdst}) {
  # add to search list
  $Exclude{dst}=$opt{excdst};
}

# exclude event origin (fw IP/addr) in search
if ($opt{O}) {
  # add to search list
  $Exclude{orig}=$opt{O};
} elsif ($opt{excorig}) {
  # add to search list
  $Exclude{orig}=$opt{excorig};
}

# exclude rule number in search
if ($opt{R}) {
  # add to search list
  $Exclude{rule}=$opt{R};
} elsif ($opt{excrule}) {
  # add to search list
  $Exclude{rule}=$opt{excrule};
}

# exclude packet protocol in search
if ($opt{T}) {
  # add to search list
  $Exclude{proto}=$opt{T};
} elsif ($opt{excproto}) {
  # add to search list
  $Exclude{proto}=$opt{excproto};
}

#
# process other arguments
#

if ($opt{allinclude}) {
  # 
  $IncludeAll=1;
}

if ($opt{allexclude}) {
  # 
  $ExcludeAll=1;
}

if ($opt{dnscache}) {
  # setup dns cache file for use
  $DNScachefile=$opt{dnscache};
}

if ($opt{resolveip}) {
  # set resolve IP address flag
  $ResolveIP=1;
}

if ($opt{statuslog}) {
  # set statuslog update flag
  $ProcessStatus=1;
}

if ($opt{debug}) {
  # set verbosity level
  $debug=$opt{debug};

  print "Verbose on, debug level = $debug.\n";
  print "Include regexs:\n";
  while ( ($key, $value) = each(%Include) ) {
    print "$key = $value\n";
  }
  print "\nExclude regexs:\n";
  while ( ($key, $value) = each(%Exclude) ) {
    print "$key = $value\n";
  }
  print "\nOptions:\n";
  while ( ($key, $value) = each(%opt) ) {
    print "$key = $value\n";
  }
  
  print "Include all = $IncludeAll\n";
  print "Exclude all = $ExcludeAll\n";
  
  my $KeyCount = keys(%Include);
  print "Number of Include keys = $KeyCount\n";
  my $KeyCount = keys(%Exclude);
  print "Number of Exclude keys = $KeyCount\n";
}

# 
# Initialise DNS cache, if specified.
#
InitDNSCache() if $CacheDNS and $ResolveIP;

#
# Initialize status/output files, if specified
#
if ($ProcessStatus) {
  open(STATUSLOG,">$StatusFile") or die "Cannot open status log $!\n";
}
if ($opt{rejectfile}) {
  # no default reject file allowed, user must specify destination
  open(REJECTFILE,">$opt{rejectfile}") or die "Cannot open reject file $!\n";
}


#
# validate options specified
#

#die "No point in specifying both resolve options\n" if ($ResolveIP and $PostResolveIP);


# -----------------------------------------------------------------------------
# Process fwm logfile input line by line
#
while(<STDIN>)
{
  # TODO - better way to clear these associative arrays?
  
  # reset all loop control vars
  foreach $key (keys %IncludeStatus) {
    if ($debug > 5) {
      print "** [$LineNumber]: Removing $key = $IncludeStatus{$key}\n";
    }
    delete($IncludeStatus{$key});
  }
  foreach $key (keys %ExcludeStatus) {
    if ($debug > 5) {
      print "** [$LineNumber]: Removing $key = $ExcludeStatus{$key}\n";
    }
    delete($ExcludeStatus{$key});
  }

  # write status update
  if ($ProcessStatus) {
    if (($LineNumber % 1000) == 0) {
      print STATUSLOG "$LineNumber ";
    }
  }

  # get current line into $line
  my $line = $_;

  # strip line term and all trailing whitespace such as dos cr
  chomp($line);
  $line =~ s/\s+$//;

  # parse line based on ;
  #my @p = split (";", $line);
  @p = split (";", $line);

  # process header line - num will always be column 0
  #if ($p[0] =~ /^num;date;time;/) {
  #if ($p[0] =~ /^num/) {
  if (($HeaderFound == 0) && ($p[0] =~ /^num/)) {
    # get the indexes of the keywords
    for (my $i=1; $i<$#p; $i++) {
      # populate ColumnIndex array based on column name
      if ($p[$i] =~ /^date$/) { $ColumnIndex{date}=$i; }
      if ($p[$i] =~ /^time$/) { $ColumnIndex{time}=$i; }
      #if ($p[$i] =~ /^i\/f_name$/) { $ColumnIndex{i\/f_name}=$i; }
      if ($p[$i] =~ /^orig$/) { $ColumnIndex{orig}=$i; }
      if ($p[$i] =~ /^action$/) { $ColumnIndex{action}=$i; }
      if ($p[$i] =~ /^service$/) { $ColumnIndex{service}=$i; }
      if ($p[$i] =~ /^rule$/) { $ColumnIndex{rule}=$i; }
      if ($p[$i] =~ /^s_port$/) { $ColumnIndex{s_port}=$i; }
      if ($p[$i] =~ /^src$/) { $ColumnIndex{src}=$i; }
      if ($p[$i] =~ /^dst$/) { $ColumnIndex{dst}=$i; }
      if ($p[$i] =~ /^proto$/) { $ColumnIndex{proto}=$i; }
    }

    # delay output of header until at least one data line
    #print "$line\n";
    $Header = $line;
    if ($debug) {
      print "\n";
      print "** [$LineNumber]: header line found:\n";
      
      print "** -------------------------\nHeader line:\n";
      while ( ($key, $value) = each(%ColumnIndex) ) {
        print "** [$LineNumber]: $key is column number $value\n";
      }
      print "** -------------------------\n";
    }
    $HeaderFound=1;
    $LineNumber++;
    next;
  }

  # ensure we have found a header
  if ($HeaderFound == 0) {
    print "No header line found.\n";
    exit;
  }

  #
  # perform matching on line
  #
  
  #
  # logic flow:
  # 1. process excludes (covers case where there are no excludes specified)
  #   a. ExcludeAll set and all exclude patterns match -> reject line
  #   b. ExcludeAll not set and any exclude patterns match -> reject line
  #
  # 2. process includes
  #   a. Include regex count is zero (no includes specified)
  #      and line hasn't been rejected -> display line (implied include=all)
  #   b. IncludeAll set and all include patterns match -> display line
  #   c. IncludeAll not set and any include patterns match -> display line
  #
  # 3. default action for remaining conditons -> reject line
  #
  # this supports the intent of iterative 'trimming' an input data set into
  # various reject files until the input data set is potentially nothing
  #
  
  #
  # process the excludes
  #
  
  # if ExcludeAll is on, line skip requires all exclude patterns to
  # match - otherwise dump the line on any exclude match

  # cycle through the Exclude regex patterns
  while ( ($key, $value) = each(%Exclude) ) {
    # process column (key) for regex (value)
    if ($debug > 3) {
      print "\n** [$LineNumber]: Processing exclude column $key = regex $value\n";
    }

    if ($p[$ColumnIndex{$key}] =~ m/$Exclude{$key}/is) {
      # found a match
      print "** [$LineNumber]: found exclude match on " if $debug;
      print "$key $p[$ColumnIndex{$key}] = regex $Exclude{$key}\n" if $debug;
      # flag exclude regex match 
      $ExcludeStatus{$key}=1;
    }
  }

  # if there are no excludes specified, only process includes
  if (keys(%Exclude) > 0) {
    # if all exclude regex hit, skip this line
    # if any exclude regex hit and ExcludeAll is off, skip this line
    if ( (keys(%ExcludeStatus) == keys(%Exclude)) ||
         (!$ExcludeAll && (keys(%ExcludeStatus) > 0)) ) {
      # 
      # write line to reject file if specified
      if ($opt{rejectfile}) {
        #
        # reject line into file - display header if needed
        if ($RejectHeaderPrinted == 0) {
          # display header line
          RejectLine(1, $line, \@p);
          $RejectHeaderPrinted=1;
        } else {
          # don't display header line
          RejectLine(0, $line, \@p);
        }

        $RejectLineNumber++;
      }
  
      # report findings if debug mode on
      if ($debug) {
        foreach $key (keys %ExcludeStatus) {
          print "** [$LineNumber]: Found exclude column ";
          print "$key = $ExcludeStatus{$key}\n";
        }
        print "** [$LineNumber]: Skipping line due to excludes.\n";
      }
      
      # skip input line
      $LineNumber++;
      next;
    }    
  }
  
  #
  # process the includes
  #

  # if no includes, display the line
  if (keys(%Include) == 0) {
    # 
    # display line 

    # display line - display header if needed
    if ($HeaderPrinted == 0) {
      # display header line
      PrintLine(1, $line, \@p);
      $HeaderPrinted=1;
    } else {
      # don't display header line
      PrintLine(0, $line, \@p);
    }

    $LineNumber++;
    next;
  }
        
  # cycle through all the Include regex patterns
  while ( ($key, $value) = each(%Include) ) {
    # process column (key) for regex (value)
    if ($debug > 3) {
      print "\n** [$LineNumber]: Processing include column $key = regex $value\n";
    }

    if ($p[$ColumnIndex{$key}] =~ m/$Include{$key}/is) {
      # found a match
      print "** [$LineNumber]: found include match on " if $debug;
      print "$key $p[$ColumnIndex{$key}] = regex $Include{$key}\n" if $debug;
      # flag include regex match
      $IncludeStatus{$key}=1;
    } else {
      if ($debug > 5) {
        print "** [$LineNumber]: attempted include match on ";
        print "$key $p[$ColumnIndex{$key}] = regex $Include{$key}\n";
      }
    }
  }

  # if all include regex hit and IncludeAll is on, display this line
  # if any include regex hit and IncludeAll is off, display this line
  if ( (keys(%IncludeStatus) == keys(%Include)) ||
       (!$IncludeAll && (keys(%IncludeStatus) > 0)) ) {
    # 
    # display line
    #PrintLine($line, @p);
    if ($HeaderPrinted == 0) {
      # display header line
      PrintLine(1, $line, \@p);
      $HeaderPrinted=1;
    } else {
      # don't display header line
      PrintLine(0, $line, \@p);
    }

    # report findings if debug mode on
    if ($debug) {
      foreach $key (keys %IncludeStatus) {
        print "** [$LineNumber]: Found include column ";
        print "$key = $IncludeStatus{$key}\n";
      }
    }
    
    # 
    $LineNumber++;
    next;
  }

  # all done includes and excludes
  # report findings if debug mode on
  if ($debug) {
    #print "\n** [$LineNumber]: Finished searching line, results:\n";
    foreach $key (keys %IncludeStatus) {
      print "** [$LineNumber]: Found include column ";
      print "$key = $IncludeStatus{$key}\n";
    }
  }

  # 
  # default case - reject line
  #
  
  # write line to reject file if specified
  if ($opt{rejectfile}) {
    #
    # reject line into file - display header if needed
    if ($RejectHeaderPrinted == 0) {
      # display header line
      RejectLine(1, $line, \@p);
      $RejectHeaderPrinted=1;
    } else {
      # don't display header line
      RejectLine(0, $line, \@p);
    }

    $RejectLineNumber++;
  }

  # report findings if debug mode on
  if ($debug) {
    print "** [$LineNumber]: Default condition - skipping line.\n";
    if ($debug > 3) { print "$line\n"; }
  }
    
  # skip input line
  $LineNumber++;
  next;
}

# finish up
close(STATUSLOG);


#-----------------------------------------------------------------------------
sub InitDNSCache {
	print("** Initialising DNS cache") if $debug;

	tie(%dnscache,'AnyDBM_File',"$DNScachefile",O_RDWR|O_CREAT,0600) or die "Can not open $DNScachefile $!\n";
	#
	# Expire old entries
	#
	my $now = time();
	my $expiry = $DNSexpire * 24 * 60 * 60;

	while (my ($ip,$value) = each %dnscache) {
		my ($host,$age) = split(/,/,$value);
		delete $dnscache{$ip} if ($now > ($age + $expiry));
	}
}
	
#-----------------------------------------------------------------------------
sub ResolveIPAddress {
	my $Address=shift;
	my $Hostname;

  # if Address param is null just return
	return unless $Address;

  # address parameter is an IP address (vs a hostname)
	if ($Address =~ /(\d+\.\d+\.\d+\.\d+)/) {
    # return (hostname) value for Address if it's in the Host array already
   	return $Host{$Address} if ($Host{$Address});
    
    # if hostname value for Address is not in Host array, look in dns cache file
		if ($CacheDNS and $dnscache{$Address}) {
			($Hostname,undef) = split(/,/,$dnscache{$Address});
			return $Hostname;
		}

    # hostname value for Address isn't in cache either, use dns to resolve			
		$Hostname = gethostbyaddr(inet_aton($Address),AF_INET) or $Hostname="$Address";
		$Host{$Address}=$Hostname;
		$dnscache{$Address}=join(',',$Hostname,time()) if $CacheDNS;		

    return $Hostname;
 	}	else {
		$Host{$Address}=$Address;
		$dnscache{$Address}=join(',',$Address,time()) if $CacheDNS;		
		return $Address;
	}
}

#-----------------------------------------------------------------------------
sub RejectLine {
	my $PrintHeader=shift;
	my $line=shift;
	#my @p=shift;

  # $ref_array_a->[0] = "zzz"; ### Changes the value of the 1st element("a")

	#return unless $Address;
  if ($debug > 3) {
    print "** RejectLine() called\n";
    print "** \$line = $line\n";    
    print "** \$PrintHeader = " . $PrintHeader . "\n";
    print "** ResolveIP = $ResolveIP\n";
  }

  if ($PrintHeader == 1) {
    print REJECTFILE "$Header\n";
  }        
  
  # output line
  # loop through parsed elements of line in order to resolveIP
  if ($ResolveIP) {
    # walk through parsed line elements so we can resolve the IP columns
    #print "\$#p = " . $#p . "\n";
    for (my $i=0; $i<$#p; $i++) {
      #print "i = $i\n";
      #print "ColumnIndex{src} = $ColumnIndex{src}\n";
      #print "p[$i] = $p[$i]\n";
      my $host;
      # if column index matches src, dst, or orig, do IP resolve if flag set
      if ($i == $ColumnIndex{src} or
          $i == $ColumnIndex{dst} or
          $i == $ColumnIndex{orig}) {
        # resolve IP
        $host=ResolveIPAddress($p[$i]);
        print REJECTFILE "$host";
      } else {
        # just display this column as is
        print REJECTFILE "$p[$i]";
      }
      print REJECTFILE ";" unless $i==$#p;
    }
    print REJECTFILE "\n";
  } else {
    # no need to process any individual columns, dump the whole line
    print REJECTFILE "$line\n";
  }
  return;
}

#-----------------------------------------------------------------------------
sub PrintLine {
	my $PrintHeader=shift;
	my $line=shift;
	#my @p=shift;

	#return unless $Address;
	
  if ($debug > 3) {
    print "** PrintLine() called\n";
    #print "** \$LineNumber = $LineNumber\n";
    #print "** \$Header = $Header\n";
    print "** \$PrintHeader = " . $PrintHeader . "\n";
  }
      

  # display header if needed
  if ($PrintHeader == 1) {
    print "$Header\n";
  }
  
  # display line
  #print "$line\n";
  # loop through parsed elements of line in order to resolveIP
  if ($ResolveIP) {
    # walk through parsed line elements so we can resolve the IP columns
    for (my $i=0; $i<$#p; $i++) {
      my $host;
      # if column index matches src, dst, or orig, do IP resolve if flag set
      if ($i == $ColumnIndex{src} or
          $i == $ColumnIndex{dst} or
          $i == $ColumnIndex{orig}) {
        # resolve IP
        $host=ResolveIPAddress($p[$i]);
        print "$host";
      } else {
        # just display this column as is
        print "$p[$i]";
      }
      print ";" unless $i==$#p;
    }
    print "\n";
  } else {
    # no need to process any individual columns, dump the whole line
    print "$line\n";
  }
  return;
}


#-----------------------------------------------------------------------------
sub Usage {
        print <<HELPMSG;
Usage: $0
 [-a|--incaction|-A|--excaction <action regex>]
 [-p|--incservice|-P|--excservice <dst port regex>]
 [-b|--incs_port|-B|--excs_port <src port regex>]
 [-s|--incsrc|-S|--excsrc <src regex>]
 [-d|--incdst|-D|--excdst <dst regex>]
 [-o|--incorig|-O|--excorig <fw regex>]
 [-r|--incrule|-R|--excrule <rule-number regex>]
 [-t|--incproto|-T|--excproto <proto regex>]

 [--dnscache <dns-cache-file>]
 [--resolveip]
 [--allinclude]
 [--allexclude]
 [--rejectfile <file>]
 [--debug <level>]

fw1logsearch.pl will search a fwm logexport text file for regex patterns
specified for supported columns (such as service, src, dst, rule, proto 
and orig).

Include and exclude regex matches may be specified on the same line, although
they both will include (print) a line or exclude (reject) a line based on 
single matches.  Allinclude or Allexclude must be specified to force a match
only on all specified column regex patterns.

Regex patterns can be enclosed with single quotes to include characters that
are special to the shell, such as the 'or' (|) operator.

Header will be output only if there are any matching lines.

Example invocations:
$ cat 2008-07-07*txt | \
  fw1logsearch.pl \
    -p '53|domain' \
    -d '192.168.1.2|host1|10.10.1.2|host2' \
    -o '192.168.2.3|10.10.2.4|10.10.4.5' \
    -S '64.65.66.67|32.33.34.35|10.10.*|192.168.*' \
    --resolveip
Will require destination port (service) to be 53, destination IP to be any of
192.168.1.2, host1, 10.10.1.2, or host2  the reporting firewall (origin) to be
any of 192.168.2.3, 10.10.2.4, or 10.10.4.5  and the source IP must not be
any of 64.65.66.67, 32.33.34.35, 10.10.*, or 192.168.*
Any lines that match this criteria, will display and the orig, src, and dst
columns will use the default DNS cache file (dynamically built/managed) to 
perform name resolution, replacing the IP addresses where possible.

Include regex patterns:
-a  --incaction    Rule action (accept, deny)
-b  --incs_port    Source port (s_port)
-p  --incservice   Destination port (service)
-s  --incsrc       Source IP|hostname
-d  --incdst       Destination IP|hostname
-o  --incorig      Reporting FW IP|hostname
-r  --incrule      Rule number that triggered entry
-t  --incproto     Protocol of connection 

Exclude regex patterns:
-A  --excaction    Rule action (accept, deny)
-B  --excs_port    Source port (s_port)
-P  --excservice   Destination port (service)
-S  --excsrc       Source IP|hostname
-D  --excdst       Destination IP|hostname
-O  --excorig      Reporting FW IP|hostname
-R  --excrule      Rule number that triggered entry
-T  --excproto     Protocol of connection 

Other options:
--debug {level} Turn on debugging
--dnscache      Specify location of DNS cache file to be used with
                the Resolve IPs option
--resolveip     Resolve IPs for orig, src, and dst columns AFTER filtering
--rejectfile    Write out all rejected lines to a specified file

Version: $version
HELPMSG

        exit 0;
}
