#!/usr/bin/perl

=head1 NAME

winbindd-wrapper - wrapper around winbindd to start the PacketFence winbindd processes

=head1 SYNOPSIS

winbindd-wrapper [options]

 Options:
   -d      Daemonize
   -h      Help
   -v      Verbose

=cut

use warnings;
use strict;
use Getopt::Std;
use Net::Pcap 0.16;
use File::Basename qw(basename);
use POSIX qw(:signal_h pause :sys_wait_h SIG_BLOCK SIG_UNBLOCK);
use Pod::Usage;
use Fcntl qw(:flock);
use Systemd::Daemon qw{ -soft };

#pf::log must always be initilized first
BEGIN {
    # log4perl init
    use constant INSTALL_DIR => '/usr/local/pf';
    use lib INSTALL_DIR . "/lib";
    use pf::log(service => 'winbindd-wrapper');
}

use pf::file_paths qw($var_dir);
use pf::config qw(%Config %ConfigDomain);
use pf::constants qw($FALSE $TRUE);
use pf::services;
use pf::util;
use pf::services::util;
use pf::StatsD;
use pf::person;
use Time::HiRes qw(time sleep);

# initialization
# --------------
# assign process name (see #1464)
our $PROGRAM_NAME = $0 = "winbindd-wrapper";
our @REGISTERED_TASKS;
our $IS_CHILD = 0;
our %CHILDREN;
our @TASKS_RUN;
our $ALARM_RECV = 0;

my $logger = get_logger( $PROGRAM_NAME );

$SIG{INT}  = \&normal_sighandler;
$SIG{HUP}  = \&normal_sighandler;
$SIG{TERM} = \&normal_sighandler;
$SIG{ALRM} = \&alarm_sighandler;
$SIG{CHLD} = \&child_sighandler;

my %args;
getopts( 'dhvr', \%args );

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

my $daemonize = $args{d};
my $verbose   = $args{v};
my $restart   = $args{r};

my $pidfile = "${var_dir}/run/winbindd-wrapper.pid";

our $HAS_LOCK = 0;
open(my $fh, ">>", $pidfile);
flock($fh, LOCK_EX | LOCK_NB) or die "cannot lock $pidfile another winbindd-wrapper is running\n";
$HAS_LOCK = 1;

our $running = 1;
our $process = 0;

# standard signals and daemonize
daemonize($PROGRAM_NAME) if ($daemonize);
our $PARENT_PID = $$;

sub start {
    Systemd::Daemon::notify( READY => 1, STATUS => "Ready", unset => 1 );
    registertasks();
    while($running) {
        run_all_winbindd();
        alarm(1);
        pause;
        alarm(0);
    }
}

start();
cleanup();

END {
    if ( !$args{h} && $HAS_LOCK ) {
        unless($IS_CHILD) {
            Systemd::Daemon::notify( STOPPING => 1 );
            deletepid();
            $logger->info("stopping winbindd-wrapper");
        }
    }
}

exit(0);

=head1 SUBROUTINES

=head2 kill_and_wait_for_children

signal children and waits for them to exit process

=cut

sub kill_and_wait_for_children {
    my ($signal,$waittime) = @_;
    signal_children($signal);
    $ALARM_RECV = 0;
    alarm $waittime;
    while (((keys %CHILDREN) != 0 ) && !$ALARM_RECV) {
        pause;
    }
    alarm(0);
}

=head2 cleanup

cleans after children

=cut

sub cleanup {
    signal_children('TERM');
}

=head2 signal_children

sends a signal to all active children

=cut

sub signal_children {
    my ($signal) = @_;
    kill ($signal, keys %CHILDREN);
}

=head2 normal_sighandler

the signal handler to shutdown the service

=cut

sub normal_sighandler {
    $running = 0;
}

=head2 run_all_winbindd

run all run_all_winbindd

=cut

sub run_all_winbindd {
    unless (@REGISTERED_TASKS) {
        return;
    }

    my $mask = POSIX::SigSet->new(POSIX::SIGCHLD());
    sigprocmask(SIG_BLOCK, $mask);
    while (@REGISTERED_TASKS) {
        my $task = shift @REGISTERED_TASKS;
        run_winbindd($task);
    }

    sleep(5);
    sigprocmask(SIG_UNBLOCK, $mask);
}

=head2 register_task

register_task

=cut

sub register_task {
    my ($task) = @_;
    push @REGISTERED_TASKS, $task;
    return ;
}

=head2 registertasks

    Register all tasks

=cut

sub registertasks {
    for my $task (keys %ConfigDomain) {
        next if isdisabled($ConfigDomain{$task}{status});
        register_task($task);
    }
}

=head2 run_winbindd

creates a new child to run a task

=cut

sub run_winbindd {
    my ($id) = @_;
    my $chroot_path = pf::domain::chroot_path($id);
    my $binary = $Config{services}{winbindd_binary};
    my $config_file = "/etc/samba/$id.conf";
    my $log_directory = "/var/log/samba$id";

    my $pid = fork();
    if (!defined $pid) {
        $logger->error("Unable to fork child for '$id' retrying : Error($!)");
        register_task($id);
    } elsif($pid) {
        $CHILDREN{$pid} = $id;
    } elsif ($pid == 0) {
        $SIG{CHLD} = 'DEFAULT';
        $IS_CHILD = 1;
        Log::Log4perl::MDC->put('tid', $$);
        unless(exec("chroot $chroot_path $binary -s $config_file -l $log_directory --foreground")) {
            $logger->error("Failed to start the winbindd process $id");
            exit 1;
        }
    }
}

=head2 child_sighandler

reaps the children

=cut

sub child_sighandler {
    local ($!, $?);
    while(1) {
        my $child = waitpid(-1, WNOHANG);
        last unless $child > 0;
        my $id = delete $CHILDREN{$child};
        if (defined $id) {
            $logger->warn("Re-registering $id");
            register_task($id);
        }
    }
}

=head2 alarm_sighandler

the alarm signal handler

=cut

sub alarm_sighandler {
    $ALARM_RECV = 1;
}

=head1 AUTHOR

Inverse inc. <info@inverse.ca>

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

=head1 COPYRIGHT

Copyright (C) 2005-2020 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


