Listing 2

use strict;
use File::Tail;
use Crypt::CBC;
use Schedule::At;
use Math::VecStat qw(sum);
use POSIX qw(strftime);
use Pod::Usage;
use Getopt::Long;
use constant PORTMIN      => 745;
use constant KNOCKLENGTH  => 8;
use constant KEY          => "knock";
use constant CIPHER       => "Blowfish";
use vars qw($opt_file);

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);
  }
}
### end of Listing 2

