#!/usr/bin/perl

=head1 NAME

pfdetect-remote - listen to snort alerts and send them to PF server

=head1 SYNOPSIS

pfdetect -p <snortpipe> [options]

  Options:
    -d  Daemonize
    -h  Help

=cut

use Getopt::Std;
use File::Basename;
use File::Tail;
use FileHandle;
use POSIX qw(:signal_h);
use SOAP::Lite;
use Sys::Syslog;
use Config::IniFiles;
use Data::Dumper;
use Pod::Usage;
use strict;
use warnings;

POSIX::sigaction(&POSIX::SIGHUP,
  POSIX::SigAction->new(
                        'restart_handler',
                        POSIX::SigSet->new(),
                        &POSIX::SA_NODEFER
                       )
) or die "pfdetect_remote: could not set SIGHUP handler: $!\n";

POSIX::sigaction(&POSIX::SIGTERM,
  POSIX::SigAction->new(
                        'normal_sighandler',
                        POSIX::SigSet->new(),
                        &POSIX::SA_NODEFER
                       )
) or die "pfdetect_remote: could not set SIGTERM handler: $!\n";

POSIX::sigaction(&POSIX::SIGINT,
  POSIX::SigAction->new(
                        'normal_sighandler',
                        POSIX::SigSet->new(),
                        &POSIX::SA_NODEFER
                       )
) or die "pfdetect_remote: could not set SIGINT handler: $!\n";


my $install_dir = '/usr/local/pf';

my @ORIG_ARGV = @ARGV;
my %args;
getopts('dhvp:', \%args);

pod2usage( -verbose => 1 ) if ($args{h} || !$args{p});

my $daemonize = $args{d};
my $snortpipe = $args{p};
my $script    = File::Basename::basename($0);
my $portscan_sid = 1200003;

my ($sid, $descr, $date, $srcmac, $srcip, $dstip);

daemonize() if ($daemonize);

openlog("pfdetect_remote",'','auth');
syslog("info", "initialized");

my $cfg = new Config::IniFiles( -file => "$install_dir/conf/pfdetect_remote.conf");
my $ADMIN_USER = $cfg->val('server','user');
my $ADMIN_PWD = $cfg->val('server','password');
my $PF_HOST = $cfg->val('server','host');

my $fh = new File::Tail ('name' => $snortpipe,
        'interval' => 2,
        'reset_tail' => 0,
        'maxinterval' => 2);
syslog("info", "listening on $snortpipe");

my $currentLine;
while (defined($currentLine=$fh->read)) {

  syslog("info", "alert received: $currentLine");
  if ($currentLine =~ /^(.+?)\s+\[\*\*\]\s+\[\d+:(\d+):\d+\]\s+(.+?)\s+.+?(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(:\d+){0,1}\s+\-\>\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(:\d+){0,1}/) {
    $date  = $1;
    $sid   = $2;
    $descr = $3;
    $srcip = $4;
    $dstip = $6;
  } elsif ($currentLine =~ /^(.+?)\s+\[\*\*\]\s+\[\d+:(\d+):\d+\]\s+Portscan\s+detected\s+from\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/) {
    $date   = $1;
    $sid    = $portscan_sid;
    $srcip  = $3;
    $descr = "PORTSCAN";
  } elsif ($currentLine =~ /^(.+?)\[\*\*\] \[\d+:(\d+):\d+\]\s+\(spp_portscan2\) Portscan detected from (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/) {
    $date  = $1;
    $sid   = $portscan_sid;
    $srcip = $3;
    $descr = "PORTSCAN";
  } else {
    syslog("warning", "unknown input: $currentLine ");
    next;
  }

  eval {
    my $soap = new SOAP::Lite(
      uri => 'http://www.packetfence.org/PFAPI',
      proxy => 'https://' . $ADMIN_USER . ':' . $ADMIN_PWD . '@' . $PF_HOST . '/webapi'
    );
    my $result = $soap->event_add($date, $srcip, "detect", $sid);
    if ($result->fault) {
      syslog("warning", "violation could not be added: " . $result->faultcode . " - " . $result->faultstring . " - " . $result->faultdetail);
    } else {
      syslog("info", "added violation $sid for $srcip");
    }
  };
  if ($@) {
    syslog("warning", "connection to $PF_HOST with username $ADMIN_USER was NOT successful: $@");
    next;
  }


}

END {
  deletepid();
  syslog("info", "stopping pfdetect_remote");
}

exit(0);

sub daemonize {
  chdir '/'               or die "Can't chdir to /: $!";
  open STDIN, '<', '/dev/null' or die "Can't read /dev/null: $!";  
  open STDOUT, '>', '/dev/null' or die "Can't write to /dev/null: $!";

  defined(my $pid = fork) or die "pfdetect_remote: could not fork: $!";
  POSIX::_exit(0) if ($pid);
  if (!POSIX::setsid()) {
    syslog("warning", "could not start a new session: $!");
  }
  open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
  createpid();
  return 1;
}

sub normal_sighandler {
  deletepid();
  syslog("info", "caught SIG".$_[0]." - terminating pfdetect_remote");
  die("pfdetect_remote: caught SIG".$_[0]." - terminating\n");
}

sub restart_handler {
  deletepid();
  syslog("info", "caught SIG".$_[0]." - restarting pfdetect_remote");
  if (!exec($0, @ORIG_ARGV)) {
    syslog("warning", "could not restart: #!");
    die "pfdetect_remote: could not restart: $!\n";
  }
}


sub createpid {
  my $pname = basename($0);
  my $pid = $$;
  my $pidfile = $install_dir."/var/$pname.pid";
  syslog("info", "$pname starting and writing $pid to $pidfile");
  my $outfile = new FileHandle ">$pidfile";
  if (defined($outfile)) {
    print $outfile $pid;
    $outfile->close;
    return($pid);
  } else {
    syslog("warning", "$pname: unable to open $pidfile for writing: $!");
    return(-1);
  }
}

sub deletepid {
  my ($pname) = @_;
  $pname = basename($0) if (!$pname);
  my $pidfile = $install_dir."/var/$pname.pid";
  unlink($pidfile) || return(-1);
  return(1);
}

=head1 AUTHOR

Dave Laporte <dave@laportestyle.org>

Kevin Amorin <kev@amorin.org>

Dominik Gehl <dgehl@inverse.ca>

=head1 COPYRIGHT

Copyright (C) 2005 Dave Laporte

Copyright (C) 2005 Kevin Amorin

Copyright (C) 2006-2009 Inverse inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
USA.

=cut

# vim: set shiftwidth=4:
# vim: set expandtab:
# vim: set backspace=indent,eol,start:
