Listing 2 portknocking server
use strict;
use File::Tail;
use Crypt::CBC;
use Schedule::At;
use Math::VecStat qw(sum);
use POSIX qw(strftime);
use Getopt::Long;
use constant PORTMIN => 745;
use constant KNOCKLENGTH => 8;
use constant KEY => "knock";
use constant CIPHER => "Blowfish";
use vars qw($opt_file);
GetOptions("file=s");
die "cannot open firewall log file (-file FILENAME)" if ! -e $opt_file || ! -r _;
my $file = File::Tail->new(name=>"$opt_file",maxinterval=>2);
my %QUEUE;
while(defined(my $line=$file->read)) {
# pay attention only to DENY packets
next unless $line =~ /DENY/;
if($line =~ /PROTO=6 ([\d.]+):\d+ [\d.]+:(\d+)/) {
my ($ip,$port) = ($1,$2);
# pay attention only to ports allocated for knocking
next if ($port < PORTMIN || $port > PORTMIN+255);
print "knock from $ip to port $port\n";
# push this port to each queue item
$QUEUE{$ip} = [] if ! $QUEUE{$ip};
push(@{$QUEUE{$ip}},$port);
print "current queue ",join(" ",@{$QUEUE{$ip}}),"\n";
# if the queue is of the expected length, process it
if(@{$QUEUE{$ip}} == KNOCKLENGTH) {
ProcessQueue($QUEUE{$ip});
print "current queue @{$QUEUE{$ip}}\n";
}
}
}
sub ProcessQueue {
my $queue = shift;
# try to decrypt the queue contents
my $cipher = Crypt::CBC->new({key => KEY,
iv =>"01234567",
prepend_iv => 0,
cipher => CIPHER});
my @data = unpack("C*",
$cipher->decrypt(pack("C*",
map {$_ - PORTMIN } @$queue)));
# decrypted list must have 7 elements, otherwise remove oldest item and keep listening
if(@data != 7) {
print "ERROR could not decrypt properly\n";
shift(@$queue);
return;
}
print "Got data ",join(" ",@data),"\n";
# check crc of knock sequence
if ((sum(@data[0..@data-2]) % 255) == $data[-1]) {
# extract information from the decrypted list
my ($remoteip,$localport,$time) = (join(".",@data[0..3]),$data[4],$data[5]);
print "$remoteip $localport $time\n";
# open port to remote ip
system("/sbin/ipchains -I input -p tcp -s $remoteip/32 -d 0/0 $localport \
-j ACCEPT") if $time != 255;
# close the port if time = 255
if($time == 255) {
system("/sbin/ipchains -D input -p tcp -s $remoteip/32 -d 0/0 $localport \
-j ACCEPT ");
} elsif ($time) {
# schedule the port to be closed in $time minutes
my $time = strftime "%Y%m%d%H%M", localtime(time+60*$time);
my $command = "/sbin/ipchains -D input -p tcp -s $remoteip/32 -d 0/0 \
$localport -j ACCEPT";
print "scheduled $time $command\n";
Schedule::At::add(TIME=>$time,COMMAND=>$command);
}
# clear the queue
@$queue = ();
} else {
print "ERROR bad crc\n";
shift(@$queue);
}
}
|