#!/usr/bin/perl

use strict;

# after $MAX failures, add to block list
my $MAX = 5;

my $conf_file = "/etc/iptables.conf";

my $ip_match = "\\d+\\.\\d+\\.\\d+\\.\\d+";
my $name_match = "\\S+";

my %blocked_ips;
my @blocked_ips;

my %process_ips;

my %new_blocked_ips;

if (! -f $conf_file) {
	die "$conf_file does not exist";
}

open(IP, $conf_file) || die "can't open $conf_file";
while (<IP>) {
	if (m,-A INPUT -s (.*)/32 -j DROP,) {
		my $ip = $1;
		$blocked_ips[$#blocked_ips+1] = $ip;
		$blocked_ips{$ip} = $#blocked_ips;
	}
}
close IP;


open (IP, "/sbin/iptables -L -n|") || die "can't execute iptables";

my %test_ips = %blocked_ips;

while (<IP>) {
	if (/^DROP\s+all\s+--\s+($ip_match)\s+0\.0\.0\.0\/0\s*$/) {
		my $ip = $1;
		if (!defined($blocked_ips{$ip})) {
			die "$conf_file does not match iptables ($ip not in $conf_file)";
		}
		delete $test_ips{$ip};
	}
}
close IP;

if (keys %test_ips) {
	print STDERR "The following ips are in iptables but not in $conf_file\n";
	foreach my $ip (keys %test_ips) {
		print STDERR "$ip\n";
	}
	exit;
}

sub process_ip {
	my ($ip) = @_;

	if (defined($blocked_ips{$ip}) || defined($new_blocked_ips{$ip})) {
		return;
	}

	if (defined($process_ips{$ip})) {
		if (++$process_ips{$ip} > $MAX) {
			$new_blocked_ips{$ip} = 1;
		}
	} else {
		$process_ips{$ip} = 1;
	}
}	

sub get_ip {
	my ($name) = @_;
	my $start = 0;
	my $ip = "0";

	open (NS, "/usr/bin/nslookup $name|") || die "nslookup";
	while (<NS>) {
		if ($start && /Address: ($ip_match)/) {
			$ip = $1;
			last;
		}
		$start = 1 if (/Name:/);
	}
	close NS;
	return $ip;
}

# add checks here to ignore the IPs
# I should make this read a file instead
sub whitelist_match {
	my ($ip) = @_;

	if ($ip =~ /^192\.168/) {
		return 1;
	}

	return 0;
}

# Reads auth, you may want to have it look elsewhere
open(AUTH, "/var/log/auth.log") || die "can't open auth.log";

while (<AUTH>) {
	if (/sshd\[\d+\]: Invalid user .* from ($ip_match|$name_match)/ ||
	    /sshd\[\d+\]: Failed password for invalid user.* from ($ip_match|$name_match)/) {
		my $ip = $1;
		$ip = get_ip($ip) if ($ip !~ /^[1-9]/);
		process_ip $ip if ($ip =~ /$ip_match/);
	}

	# I've been to hotels where this matches :-(
	if (0 && /sshd\[\d+\]: .*\[($ip_match)].*POSSIBLE BREAK/) {
		my $ip = $1;

		# ignore local network
		if (!whitelist_match($ip)) {
			if (!defined($blocked_ips{$ip}) && !defined($new_blocked_ips{$ip})) {
				$new_blocked_ips{$ip} = 1;
			}
		}
	}
	if (/sshd\[\d+\]: error: .* failure for root from ($ip_match|$name_match)/) {
		my $ip = $1;
		$ip = get_ip($ip) if ($ip !~ /^[1-9]/);
		if (!whitelist_match($ip)) {
			process_ip $ip if ($ip =~ /$ip_match/);
		}
	}
}
close AUTH;

if (! keys %new_blocked_ips) {
	exit;
}

foreach my $ip (keys %new_blocked_ips) {
	print "adding $ip to iptables blocked list\n";
	`/sbin/iptables -A INPUT -s $ip/32 -j DROP`;
	if ($?) {
		die "failed adding $ip to iptables";
	}
}

# make nice new line at the end
print "\n";

`/sbin/iptables-save > $conf_file`;
if ($?) {
	die "failed updating $conf_file"
}

