#!/usr/bin/perl

=head1 NAME

pfdetect - listen to alerts and create PF violations

=head1 SYNOPSIS

pfdetect [options]

 Options:
   -d     Daemonize
   -h     Help

=cut

use strict;
use warnings;
use File::Basename qw(basename);
use Getopt::Std;
use Pod::Usage;
use POSIX qw(:signal_h pause _exit);

BEGIN {
    # log4perl init
    use constant INSTALL_DIR => '/usr/local/pf';
    use lib INSTALL_DIR . "/lib";
    use pf::log(service => 'pfdetect');
}

use pf::action;
use pf::class;
use pf::config;
use pf::config::cached;
use pf::db;
use pf::iplog;
use pf::node;
use pf::person;
use pf::util;
use pf::services::util;
use pf::violation;
use pf::SwitchFactory;

pf::SwitchFactory::preLoadModules();
use pf::factory::detect::parser;
use pf::client;
use pfconfig::cached_hash;

# initialization
# --------------
# assign process name (see #1464)
our $PROGRAM_NAME = $0 = "pfdetect";

my $logger = get_logger( $PROGRAM_NAME );

# init signal handlers
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( 'dh:', \%args );

my $daemonize = $args{d};
my $portscan_sid = 1200003;

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

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

daemonize($PROGRAM_NAME) if ($daemonize);
our $PARENT_PID = $$;

$logger->info("initialized");

my %CHILDREN;
my $IS_CHILD = 0;
my $running = 1;

sub start_detectors {
    foreach my $id (keys %ConfigDetect) {
        run_detector($id);
    }
}

=head2 run_detector

creates a new child to run a task

=cut

sub run_detector {
    my ($id) = @_;
    my $detector = $ConfigDetect{$id};
    $detector->{id} = $id;
    my $pid = fork();
    if($pid) {
        $CHILDREN{$pid} = $detector->{id};
        $SIG{CHLD} = "IGNORE";
    } elsif ($pid == 0) {
        $SIG{CHLD} = "DEFAULT";
        $IS_CHILD = 1;
        _run_detector($detector);
    } else {
    }
}

=head2 _run_detector

the task to is ran in a loop until it is finished

=cut

sub _run_detector {
    my ($detector) = @_;
    $0 = "pfdetect - ".$detector->{id};

    my $alert_pipe = $detector->{path};
    my $alert_pipe_fh;

    if ( !open( $alert_pipe_fh, '<', "$alert_pipe" ) ) {
        $logger->error("unable to open alert pipe ($alert_pipe): $!");
        _exit(1);
    } else {
        $logger->info("listening on $alert_pipe");
    }

    my $parser_type = $detector->{type};

    my $parser = pf::factory::detect::parser->new($parser_type);

    my $client = pf::client->getClient();

    while (<$alert_pipe_fh>) {
        last unless $running;

        my $line = $_;
        $logger->info("alert received: '$line'");
        my $data = $parser->parse($line);
        unless($data) {
            $logger->warn("unknown input: $line ");
            next;
        }
        while (my ($type, $value) = each %{$data->{events}}) {
            $client->notify( event_add => ( $data->{date}, $data->{srcip}, $type, $value ));
        }

        #Stop running if parent is no longer alive
        unless(is_parent_alive()) {
            $logger->error("Parent is no longer running shutting down");
            $running = 0;
        }

        #reload all cached configs after each iteration
        pf::config::cached::ReloadConfigs();
    }

    $logger->trace("$$ shutting down");
    _exit(0);
}

=head2 is_parent_alive

Checks to see if parent is alive

=cut

sub is_parent_alive {
    kill (0,$PARENT_PID)
}

start_detectors();
while($running){
    sleep 1;
    foreach my $pid (keys %CHILDREN){
        unless(kill(0,$pid)){
            get_logger->error("Child $pid ($CHILDREN{$pid}) is dead. Respawning it.");
            run_detector($CHILDREN{$pid});
            delete $CHILDREN{$pid};
        }
    }
}


END {
    deletepid();
}

exit(0);

sub normal_sighandler {
    foreach my $pid (keys %CHILDREN){
        kill(SIGKILL, $pid);
    }
    deletepid();
    $logger->logdie( "caught SIG" . $_[0] . " - terminating" );
}


=head1 AUTHOR

Inverse inc. <info@inverse.ca>

Minor parts of this file may have been contributed. See CREDITS.

=head1 COPYRIGHT

Copyright (C) 2005-2016 Inverse inc.

Copyright (C) 2005 Kevin Amorin

Copyright (C) 2005 David LaPorte

=head1 LICENSE

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

