retire da-backup checks
[mirror/dsa-nagios.git] / dsa-nagios-checks / checks / dsa-check-packages
index 29e7e4d..03af4ec 100755 (executable)
@@ -38,6 +38,7 @@ use warnings;
 use English;
 
 my $IGNORE = "/etc/nagios/obsolete-packages-ignore";
+my $IGNORED = "/etc/nagios/obsolete-packages-ignore.d";
 
 my %CODE = (
        'OK'            => 0,
@@ -65,57 +66,108 @@ sub get_packages {
        close(F);
        chomp(@lines);
 
-       shift @lines while ($lines[0] !~ /\+\+\+/);
-       shift @lines;
+       my $line;
+       my $has_arch = 0;
+       while (defined($line = shift @lines) && ($line !~ /\+\+\+/)) {
+               if ($line =~ /Architecture/) { $has_arch = 1; }
+       }
 
        my %pkgs;
-       for my $line (@lines) {
-               my ($state, $pkg, $version, undef) = split(/  */, $line);
-               $pkgs{$state}{$pkg} = { 'installed' => $version }
+       for $line (@lines) {
+               my ($state, $pkg, $version, $arch, undef) = split(/  */, $line);
+               $arch = '' unless $has_arch;
+               $pkgs{$state}{$pkg} = { 'installed' => $version, arch => $arch }
        }
 
        my $installed = $pkgs{'ii'};
        delete $pkgs{'ii'};
 
+       my @installed_packages = keys(%$installed);
+       my @cmd = ("apt-cache", "policy", @installed_packages);
+
        open my $olderr, ">&STDERR"   or die "Can't dup STDERR: $!";
        open     STDERR, ">/dev/null" or die "Can't dup STDOUT: $!";
-
-       open (F, "apt-cache policy ".(join(" ", keys(%$installed)))." |") or die ("Cannot run apt-cache policy: $!\n");
+       open (F, "-|", @cmd) or die ("Cannot run apt-cache policy: $!\n");
        @lines = <F>;
        close(F);
-       chomp(@lines);
        open STDERR, ">&", $olderr  or die "Can't dup OLDERR: $!";
+       chomp(@lines);
 
-       my $line;
        my $pkgname = undef;
+       my $candidate_found = 0;
        while (defined($line = shift @lines)) {
                if ($line =~ /^([^ ]*):$/) {
+                       # when we have multi-arch capable fu, we require that
+                       # apt-cache policy output is in the same order as its
+                       # arguments.
+                       #
+                       # We need this, because the output block in apt-cache
+                       # policy does not show the arch:
+                       #
+                       # | weasel@stanley:~$ apt-cache policy libedit2:amd64
+                       # | libedit2:
+                       # |   Installed: 2.11-20080614-5
+                       # |   Candidate: 2.11-20080614-5
+                       #
+                       # We replace the package name in the output with the
+                       # one we asked for ($pkg:$arch) - but to match this up
+                       # sanely we need the order to be correct.
+                       #
+                       # For squeeze systems (no m-a), apt-cache policy output
+                       # is all different.
                        $pkgname = $1;
+                       $candidate_found = 0;
+                       if ($has_arch) {
+                               my $from_list = shift @installed_packages;
+                               next if ($pkgname eq $from_list); # no :$arch in pkgname we asked for
+
+                               my $ma_fix_pkgname = $pkgname.':'.$installed->{$from_list}->{'arch'};
+                               my $ma_fix_from_list = $from_list.':'.$installed->{$from_list}->{'arch'};
+
+                               if ($pkgname eq $ma_fix_from_list || # e.g. ia32-libs-i386.  dpkg -l: ia32-libs-i386, apt-cache policy: ia32-libs-i386:i386
+                                   $ma_fix_pkgname eq $from_list) {
+                                       $pkgname = $from_list;
+                               } else {
+                                       die "Unexpected order mismatch in apt-cache policy output (apt-cache policy name: $pkgname - dpkg -l name: $from_list)\n";
+                               }
+                       }
                } elsif ($line =~ /^ +Installed: (.*)$/) {
                        # etch dpkg -l does not print epochs, so use this info, it's better
                        $installed->{$pkgname}{'installed'} = $1;
+                       # initialize security-update
+                       $installed->{$pkgname}{'security-update'} = 0;
                } elsif ($line =~ /^ +Candidate: (.*)$/) {
                        $installed->{$pkgname}{'candidate'} = $1;
+               } elsif ($line =~ /     ([^ ]+) [0-9]+/) {
+                       # check if the next lines show the sources of our candidate
+                       if ($1 eq $installed->{$pkgname}{'candidate'}) {
+                               $candidate_found = 1;
+                       }
+               } elsif (($line =~ / +[0-9]+ [^ ]+\/(security\.([^ ]+\.)?debian\.org|debian-security).*\/updates\//) && $candidate_found ) {
+                       $installed->{$pkgname}{'security-update'} = 1;
                } elsif ($line =~ /^ +\*\*\*/) {
-                       my @l;
-                       @l = split(/ +/, $line);
                        $line = shift @lines;
-                       @l = split(/ +/, $line);
+                       my @l = split(/ +/, $line);
                        $installed->{$pkgname}{'origin'} = $l[2];
+                       $candidate_found = 0;
                }
        }
 
-       my (%current, %obsolete, %outofdate);
+       my (%current, %obsolete, %outofdate, %security_outofdate);
        for my $pkgname (keys %$installed) {
                my $pkg = $installed->{$pkgname};
 
                unless (defined($pkg->{'candidate'}) && defined($pkg->{'origin'})) {
-                        $obsolete{$pkgname} = $pkg;
-                        next;
-              }
-                       
+                       $obsolete{$pkgname} = $pkg;
+                       next;
+               }
+
                if ($pkg->{'candidate'} ne $pkg->{'installed'}) {
-                       $outofdate{$pkgname} = $pkg;
+                       if ($pkg->{'security-update'}) {
+                               $security_outofdate{$pkgname} = $pkg;
+                       } else {
+                               $outofdate{$pkgname} = $pkg;
+                       }
                        next;
                };
                if ($pkg->{'origin'} eq '/var/lib/dpkg/status') {
@@ -127,21 +179,39 @@ sub get_packages {
 
        $pkgs{'current'} = \%current;
        $pkgs{'outofdate'} = \%outofdate;
+       $pkgs{'security_outofdate'} = \%security_outofdate;
        $pkgs{'obsolete'} = \%obsolete;
        return \%pkgs;
 }
 
 sub load_ignores {
-       my ($ignorefile, $require_file) = @_;
+       my ($ignorefiles, $require_file) = @_;
 
        my @ignores;
-       if (!$require_file and ! -e $ignorefile) {
-               return \@ignores;
-       }
 
-       open (F, "< $ignorefile") or die ("Cannot open $ignorefile: $!\n");
-       @ignores = <F>;
-       close F;
+       for my $ignoreitem (@$ignorefiles) {
+               next if (!$require_file and ! -e $ignoreitem);
+
+               my @filestoopen;
+               if (-d $ignoreitem) {
+                       opendir(DIR, $ignoreitem) or die ("Cannot open dir $ignoreitem: $!\n");
+                       @filestoopen = readdir(DIR);
+                       closedir(DIR);
+
+                       @filestoopen = grep { -f ($ignoreitem.'/'.$_) } @filestoopen;
+                       @filestoopen = grep { /^([a-z0-9_.-]+)+[a-z0-9]+$/i } @filestoopen;
+                       @filestoopen = grep { !/dpkg-(old|dist|new|tmp)$/ } @filestoopen;
+                       @filestoopen = map { ($ignoreitem.'/'.$_) } @filestoopen;
+               } else {
+                       push @filestoopen, $ignoreitem;
+               }
+
+               for my $f (@filestoopen) {
+                       open (F, "< $f") or die ("Cannot open $f: $!\n");
+                       push @ignores, <F>;
+                       close F;
+               }
+       }
        chomp(@ignores);
        return \@ignores;
 }
@@ -182,21 +252,20 @@ sub filter_ignored {
 
 sub usage {
        my ($fd, $exit) = @_;
-       print $fd "Usage: $PROGRAM_NAME [<ignorefile>]\n";
+       print $fd "Usage: $PROGRAM_NAME [<ignorefile|dir> [<ignorefile|dir> ...]]\n";
        exit $exit;
 }
 
-my $ignorefile = $IGNORE;
+my $ignorefiles = [$IGNORE, $IGNORED];
 my $ignorefile_userset = 0;
-usage(\*STDERR, 1) if (@ARGV > 1);
-if (@ARGV == 1) {
+if (@ARGV >= 1) {
        usage(\*STDOUT, 0) if ($ARGV[0] eq "-h");
        usage(\*STDOUT, 0) if ($ARGV[0] eq "--help");
-       $ignorefile = $ARGV[0];
        $ignorefile_userset = 1;
+       $ignorefiles = \@ARGV;
 };
 
-my $ignores = load_ignores($ignorefile, $ignorefile_userset);
+my $ignores = load_ignores($ignorefiles, $ignorefile_userset);
 my $packages = get_packages();
 
 filter_ignored($packages, $ignores);
@@ -207,46 +276,61 @@ my @reportform = (
        { 'key' => 'obsolete',
          'listpackages' => 1,
          'long' => "%d local or obsolete packages: %s",
-         'short' => "%d obs",
+         'short' => "%d obs/loc",
+         'perf' => "obs_loc=%d;1;5;0",
          'status' => 'WARNING' },
        { 'key' => 'outofdate',
          'listpackages' => 1,
          'long' => "%d out of date packages: %s",
          'short' => "%d updates",
+         'perf' => "outdated=%d;1;5;0",
          'status' => 'WARNING' },
        { 'key' => 'current',
          'listpackages' => 0,
          'long' => "%d packages current.",
          'short' => "%d ok",
+         'perf' => "current=%d;;;0",
          'status' => 'OK' },
        { 'key' => 'obsolete-ignored',
          'listpackages' => 1,
          'long' => "%d whitelisted local or obsolete packages: %s",
-         'short' => "%d obs(ignored)",
+         'short' => "%d obs/loc(ignored)",
+         'perf' => "obs_ign=%d;;;0",
          'status' => 'OK' },
        { 'key' => 'rc',
          'listpackages' => 1,
          'long' => "%d packages removed but not purged: %s",
          'short' => "%d rc",
+         'perf' => "rm_unprg=%d;;;0",
          'status' => 'OK' },
        { 'key' => 'hi',
          'listpackages' => 1,
          'long' => "%d packages on hold: %s",
          'short' => "%d hi",
+         'perf' => "hold=%d;;;0",
          'status' => 'OK' },
        { 'key' => 'pc',
          'listpackages' => 1,
          'long' => "%d packages requested to be purged but conffiles still installed: %s",
          'short' => "%d pc",
+         'perf' => "prg_conf=%d;1;;0",
          'status' => 'WARNING' },
+       { 'key' => 'security_outofdate',
+         'listpackages' => 1,
+         'long' => "%d packages with outstanding security updates: %s",
+         'short' => "%d security-updates",
+         'perf' => "security_outdated=%d;;1;0",
+         'status' => 'CRITICAL' },
        );
 
 my @longout;
+my @perfout;
 my @shortout;
 for my $form (@reportform) {
        my $pkgs = $packages->{$form->{'key'}};
        delete $packages->{$form->{'key'}};
        my $num = scalar keys %$pkgs;
+       push @perfout, sprintf($form->{'perf'}, $num);
        next unless ($num > 0);
        if ($form->{'listpackages'}) {
                my $list = join(", ", keys %$pkgs);
@@ -269,8 +353,10 @@ if (scalar keys %$packages) {
 
 my $shortout = $EXITCODE.": ".join(", ", @shortout);
 my $longout = join("\n", @longout);
+my $perfout = "|".join(" ", @perfout);
 
 print $shortout,"\n";
 print $longout,"\n";
+print $perfout,"\n";
 
 exit $CODE{$EXITCODE};