New bice using IPsets
authorAndrew DeFaria <Andrew@DeFaria.com>
Fri, 21 Aug 2020 18:37:11 +0000 (11:37 -0700)
committerAndrew DeFaria <Andrew@DeFaria.com>
Fri, 21 Aug 2020 18:37:11 +0000 (11:37 -0700)
bin/bice.pl
lib/Logger.pm

index d3b35b7..cd25b1b 100755 (executable)
@@ -71,7 +71,18 @@ my $UTC      = 'UTC-7';
 my $mailhost = $domain;
 # End customize these variables
 
-my $verbose;
+# Current IPset. This is the name of an IP match set (See 
+# https://kirkkosinski.com/2013/11/mass-blocking-evil-ip-addresses-iptables-ip-sets/)
+# Each set can hold up to 65535 entries. We are currently on set 2.
+#
+# TODO: This code should handle the case where the set fills and we need to go
+#       to the next set. Something like "ipset list <current set> | wc - " and
+#       if it's > than say 60000, start a new set.
+#
+#       Also, when a new set comes around we need to do:
+#         $ iptables -A FORWARD -m set --mach-set <newset> src -j DROP
+my $currIPSet = 'BICE2';
+
 my $update    = 1;
 my $email     = 1;
 my $hostname  = `hostname`;
@@ -81,46 +92,19 @@ if ($hostname =~ /(\w*)\./) {
   $hostname = $1;
 } # if
 
-sub AddToIPTables (@) {
-  my (@ips) = @_;
-
-  # We shouldn't need to weed out duplicate but ya never know  
-  my $ipfilename = '/etc/ipblock';
-
-  my $result = open my $ipfile, '<', $ipfilename;
-
-  my (%ips, @oldips);
+sub AddToIPSet($) {
+  my ($ip) = @_;
 
-  if ($result) {
-    @oldips = <$ipfile>; 
+  my ($status, @output) = Execute "/sbin/ipset add $currIPSet $ip 2>&1";
 
-    close $ipfile if $ipfile;
+  if ($status) {
+    return if $output[0] =~ /already added/;
 
-    chomp @oldips;
+    error "Unable to add $ip to ipset $currIPSet" . join ("\n", @output), 1;
+  } else {
+    return;
   } # if
-
-  map { $ips{$_} = 1 } @oldips;
-  map { $ips{$_} = 1 } <@ips>;
-
-  open $ipfile, '>', "$ipfilename"
-    or error "Unable to open $ipfilename - $!", 1;
-
-  foreach (sort keys %ips) {
-    print $ipfile "$_\n";
-  } # foreach
-
-  close $ipfile;
-
-  # Recreate the BICE chain
-  `/sbin/iptables -F BICE`;
-  `/sbin/iptables -X BICE`;
-  `/sbin/iptables -N BICE`;
-
-  # Add all new @ips to iptables
-  `/sbin/iptables -A BICE -s $_ -p tcp -j DROP` foreach (sort keys %ips);
-
-  return;
-} # AddToIPTables
+} # AddToIPSet
 
 # Use whois(1) to get the email addresses of the responsible parties for an IP
 # address. Note that a hash is used to eliminate duplicates.
@@ -139,7 +123,7 @@ sub GetEmailAddresses ($) {
 
   my %email_addresses;
 
-  foreach (@whois_list) {
+  for (@whois_list) {
     my @lines;
 
     if ($_ eq "") {
@@ -148,7 +132,7 @@ sub GetEmailAddresses ($) {
       @lines = grep {/.*\@.*/ } `whois -h $_ $ip`;
     } # if
 
-    foreach (@lines) {
+    for (@lines) {
       my @fields = split /:/, $_;
 
       $_ = $fields [@fields - 1];
@@ -156,23 +140,23 @@ sub GetEmailAddresses ($) {
       if (/(\S+\@\S[\.\S]+)/) {
         $email_addresses{$1} = "";
       } # if
-    } # foreach
+    } # for
 
     # Break out of loop if we found email addresses
     last unless keys %email_addresses;
-  } # foreach
+  } # for
 
   return keys %email_addresses;
 } # GetEmailAddresses
 
 # Send email to the responsible parties.
-sub SendEmail ($$$$$) {
-  my ($to, $subject, $message, $ip, $violations) = @_;
+sub SendEmail ($$$$$$) {
+  my ($to, $subject, $message, $ip, $attempts, $violationNbr) = @_;
 
   if ($email) {
-    verbose "Reporting $ip ($violations violations) to $to";
+    verbose "$violationNbr: Reporting $ip ($attempts violations) to $to";
   } else {
-    verbose "Would have reported $ip ($violations violations) to $to";
+    verbose "$violationNbr: Would have reported $ip ($attempts violations) to $to";
     return;
   } # if
 
@@ -243,7 +227,7 @@ sub processLogfile () {
   flock $writelog, LOCK_EX
     or error "Unable to flock $security_logfile", 1;
 
-  print $writelog $_ foreach @lines;
+  print $writelog $_ for @lines;
 
   flock $writelog, LOCK_UN
     or error "Unable to unlock $security_logfile", 1;
@@ -267,14 +251,15 @@ sub ReportBreakins () {
     verbose "$nbrViolations sites attempting to violate our perimeter";
   } # if
 
-  foreach (sort keys %violations) {
-    my $ip = $_;
+  my $violations;
 
+  for my $ip (sort keys %violations) {
     my $attempts;
 
-    $attempts += @{$violations{$ip}{$_}} foreach (keys %{$violations{$ip}});
+    $violations++;
+    $attempts += @{$violations{$ip}{$_}} for (keys %{$violations{$ip}});
 
-    my @emails   = GetEmailAddresses $ip;
+    my @emails = GetEmailAddresses $ip;
 
     unless (@emails) {
       verbose 'Unable to find any responsible parties for detected breakin '
@@ -310,28 +295,27 @@ attempted and the time of the attempt:</p>
 <ol>
 END
     # Report users
-    foreach my $user (sort keys %{$violations{$ip}}) {
+    for my $user (sort keys %{$violations{$ip}}) {
       if (@{$violations{$ip}{$user}} == 1) {
         $message .= "<li>The user <b>$user</b> attempted access on $violations{$ip}{$user}[0]</li>";
       } else {
         $message .= "<li>The user <b>$user</b> attemped access on the following date/times:</li>"; 
         $message .= "<ol>";
-        $message .= "<li>$_</li>" foreach (@{$violations{$ip}{$user}});
+        $message .= "<li>$_</li>" for (@{$violations{$ip}{$user}});
         $message .= "</ol>";
       } # if
-    } # foreach
+    } # for
 
     $message .= '</ol><p>Your prompt attention to this matter is expected '
               . 'and will be appreciated.</p>';
-    SendEmail $to, $subject, $message, $ip, $attempts;
-  } # foreach
+    SendEmail $to, $subject, $message, $ip, $attempts, $violations;
+    AddToIPSet $ip;
+  } # for
 
-  AddToIPTables keys %violations;
+  return;
 } # ReportBreakins
 
 ## Main
-
-# Get options
 GetOptions (
   'verbose', sub { set_verbose },
   'debug',   sub { set_debug },
@@ -341,8 +325,7 @@ GetOptions (
   'file=s',  \$security_logfile,
 ) || Usage;
 
-Usage 'Must specify filename'
-  unless $security_logfile;
+Usage 'Must specify filename' unless $security_logfile;
 
 ReportBreakins;
 
index e1fe824..a1ad323 100644 (file)
@@ -93,7 +93,7 @@ BEGIN {
   $me =~ s/\.pl$//;
 } # BEGIN
 
-sub new (;%){
+sub new(;%) {
   my ($class, %parms) = @_;
 
 =pod
@@ -166,7 +166,7 @@ Returns:
   my $append      = $parms{append}      ? '>>'                : '>';
   my $logfile;
 
-  if (defined $parms{extension}) {
+  if ($parms{extension}) {
     $name .= ".$parms{extension}" unless $parms{extension} eq '';
   } else {
     $name .= '.log';
@@ -176,7 +176,7 @@ Returns:
     or error "Unable to open logfile $path/$name - $!", 1;
 
   # Set unbuffered output
-  $logfile->autoflush ();
+  $logfile->autoflush();
 
   set_verbose if $ENV{VERBOSE};
   set_debug   if $ENV{DEBUG};
@@ -192,7 +192,7 @@ Returns:
   }, $class; # bless
 } # new
 
-sub append ($) {
+sub append($) {
   my ($self, $filename) = @_;
 
 =pod
@@ -241,7 +241,7 @@ Returns:
   return;
 } # append
 
-sub name () {
+sub name() {
   my ($self) = @_;
 
 =pod
@@ -279,7 +279,7 @@ Returns:
   return $self->{name};
 } # name
 
-sub fullname () {
+sub fullname() {
   my ($self) = @_;
 
 =pod
@@ -317,7 +317,7 @@ Returns:
   return "$self->{path}/$self->{name}";
 } # fullname
 
-sub msg ($;$) {
+sub msg($;$) {
   my ($self, $msg, $nolinefeed) = @_;
 
 =pod
@@ -366,7 +366,7 @@ Returns:
   return;
 } # msg
 
-sub disp ($;$) {
+sub disp($;$) {
   my ($self, $msg, $nolinefeed) = @_;
 
 =pod
@@ -415,7 +415,7 @@ Returns:
   return;
 } # disp
 
-sub incrementErr (;$) {
+sub incrementErr(;$) {
   my ($self, $increment) = @_;
 
 =pod
@@ -459,7 +459,7 @@ Returns:
   return;
 } # incrementErr
 
-sub err ($;$) {
+sub err($;$) {
   my ($self, $msg, $errno) = @_;
 
 =pod
@@ -522,7 +522,7 @@ Returns:
   return;
 } # err
 
-sub maillog (%) {
+sub maillog(%) {
   my ($self, %parms) = @_;
 
 =pod
@@ -568,8 +568,7 @@ Returns:
   my $footing = $parms{footing};
   my $mode    = $parms{mode};
 
-  $mode = "plain" 
-    unless $mode;
+  $mode = "plain" unless $mode;
 
   my $log_filename = "$self->{path}/$self->{name}";
 
@@ -584,7 +583,7 @@ Returns:
               . $footing;
   } # if
 
-  mail (
+  mail(
     from    => $from,
     to      => $to,
     cc      => $cc,
@@ -601,7 +600,7 @@ Returns:
   return;
 } # maillog
 
-sub log {
+sub log($;$) {
   my ($self, $msg, $nolinefeed) = @_;
 
 =pod
@@ -651,7 +650,7 @@ Returns:
   return;
 } # log
 
-sub logcmd ($) {
+sub logcmd($) {
   my ($self, $cmd) = @_;
 
 =pod
@@ -710,7 +709,7 @@ Returns:
   return ($?, @output);
 } # logcmd
 
-sub loglines () {
+sub loglines() {
   my ($self) = @_;
 
 =pod
@@ -748,7 +747,7 @@ Returns:
   return ReadFile "$self->{path}/$self->{name}";
 } # loglines
 
-sub warn ($;$) {
+sub warn($;$) {
   my ($self, $msg, $warnno) = @_;
 
 =pod
@@ -805,7 +804,7 @@ Returns:
   return;
 } # warn
 
-sub errors () {
+sub errors() {
   my ($self) = @_;
 
 =pod
@@ -843,7 +842,15 @@ Returns:
   return $self->{errors};
 } # errors
 
-sub warnings () {
+sub dbug($) {
+  my ($self, $msg) = @_;
+
+  $self->log("DEBUG: $msg") unless get_debug;
+
+  return;
+} # dbug
+
+sub warnings() {
   my ($self) = @_;
 
 =pod
@@ -881,7 +888,7 @@ Returns:
   return $self->{warnings};
 } # warnings
 
-sub DESTROY () {
+sub DESTROY() {
   my ($self) = @_;
 
   close ($self->{handle});