135 lines
2.8 KiB
Perl
135 lines
2.8 KiB
Perl
|
#!/usr/bin/perl
|
||
|
|
||
|
use strict;
|
||
|
|
||
|
use Sys::Syslog;
|
||
|
use POSIX;
|
||
|
|
||
|
our $IDIOT_FILE = '/var/cache/ssh-morons';
|
||
|
our $AUTH_LOG = '/var/log/auth.log';
|
||
|
our $MAX_FAIL = 5;
|
||
|
our $BL_RULE = 'BLACKLIST';
|
||
|
|
||
|
|
||
|
sub writeLog
|
||
|
{
|
||
|
openlog( 'ban-ssh-morons' , 'nofatal,pid,perror' , 'LOCAL0' );
|
||
|
syslog( @_ );
|
||
|
closelog( );
|
||
|
}
|
||
|
|
||
|
|
||
|
sub checkForIdiots
|
||
|
{
|
||
|
my $MAX_FAIL = 5;
|
||
|
my $fn = shift;
|
||
|
|
||
|
my %idiots = ( );
|
||
|
if ( open( my $fh , $IDIOT_FILE ) ) {
|
||
|
while ( my $idiot = <$fh> ) {
|
||
|
chop $idiot;
|
||
|
$idiots{ $idiot } = $MAX_FAIL;
|
||
|
}
|
||
|
close $fh;
|
||
|
}
|
||
|
|
||
|
$fn = $AUTH_LOG unless defined $fn;
|
||
|
|
||
|
my $foundNewIdiots = 0;
|
||
|
open( my $fh , '<' , $fn )
|
||
|
or die "couldn't open $fn\n";
|
||
|
while ( my $line = <$fh> ) {
|
||
|
chop $line;
|
||
|
next unless $line =~ /sshd.*Failed password for( invalid user)? .* from (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) port.*/;
|
||
|
my $newIdiot = $2;
|
||
|
next if ( defined $idiots{ $newIdiot } && $idiots{ $newIdiot } >= $MAX_FAIL );
|
||
|
$idiots{ $newIdiot } = 0 unless defined $idiots{ $newIdiot };
|
||
|
$idiots{ $newIdiot } ++;
|
||
|
if ( $idiots{ $newIdiot } >= $MAX_FAIL ) {
|
||
|
writeLog( 'notice' , 'Adding %s to SSH blacklist' , $newIdiot );
|
||
|
$foundNewIdiots = 1;
|
||
|
}
|
||
|
}
|
||
|
close $fh;
|
||
|
|
||
|
writeLog( 'info' , 'Blacklist now contains %d entries' , scalar( keys( %idiots ) ) )
|
||
|
if $foundNewIdiots;
|
||
|
|
||
|
my @commands = ( );
|
||
|
open( my $fh , '>' . $IDIOT_FILE );
|
||
|
foreach my $cretin ( keys %idiots ) {
|
||
|
next unless $idiots{ $cretin } >= $MAX_FAIL;
|
||
|
print $fh "$cretin\n";
|
||
|
push @commands , "iptables -A $BL_RULE -s $cretin -j DROP";
|
||
|
}
|
||
|
close $fh;
|
||
|
|
||
|
system( 'iptables -F ' . $BL_RULE );
|
||
|
foreach my $cmd ( @commands ) {
|
||
|
system( $cmd );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
sub mainLoop
|
||
|
{
|
||
|
my $mustExit = 0;
|
||
|
my $sigHandler = sub {
|
||
|
$mustExit = 1;
|
||
|
};
|
||
|
|
||
|
local $SIG{TERM} = $sigHandler;
|
||
|
local $SIG{INT} = $sigHandler;
|
||
|
|
||
|
my $signals = new POSIX::SigSet( &POSIX::SIGINT , &POSIX::SIGTERM , &POSIX::SIGHUP );
|
||
|
|
||
|
writeLog( 'info' , 'SSH blacklist updater starting' );
|
||
|
while ( !$mustExit ) {
|
||
|
sigprocmask( SIG_BLOCK , $signals , new POSIX::SigSet( ) );
|
||
|
checkForIdiots;
|
||
|
sigprocmask( SIG_UNBLOCK , $signals , new POSIX::SigSet( ) );
|
||
|
sleep 60;
|
||
|
}
|
||
|
writeLog( 'info' , 'SSH blacklist updater terminating' );
|
||
|
}
|
||
|
|
||
|
|
||
|
sub runDaemon
|
||
|
{
|
||
|
# Fork to background
|
||
|
exit 0 if fork( );
|
||
|
close( STDIN );
|
||
|
close( STDOUT );
|
||
|
close( STDERR );
|
||
|
open( STDIN , "/dev/null" );
|
||
|
open( STDOUT , ">/dev/null" );
|
||
|
open( STDERR , ">/dev/null" );
|
||
|
|
||
|
# Write PID file
|
||
|
my $pidFile = '/var/run/ban-ssh-morons.pid';
|
||
|
if ( -e $pidFile ) {
|
||
|
writeLog( 'crit' , 'PID file %s exists; exiting' , $pidFile );
|
||
|
die;
|
||
|
}
|
||
|
if ( open( my $f , '>' , $pidFile ) ) {
|
||
|
print $f "$$\n";
|
||
|
close $f;
|
||
|
} else {
|
||
|
writeLog( 'crit' , 'unable to create PID file %s' , $pidFile );
|
||
|
die;
|
||
|
}
|
||
|
|
||
|
# Run main loop
|
||
|
mainLoop;
|
||
|
|
||
|
# Delete PID file
|
||
|
unlink $pidFile;
|
||
|
}
|
||
|
|
||
|
|
||
|
if ( @ARGV ) {
|
||
|
checkForIdiots( $ARGV[0] );
|
||
|
} else {
|
||
|
runDaemon;
|
||
|
}
|