#!/usr/bin/perl -w

use strict;
use Date::Parse qw(str2time);
use Monitoring::Plugin qw(%ERRORS %STATUS_TEXT);
use LWP::UserAgent;

my %opts;
my $plugin = Monitoring::Plugin->new( usage => '',);

$plugin->add_arg(
    spec => 'warning|w=s',
    help => '',
    label => 'days old',
    default => 8,
);

$plugin->add_arg(
    spec => 'critical|c=s',
    help => '',
    label => 'days old',
    default => 14,
);

$plugin->add_arg(
    spec => 'debug|d=i',
    help => '',
    label => 'show debugging messages',
    default => 0,
);

$plugin->add_arg(
    spec => 'cache_file|f=s',
    help => '',
    label => 'path to cache file',
    default => "/var/lib/nagios/check_held_packages.txt",
);

$plugin->add_arg(
    spec => 'exclude|x=s',
    help => '',
    label => 'excluded packages separated by comma',
    default => "",
);
$plugin->getopts;

$ENV{LANG} = 'C';


my $status = 'OK';
my @status_text;
my %new_cache;
my %cache;
my @held_packages;
my @excluded_packages = split(/,/, $plugin->opts->exclude);
my $agent = LWP::UserAgent->new(env_proxy => 1);

if ( -f $plugin->opts->cache_file ) {
    open(CACHE, $plugin->opts->cache_file) or $plugin->nagios_exit($ERRORS{'CRITICAL'}, "unable to open cache file: $!");
    while(<CACHE>) {
        my ($package, $time) =  split /\s+/, $_;
        chomp($time);
        $cache{$package} = $time;
    }
    close(CACHE);

}

open(HELD_PACKAGES, 'apt-mark showhold |') or $plugin->nagios_exit($ERRORS{'CRITICAL'}, "can't get held packages: $!");
while(<HELD_PACKAGES>) {
    my $package = $_;
    $package =~ s/^\s+|\s+$|\R//g;  # Trim package name
    next if (grep(/^$package$/, @excluded_packages));
    chomp($package);
    push @held_packages, $package;
    if (open(PACKAGE_POLICY, "apt-cache policy \"$package\" |")) {
        my %versions;
        while (<PACKAGE_POLICY>) {
            if ( m/^\s*(Installed|Candidate)\s*:\s*(.+)$/i ) {
                $versions{lc($1)} = $2;
            }
        }
        close(PACKAGE_POLICY);
        unless ( $versions{installed} && $versions{candidate} ) {
            $status = 'CRITICAL';
            push @status_text, "$package: missing installed/candidate versions";
            next;
        }
        if ( $versions{installed} ne $versions{candidate} ) {
            `dpkg --compare-versions "$versions{candidate}" gt "$versions{installed}"`;
            if ( $? == 0 ) {
                print "DEBUG - package \"$package\" / ".$versions{installed}." / ".$versions{candidate}.", cache: ".($cache{$package}|| '')."\n" if ( $plugin->opts->debug );
                unless ( $cache{$package} ) {
                    my $candidate_time;
                    # Try to get time from remote file
                    my $candidate_uri = `apt-get download --print-uris "$package=$versions{candidate}" | cut -d"'" -f2 `;
                    chomp($candidate_uri);
                    print "DEBUG: URI $candidate_uri\n" if ( $plugin->opts->debug );
                    if ( $candidate_uri && $candidate_uri =~ m!^https?://! ) {
                        my $response = $agent->head($candidate_uri);
                        if ( $response && $response->is_success && $response->header('Last-Modified') ) {
                            my $candidate_date = $response->header('Last-Modified');
                            $candidate_time = str2time($candidate_date);
                            print "DEBUG: found date $candidate_date ($candidate_time) for $package=$versions{candidate}\n" if ( $plugin->opts->debug );
                        } else {
                            print "DEBUG: unable to get candidate release date: $!\n" if ( $plugin->opts->debug );
                        }
                    }
                    $cache{$package} = $candidate_time || 0;
                }
                my $age = time - $cache{$package};
                if ( $age >= ($plugin->opts->critical * 86400) ) {
                    $status = 'CRITICAL';
                    if ($cache{$package} == 0) {
                        push @status_text, "$package has update available since (UNKNOWN)";
                    } else {
                        push @status_text, "$package has update available since ".int($age / 86400)." day(s)";
                    }
                } elsif ( $age >= ($plugin->opts->warning * 86400) ) {
                    $status = 'WARNING' unless ( $status eq 'CRITICAL');
                    if ($cache{$package} == 0) {
                        push @status_text, "$package has update available since (UNKNOWN)";
                    } else {
                        push @status_text, "$package has update available since ".int($age / 86400)." day(s)";
                    }
                }
                $new_cache{$package} = $cache{$package};
            } elsif ( $? == 1 ) {
                print "DEBUG - package \"$package\": installed version is newer than available version\n" if ( $plugin->opts->debug );
            } else {
                $status = 'CRITICAL';
                push @status_text, "$package: error comparing versions: $!";
            }
        } else {
            print "DEBUG - package \"$package\": no new version available\n" if ( $plugin->opts->debug );
        }
    } else {
        $status = 'CRITICAL';
        push @status_text, "$package: unable to get policy: $!";
    }

}
close(HELD_PACKAGES);

open(CACHE, "> ".$plugin->opts->cache_file) or $plugin->nagios_exit($ERRORS{'CRITICAL'}, join(', ', @status_text, "Can't update cache file: $!"));
foreach my $package (sort { $a cmp $b } keys %new_cache) {
    print CACHE "$package ".$new_cache{$package}."\n";
}
close(CACHE);

unless ( scalar(@status_text) ) {
    push @status_text, scalar(@held_packages)." held package(s)";
}

$plugin->nagios_exit($ERRORS{$status}, join(', ', @status_text));
