All pastes #2047684 Raw Edit

Untitled

public text v1 · immutable
#2047684 ·published 2011-04-18 09:37 UTC
rendered paste body
#!/usr/bin/env perl

$0 = "portlimit";

## modules
use strict;
use File::Tail;
use POSIX qw(setsid);

chdir '/'                 or die "Can't chdir to /: $!";
umask 0;
open STDIN, '/dev/null'   or die "Can't read /dev/null: $!";
open STDOUT, '>/dev/null' or die "Can't write to /dev/null: $!";
open STDERR, '>/dev/null' or die "Can't write to /dev/null: $!";
defined(my $pid = fork)   or die "Can't fork: $!";
exit if $pid;
setsid                    or die "Can't start a new session: $!";

open(PID, ">/var/run/portlimit.pid") or die $!;
print PID $$;
close(PID);

## conf
my $DLOG     = "/var/log/portlimit";
my $LOGFILE  = "/var/log/auth.log";
my $LOGRETRY = "5";
my $ATTEMPTS = 3;  # after how many attempts we impose a block
my $BTTL     = 60; # initially blocked for that many seconds, gets incremented
                   # by BTTL for each firewall-block-event
my $STTL     = 30; # remove ip from tracker if it was last seen STTL seconds ago


## main
my %badip;
my $ret;
my $curpos;

$| = 1;

# flush everything and restart from scratch
$ret = `pfctl -q -t blocks -T flush`;

# create the badguys table lookup
#$ret = `ipfw add 10000 deny ip from table\(1\) to any in`;


# precompiled re's
my $r_ip = qr/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/;


my @tail;
my $timeout = 5;
push(@tail, File::Tail->new(
        name=>$LOGFILE,
        maxinterval=>2,
        interval=>1,
        resetafter=>5
    ));


debug("started up");
while(1)
{   
    my($nfound, $timeleft, @pending) =
    File::Tail::select(undef,undef,undef,$timeout,@tail);
    unless ($nfound)
    {
        # housekeeping stuff here
        my $now = time;
        while(my($ip, $val) = each(%badip))
        {

            # this is also a parachute if bttl gets too high
            if($now - $badip{$ip}{'seen'} > 600)
            {
                debug("delete $ip because not seen anymore");
                delete($badip{$ip});
                next;
            }

            if(! defined($badip{$ip}{'blocked_since'}))
            {
                next;
            }

            if($now - $badip{$ip}{'blocked_since'} < $badip{$ip}{'bttl'})
            {
                next;
            }

            if( ipfw('delete', $ip) == 0)
            {
                debug("deleted $ip");
                delete $badip{$ip}{'blocked_since'};
                $badip{$ip}{'count'} = $ATTEMPTS - 1;
            }
            else
            {
                debug("couldnt delete ip: $ip via ipfw(4)");
            }
        }
    }
    else
    {
        # got a new log line somewhere
        foreach(@pending)
        {
            my $line = $_->read;
            my $ip;
            if
            (
                ($line =~ /authentication error .*?\b($r_ip)\b/i) ||
                ($line =~ /sshd.*invalid user.*from.*?\b($r_ip)\b/i)
            )
            {
                $ip = $1;

                if( $badip{$ip}{'count'} >= $ATTEMPTS &&
                    (! $badip{$ip}{'blocked_since'})
                )
                {
                    if( ipfw('add', $ip) == 0 )
                    {
                        $badip{$ip}{'blocked_since'} = time;
                        $badip{$ip}{'seen'} = $badip{$ip}{'blocked_since'};
                        $badip{$ip}{'bttl'} += $BTTL;
                        debug("added $ip to table 1");
                    }
                }
                if( ++$badip{$ip}{'count'} < $ATTEMPTS )
                {
                    $badip{$ip}{'seen'} = time;
                    debug("seen $ip");
                }

            }
        }
    }
}

#
# usage: errno = block( [ add | del ], ip )
# executes ipfw(4) and returns its exits status
#
sub ipfw($,$)
{
    my $cmd = shift;
    my $ip  = shift;

    if($cmd !~ /^(add|delete)$/)
    {
        return(1);
    }
    my $output = `pfctl -q -t blocks -T $cmd $ip/32; pfctl -q -k $ip`;
    my $exitcode = $? >> 8;
    return($exitcode);
}


sub debug
{
    open(DLOG, ">>$DLOG") or die $!;
    my $msg = join(' ', @_);
    print DLOG gmtime()."[UTC]: $0: debug: $msg\n";
    close(DLOG);
}



# vim: ts=4:sw=4