319 lines
7.6 KiB
Perl
Executable File
319 lines
7.6 KiB
Perl
Executable File
#!/usr/local/bin/perl
|
|
|
|
use DBI;
|
|
use strict;
|
|
use warnings;
|
|
use Glib;
|
|
use sigtrap qw/handler signal_handler normal-signals/;
|
|
use My::Savepid qw(savepid loadpid);
|
|
use Tie::Syslog;
|
|
use LWP::UserAgent;
|
|
|
|
my $pid = $$;
|
|
my $program = $0;
|
|
my $daemonize = 1;
|
|
my $verbose = 1;
|
|
my $logfile = '/var/log/flush_reject.log';
|
|
my $pidfile = '/var/run/flush_reject.pid';
|
|
my $time_keep = '10 MINUTE'; # for how long do we want to block blacklisted host, sql time (SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR)
|
|
my $default_ban = '6 HOUR'; # how long do we ban offenders? sql time
|
|
my $max_block = 5; #max amount of blocks before we stop freeing them (aka permanent ban)
|
|
my $max_time = '31 DAY'; #for how long do permanent bans last? sql time
|
|
my $sql_user = '';
|
|
my $sql_pwd = '';
|
|
my $sql_host = '';
|
|
my $table = '';
|
|
my $block_table = '';
|
|
my $opnsense = '';
|
|
my $api_key = '';
|
|
my $api_secret = '';
|
|
my $api_alias = '';
|
|
|
|
my $logf;
|
|
my $dp;
|
|
my $dbh;
|
|
my $loop;
|
|
my $timer;
|
|
my $x;
|
|
if($daemonize) {
|
|
if(my $tp = fork) {
|
|
exit 0;
|
|
} else {
|
|
$pid = $$;
|
|
&init;
|
|
}
|
|
} else {
|
|
&init;
|
|
}
|
|
|
|
sub init {
|
|
&savepid($pid,$pidfile);
|
|
open($logf, '>>', "$logfile") || die "Could not write to logfile";
|
|
select $logf; $| = 1;
|
|
if($daemonize) {
|
|
$x = tie *STDERR, 'Tie::Syslog','local0.err',$program,'pid','unix';
|
|
$x->ExtendedSTDERR();
|
|
close STDIN;
|
|
}
|
|
&log(1,"$0($pid) started");
|
|
$loop = Glib::MainLoop->new;
|
|
$timer = Glib::Timeout -> add_seconds(300, \&update);
|
|
$dbh = &connect;
|
|
&update;
|
|
$loop -> run;
|
|
}
|
|
|
|
sub update {
|
|
$dbh = &connect unless($dbh->ping);
|
|
&update_old;
|
|
my @work = &find_work;
|
|
my $selrows = scalar(@work);
|
|
&log(2,"Found $selrows fresh entries to work on") if($selrows);
|
|
if($selrows) {
|
|
foreach my $host(@work) {
|
|
my @tolog;
|
|
my $rows = &find_fresh($host);
|
|
push(@tolog,"$rows nonsleeping < 6hours");
|
|
if($rows > 1) {
|
|
my $setsleeprows = &set_sleeping($host);
|
|
push(@tolog,"$setsleeprows set as sleeping") if($setsleeprows);
|
|
}
|
|
my $trows = &find_sleeping($host);
|
|
push(@tolog,"$trows sleeping entries") if($trows);
|
|
my $age = &find_newest_entry($host);
|
|
unless($trows) {
|
|
if($rows <= 1) {
|
|
if(&find_total($host) < $max_block) {
|
|
push(@tolog, "deleting pf rule.");
|
|
&clean_host($host,$age);
|
|
} else {
|
|
push(@tolog, "doing nothing, $host has too many blocks");
|
|
}
|
|
}
|
|
} else {
|
|
push(@tolog, "$trows sleeping, noop");
|
|
}
|
|
if($age) {
|
|
my $todel = 21600 - $age;
|
|
my $page = 'last entry is '.&gettime($age).' old';
|
|
my $ptodel = '';
|
|
$ptodel = ', will be deleted in '.&gettime($todel) if($trows);
|
|
push(@tolog,"$page$ptodel") if($age);
|
|
}
|
|
my $logline = "$host: ";
|
|
$logline .= join(',', @tolog);
|
|
&log(2,"$logline");
|
|
}
|
|
}
|
|
system("/sbin/pfctl -t $block_table -T show > /etc/pf.$block_table");
|
|
$dbh->disconnect;
|
|
return 1;
|
|
}
|
|
|
|
sub opnsense_api_delete {
|
|
my $ip = shift;
|
|
my $ua = LWP::UserAgent->new;
|
|
$ua->agent('flush_reject');
|
|
$ua->timeout(1);
|
|
my $req = HTTP::Request->new(POST => 'http://'.$opnsense.'/api/firewall/alias_util/delete/'.$api_alias);
|
|
$req->authorization_basic($api_key, $api_secret);
|
|
$req->content_type('application/json');
|
|
$req->content('{"address":"'.$ip.'"}');
|
|
my $res = $ua->request($req);
|
|
unless ($res->is_success) {
|
|
&log(2, 'Failed to request to delete '.$ip.' from opnsense.');
|
|
}
|
|
}
|
|
|
|
sub find_total {
|
|
my $host = shift;
|
|
my $sth = $dbh->prepare("
|
|
SELECT
|
|
count(ip)
|
|
FROM
|
|
$table
|
|
WHERE
|
|
ip = ? AND
|
|
time > DATE_SUB(NOW(), INTERVAL $max_time)
|
|
");
|
|
$sth->execute($host);
|
|
my $rows = $sth->fetchrow_arrayref->[0] || 0;
|
|
return $rows;
|
|
}
|
|
|
|
sub update_old {
|
|
## Set entries older than 6 hours as not sleeping if they are set as so, this means they will be deleted unless they're permanently banned
|
|
my $sth = $dbh->prepare("
|
|
UPDATE $table
|
|
SET sleep = 0
|
|
WHERE deleted = 0 AND
|
|
SLEEP = 1 AND
|
|
time < DATE_SUB(NOW(), INTERVAL $default_ban)")
|
|
|| die "Failed to update old records".DBI::errstr;
|
|
$sth->execute();
|
|
my $rows = $sth->rows || 0;
|
|
return $rows;
|
|
}
|
|
|
|
sub find_work {
|
|
my @work;
|
|
my %ret;
|
|
my $sth = $dbh->prepare("
|
|
SELECT ip
|
|
FROM $table
|
|
WHERE
|
|
deleted = 0 AND
|
|
time < DATE_SUB(NOW(), INTERVAL $time_keep)")
|
|
|| die "Failed to find records to work on".DBI::errstr;
|
|
$sth->execute;
|
|
my $rows = $sth->rows;
|
|
while(my $ref = $sth->fetchrow_hashref) {
|
|
my $workip = $$ref{'ip'};
|
|
$ret{$workip} = 1;
|
|
}
|
|
foreach my $key(keys %ret) {
|
|
push(@work,$key);
|
|
}
|
|
return @work;
|
|
}
|
|
|
|
sub set_sleeping {
|
|
my $host = shift;
|
|
my $sth = $dbh->prepare("
|
|
UPDATE $table
|
|
SET sleep = 1
|
|
WHERE
|
|
ip = ? AND
|
|
deleted = 0
|
|
AND sleep = 0")
|
|
|| die "Failed to update record for $host with sleep=1 ".DBI::errstr;
|
|
$sth->execute($host);
|
|
my $rows = $sth->rows || 0;
|
|
return $rows;
|
|
}
|
|
|
|
sub find_fresh {
|
|
my $host = shift;
|
|
my $sth = $dbh->prepare("
|
|
SELECT count(*)
|
|
FROM $table
|
|
WHERE
|
|
ip = ? AND
|
|
sleep = 0 AND
|
|
time > DATE_SUB(NOW(), INTERVAL $default_ban)")
|
|
|| die "Failed to find number of records for $host newer than 6 hours ".DBI::errstr;
|
|
$sth->execute($host);
|
|
my $rows = $sth->fetchrow_arrayref->[0];
|
|
return $rows;
|
|
}
|
|
|
|
sub find_sleeping {
|
|
my $host = shift;
|
|
my $sth = $dbh->prepare("
|
|
SELECT count(*)
|
|
FROM $table
|
|
WHERE
|
|
ip = ? AND
|
|
sleep = 1")
|
|
|| die "Failed to find number of records for sleeping host $host ".DBI::errstr;
|
|
$sth->execute($host);
|
|
my $trows = $sth->fetchrow_arrayref->[0] || 0;
|
|
return $trows;
|
|
}
|
|
|
|
sub find_newest_entry {
|
|
my $host = shift;
|
|
my $sth = $dbh->prepare("
|
|
SELECT TIMESTAMPDIFF(SECOND, time, now()) AS age
|
|
FROM $table
|
|
WHERE ip = ?
|
|
ORDER BY time DESC
|
|
LIMIT 1")
|
|
|| die "Failed to find age of last entry of $host ".DBI::errstr;
|
|
$sth->execute($host);
|
|
my $age = $sth->fetchrow_arrayref->[0] || 0;
|
|
return $age;
|
|
}
|
|
|
|
sub clean_host {
|
|
my $ip = shift;
|
|
my $age = shift || 0;
|
|
my $sth = $dbh->prepare("
|
|
UPDATE $table
|
|
SET deleted = 1
|
|
WHERE
|
|
ip = ? AND
|
|
deleted = 0")
|
|
|| die "Failed to update record for $ip with deleted=1".DBI::errstr;
|
|
$sth->execute($ip) || die "Failed to update record for $ip:".DBI::errstr;
|
|
my $delrows = $sth->rows || 0;
|
|
system "/sbin/pfctl -q -t $block_table -T delete $ip" if($delrows);
|
|
opnsense_api_delete($ip);
|
|
my $prettyage = &gettime($age);
|
|
&log(2,"Updated $delrows rows for host $ip to mark them as deleted");
|
|
&log(1,"Cleared $ip after $prettyage.");
|
|
}
|
|
|
|
sub log {
|
|
my $sev = shift;
|
|
my $msg = shift;
|
|
unless($msg) {
|
|
$msg = $sev;
|
|
$sev = 0;
|
|
}
|
|
print STDERR "$msg" unless($sev);
|
|
if($verbose >= $sev) {
|
|
my $now = localtime();
|
|
print $logf "$now: $msg\n";
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
sub reload_log {
|
|
close($logf);
|
|
open($logf, '>>', "$logfile") || die "Could not write to logfile";
|
|
select $logf; $| = 1;
|
|
}
|
|
|
|
sub signal_handler {
|
|
my $signal = shift;
|
|
&log(1,"Recieved $signal: ");
|
|
if($signal eq 'HUP') {
|
|
&log(1,'Reloaded log');
|
|
&reload_log;
|
|
} else {
|
|
system("/sbin/pfctl -t $block_table -T show > /etc/pf.$block_table");
|
|
&log(1,"$program($pid) Shutting down");
|
|
$dbh->disconnect;
|
|
undef $x;
|
|
untie *STDERR;
|
|
close($logf);
|
|
exit 1;
|
|
}
|
|
}
|
|
|
|
sub connect {
|
|
my $databasec;
|
|
while(1) {
|
|
last if($databasec = DBI->connect("DBI:mysql:database=syslog;host=$sql_host",$sql_user,$sql_pwd, { PrintError => 1, mysql_auto_reconnect=>1, AutoCommit => 1 }));
|
|
&log(2,'Connection problems, reconnecting in 10 seconds');
|
|
sleep(10);
|
|
}
|
|
return $databasec;
|
|
}
|
|
|
|
sub gettime # gettime(seconds)
|
|
{
|
|
my $time = $_[0];
|
|
if($time > 0) {
|
|
my $days = int($time/24/60/60);
|
|
my $hours = sprintf("%02d", (int($time/60/60)%24));
|
|
my $minutes = sprintf("%02d", (int($time/60)%60));
|
|
my $seconds = sprintf("%02d", (int($time)%60));
|
|
$time = "$minutes:$seconds" if($minutes > 0);
|
|
$time = "$hours hours $minutes:$seconds" if($hours > 0);
|
|
$time = "$days days $hours:$minutes:$seconds" if($days > 0);
|
|
return $time;
|
|
}
|
|
}
|