#!/usr/bin/env perl

# Parse the output of cfshow to detect problems with regular cfengine updates
# on any of our machines.  Cfshow uses /var/cfengine/inputs/hosts.192_160_0 
# to determine last seen times, so add any machines which don't 
# (and shouldn't!) have a cfengine install to the exclude hash. 
# $Id: nmi_cfshow_parser.pl,v 1.6 2007/05/25 18:45:29 tlmiller Exp $ 

use Getopt::Long;
use Mail::Mailer;
use DBI;

use lib $ENV{'NMI_LIB'} || "/nmi/lib";
use NmiConf;
use NmiTools;
my $nmiconf = $NmiConf::NMICONF_DEFAULT if -f $NmiConf::NMICONF_DEFAULT;

if (defined $main::opt_nmiconf) {
  die "Config file ${main::opt_nmiconf} does not exist. Exiting...\n"
    unless -f $main::opt_nmiconf;
  $nmiconf = $main::opt_nmiconf;
}

die "FATAL: $0: No config file specified.\n" unless defined $nmiconf;
die "FATAL: $0: Config file $nmiconf is not readable.\n" unless -r $nmiconf;

## ---------------------------------------------
## Load NMI Configuration
## ---------------------------------------------

my %conf = NmiConf::init($main::opt_nmiconf);

## ---------------------------------------------
## Database Connection
## ---------------------------------------------

my $DB_HOST = $conf{'DB_HOST'};
my $DB_PORT = $conf{'DB_PORT'};
my $DB_NAME = $conf{'DB_NAME'};
my $DB_READER_USER = $conf{'DB_READER_USER'};
my $DB_READER_PASS = $conf{'DB_READER_PASS'};


my %answered;
my %hailed;
my %exclude_list;

my $cfshow_file = "/tmp/cfshow-1";
my $cmd = "/usr/sbin/cfshow -s > $cfshow_file";
`$cmd`; 

GetOptions('mailto=s' => \$main::opt_mailto,
           'hours=s'  => \$main::opt_hours,
           'help'     => \$main::opt_help);

if (defined $main::opt_help) {
  print <<"END";
usage: $0 [--mailto=<addrList>] [--hours=<hours, defaults to 2>] [--help]
END
  exit 0;
}

if (defined($opt_hours)) {
  $hours = $opt_hours;
} else {
  $hours = 2;
}

if (defined($opt_mailto)) {
  $mailto = $opt_mailto;
} else {
  die "You must specify an address for mailto\n";
}                             

&populate_exclude_list();
&get_valid_machines();
&find_status();
&send_report();

sub populate_exclude_list {
  # These are machines that cfengine isn't running on, for various reasons.
  $exclude{"198.51.254.109"} = "grandcentral.cs.wisc.edu"; # grandcentral public addr
  $exclude{"198.51.254.201"} = "nmi-build11"; # not sure what this machine is for
  $exclude{"198.51.254.203"} = "nmi-build13"; # not sure what this machine is for
  $exclude{"192.168.0.1"} = "gateway.cs.wisc.edu"; # cfengine not installed
  $exclude{"192.168.0.3"} = "rebbie.cs.wisc.edu"; # W2k
  $exclude{"192.168.0.9"} = "janet.cs.wisc.edu"; # W2k
  $exclude{"192.168.0.26"} = "george.cs.wisc.edu"; # currently down
  $exclude{"192.168.0.43"} = "nmi-build41"; # W2k
  $exclude{"192.168.0.53"} = "nmi-hpux"; # cfengine not installed
  $exclude{"192.168.0.54"} = "nmi-tru64"; # cfengine not installed
}

sub get_valid_machines {

  # Fetch list of hosts.
  my @hosts;
  my $dbh = DBI->connect("DBI:mysql:database=infrastructure;host=localhost", 'web_user', '');
  unless (defined $dbh) {
      add($report, "warning: mysql connect() failed: $DBI::errstr");
      return;
  }

  my $sth = $dbh->prepare('select hostname from machine, project where machine.project_id=project.project_id and project.project_id=9 and allok=1;');
  $sth->execute || print "warning: failed to execute a query: $DBI::errstr";

  while ( my $row = $sth->fetchrow_hashref() ) {
      my $hostname = $row->{'hostname'};
      $hostname = "$hostname" . ".cs.wisc.edu";
      $hosts{"$hostname"} = "";
  }
  $sth->finish();
  $dbh->disconnect;
}

sub find_status {
  open ( STATUS, "<$cfshow_file") || die "Can't open  $cfshow_file: $!\n";
  while ( <STATUS> ) {
    if ( /hailed/ ) {
      @tmp = split (" ", $_);
      $ip = @tmp[0];
      $lastseen = @tmp[13];
      $lastseen =~ s/!//g;  

      if ( $lastseen > $hours ) {
        if ( $ip =~ /cs/  && exists($hosts{$ip}) ) {
          $hailed{$ip} = $lastseen;
        } elsif ( $ip =~ /192/ ) {
          $hailed{$ip} = $lastseen;
        }
      }     
    }
  }
  close STATUS;
  # Remove all entries from the array if they are on the exclude list
  foreach $ip (keys %exclude) {
    delete $hailed{$ip};
  }
}

sub send_report {

  my @rcpts = $mailto;
  my $mailer = Mail::Mailer->new;
  my $username = `whoami`;
  chomp $username;

  print "Sending email report to: ", join ' ', @rcpts, "\n";
  $mailer->open( {To => [@rcpts],
                  From => "$username",
                   Subject => "Hosts with cfengine lastseen time > $hours hours"});

  print $mailer "Ignore any malformed IPs or entries in the list below. \n";
  print $mailer "HAILED                        lastseen\n";
  foreach $ip (keys %hailed) {
    $lastseen = $hailed{$ip};
    printf $mailer "%-30s %-10s", $ip, $lastseen;
    printf $mailer "\n";
  }   

  $mailer->close;
}

