[2/5] ipblacklist: WUI and language file

Message ID 20191125201309.10840-3-ipfr@tfitzgeorge.me.uk
State Superseded
Headers show
Series ipblacklist: IP Address Blacklists | expand

Commit Message

Tim FitzGeorge Nov. 25, 2019, 8:13 p.m. UTC
Main WUI page and update to system logs page

Signed-off-by: Tim FitzGeorge <ipfr@tfitzgeorge.me.uk>
---
 config/menu/50-firewall.menu  |   5 +
 html/cgi-bin/ipblacklist.cgi  | 725 ++++++++++++++++++++++++++++++++++++++++++
 html/cgi-bin/logs.cgi/log.dat |   2 +
 langs/en/cgi-bin/en.pl        |  31 ++
 4 files changed, 763 insertions(+)
 create mode 100644 html/cgi-bin/ipblacklist.cgi

Patch

diff --git a/config/menu/50-firewall.menu b/config/menu/50-firewall.menu
index 5ec1f67fc..cd82bfaa3 100644
--- a/config/menu/50-firewall.menu
+++ b/config/menu/50-firewall.menu
@@ -21,6 +21,11 @@ 
                                 'title' => "$Lang::tr{'intrusion detection system'}",
 				'enabled' => 1,
                                 };
+    $subfirewall->{'45.ipblacklist'} = {'caption' => $Lang::tr{'ipblacklist'},
+				'uri' => '/cgi-bin/ipblacklist.cgi',
+				'title' => "$Lang::tr{'ipblacklist'}",
+				'enabled' => 1,
+				};
 	$subfirewall->{'50.p2p'} = {
 				'caption' => $Lang::tr{'p2p block'},
 				'uri' => '/cgi-bin/p2p-block.cgi',
diff --git a/html/cgi-bin/ipblacklist.cgi b/html/cgi-bin/ipblacklist.cgi
new file mode 100644
index 000000000..b2ccf7b3f
--- /dev/null
+++ b/html/cgi-bin/ipblacklist.cgi
@@ -0,0 +1,725 @@ 
+#!/usr/bin/perl
+
+###############################################################################
+#                                                                             #
+# IPFire.org - A linux based firewall                                         #
+#                                                                             #
+# 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 3 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, see <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+# Copyright (C) 2018 - 2019 The IPFire Team                                   #
+#                                                                             #
+###############################################################################
+
+use strict;
+use CGI qw/:standard/;
+#enable only the following on debugging purpose
+#use warnings;
+#use CGI::Carp 'fatalsToBrowser';
+use Sort::Naturally;
+use Socket;
+
+require '/var/ipfire/general-functions.pl';
+require "${General::swroot}/lang.pl";
+require "${General::swroot}/header.pl";
+
+###############################################################################
+# Initialize variables and hashes
+###############################################################################
+
+my $settings      = "${General::swroot}/ipblacklist/settings";
+my $modified      = "${General::swroot}/ipblacklist/modified";
+my $sources       = "${General::swroot}/ipblacklist/sources";
+my $getipstat     = '/usr/local/bin/getipstat';
+my $getipsetstat  = '/usr/local/bin/getipsetstat';
+my $control       = '/usr/local/bin/ipblacklistctrl';
+my $lockfile      = '/var/run/ipblacklist.pid';
+my $autoblacklist = 'AUTOBLACKLIST';
+my %cgiparams     = ('ACTION' => '', 'AUTOACTION' => '');
+my $errormessage  = '';
+my $updating      = 0;
+my %mainsettings;
+my %color;
+my %modified;
+my %sources;
+my %stats;
+my %autoblock_addresses;
+
+my %settings = ( 'DEBUG'           => 0,
+                 'LOGGING'         => 'on',
+                 'RATE'            => 24,
+                 'ENABLE'          => 'off',
+                 'BLOCK_THRESHOLD' => 10,
+                 'BLOCK_PERIOD'    => 3600,
+                 $autoblacklist    => 'off' );
+
+# Read all parameters for site
+Header::getcgihash( \%cgiparams);
+General::readhash( "${General::swroot}/main/settings", \%mainsettings );
+General::readhash( "/srv/web/ipfire/html/themes/".$mainsettings{'THEME'}."/include/colors.txt", \%color );
+General::readhash( $settings, \%settings ) if (-r $settings);
+General::readhash( $modified, \%modified)  if (-r $modified);
+eval qx|/bin/cat $sources|                 if (-r $sources);
+
+# Show Headers
+
+Header::showhttpheaders();
+
+# Process actions
+
+if ($cgiparams{'ACTION'} eq "$Lang::tr{'save'}")
+{
+  #Save Button on configsite
+
+  my %new_settings = ( 'ENABLE'          => 'off',
+                       'RATE'            => 24,
+                       'LOGGING'         => 'off',
+                       'DEBUG'           => 0,
+                       'BLOCK_THRESHOLD' => $settings{'BLOCK_THRESHOLD'} || 10,
+                       'BLOCK_PERIOD'    => $settings{'BLOCK_PERIOD'} || 3600,
+                       $autoblacklist    => $settings{$autoblacklist} );
+
+  $errormessage .= "$Lang::tr{'ipblacklist invalid check rate'}<br>" if (($cgiparams{'RATE'} !~ m/^\d+$/) or
+                                                                         ($cgiparams{'RATE'} < 1) or
+                                                                         ($cgiparams{'RATE'} > 1000));
+
+  $new_settings{'RATE'} = $cgiparams{'RATE'};
+  delete $cgiparams{'RATE'};
+
+  foreach my $item ('LOGGING', 'ENABLE', keys %sources)
+  {
+    $new_settings{$item} = (exists $cgiparams{$item}) ? 'on' : 'off';
+
+    $updating = 1 if (not exists $settings{$item} or $new_settings{$item} ne $settings{$item});
+  }
+
+  # Check for redundant blacklists being enabled
+
+  foreach my $list (keys %sources)
+  {
+    if (exists $settings{$list} and
+        $settings{$list} eq 'on' and
+        exists $sources{$list}{'override'} and
+        $settings{$sources{$list}{'override'}} eq 'on')
+    {
+      $settings{$sources{$list}{'override'}} = 'off';
+
+      $updating      = 1;
+      $errormessage .= "$Lang::tr{'ipblacklist disable pre'} $sources{$list}{'override'} " .
+                       "$Lang::tr{'ipblacklist disable mid'} $list $Lang::tr{'ipblacklist disable post'}<br>\n";
+    }
+  }
+
+  if ($settings{'LOGGING'} ne $new_settings{'LOGGING'})
+  {
+    if ($new_settings{'LOGGING'} eq 'on')
+    {
+      system( "$control log-on" );
+    }
+    else
+    {
+      system( "$control log-off" );
+    }
+  }
+
+  if ($settings{'ENABLE'} ne $new_settings{'ENABLE'})
+  {
+    if ($new_settings{'ENABLE'} eq 'on')
+    {
+      system( "$control enable" );
+    }
+    else
+    {
+      $settings{$autoblacklist} = 'off';
+      system( "$control disable" );
+    }
+
+    $updating = 1;
+  }
+
+  %settings = %new_settings;
+
+  if ($errormessage)
+  {
+    $updating = 0;
+  }
+  else
+  {
+    General::writehash($settings, \%new_settings);
+
+    if ($updating)
+    {
+      system( "$control update &" );
+      get_ipset_stats();
+      show_running();
+      exit 0;
+    }
+  }
+}
+elsif ($cgiparams{'AUTOACTION'} eq "$Lang::tr{'save'}")
+{
+  $updating = 1 if ($settings{$autoblacklist} eq 'on' and not exists $cgiparams{$autoblacklist});
+  $updating = 1 if ($settings{$autoblacklist} eq 'off' and exists $cgiparams{$autoblacklist});
+
+  $settings{$autoblacklist}    = (exists $cgiparams{$autoblacklist}) ? 'on' : 'off';
+  $settings{'BLOCK_THRESHOLD'} = $cgiparams{'BLOCK_THRESHOLD'};
+  $settings{'BLOCK_PERIOD'}    = $cgiparams{'BLOCK_PERIOD'} ;
+
+  if (($cgiparams{'BLOCK_THRESHOLD'} !~ m/^\d+$/) or
+      ($cgiparams{'BLOCK_THRESHOLD'} < 1) or
+      ($cgiparams{'BLOCK_THRESHOLD'} > 1000000))
+  {
+    $errormessage .= "$Lang::tr{'ipblacklist invalid threshold'}: $cgiparams{'BLOCK_THRESHOLD'}<br>";
+  }
+
+  if (($cgiparams{'BLOCK_PERIOD'} !~ m/^\d+$/) or
+      ($cgiparams{'BLOCK_PERIOD'} < 1) or
+      ($cgiparams{'BLOCK_PERIOD'} > 86400))
+  {
+    $errormessage .= "$Lang::tr{'ipblacklist invalid block time'}: $cgiparams{'BLOCK_PERIOD'}<br>";
+  }
+
+  if ($errormessage)
+  {
+    $updating = 0;
+  }
+  else
+  {
+    General::writehash($settings, \%settings);
+    system( "$control autoblacklist-update" ) if ($updating);
+  }
+}
+elsif ($cgiparams{'AUTOACTION'} eq "$Lang::tr{'unblock all'}")
+{
+  system( "$control autoblacklist-clear" );
+}
+
+get_ipset_stats();
+
+if (is_running())
+{
+  show_running();
+  exit 0;
+}
+
+# Get blacklist statistics
+
+get_iptables_stats();
+
+# Show site
+
+Header::openpage($Lang::tr{'ipblacklist'}, 1, '');
+Header::openbigbox('100%', 'left');
+error();
+
+showstatus() if ($settings{ENABLE} eq 'on');
+
+configsite();
+
+# End of page
+
+Header::closebigbox();
+Header::closepage();
+
+exit 0;
+
+
+#------------------------------------------------------------------------------
+# sub configsite()
+#
+# Displays configuration
+#------------------------------------------------------------------------------
+
+sub configsite
+{
+  # Find preselections
+
+  my $enable = 'checked';
+  Header::openbox('100%', 'left', $Lang::tr{'settings'});
+
+  #### JAVA SCRIPT ####
+
+  print<<END;
+<script>
+  \$(document).ready(function()
+  {
+    // Show/Hide elements when ENABLE checkbox is checked.
+    if (\$("#ENABLE").attr("checked"))
+    {
+      \$(".sources").show();
+    }
+    else
+    {
+      \$(".sources").hide();
+    }
+
+    // Toggle Source list elements when "ENABLE" checkbox is clicked
+    \$("#ENABLE").change(function()
+    {
+      \$(".sources").toggle();
+    });
+  });
+</script>
+END
+
+  ##### JAVA SCRIPT END ####
+
+  # Enable checkbox
+
+  $enable = ($settings{'ENABLE'} eq 'on') ? ' checked' : '';
+
+  print<<END;
+  <form method='post' action='$ENV{'SCRIPT_NAME'}'>
+  <table style='width:100%' border='0'>
+  <tr>
+    <td style='width:24em'>$Lang::tr{'ipblacklist use ipblacklists'}</td>
+    <td><input type='checkbox' name='ENABLE' id='ENABLE'$enable></td>
+  </tr>
+  </table><br>
+
+END
+
+  # The following are only displayed if the blacklists are enabled
+
+  $enable = ($settings{'LOGGING'} eq 'on') ? ' checked' : '';
+
+  print <<END;
+<div class='sources'>
+  <table style='width:100%' border='0'>
+  <tr>
+    <td style='width:24em'>$Lang::tr{'ipblacklist log'}</td>
+    <td><input type='checkbox' name="LOGGING" id="LOGGING"$enable></td>
+    <td style='width:24em'>$Lang::tr{'ipblacklist check rate'}</td>
+    <td>
+      <input type='number' name='RATE' min='1' max='1000' maxlength='7' pattern='\\d+' value='$settings{'RATE'}'>
+    </td>
+  </tr>
+  </table>
+  <br><br>
+  <h2>$Lang::tr{'ipblacklist blacklist settings'}</h2>
+  <table width='100%' cellspacing='1' class='tbl'>
+  <tr>
+    <th align='left'>$Lang::tr{'ipblacklist id'}</th>
+    <th align='left'>$Lang::tr{'ipblacklist name'}</th>
+    <th align='center'>$Lang::tr{'ipblacklist safe'}</th>
+    <th align='center'>$Lang::tr{'ipblacklist enable'}</th>
+  </tr>
+END
+
+  # Iterate through the list of sources
+
+  my $lines = 0;
+
+  foreach my $list (sort keys %sources)
+  {
+    my $name    = escapeHTML( $sources{$list}{'name'} );
+    my $safe    = $Lang::tr{$sources{$list}{safe}};
+    $enable     = '';
+    my $col     = ($lines++ % 2) ? "bgcolor='$color{'color20'}'" : "bgcolor='$color{'color22'}'";
+
+    $enable = ' checked' if (exists $settings{$list} and $settings{$list} eq 'on');
+
+    print <<END;
+    <tr $col>
+    <td>
+END
+
+    if ($sources{$list}{info})
+    {
+      print "<a href='$sources{$list}{info}' target='_blank'>$list</a>\n";
+    }
+    else
+    {
+      print "$list\n";
+    }
+
+    print <<END;
+    </td>
+    <td>$name</td>
+    <td align='center'>$safe</td>
+    <td align='center'><input type='checkbox' name="$list" id="$list"$enable></td>
+    </tr>\n
+END
+  }
+
+    # The save button at the bottom of the table
+
+    print <<END;
+    </table>
+    <p>$Lang::tr{'ipblacklist safe note'}</p>
+  </div>
+    <table style='width:100%;'>
+    <tr>
+        <td colspan='3' display:inline align='right'><input type='submit' name='ACTION' value='$Lang::tr{'save'}'></td>
+    </tr>
+    </table>
+  <div class='sources'>
+    <br>
+END
+
+    $enable = $settings{$autoblacklist} eq 'on' ? ' checked' : '';
+
+    print <<END;
+    <br><br>
+    <h2>$Lang::tr{'ipblacklist auto list'}</h2>
+      <table style='width:100%' border='0'>
+      <tr>
+        <td>
+          $Lang::tr{'ipblacklist autoblacklist enable'}
+        </td>
+        <td>
+          <input type='checkbox' name="$autoblacklist" id="$autoblacklist"$enable>
+        </td>
+        <td>
+          &nbsp;
+        </td>
+      </tr>
+      <tr>
+        <td>
+          $Lang::tr{'ipblacklist autoblacklist threshold'}
+        </td>
+        <td>
+          <input type='number' name='BLOCK_THRESHOLD' min='1' max='1000000' maxlength='7' pattern='\\d+' value='$settings{BLOCK_THRESHOLD}'>
+          </input>
+        </td>
+      </tr>
+      <tr>
+        <td>
+          $Lang::tr{'ipblacklist autoblacklist block time'}
+        </td>
+        <td>
+          <input type='number' name='BLOCK_PERIOD' min='1' max='86400' maxlength='7' pattern='\\d+' value='$settings{BLOCK_PERIOD}'>
+          </input>
+        </td>
+      </tr>
+      </table>
+      <table style='width:100%;'>
+      <tr>
+          <td colspan='3' display:inline align='right'><input type='submit' name='AUTOACTION' value='$Lang::tr{'save'}'></td>
+      </tr>
+      </table>
+    </form>
+  </div>
+END
+
+  Header::closebox();
+}
+
+
+#------------------------------------------------------------------------------
+# sub showstatus()
+#
+# Displays current blacklist status
+#------------------------------------------------------------------------------
+
+sub showstatus
+{
+  Header::openbox('100%', 'center', $Lang::tr{'status'});
+
+  print <<END;
+  <table width='100%' cellspacing='1'>
+  <tr>
+    <th align='left'>$Lang::tr{'ipblacklist id'}</th>
+    <th align='right'>$Lang::tr{'ipblacklist entries'}</th>
+    <th align='right'>$Lang::tr{'ipblacklist pkts in'}</th>
+    <th align='right'>$Lang::tr{'ipblacklist bytes in'}</th>
+    <th align='right'>$Lang::tr{'ipblacklist pkts out'}</th>
+    <th align='right'>$Lang::tr{'ipblacklist bytes out'}</th>
+    <th align='center'>$Lang::tr{'ipblacklist updated'}</th>
+  </tr>
+END
+
+  # Iterate through the list of sources
+
+  foreach my $list ($autoblacklist, sort keys %sources)
+  {
+    next unless ($settings{$list} eq 'on');
+
+    my $size      = '&nbsp;';
+    my $pkts_in   = '&nbsp;';
+    my $bytes_in  = '&nbsp;';
+    my $pkts_out  = '&nbsp;';
+    my $bytes_out = '&nbsp;';
+    my $updated   = '&nbsp;';
+
+    if (exists $stats{$list})
+    {
+      ($pkts_in,  $bytes_in)  = @{ $stats{$list}{IPBLACKLISTREDIN} }  if (exists $stats{$list}{IPBLACKLISTREDIN});
+      ($pkts_out, $bytes_out) = @{ $stats{$list}{IPBLACKLISTREDOUT} } if (exists $stats{$list}{IPBLACKLISTREDOUT});
+      $size                   = $stats{$list}{size}                   if (exists $stats{$list}{size});
+    }
+
+    if (exists $modified{$list} and $modified{$list} > 0)
+    {
+      $updated = localtime( $modified{$list} );
+    }
+
+    print <<END;
+    <tr>
+    <td>$list</td>
+    <td align='right'>$size</td>
+    <td align='right'>$pkts_in</td>
+    <td align='right'>$bytes_in</td>
+    <td align='right'>$pkts_out</td>
+    <td align='right'>$bytes_out</td>
+    <td align='center'>$updated</td>
+    </tr>\n
+END
+
+  }
+
+  print <<END;
+  </table>
+END
+
+  if ($settings{$autoblacklist} eq 'on')
+  {
+    print <<END;
+    <br><br>
+    <h2>$Lang::tr{'ipblacklist auto list'}</h2>
+    <table width='60%' cellspacing='1'>
+    <tr>
+      <th align='left'>$Lang::tr{'ip address'}</th>
+      <th align='center'>$Lang::tr{'ipblacklist block time remaining'}</th>
+    </tr>
+END
+
+    foreach my $address (nsort keys %autoblock_addresses )
+    {
+      print "<tr><td>$address</td><td align='center'>$autoblock_addresses{$address}</td></tr>\n";
+    }
+
+    print <<END;
+    </table>
+    <table width='60%' border='0'>
+      <form method='post' action='$ENV{'SCRIPT_NAME'}'>
+        <tr>
+            <td align='right' width='15%'><input type='submit' name='ACTION' value='$Lang::tr{'unblock all'}'></td>
+        </tr>
+      </form>
+    </table>
+END
+  }
+
+  Header::closebox();
+}
+
+
+#------------------------------------------------------------------------------
+# sub get_ipset_stats()
+#
+# Gets the number of entries in each IPSet.
+#------------------------------------------------------------------------------
+
+sub get_ipset_stats
+{
+  my $name;
+
+  system( $getipsetstat );
+
+  # Get the number of entries in each IP set
+
+  if (-r '/var/tmp/ipsets.txt')
+  {
+    open STATS, '<', '/var/tmp/ipsets.txt' or die "Can't open IP Sets stats file: $!";
+
+    foreach my $line (<STATS>)
+    {
+      if ($line =~ m/Name: (\w+)/)
+      {
+        $name = $1;
+        next;
+      }
+
+      if ($line =~ m/Number of entries: (\d+)/)
+      {
+        $stats{$name}{'size'} = $1;
+      }
+    }
+
+    close STATS;
+
+    unlink( '/var/tmp/ipsets.txt' );
+  }
+
+  # Get the IP addresses in the autoblacklist
+
+  if (-r '/var/tmp/autoblacklist.txt')
+  {
+    open HASHTABLE, '<', '/var/tmp/autoblacklist.txt' or die "Can't open autoblacklist address file: $!";
+
+    # Iterate through the blocked addresses
+
+    foreach my $line (<HASHTABLE>)
+    {
+      next unless ($line =~ m/(\d+\.\d+\.\d+\.\d+) timeout (\d+)/);
+
+      $autoblock_addresses{$1} = format_time( $2 );
+    }
+
+    close HASHTABLE;
+
+    unlink( '/var/tmp/autoblacklist.txt' );
+  }
+
+}
+
+
+#------------------------------------------------------------------------------
+# sub get_iptables_stats()
+#
+# Gets information on the number of packets and bytes rejected by each
+# blacklist
+#------------------------------------------------------------------------------
+
+sub get_iptables_stats
+{
+  system( $getipstat );
+  unlink( '/var/tmp/iptablesmangle.txt' );
+  unlink( '/var/tmp/iptablesnat.txt' );
+
+  return unless (-r '/var/tmp/iptables.txt');
+
+  open STATS, '<', '/var/tmp/iptables.txt' or die "Can't open IP Tables stats file: $!";
+
+  my $table = 'Unknown';
+
+  foreach my $line (<STATS>)
+  {
+    if ($line =~ m/^Chain (\w+)/)
+    {
+      $table = $1;
+      next;
+    }
+
+    next unless ($line =~ m/_BLOCK/);
+
+    my ($pkts, $bytes, $chain) = $line =~ m/^\s*(\d+\w?)\s+(\d+\w?)\s+(\w+)_BLOCK/;
+    $stats{$chain}{$table} = [ $pkts, $bytes ];
+  }
+
+  close STATS;
+
+  unlink( '/var/tmp/iptables.txt' );
+}
+
+
+#------------------------------------------------------------------------------
+# sub is_running()
+#
+# Checks to see if the main script is running
+#------------------------------------------------------------------------------
+
+sub is_running
+{
+  return 0 unless (-r $lockfile);
+
+  open LOCKFILE, '<', $lockfile or die "Can't open lockfile";
+  my $pid = <LOCKFILE>;
+  close LOCKFILE;
+
+  chomp $pid;
+
+  return (-e "/proc/$pid");
+}
+
+
+#------------------------------------------------------------------------------
+# sub show_running
+#
+# Displayed when update is running
+#------------------------------------------------------------------------------
+
+sub show_running
+{
+  # Open site
+
+  Header::openpage( $Lang::tr{'ipblacklist'}, 1, '<meta http-equiv="refresh" content="1;url=/cgi-bin/ipblacklist.cgi">' );
+  Header::openbigbox( '100%', 'center' );
+  error();
+  Header::openbox( 'Working', 'center', "$Lang::tr{'ipblacklist working'}" );
+
+  print <<END;
+  <table width='100%'>
+    <tr>
+      <td align='center'>
+        <img src='/images/indicator.gif' alt='$Lang::tr{'aktiv'}'>&nbsp;
+      <td>
+    </tr>
+    </table>
+    <br>
+    <table cellspacing='1' align='center'>
+    <tr><th>$Lang::tr{'ipblacklist id'}</th><th>$Lang::tr{'ipblacklist entries'}</th></tr>
+END
+
+  foreach my $name (keys %sources)
+  {
+    $stats{$name}{'size'} = '&nbsp;' if (not exists ($stats{$name})  and
+                                             exists $settings{$name} and
+                                             $settings{$name} eq 'on');
+  }
+
+  foreach my $name (sort keys %stats)
+  {
+    print "<tr><td>$name</td><td align='right'>$stats{$name}{'size'}</td></tr>\n" if (exists $stats{$name}{'size'});
+  }
+
+  print <<END;
+    </table>
+END
+
+  Header::closebox();
+
+  Header::closebigbox();
+  Header::closepage();
+}
+
+
+#------------------------------------------------------------------------------
+# sub error()
+#
+# Shows error messages
+#------------------------------------------------------------------------------
+
+sub error
+{
+  if ($errormessage)
+  {
+    Header::openbox('100%', 'left', $Lang::tr{'error messages'});
+    print "<class name='base'>$errormessage\n";
+    print "&nbsp;</class>\n";
+    Header::closebox();
+  }
+}
+
+
+#------------------------------------------------------------------------------
+# sub format_time( seconds )
+#
+# Converts time in seconds to HH:MM:SS
+#------------------------------------------------------------------------------
+
+sub format_time($) {
+	my $time = shift;
+
+	my $seconds = $time % 60;
+	my $minutes = $time / 60;
+
+	my $hours = 0;
+	if ($minutes >= 60) {
+		$hours = $minutes / 60;
+		$minutes %= 60;
+	}
+
+	return sprintf("%3d:%02d:%02d", $hours, $minutes, $seconds);
+}
diff --git a/html/cgi-bin/logs.cgi/log.dat b/html/cgi-bin/logs.cgi/log.dat
index 8ca32d675..ba1a482c8 100644
--- a/html/cgi-bin/logs.cgi/log.dat
+++ b/html/cgi-bin/logs.cgi/log.dat
@@ -59,6 +59,7 @@  my %sections = (
         'dhcp' => '(dhcpd: )',
         'dma' => '(dma: |dma\[.*\]: |postfix/\w*\[\d*\]: )',
         'guardian' => '(guardian\[.*\]: )',
+        'ipblacklist' => '(ipblacklist: )',
         'ipfire' => '(ipfire: )',
         'ipsec' => '(ipsec_[\w_]+: |pluto\[.*\]: |charon: |vpnwatch: )',
         'kernel' => '(kernel: (?!DROP_))',
@@ -87,6 +88,7 @@  my %trsections = (
         'dhcp' => "$Lang::tr{'dhcp server'}",
         'dma' => 'Mail',
         'guardian' => "$Lang::tr{'guardian'}",
+        'ipblacklist' => "$Lang::tr{'ipblacklist'}",
         'ipfire' => 'IPFire',
         'ipsec' => 'IPSec',
         'kernel' => "$Lang::tr{'kernel'}",
diff --git a/langs/en/cgi-bin/en.pl b/langs/en/cgi-bin/en.pl
index b40ef9390..5acf3678c 100644
--- a/langs/en/cgi-bin/en.pl
+++ b/langs/en/cgi-bin/en.pl
@@ -1518,6 +1518,37 @@ 
 'ip alias changed' => 'External IP alias changed',
 'ip alias removed' => 'External IP alias removed',
 'ip info' => 'IP Information',
+'ipblacklist auto list' => 'Automatic blacklist',
+'ipblacklist autoblacklist enable' => 'Enable automatically updating local blacklist',
+'ipblacklist autoblacklist threshold' => 'Threshold (packets/hour)',
+'ipblacklist autoblacklist block time' => 'Block time (seconds)',
+'ipblacklist blacklist settings' => 'Blacklist settings',
+'ipblacklist bytes in' => 'bytes in',
+'ipblacklist bytes out' => 'bytes out',
+'ipblacklist check rate' => 'Update check rate (hours)',
+'ipblacklist day' => 'day',
+'ipblacklist disable pre' => 'Disabling',
+'ipblacklist disable mid' => 'because it is included in',
+'ipblacklist disable port' => '',
+'ipblacklist enable' => 'Enable',
+'ipblacklist entries' => 'Entries',
+'ipblacklist hour' => 'hour',
+'ipblacklist id' => 'Blacklist',
+'ipblacklist invalid block time' => 'Invalid automatic blacklist block time',
+'ipblacklist invalid check rate' => 'Invalid update check rate',
+'ipblacklist invalid threshold' => 'Invalid automatic blacklist threshold',
+'ipblacklist log' => 'Log dropped packets',
+'ipblacklist name' => 'Name',
+'ipblacklist pkts in' => 'pkts in',
+'ipblacklist pkts out' => 'pkts out',
+'ipblacklist safe note' => 'Note: safe blacklists block addresses that only generate malicious traffic and therefore will not block any wanted sites.',
+'ipblacklist safe' => 'Safe',
+'ipblacklist sixhour' => 'six hours',
+'ipblacklist updated' => 'Last updated',
+'ipblacklist use ipblacklists' => 'Enable IP Blacklists',
+'ipblacklist week' => 'week',
+'ipblacklist working' => 'Updating IP address blacklists...',
+'ipblacklist' => 'IP Address Blacklists',
 'ipfire has now rebooted' => 'IPFire is rebooting now.',
 'ipfire has now shutdown' => 'IPFire is shutting down now.',
 'ipfire side' => 'IPFire side:',