#!/usr/bin/perl -w
use strict;
use warnings;
use Getopt::Long;
use File::Basename;

# deal with unexpected problems.
$SIG{__DIE__} = sub { print "@_"; exit 3; };

GetOptions(
  "r|required=s"    => \( my $required_list ),
  "p|prohibited=s"  => \( my $prohibited_list ),
  "l|local=s"       => \( my $local_list ),
  "h|help"          => \&usage,
) || die usage();

my %required   = get_packages($required_list);
my %prohibited = get_packages($prohibited_list);
my %local      = get_packages($local_list);

my %counts;
++$counts{$_} for (keys %required, keys %prohibited);
my @dups = grep { $counts{$_} > 1 } keys %counts;

my @pro_installed;

for my $package (installed_packages()) {
  delete $required{$package};
  if ($prohibited{$package} && !$local{$package}) { push (@pro_installed, $package) };
}

my $message;
my $exit_code = (%required || @dups || @pro_installed) ? 2 : 0;

$message .= "Missing - " . join(" ", keys %required) . " " if %required;
$message .= "Config Error - @dups "                        if @dups;
$message .= "Prohibited - @pro_installed "                 if @pro_installed;

$message = "OK: Packages are OK." unless $message;

print "$message\n";
exit $exit_code;

###################### Subs and functions ######################

sub installed_packages {
  # delegate to the subroutine that knows about the systems packages
  return installed_redhat_packages() if -e '/etc/redhat-release';
  return installed_debian_packages() if -e '/etc/debian_version';
  die "I don't know about this systems package manager...\n";
}

#--------------------------------------------------------------#

sub installed_redhat_packages {
  # very simplistic but it works.
  my @packages = `rpm -qa --qf '%{NAME}\n'`;
  die "Couldn't run rpm -qa --qf '%{NAME}\n' \n" if $?;
  chomp @packages;
  return @packages;
}

#--------------------------------------------------------------#

sub installed_debian_packages {
  require Parse::Debian::Packages;
  my $pkgfile = '/var/lib/dpkg/status';
  my @packages;

  open (my $pkg_fh, $pkgfile)
    || die "Failed to open $pkgfile: $!\n";

  my $parser = Parse::Debian::Packages->new( $pkg_fh );

  # we assume that packages are not pending. State isn't checked.
  while (my %data = $parser->next) {
    my @status = split(/\s+/, $data{Status});
    push(@packages, $data{Package}) if $status[0] eq 'install';
  }

  return @packages;
}

#--------------------------------------------------------------#

sub get_packages {
  my $file = shift;
  return unless $file;

  open(my $file_fh, $file)
    || die "Failed to open package $file: $!\n";

  my %packages = map { chomp; $_ => 1 } grep(!/^#/, <$file_fh>);

  close $file_fh;

  return %packages;
}

#--------------------------------------------------------------#

sub usage {
  my $app = basename($0);

  print<<EOU;

$app - Copyright (c) Dean Wilson. Licensed under the GPL

This program reads in lists of packages that are either required or
prohibited. It then checks the package management system for their
prescense and prints any violations.

It also allows a local override file (that is the final file processed) so
you can have a blanket "prohibited" list and allow certain packages on a
host-by host failiure (such as gcc on a build machine).

Usage Examples:
  $app -h (shows this information)

  # check required packages are installed
  $app -r required.packages

  # check for required and prohibited packages, and allow local overides
  $app -r required.pkgs -p prohibited.pkgs -l hostname.pkgs

Optional Options:
  -l | -local
    A file listing packages that must be installed on this host.
  -r | -required
    A file listing packages that must be installed on this host.
  -p | -prohibited
    A file listing packages that must not be installed on this host.
  -h
    This help and usage information

EOU
  exit 1;
}

#--------------------------------------------------------------#
