#!/usr/bin/perl -w

# Copyright (C) 2005, 2006, 2007, 2008 Peter Palfrader <peter@palfrader.org>
# Porting to hobbit Copyright (C) 2007 Christoph Berg <myon@debian.org>
# Copyright (C) 2011-2014 Axel Beckert <abe@debian.org>
# Copyright (C) 2014 Elmar Heeb <elmar@heebs.ch>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

use strict;
use English;
use Hobbit;
use YAML::Tiny;
use POSIX qw(uname);

$ENV{'PATH'} = '/bin:/sbin:/usr/bin:/usr/sbin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

my $LSOF = '/usr/bin/lsof';
$LSOF = '/usr/sbin/lsof' if (-x '/usr/sbin/lsof'); # CentOS location
my $LSOF_OPTIONS = '-n -FpcLfn0';
my $SUDO = '/usr/bin/sudo';

my @CONFIG = qw(/etc/xymon/libs.yaml /etc/xymon/libs.local.yaml);

my $bb = new Hobbit ('libs');

################### To permit exceptions
our %whitelist = ();
for my $filename (@CONFIG) {
    if ( -f $filename ) {
        my $config_yaml   = YAML::Tiny->read($filename);
        my $whitelist_cfg = $config_yaml->[0]{whitelist};
        foreach my $key (keys %{$whitelist_cfg}) {
            $whitelist{$key} ||= [];
            push(@{$whitelist{$key}}, @{$whitelist_cfg->{$key}});
        }
    }
}

my %processes;

sub getPIDs($$) {
	my ($user, $process) = @_;
	return join(' ', sort keys %{ $processes{$user}->{$process} });
};
sub getProcs($) {
	my ($user) = @_;

	return join("\n", map { "  $_ (".getPIDs($user, $_).')' } (sort {$a cmp $b} keys %{ $processes{$user} }));
};
sub getUsers() {
	return join("\n", (map { "$_:\n".getProcs($_) } (sort {$a cmp $b} keys %processes)));
};
sub inVserver() {
	my ($f, $key);
	if (-e "/proc/self/vinfo" ) {
		$f = "/proc/self/vinfo";
		$key = "XID";
	} else {
		$f = "/proc/self/status";
		$key = "s_context";
	};
	open(F, '<', $f) or return 0;
	while (<F>) {
		my ($k, $v) = split(/: */, $_, 2);
		if ($k eq $key) {
			close F;
			return ($v > 0);
		};
	};
	close F;
	return 0;
}

my $INVSERVER = inVserver();

if (!-x $SUDO or !-x $LSOF) {
    $bb->color_line ('clear',
"$SUDO and/or $LSOF not found or not executable.

This library check needs lsof to be run with root rights. See the file
/etc/sudoers.d/xymon for how lsof would be run with root rights via
sudo.

");

    $bb->send;
    exit 0
}

my $lsof_cmd;
unless (open($lsof_cmd, '-|', "$SUDO $LSOF $LSOF_OPTIONS 2> /dev/null")) {
	$bb->color_line ('red', "Cannot run $LSOF $LSOF_OPTIONS: $OS_ERROR\n");
	$bb->send;
	exit;
}

{
    my ($process, $pid, $user, $fd, $path, $rest);
    $user //= 'unknown';
    while (my $line = <$lsof_cmd>)  {
        my %params = %{_split_lsof_fields($line)};
        if ($line =~ m{\Ap}xms) {
            ($pid, $process, $user) = ($params{p}, $params{c}, $params{L} // 'unknown');
            next;
        }
        elsif ($line =~ m{\Af}xms) {
            ($fd, $path) = ($params{f}, $params{n});
            if ($fd eq 'txt') { # 'txt' indicates executable path
                next if ($path =~ m{\A/proc/}xms); # kernel processes
                $process = $path; # path more specific than process name
            }
            next if $fd eq 'cwd';
            if ($path =~ m{\.dpkg-} || $path =~ m{path inode=} || $path =~ m{\s\(deleted\)} || $fd eq 'DEL') {
                next if _is_whitelisted($path, $process);
                next if ($INVSERVER && ($process eq 'init') && ($pid == 1) && ($user eq 'root'));
                $processes{$user}->{$process}->{$pid} = 1;
            }
        }
    }
}

close $lsof_cmd;
if ($CHILD_ERROR) { # program failed
    $bb->color_line ('red', "$LSOF $LSOF_OPTIONS returned with non-zero exit code: ".($CHILD_ERROR / 256)."\n");

    if (!-e '/etc/sudoers.d/README') {
        $bb->color_line ('yellow',

"sudo seems to be installed, but an older version without support for files in /etc/sudoers.d/.

Please add the contents of /etc/sudoers.d/xymon to /etc/sudoers
and create /etc/sudoers.d/README to disable this warning:

  cat /etc/sudoers.d/xymon >> /etc/sudoers
  echo See /usr/lib/xymon/client/ext/libs >> /etc/sudoers.d/README

");

    }

    $bb->send;
    exit;
};

sub _is_whitelisted {
    my ($path, $process) = @_;
    foreach my $proc_name (keys %whitelist) {
        if (($proc_name eq '*' ) or ($proc_name eq $process)) {
            foreach my $regexp (@{$whitelist{$proc_name}}) {
                return 1 if $path =~ $regexp;
            }
        }
    }
    return 0;
}

sub _split_lsof_fields {
    my ($lsof_line) = @_;
    chomp $lsof_line;

    my $params_href = {};
    foreach my $option (split("\0", $lsof_line)) {
        my ($key, $value) = ($option =~ m{(.)(.*)}xms);
        $params_href->{$key} = $value;
    }
    return $params_href;
}

if (keys %processes) {
	$bb->color_line ('yellow', "The following processes have libs linked that were upgraded:\n\n". getUsers() . "\n");
} else {
	$bb->color_line ('green', "No upgraded libs linked in running processes\n");
};

$bb->add_color ('green');
$bb->send;
