#!/usr/bin/perl

=head1 NAME

pfdetect - listen to snort alerts and create PF violations

=head1 SYNOPSIS

pfdetect -p <snortpipe> [options]

 Options:
   -d     Daemonize
   -h     Help

=cut

use strict;
use warnings;
use English qw( ‐no_match_vars ) ;  # Avoids regex performance penalty
use File::Basename qw(basename);
use Getopt::Std;
use Log::Log4perl;
use Pod::Usage;
use POSIX qw(:signal_h);

use constant INSTALL_DIR => '/usr/local/pf';

use lib INSTALL_DIR . "/lib";
use pf::action;
use pf::class;
use pf::config;
use pf::db;
use pf::iplog;
use pf::node;
use pf::os;
use pf::person;
use pf::trigger;
use pf::util;
use pf::violation;

Log::Log4perl->init_and_watch( INSTALL_DIR . "/conf/log.conf", $LOG4PERL_RELOAD_TIMER );
my $logger = Log::Log4perl->get_logger( basename($0) );
Log::Log4perl::MDC->put( 'proc', basename($0) );
Log::Log4perl::MDC->put( 'tid',  $PID );

POSIX::sigaction(
    &POSIX::SIGHUP,
    POSIX::SigAction->new(
        'normal_sighandler', POSIX::SigSet->new(), &POSIX::SA_NODEFER
    )
) or $logger->logdie("pfdetect: could not set SIGHUP handler: $!");

POSIX::sigaction(
    &POSIX::SIGTERM,
    POSIX::SigAction->new(
        'normal_sighandler', POSIX::SigSet->new(), &POSIX::SA_NODEFER
    )
) or $logger->logdie("pfdetect: could not set SIGTERM handler: $!");

POSIX::sigaction(
    &POSIX::SIGINT,
    POSIX::SigAction->new(
        'normal_sighandler', POSIX::SigSet->new(), &POSIX::SA_NODEFER
    )
) or $logger->logdie("pfdetect: could not set SIGINT handler: $!");


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

my $daemonize = $args{d};
my $snortpipe = $args{p};
my $snortpipe_fh;
my $script    = basename($0);

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

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

daemonize() if ($daemonize);

$logger->info("initialized");

if ( !open( $snortpipe_fh, '<', "$snortpipe" ) ) {
    $logger->logdie("unable to open snort pipe ($snortpipe): $!");
} else {
    $logger->info("listening on $snortpipe");
}

while (<$snortpipe_fh>) {

    $logger->info("alert received: $_");
    if ( $_
        =~ /^(.+?)\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 ( $_
        =~ /^(.+?)\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 ( $_
        =~ /^(.+?)\[\*\*\] \[\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 {
        $logger->warn("unknown input: $_ ");
        next;
    }

    $srcmac = ip2mac($srcip);

    if ($srcmac) {

        $logger->info("pfdetect: violation $sid [$descr]: $srcmac");
        violation_trigger($srcmac, $sid, "detect", ( ip => $srcip ));
    } else {
        $logger->warn(
            "pfdetect: $srcip MAC NOT FOUND for violation $sid [$descr]");
    }
}

END {
    deletepid();
    if (defined($snortpipe_fh)) {
        $logger->info("stopping pfdetect");
        close($snortpipe_fh);
    }
}

exit(0);

sub daemonize {
    chdir '/' or $logger->logdie("Can't chdir to /: $!");
    open STDIN, '<', '/dev/null'
        or $logger->logdie("Can't read /dev/null: $!");
    my $log_file = "$install_dir/logs/pfdetect";
    open STDOUT, '>>', $log_file
        or $logger->logdie("Can't write to $log_file: $!");

    defined( my $pid = fork )
        or $logger->logdie("pfdetect: could not fork: $!");
    POSIX::_exit(0) if ($pid);
    if ( !POSIX::setsid() ) {
        $logger->warn("could not start a new session: $!");
    }
    open STDERR, '>&STDOUT' or $logger->logdie("Can't dup stdout: $!");
    my $daemon_pid = createpid();

    # updating Log4perl's pid info
    Log::Log4perl::MDC->put( 'tid',  $daemon_pid );
}

sub normal_sighandler {
    deletepid();
    $logger->logdie( "caught SIG" . $_[0] . " - terminating" );
}


=head1 AUTHOR

Dave Laporte <dave@laportestyle.org>

Kevin Amorin <kev@amorin.org>

Dominik Gehl <dgehl@inverse.ca.>

Olivier Bilodeau <obilodeau@inverse.ca>

=head1 COPYRIGHT

Copyright (C) 2005 Dave Laporte

Copyright (C) 2005 Kevin Amorin

Copyright (C) 2009,2010 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

