#!/usr/bin/perl -w

=encoding utf8

=head1 NAME

yum - Munin plugin to monitor pending update packages.
It will send/show a critical warning, if security updates are pending.

=head1 APPLICABLE SYSTEMS

Linux distributions that use yum as software package management tool.

=head1 CONFIGURATION

The plugin will not show up in the Munin website per default
as it contains information that is useful for any attacker.

That is done by the following config directive:

=over 2

# munin-run yum config

B<graph no>

=back

If you have password protected your Munin website and trust
your known visitors you can make graph and more information
visible by setting the following environment variables in the plugins configuration.

=over 2

shownames - set to value 1, if you want to see the names of the pending packages in the field 'pending.extinfo'

showgraph - set to value 'yes', if you want the plugins graph to show up in Munin website.
            Remove or comment the declaration of env.showgraph in plugin-conf.d if you want to
            hide the plugins graph again.

=back

B<Example>

=over 2

  [yum]
  env.showgraph yes
  env.shownames 0

=back

=head2 SPECIAL YUM PLUGIN NEEDED

Package yum-plugin-security is needed as this Munin plugin investigates
how many security updates are pending.

Install the package with the command:

# yum install yum-plugin-security

=head2 CRONJOB

A cronjob has to be run outside of Munin to create and freshen up the
plugins statefile with recent info from yum.
Therein the plugin is called with the 'update' option.

E.g. place a file with the following line in directory /etc/crond.d/:

  # Do not start at the same time like munin-cron,
  # as both will access the plugins statefile
  38 */6 * * * root /usr/sbin/munin-run yum update

=head1 AUTHOR

Copyright 2006 Dagfinn Ilmari Mannsåker <ilmari@lonres.com>

Copyright 2014 Gabriele Pohl <contact@dipohl.de>

=head1 LICENSE

GPLv2

=head1 MAGIC MARKERS

 #%# family=manual
 #%# capabilities=autoconf

=cut

use strict;

my $counter=0;
my $statefile = "$ENV{MUNIN_PLUGSTATE}/yum.state";

sub update {
    if (-l $statefile) {
    die "$statefile is a symlink, not touching.\n";
    }

    open my $state, '>', $statefile
    or die "Can't open $statefile for writing: $!\n";

    # Get number of security updates
    open my $yum, '-|', 'yum -q --security list updates'
    or die "Can't run 'yum -q --security list updates': $!";

    while (<$yum>) {
        next unless /^(\S+)\.\S+\s+\S+\s+\S+/;
        $counter++;
    }
    print $state "$counter pending security updates\n";
    close $yum or die "Error running 'yum -q --security list updates': $!\n";

    open $yum, '-|', 'yum -q list updates'
    or die "Can't run 'yum -q list updates': $!";

    while (<$yum>) {
    next unless /^(\S+)\.\S+\s+\S+\s+\S+/;
    print $state "$1\n";
    }

    close $yum or die "Error running 'yum -q list updates': $!\n";
    close $state or die "Error writing $statefile: $!\n";
}

sub autoconf {
    if (system('yum --version >/dev/null 2>/dev/null') != 0) {
        print "no (Could not run yum)\n";
    }
    elsif (! -r $statefile) {
        print "no (Could not find statefile.  Please read 'munindoc yum')\n";
    }
    else {
        print "yes\n";
    }
    exit 0;
}

sub config {

    my $showgraph = exists $ENV{'showgraph'} ? 'yes' : 'no';

    print "graph_title Pending Updates\n";
    print "graph_category security\n";
    print "graph ", $showgraph, "\n";
    print "security.label security\n";
    print "security.info Number of pending security updates\n";
    print "security.draw AREA\n";
    print "security.colour COLOUR2\n";
    print "security.critical 0\n";
    print "pending.label pending\n";
    print "pending.info Number of all pending updates (including security updates)\n";
    print "pending.draw LINE2\n";
    print "pending.warning 20\n";

}

sub report {
    my @packages;

    open my $state, '<', $statefile
    or die "Can't open $statefile for reading: $!
Please read 'munindoc yum' to understand why if the file does not exist.\n";

    # read number of security updates in first line
    my $line = <$state>;
    if ($line =~ /^(\d+) pending security updates$/) {
        print 'security.value ', $1, "\n";
    } else {
        die "Missing info about pending security updates in plugins statefile\n";
    }

    chomp(@packages = <$state>);
    close $state;

    print 'pending.value ', scalar(@packages), "\n";

    if (exists $ENV{'shownames'}) {
        print 'pending.extinfo ', join(' ', @packages), "\n"
        if (@packages && ($ENV{'shownames'} eq 1));
    }
}

if ($ARGV[0]) {
    my $arg = $ARGV[0];
    my %funcs = (
        update   => \&update,
        config   => \&config,
        autoconf => \&autoconf,
    );

    if (exists $funcs{$arg}) {
    $funcs{$arg}->();
    } else {
    die "Unknown argument '$arg'\n";
    }
} else {
    report();
}
