#!/usr/bin/perl
# --------------------------------
# 2007-06-16  First public release
# --------------------------------
#
# To save you the trouble of writing this, here it is...
# Use whatever license you find appropriate from
# http://www.opensource.org/licenses/alphabetical
# just dont come crying to me (but feel free to report bugs)
#
require IO::Handle;
use POSIX qw(setsid);

defined(my $pid = fork) or die "Can't fork: $!";
exit if $pid;
setsid() or die "Can't start a new session: $!";

`iptables -F BAN`;

my %banned;
my $log_file = "/root/ban.log";
my $last_mtime; # when was auth.log last modified?

open(LOG,">>$log_file");
LOG->autoflush(1);

sub log_message {
  my ($s) = @_;
  my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
  $year+=1900; $mon+=1;
  printf(LOG "%04d-%02d-%02d %02d.%02d.%02d  %s\n",
         $year,$mon,$mday, $hour,$min,$sec,$s
  );
}

#
# SIGNAL HANDLERS
#

# USR1 flushes ban list
sub clear_ban_list {
  foreach my $k (keys %banned) { 
    delete($banned{$k});
  }
  `iptables -F BAN`;
  log_message("All bans cleared");
  $last_mtime = 0; # force a rescan of auth.log within the next 10s
}
$SIG{USR1} = \&clear_ban_list;

# USR2 dumps ban list
sub dump_ban_list {
  foreach my $k (sort keys %banned) {
    log_message("Currently banned: $k");
  }
}
$SIG{USR2} = \&dump_ban_list;

# HUP reopens log
sub reopen_log {
  log_message("Log closed");
  close(LOG);
  open(LOG,">>$log_file");
  LOG->autoflush(1);
  log_message("New log started");
}
$SIG{HUP} = \&reopen_log;

# TERM clears the ban list and exits
sub exit_program {
  `iptables -F BAN`;
  log_message("Exiting...");
  exit;
}
$SIG{TERM} = \&exit_program;

#
# MAIN LOOP
#
log_message("ban.pl started");
while (1) {
  # save resouces by scanning the file only when it changes
  ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat("/var/log/auth.log");
  if ($mtime != $last_mtime) {
    $last_mtime = $mtime;
    #log_message("Scanning /var/log/auth.log");

    my @authlog;
    open(IN,"</var/log/auth.log");
    @authlog=<IN>;
    close(IN);

    # find all ip trying to log on with illegal usernames
    my %newips;
    my $last_ip;
    foreach my $l (@authlog) {
      if ($l =~ m/POSSIBLE BREAKIN ATTEMPT!/) {
        $newips{$last_ip}+=10;
      }
      elsif ($l =~ m/Did not receive identification string from (\d+\.\d+\.\d+\.\d+)/) {
        $newips{$1}+=3;
        $last_ip = $1;
      }
      elsif ($l =~ m/Invalid user .+ from (\d+\.\d+\.\d+\.\d+)/) {
        $newips{$1}+=5;
        $last_ip = $1;
      }
      elsif ($l =~ m/Failed password for .+ from (\d+\.\d+\.\d+\.\d+)/) {
        $newips{$1} += 1;
        $last_ip = $1;
      }
    }
    # clean out already banned IPs to avoid double-banning
    foreach my $k (keys %banned) {
      delete($newips{$k});
    }
    # ban all new ips that has more than 20p
    foreach my $ip (keys %newips) {
      if ($newips{$ip}>10) {
        log_message("Banning $ip ($newips{$ip} p.)");
        `iptables -A BAN -s $ip -j DROP`;
        $banned{$ip}=time();
      }
    }
  }
  sleep(1);
}

