summaryrefslogtreecommitdiff
path: root/network/sshblock
diff options
context:
space:
mode:
Diffstat (limited to 'network/sshblock')
-rw-r--r--network/sshblock/README7
-rw-r--r--network/sshblock/doinst.sh14
-rw-r--r--network/sshblock/rc.sshblock56
-rw-r--r--network/sshblock/slack-desc19
-rw-r--r--network/sshblock/sshblock.SlackBuild95
-rw-r--r--network/sshblock/sshblock.info10
-rw-r--r--network/sshblock/sshblock.pl.tpl230
-rw-r--r--network/sshblock/sshblock.tpl8
-rw-r--r--network/sshblock/sshunblock.pl.tpl179
9 files changed, 618 insertions, 0 deletions
diff --git a/network/sshblock/README b/network/sshblock/README
new file mode 100644
index 0000000000..c601150cc6
--- /dev/null
+++ b/network/sshblock/README
@@ -0,0 +1,7 @@
+SSHblock is intended to dynamically and automatically stop SSH-based
+dictionary attacks by blocking any IP address that fails an SSH
+login too many times too quickly, and automatically unblocks it
+after a while.
+
+You may change the pre-defined configuration of SSHblock in
+sshblock.pl.tpl (whitelist IP, email, and hostname).
diff --git a/network/sshblock/doinst.sh b/network/sshblock/doinst.sh
new file mode 100644
index 0000000000..74db18c0e3
--- /dev/null
+++ b/network/sshblock/doinst.sh
@@ -0,0 +1,14 @@
+config() {
+ NEW="$1"
+ OLD="$(dirname $NEW)/$(basename $NEW .new)"
+ # If there's no config file by that name, mv it over:
+ if [ ! -r $OLD ]; then
+ mv $NEW $OLD
+ elif [ "$(cat $OLD | md5sum)" = "$(cat $NEW | md5sum)" ]; then
+ # toss the redundant copy
+ rm $NEW
+ fi
+ # Otherwise, we leave the .new copy for the admin to consider...
+}
+
+config etc/rc.d/rc.sshblock.new
diff --git a/network/sshblock/rc.sshblock b/network/sshblock/rc.sshblock
new file mode 100644
index 0000000000..68221bef38
--- /dev/null
+++ b/network/sshblock/rc.sshblock
@@ -0,0 +1,56 @@
+#!/bin/bash
+
+if [ ! $UID ]; then
+ echo "You must be root to use SSHblock."
+ exit 1;
+fi
+
+case "$1" in
+ 'start')
+ swatch -c /etc/swatch/sshblock -t /var/log/messages &> /dev/null &
+ if [ ! `ls /etc/cron.hourly | grep sshunblock` ]; then
+ ln -s /usr/sbin/sshunblock.pl /etc/cron.hourly
+ fi
+ ;;
+ 'stop')
+ pid=`ps auxwww | grep swatch | grep -v grep | grep sshblock | awk '{print $2}'`
+ kill $pid
+ ;;
+ 'clear')
+ for ip in `iptables -nL INPUT | tail +3 | grep DROP | grep dpt:22 | awk '{print $4}'`; do
+ iptables -D INPUT -p tcp -s $ip --dport 22 --syn -j DROP
+ done
+ ;;
+ 'list')
+ echo "Blocked IP addresses:"
+ iptables -nL INPUT | tail +3 | grep DROP | grep dpt:22 | awk '{print $4}'
+ ;;
+ 'status')
+ blocking=`ps auxwww | grep swatch | grep -v grep | grep sshblock | wc -l`
+ blocked=`iptables -nL INPUT | tail +3 | grep DROP | grep dpt:22 | wc -l`
+ unblocking=`ls -l /etc/cron.hourly | grep sshunblock | wc -l`
+ if [ $blocked -eq 1 ]; then
+ pl=''
+ verb='is'
+ else
+ pl='es'
+ verb='are'
+ fi
+ if [ $blocking -gt 0 ]; then
+ echo "SSHblock is active"
+ else
+ echo "SSHblock is not running"
+ fi
+ echo "There $verb currently $blocked address$pl blocked."
+ ;;
+ *)
+ echo "Usage: $0 [start|stop|clear|status|list]"
+ echo " "
+ echo "start: Start SSHblock system"
+ echo "stop: Stop blocking new IPs; old ones will still expire at the usual rate"
+ echo "clear: Clear all blocked addresses"
+ echo "status: Report whether SSHblock is running, how many IPs are blocked"
+ echo "list: List all blocked IP addresses"
+ exit
+ ;;
+esac
diff --git a/network/sshblock/slack-desc b/network/sshblock/slack-desc
new file mode 100644
index 0000000000..15216f8a35
--- /dev/null
+++ b/network/sshblock/slack-desc
@@ -0,0 +1,19 @@
+# HOW TO EDIT THIS FILE:
+# The "handy ruler" below makes it easier to edit a package description.
+# Line up the first '|' above the ':' following the base package name, and
+# the '|' on the right side marks the last column you can put a character in.
+# You must make exactly 11 lines for the formatting to be correct. It's also
+# customary to leave one space after the ':' except on otherwise blank lines.
+
+ |-----handy-ruler------------------------------------------------------|
+sshblock: sshblock (an SSH Dictionary-Attack Blocker)
+sshblock:
+sshblock: SSHblock is intended to dynamically and automatically stop SSH-based
+sshblock: dictionary attacks by blocking any IP address that fails an SSH
+sshblock: login too many times too quickly, and automatically unblocks it
+sshblock: after a while.
+sshblock:
+sshblock: Project Website: http://kagan.mactane.org/software/sshblock/
+sshblock:
+sshblock:
+sshblock:
diff --git a/network/sshblock/sshblock.SlackBuild b/network/sshblock/sshblock.SlackBuild
new file mode 100644
index 0000000000..0b9ef3d66f
--- /dev/null
+++ b/network/sshblock/sshblock.SlackBuild
@@ -0,0 +1,95 @@
+#!/bin/sh
+
+# Slackware build script for sshblock
+
+# Copyright 2012 Willy Sudiarto Raharjo <willysr@slackware-id.org>
+# All rights reserved.
+#
+# Redistribution and use of this script, with or without modification, is
+# permitted provided that the following conditions are met:
+#
+# 1. Redistributions of this script must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+PRGNAM=sshblock
+VERSION=${VERSION:-0.5}
+BUILD=${BUILD:-1}
+TAG=${TAG:-_SBo}
+
+if [ -z "$ARCH" ]; then
+ case "$( uname -m )" in
+ i?86) ARCH=i486 ;;
+ arm*) ARCH=arm ;;
+ *) ARCH=$( uname -m ) ;;
+ esac
+fi
+
+CWD=$(pwd)
+TMP=${TMP:-/tmp/SBo}
+PKG=$TMP/package-$PRGNAM
+OUTPUT=${OUTPUT:-/tmp}
+
+if [ "$ARCH" = "i486" ]; then
+ SLKCFLAGS="-O2 -march=i486 -mtune=i686"
+ LIBDIRSUFFIX=""
+elif [ "$ARCH" = "i686" ]; then
+ SLKCFLAGS="-O2 -march=i686 -mtune=i686"
+ LIBDIRSUFFIX=""
+elif [ "$ARCH" = "x86_64" ]; then
+ SLKCFLAGS="-O2 -fPIC"
+ LIBDIRSUFFIX="64"
+else
+ SLKCFLAGS="-O2"
+ LIBDIRSUFFIX=""
+fi
+
+set -e
+
+rm -rf $PKG
+mkdir -p $TMP $PKG $OUTPUT
+cd $TMP
+rm -rf $PRGNAM-$VERSION
+tar xvf $CWD/$PRGNAM-$VERSION.tar.bz2
+cd $PRGNAM-$VERSION
+chown -R root:root .
+find . \
+ \( -perm 777 -o -perm 775 -o -perm 711 -o -perm 555 -o -perm 511 \) \
+ -exec chmod 755 {} \; -o \
+ \( -perm 666 -o -perm 664 -o -perm 600 -o -perm 444 -o -perm 440 -o -perm 400 \) \
+ -exec chmod 644 {} \;
+
+mkdir -p $PKG/usr/sbin $PKG/etc/ssh $PKG/etc/swatch $PKG/etc/rc.d $PKG/etc/cron.hourly
+touch $PKG/etc/ssh/block-history
+
+install -m 0644 $CWD/rc.sshblock $PKG/etc/rc.d/rc.sshblock.new
+install -m 0755 $CWD/sshblock.pl.tpl $PKG/usr/sbin/sshblock.pl
+install -m 0755 $CWD/sshunblock.pl.tpl $PKG/usr/sbin/sshunblock.pl
+install -m 0755 $CWD/sshblock.tpl $PKG/etc/swatch/sshblock
+install -m 0755 $CWD/sshunblock.pl.tpl $PKG/etc/cron.hourly/sshunblock
+
+find $PKG -print0 | xargs -0 file | grep -e "executable" -e "shared object" | grep ELF \
+ | cut -f 1 -d : | xargs strip --strip-unneeded 2> /dev/null || true
+
+mkdir -p $PKG/usr/doc/$PRGNAM-$VERSION
+cp -a \
+ README \
+ $PKG/usr/doc/$PRGNAM-$VERSION
+cat $CWD/$PRGNAM.SlackBuild > $PKG/usr/doc/$PRGNAM-$VERSION/$PRGNAM.SlackBuild
+
+mkdir -p $PKG/install
+cat $CWD/slack-desc > $PKG/install/slack-desc
+cat $CWD/doinst.sh > $PKG/install/doinst.sh
+
+cd $PKG
+/sbin/makepkg -l y -c n $OUTPUT/$PRGNAM-$VERSION-$ARCH-$BUILD$TAG.${PKGTYPE:-tgz}
diff --git a/network/sshblock/sshblock.info b/network/sshblock/sshblock.info
new file mode 100644
index 0000000000..100a837654
--- /dev/null
+++ b/network/sshblock/sshblock.info
@@ -0,0 +1,10 @@
+PRGNAM="sshblock"
+VERSION="0.5"
+HOMEPAGE="http://kagan.mactane.org/software/sshblock/"
+DOWNLOAD="http://kagan.mactane.org/software/libraries/download/sshblock-0.5.tar.bz2"
+MD5SUM="dbfaee5f45296de2f9a22d5fe79e7332"
+DOWNLOAD_x86_64=""
+MD5SUM_x86_64=""
+REQUIRES="swatch"
+MAINTAINER="Willy Sudiarto Raharjo"
+EMAIL="willysr@slackware-id.org"
diff --git a/network/sshblock/sshblock.pl.tpl b/network/sshblock/sshblock.pl.tpl
new file mode 100644
index 0000000000..bc2f166d8e
--- /dev/null
+++ b/network/sshblock/sshblock.pl.tpl
@@ -0,0 +1,230 @@
+#!/usr/bin/perl -wT
+
+# Copyright 2009 Kagan D. MacTane
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 5.004;
+use strict;
+use Sys::Syslog;
+
+# -------------------------------------------------------------------
+
+# This is the whitelist. Any IP address that's listed in this array will
+# never be blocked.
+my @never_block_these = qw(127.0.0.1 192.168.1.1);
+
+# Where should SSHblock track which addresses it's blocked? This will be
+# a text file with tab-delimited fields:
+# IP address - # of times blocked # timestamp of last block
+my $history_file = '/etc/ssh/block-history';
+
+# If email notifications
+# Set to an empty string to suppress email notifications.
+my $send_email = '';
+
+# Only send email if IP has been blocked at least this many times.
+# E.g., at $email_level = 3, email will only be sent if an IP is
+# blocked for the 3rd (or greater) time.
+my $email_level = 2;
+
+# This is just to keep SSHblock from having to run `hostname` every time
+# it wants to notify you that it's blocked something. This text is only
+# used in the notification email, and can be safely altered.
+my $myhostname = 'localhost';
+
+my $Syslog_Level = 'info';
+my $Syslog_Facility = 'user';
+my $Syslog_Options = '';
+
+my $VERSION = 0.5;
+
+# -------------------------------------------------------------------
+
+$ENV{PATH} = '/sbin:/usr/sbin:/usr/bin:/bin';
+
+unless (scalar @ARGV) {
+ LogMessage("Called with no arguments; aborting.");
+ print "Usage: $0 ip_addr\n\"perldoc $0\" for full man page\n";
+ exit 1;
+}
+my $ip_addr = shift;
+
+# Ensure we were passed a valid IP address as first argument
+if ($ip_addr =~ /^((\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3}))$/) {
+ $ip_addr = $1;
+} else {
+ LogMessage("first arg not IP address: $ip_addr");
+ exit 1;
+}
+if ($2 > 255 || $3 > 255 || $4 > 255 || $5 > 255) {
+ exit 1;
+}
+
+# Abort if you're not running as root
+if ($>) {
+ print "Only root can run this program.\n";
+ exit 1;
+}
+
+# And don't waste processor cycles if the IP's already blocked
+exit 0 if (is_blocked($ip_addr));
+
+if (grep { $_ eq $ip_addr } @never_block_these) {
+ LogMessage("Not bothering to block whitelisted IP $ip_addr");
+ exit 3;
+}
+
+# block command:
+`iptables -A INPUT -p tcp -s $ip_addr --dport 22 --syn -j DROP`;
+
+if ($?) {
+ LogMessage("Failed to block IP $ip_addr; error $?: $!");
+ exit $?;
+}
+
+# Assuming that succeeded, add a record to the history file.
+
+my @history;
+open FH, "$history_file" || exit 7;
+@history = <FH>;
+close FH;
+
+my ($prev) = grep /^$ip_addr\s/, @history;
+my $how_many;
+
+if ($prev) {
+ my @prev = split /\s+/, $prev;
+ $prev[1]++;
+ splice(@prev, 2, 1, time());
+ map { if ($_ =~ /^$ip_addr\s/) { $_ = join("\t", @prev)."\n"; } } @history;
+ $how_many = ordinal($prev[1]);
+} else {
+ push(@history, join("\t", $ip_addr, 1, time()), "\n");
+ $how_many = ordinal(1);
+}
+
+open FH, ">$history_file" || exit 8;
+print FH @history;
+close FH;
+
+LogMessage("Blocked IP $ip_addr for $how_many time");
+
+my $num = $how_many;
+$num =~ s/\D//g;
+
+
+if ($send_email && $num >= $email_level) {
+ open MAIL, "|[[SENDMAIL_PATH]] -t";
+ print MAIL "From: sshblock <noreply\@$myhostname>\nTo: $send_email\nSubject: SSH Block: $ip_addr\n\nBlocked IP address $ip_addr for $how_many time.\n\n";
+ close MAIL;
+}
+
+exit 0;
+
+sub is_blocked {
+ my $ip_addr = shift;
+ return grep /^$ip_addr/, split /\n/, `iptables -nL | grep DROP | grep 'dpt:22' | grep '0x17/0x02' | awk '{print \$4}'`;
+}
+
+sub ordinal {
+ my $num = shift;
+ if (length($num) > 1 && substr($num, -2, 1) == 1) {
+ return $num . 'th';
+ }
+ if (substr($num, -1) == 1) {
+ return $num . 'st';
+ } elsif (substr($num, -1) == 2) {
+ return $num . 'nd';
+ } elsif (substr($num, -1) == 3) {
+ return $num . 'rd';
+ } else {
+ return $num . 'th';
+ }
+}
+
+
+
+sub LogMessage {
+ my $format = shift;
+ openlog('sshblock', $Syslog_Options, $Syslog_Facility);
+ syslog($Syslog_Level, $format, @_);
+ closelog();
+}
+
+
+__END__
+
+=head1 NAME
+
+sshblock.pl - SSH dictionary attack blocker
+
+=head1 SYNOPSIS
+
+B<sshblock.pl> I<ip_address>
+
+=head1 DESCRIPTION
+
+This is part of the SSHblock system; the B<sshblock.pl> executable is responsible for blocking IP addresses from access to port 22. B<sshblock.pl> does this by adding a firewall rule to B<iptables>, which must be present on the system. Because of this, SSHblock must be run as root.
+
+B<sshblock.pl> only blocks addresses; unblocking them is the responsibility of B<sshunblock.pl>, which should be run as an hourly cron(8) job.
+
+=head1 INVOCATION
+
+B<sshblock.pl> is intended to be called by swatch(1) or a similar automated process. You I<can> call it from the command line, passing it a single IP address to block, and this won't actualyl cause any problems, but it will only take one argument per invocation.
+
+By default, SSHblock logs its activity to syslog(8), using the "user" facility at level "info".
+
+=head1 CONFIGURATION
+
+SSHblock can be configured by changing the following options in the program's source code:
+
+=over
+
+=item B<@never_block_these>
+
+This array holds SSHblock's whitelist. Any IP address found in this array will never be blocked.
+
+=item B<$history_file>
+
+Where SSHblock should store its history file. This file keeps a record of all IP addresses SSHblock has ever blocked, one per line. Each line consists of three tab-delimited fields: the IP address; the total number of times it's been blocked; and the timestamp it was last blocked at.
+
+=item B<$email_level>
+
+If this number is nonzero, then SSHblock will send an email to the address specified in B<$send_email> whenever an address is blocked for the Nth or greater time. For example, if $email_level is 3, SSHblock will remain silent when it blocks an address for the 1st or 2nd time, but send email on the 3rd time.
+
+=back
+
+=head1 FILES
+
+=over
+
+=item F</etc/ssh/block-history>
+
+=back
+
+
+=head1 BUGS
+
+Please let me know if you find any.
+
+=head1 AUTHOR
+
+Kagan D. MacTane (kai@mactane.org)
+
+=head1 SEE ALSO
+
+sshunblock(8), iptables(8), L<http://sourceforge.net/projects/swatch/>
+
+=cut
+
diff --git a/network/sshblock/sshblock.tpl b/network/sshblock/sshblock.tpl
new file mode 100644
index 0000000000..d8d146836b
--- /dev/null
+++ b/network/sshblock/sshblock.tpl
@@ -0,0 +1,8 @@
+watchfor /Failed password for invalid user \w+ from ([\d\.]+) port/
+ exec "/usr/sbin/sshblock.pl $1"
+ threshold track_by=$1, type=threshold, count=3, seconds=90
+
+watchfor /Failed password for root from ([\d\.]+) port/
+ exec "/usr/sbin/sshblock.pl $1"
+ threshold track_by=$1, type=threshold, count=3, seconds=30
+
diff --git a/network/sshblock/sshunblock.pl.tpl b/network/sshblock/sshunblock.pl.tpl
new file mode 100644
index 0000000000..298e96da09
--- /dev/null
+++ b/network/sshblock/sshunblock.pl.tpl
@@ -0,0 +1,179 @@
+#!/usr/bin/perl -wT
+
+# Copyright 2009 Kagan D. MacTane
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 5.004;
+use strict;
+use Sys::Syslog;
+
+# -------------------------------------------------------------------
+
+# This option *MUST* match the corresponding value in sshblock.pl!
+my $history_file = '/etc/ssh/block-history';
+
+
+# Blocking duration formula: If blocking for the Nth time, and
+# b = $base and m = $mult, then block for:
+#
+# T = m * ( b ^ (N-1) )
+#
+# Time T is expressed in hours.
+my $base = 4;
+my $mult = 3;
+
+my $Syslog_Level = 'info';
+my $Syslog_Facility = 'user';
+my $Syslog_Options = '';
+
+my $VERSION = 0.5;
+
+$ENV{'PATH'} = '/bin:/usr/bin:/usr/sbin:/sbin';
+
+# -------------------------------------------------------------------
+
+# Abort if you're not running as root
+if ($>) {
+ print "Only root can run this program.\n";
+ exit 1;
+}
+
+my $now = time();
+
+my @history;
+open FH, "$history_file" || exit 7;
+@history = <FH>;
+close FH;
+
+my @input_chain = `iptables -nL INPUT | tail +3`;
+
+# Your iptables output needs to look like:
+# DROP tcp -- 1.2.3.4 0.0.0.0/0 tcp dpt:22 flags:0x17/0x02
+
+if (scalar @input_chain > 0) {
+ LogMessage("Checking ".scalar @input_chain." blocked IPs against ".scalar @history." block-history entries.");
+}
+
+foreach my $item (@input_chain) {
+ my @stats = split(/\s+/, $item);
+ next unless ($stats[0] eq 'DROP');
+ next unless ($stats[1] eq 'tcp');
+ next unless ($stats[6] eq 'dpt:22');
+ next unless ($stats[7] eq 'flags:0x17/0x02');
+ my $curr_ip = $stats[3];
+
+ if ($curr_ip =~ /^(\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b)$/) {
+ $curr_ip = $1;
+ } else {
+ LogMessage("Invalid IP address in output from iptables?!?");
+ next;
+ }
+ for ( my $i = 0; $i < scalar @history; $i++ ) {
+my $hist_ip = (split(/\t/, $history[$i]))[0];
+ if ((split(/\t/, $history[$i]))[0] eq $curr_ip) {
+ my ($times_blocked, $blocked_since) = (split(/\t/, $history[$i]))[1,2];
+ my $duration = $now - $blocked_since;
+ $duration /= 3600;
+ $duration = sprintf("%.2f", $duration);
+ my $block_for = $mult * ($base ** ($times_blocked - 1));
+ if ($duration > $block_for) {
+ `iptables -D INPUT -p tcp -s $curr_ip --dport 22 --syn -j DROP`;
+ if ($?) {
+ LogMessage("Couldn't unblock IP $curr_ip (now blocked for $duration hours)! Error $?: $!");
+ } else {
+ LogMessage("Unblocked IP $curr_ip after $duration hours.");
+ }
+ }
+ }
+ }
+}
+
+
+exit 0;
+
+
+
+sub is_blocked {
+ my $ip_addr = shift;
+ return grep /^$ip_addr/, split /\n/, `iptables -nL | grep DROP | grep 'dpt:22' | grep '0x17/0x02' | awk '{print \$4}'`;
+}
+
+
+sub LogMessage {
+ my $format = shift;
+ openlog('sshblock', $Syslog_Options, $Syslog_Facility);
+ syslog($Syslog_Level, $format, @_);
+ closelog();
+}
+
+
+__END__
+
+=head1 NAME
+
+sshunblock.pl - SSH dictionary attack (un)blocker
+
+=head1 SYNOPSIS
+
+B<sshblock.pl>
+
+=head1 DESCRIPTION
+
+This is part of the SSHblock system; the B<sshunblock.pl> executable is responsible for unblocking blocked IP addresses after a suitable length of time has passed. It does this by removing the B<iptables> firewall rules created by B<sshblock.pl>. In order to use iptables, sshunblock.pl must be run as root.
+
+=head1 INVOCATION
+
+B<sshunblock.pl> is intended to be called as an hourly cron(8) job. Calling it more or less frequently will not interfere with its operation.
+
+By default, SSHblock logs its activity to syslog(8), using the "user" facility at level "info".
+
+=head1 CONFIGURATION
+
+F<sshunblock.pl> can be configured by changing the following options in the program's source code.
+
+=over
+
+=item B<$history_file>
+
+Note that B<this option MUST match the value in sshblock.pl!> This is where SSHblock should store its history file. This file keeps a record of all IP addresses SSHblock has ever blocked, one per line. Each line consists of three tab-delimited fields: the IP address; the total number of times it's been blocked; and the timestamp it was last blocked at.
+
+=item B<$base, $mult>
+
+These control the behavior of SSHblock's exponential increase algorithm. By tweaking these, you can make SSHblock block attacking IP addresses for longer or shorter periods of time.
+
+=back
+
+=head1 FILES
+
+=over
+
+=item F</etc/ssh/block-history>
+
+=back
+
+
+=head1 BUGS
+
+Please let me know if you find any.
+
+=head1 AUTHOR
+
+Kagan D. MacTane (kai@mactane.org)
+
+=head1 SEE ALSO
+
+sshblock(8), iptables(8), L<http://sourceforge.net/projects/swatch/>
+
+=cut
+