changeset 54:19dace24ad46

Remove default scanfiles; Clean up update_entry() code; Add "SCANFILE_ONCE" setting feature; Add sanity checking of some configuration values; Move more code from main to appropriate functions to handle reinitialization HUPs better.
author Matti Hamalainen <ccr@tnsp.org>
date Sun, 16 Aug 2009 17:11:47 +0300
parents dc072a56f343
children 30a5b56b753e
files example.conf maltfilter
diffstat 2 files changed, 100 insertions(+), 63 deletions(-) [+]
line wrap: on
line diff
--- a/example.conf	Sun Aug 16 05:03:19 2009 +0300
+++ b/example.conf	Sun Aug 16 17:11:47 2009 +0300
@@ -72,6 +72,12 @@
 SCANFILE = "/var/log/httpd/error.log"
 SCANFILE = "/var/log/httpd/access.log"
 
+## SCANFILE_ONCE is like SCANFILE setting, but these files are only
+## parsed through once, and are NOT followed for changes in "tail -f"
+## style as SCANFILEs are. This is useful if you want to include data
+## from logrotated files. (Notice that gzip compressed files are not supported.)
+SCANFILE_ONCE = "/var/log/auth.log.1"
+
 
 #############################################################################
 ### Checks / tests
--- a/maltfilter	Sun Aug 16 05:03:19 2009 +0300
+++ b/maltfilter	Sun Aug 16 17:11:47 2009 +0300
@@ -51,13 +51,6 @@
   "PASSWD"              => "/etc/passwd",
 );
 
-# Default logfiles to monitor (SCANFILES setting of configuration overrides these)
-my @scanfiles_def = (
-  "/var/log/auth.log",
-  "/var/log/httpd/error.log",
-  "/var/log/httpd/access.log"
-);
-
 my @noblock_ips_def = (
   "127.0.0.1",
 );
@@ -129,6 +122,7 @@
 #############################################################################
 my $reportmode = 0;      # Full report mode
 my @scanfiles = ();      # Files to scan
+my @scanfiles_once = (); # Files to scan only once during startup or HUP (e.g. not continuously followed)
 my @noblock_ips = ();    # IPs not to block
 my %filehandles = ();    # Global hash holding opened scanned log filehandles
 my $pid_file = "";       # Name of Maltfilter daemon pid file
@@ -536,8 +530,10 @@
 
 ### Get current Netfilter INPUT table entries that match
 ### entry types we manage, e.g. blocklist
-sub update_blocklist()
+sub update_blocklist($)
 {
+  # NOTICE: argument not used now
+  
   $ENV{"PATH"} = "";
   open(STATUS, $settings{"IPTABLES"}." -v -n -L INPUT |") or
     mdie("Could not execute ".$settings{"IPTABLES"}."\n");
@@ -628,40 +624,47 @@
   }
 }
 
-### Update one entry of
+### Update one entry data
+sub update_date($$)
+{
+  if (!defined($_[0]->{"date1"}) || ($_[1] > 0 && $_[0]->{"date1"} < 0)) {
+    $_[0]->{"date1"} = $_[1];
+  }
+  if (!defined($_[0]->{"date2"}) || $_[1] > $_[0]->{"date2"}) {
+    $_[0]->{"date2"} = $_[1];
+  }
+}
+
 sub update_entry($$$$$$)
 {
   my ($struct, $mip, $mdate, $mclass, $mreason, $addhits) = @_;
 
+  $struct->{$mip} = {} unless defined($struct->{$mip});
+  my $entry = $struct->{$mip};
+  $struct->{$mip}{"reason"}{$mclass} = {};
+  my $reason = $struct->{$mip}{"reason"}{$mclass};
+
+  # Add hits only when requested  
   if ($addhits) {
-    $struct->{$mip}{"hits"}++;
-    $struct->{$mip}{"reason"}{$mclass}{"hits"}++;
+    $entry->{"hits"}++;
+    $reason->{"hits"}++;
   } else {
-    $struct->{$mip}{"hits"} = 1 unless defined($struct->{$mip}{"hits"});
-    $struct->{$mip}{"reason"}{$mclass}{"hits"} = 1 unless defined($struct->{$mip}{"reason"}{$mclass}{"hits"});
-  }
-
-  if ($reportmode) {
-    push(@{$struct->{$mip}{"reason"}{$mclass}{"msg"}}, $mreason);
-  } else {
-    $struct->{$mip}{"reason"}{$mclass}{"msg"} = $mreason;
+    $entry->{"hits"} = 1 unless defined($entry->{"hits"});
+    $reason->{"hits"} = 1 unless defined($reason->{"hits"});
   }
 
-  if (!defined($struct->{$mip}{"date1"}) || ($mdate > 0 && $struct->{$mip}{"date1"} < 0)) {
-    $struct->{$mip}{"date1"} = $mdate;
-  }
-  if (!defined($struct->{$mip}{"date2"}) || $mdate > $struct->{$mip}{"date2"}) {
-    $struct->{$mip}{"date2"} = $mdate;
-  }
-    
-  if (!defined($struct->{$mip}{"reason"}{$mclass}{"date2"}) || ($mdate > 0 && $struct->{$mip}{"reason"}{$mclass}{"date2"} < 0)) {
-    $struct->{$mip}{"reason"}{$mclass}{"date2"} = $mdate;
-  }
-  if (!defined($struct->{$mip}{"reason"}{$mclass}{"date2"}) || $mdate > $struct->{$mip}{"reason"}{$mclass}{"date2"}) {
-    $struct->{$mip}{"reason"}{$mclass}{"date2"} = $mdate;
+  # Messages is an array in reportmode
+  if ($reportmode) {
+    push(@{$reason->{"msg"}}, $mreason);
+  } else {
+    $reason->{"msg"} = $mreason;
   }
 
-  return $struct->{$mip}{"hits"};
+  # Update timestamps (generic and reason)
+  update_date($entry, $mdate);
+  update_date($reason, $mdate);
+  
+  return $entry->{"hits"};
 }
 
 ### Check if given "try count" exceeds treshold and if entry
@@ -720,29 +723,39 @@
 ### Like Perl's die(), but also print a logfile entry.
 sub mdie($)
 {
-  mlog(-1, $_[0]);
+  mlog(-1, $_[0]) if ($LOGFILE);
   die($_[0]);
 }
 
 ### Initialize
 sub malt_init
 {
-  mlog(0, "Updating initial blocklist from netfilter.\n");
-  update_blocklist();
+  foreach my $filename (@scanfiles_once) {
+    mlog(0, "Parsing [once] ".$filename." ...\n");
+    if (open(INFILE, "<", $filename)) {
+      while (<INFILE>) {
+        chomp;
+        check_log_line($_);
+      }
+    } else {
+      mlog(-1, "Could not open '".$filename."', skipping now.\n");
+    }
+    close(INFILE);
+  }
 
   foreach my $filename (@scanfiles) {
     local *INFILE;
     mlog(0, "Parsing ".$filename." ...\n");
-    open(INFILE, "<", $filename) or mdie("Could not open '".$filename."'!\n");
-    $filehandles{$filename} = *INFILE;
-    while (<INFILE>) {
-      chomp;
-      check_log_line($_);
+    if (open(INFILE, "<", $filename)) {
+      $filehandles{$filename} = *INFILE;
+      while (<INFILE>) {
+        chomp;
+        check_log_line($_);
+      }
+    } else {
+      mlog(-1, "Could not open '".$filename."', skipping now.\n");
     }
   }
-  
-  mlog(0, "Weeding old entries.\n");
-  weed_entries();
 }
 
 ### Quick cleanup (not complete shutdown)
@@ -810,7 +823,7 @@
       # (in case entries have appeared there from "outside")
       # and perform weeding of old entries.
       $counter = 0;
-      update_blocklist();
+      update_blocklist(time());
       weed_entries();
       generate_status($settings{"STATUS_FILE_PLAIN"}, 0);
       generate_status($settings{"STATUS_FILE_HTML"}, 1);
@@ -848,7 +861,9 @@
       my $key = uc($1);
       my $value = $2;
       if ($key eq "SCANFILE") {
-        push(@scanfiles_def, $value);
+        push(@scanfiles, $value);
+      } elsif ($key eq "SCANFILE_ONCE") {
+        push(@scanfiles_once, $value);
       } elsif ($key eq "NOBLOCK_IPS") {
         push(@noblock_ips_def, $value);
       } elsif (defined($settings{$key})) {
@@ -874,13 +889,43 @@
 sub malt_configure
 {
   # Let user define his/her own logfiles to scan
-  @scanfiles_def = ();
-  undef(@scanfiles_def);
+  @scanfiles = ();
+  undef(@scanfiles);
+
+  @scanfiles_once = ();
+  undef(@scanfiles_once);
+
   foreach my $filename (@configfiles) {
     mdie("Errors in configuration file '$filename', bailing out.\n")
       unless (malt_read_config($filename) == 0);
   }
 
+  # Clean up certain arrays duplicate entries
+  my %saw = ();
+  @scanfiles = grep(!$saw{$_}++, @scanfiles);
+
+  %saw = ();
+  @scanfiles_once = grep(!$saw{$_}++, @scanfiles_once);
+
+  %saw = ();
+  @noblock_ips = grep(!$saw{$_}++, @noblock_ips_def);
+  undef(%saw);
+
+  mlog(-1, "Not blocking following IPs: ".join(", ", @noblock_ips)."\n");
+
+  # Check if we have anything to do
+  if ($reportmode) {
+    mdie("Nothing to do, no SCANFILE(s) or SCANFILE_ONCE(s) defined in configuration.\n") unless ($#scanfiles > 0 || $#scanfiles_once > 0);
+  } else {
+    mdie("Nothing to do, no SCANFILE(s) defined in configuration.\n") unless ($#scanfiles > 0);
+  }
+
+  # Test existence of iptables
+  if (! -e $settings{"IPTABLES"} || ! -x $settings{"IPTABLES"}) {
+    mdie("iptables binary does not exist or is not executable: ".$settings{"IPTABLES"}."\n");
+  }
+
+  # Check settings
   mdie("SYSACCT_MIN_UID must be >= 1.\n") unless ($settings{"SYSACCT_MIN_UID"} >= 1);
   mdie("SYSACCT_MAX_UID must be >= SYSACCT_MIN_UID.\n") unless ($settings{"SYSACCT_MAX_UID"} >= $settings{"SYSACCT_MIN_UID"});
 
@@ -932,15 +977,6 @@
 
 malt_configure();
 
-
-# Clean up certain arrays duplicate entries
-my %saw = ();
-@scanfiles = grep(!$saw{$_}++, @scanfiles_def);
-
-%saw = ();
-@noblock_ips = grep(!$saw{$_}++, @noblock_ips_def);
-undef(%saw);
-
 # Open logfile
 if ($settings{"DRY_RUN"}) {
   print $progbanner.
@@ -953,14 +989,9 @@
   mlog(-1, "Log started\n");
 }
 
-# Test existence of iptables
-if (! -e $settings{"IPTABLES"} || ! -x $settings{"IPTABLES"}) {
-  mdie("iptables binary does not exist or is not executable: ".$settings{"IPTABLES"}."\n");
-}
-
-mlog(-1, "Not blocking following IPs: ".join(", ", @noblock_ips)."\n");
-
 # Initialize
+mlog(0, "Updating initial blocklist from netfilter.\n");
+update_blocklist(-1);
 malt_init();
 
 # Fork to background, unless dry-running