#!/usr/bin/perl -wT
#
# check_snmp_procs.pl
#    Nagios script to check processes on remote host via snmp
#
# 
# Copyright (c) 2003 David Alden
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
#
# History
# -------
#  02-25-2003 - Dave Alden <alden@math.ohio-state.edu>
#    Initial creation
#
#
# TODO
# ----
#    make it work with snmp version 3
#    Suggestions???
#

require 5.004;
use strict;
use lib utils.pm ;
use Plugin;
use Plugin::Parameter qw(:DEFAULT :standard :snmp :thresholds $P_opt);

use Net::SNMP qw (oid_lex_sort oid_base_match SNMP_VERSION_1);

use vars qw($opt_authprotocol $opt_authpassword $opt_community
	    $opt_critical $opt_host $opt_oidname $opt_port $opt_privpassword
	    $opt_regexp $opt_snmp_version $opt_timeout $opt_username
	    $opt_verbose $opt_wanted_procs $opt_warning $opt_password $PROGNAME);

use utils qw(%ERRORS &print_revision &support &usage);

my $a_opt = $authprotocolparameter;
my $A_opt = $authpasswordparameter;
my $S_opt = $snmpversionparameter;
my $U_opt = $u_opt;

$H_opt->binding(\$opt_host);
my $N_opt = new Plugin::Parameter(-name => "names", -flags => [ 'N', 'names' ],
				  -optional => "no", -valueoptional => "no", -type => "processname:[minimum],...",
				  -binding => \$opt_wanted_procs,
				  -description => "Process names to check, (optional) minimum number of processes");
$a_opt->binding(\$opt_authprotocol);
$A_opt->binding(\$opt_authpassword);
$C_opt->binding(\$opt_community);
$c_opt->binding(\$opt_critical);
$c_opt->optional("yes");
my $o_opt = new Plugin::Parameter(-name => "oidname", -flags => [ 'o', 'oidname' ],
				  -optional => "yes", -valueoptional => "no", -type => "OIDNAME",
				  -binding => \$opt_oidname,
				  -default => "hrSWRunName",
				  -description => "Which oid tree to search, hrSWRunName or hrSWRunPath");
$P_opt->binding(\$opt_password);
$P_opt->flags([ 'P', 'password' ]);
$p_opt->binding(\$opt_port);
$p_opt->default("snmp");
my $r_opt = new Plugin::Parameter(-name => "regex", -flags => [ 'r', 'regexp' ],
				  -optional => "yes", -valueoptional => "yes", -type => "NONE",
				  -description => "Use regular expression match for <process>");
$S_opt->binding(\$opt_snmp_version);
$S_opt->default("snmpv2c");
$t_opt->binding(\$opt_timeout);
$U_opt->binding(\$opt_username);
$v_opt->binding(\$opt_verbose);
$w_opt->binding(\$opt_warning);
$w_opt->optional("yes");
my $plugin = new Plugin(-revision => '$Revision: 1.1 $',
			-copyright => "2003 David Alden, 2004 Howard Wilkinson <howard\@cohtech.com>",
			-shortcomment => "Check if processes are running on a host via snmp",
			-longcomment => "A CRITICAL error will be indicated unless there are at least <minimum> number of processes running (unless <minimum> is set to 0 -- useful if you don't mind that there are none of the processes running).\n\nIf no processes are specified, the program will still connect to the remote host and download the current list of running processes.  It will then exit with an OK (unless it wasn't able to connect) -- useful if you want to make sure that the remote snmpd process is running and returning a list of procs.",
			-parameterlists => [ [ $H_opt, $r_opt, $v_opt, $N_opt, $a_opt, $A_opt, $U_opt, $P_opt, $o_opt, $S_opt,
					       $C_opt, $p_opt, $t_opt, $w_opt, $c_opt ], $h_opts, $V_opts ]);

#
my $max_no_processes = 999999;
my $session;
my $error;
my $no_procs;
my $exit_status;

#
my %OIDS = (hrSWRunName => '1.3.6.1.2.1.25.4.2.1.2',
	    hrSWRunPath => '1.3.6.1.2.1.25.4.2.1.4');

my %OIDS_L = (hrSWRunName => length($OIDS{hrSWRunName}),
	      hrSWRunPath => length($OIDS{hrSWRunPath}));

#
$ENV{'PATH'}='';
$ENV{'BASH_ENV'}=''; 
$ENV{'ENV'}='';

$plugin->init();

my ($longest_wanted, @wanted_procs) = parse_wanted_procs($opt_verbose, $opt_wanted_procs, $opt_warning, $opt_critical);

$plugin->start_timeout($opt_timeout, "Timeout: No Answer from Client");

my ($longest_current, %current_process_list) = get_process_list($opt_verbose, $opt_host, $opt_username, $opt_privpassword, $opt_authprotocol, $opt_authpassword, $opt_community, $opt_port, $opt_oidname, $opt_snmp_version);

$exit_status = compare_process_list($opt_regexp, \%current_process_list, @wanted_procs);

if ($opt_verbose) {
	print_info($longest_current, \%current_process_list, $longest_wanted, @wanted_procs);
}

exit($exit_status);


#
sub compare_process_list {

	my($regexp, $current_process_list, @wanted_procs) = @_;
	my($proc, $i, $no_running_procs, @warning, @critical);
	my $exit = $ERRORS{'OK'};

	for ($i = 0; $i <= $#wanted_procs; $i++) {

		$proc = $wanted_procs[$i];

		$no_running_procs = get_running_procs($regexp, $$proc{name}, $current_process_list);

		$$proc{no_matches} += $no_running_procs;

		if (($no_running_procs >= $$proc{warn_low}) &&
		    ($no_running_procs <= $$proc{warn_high})) {

			push(@warning, $$proc{name} . "($no_running_procs)");

			if ($exit != $ERRORS{'CRITICAL'}) {
				$exit = $ERRORS{'WARNING'};
			}

		} elsif (($no_running_procs < $$proc{minimum}) ||
			 ($no_running_procs >= $$proc{critical_low}) &&
			 ($no_running_procs <= $$proc{critical_high})) {

			push(@critical, $$proc{name} . "($no_running_procs)");

			$exit = $ERRORS{'CRITICAL'};
		}
	}

	print "$PROGNAME ";

	if ($#critical >= 0) {
		print "CRITICAL:";
	} elsif ($#warning >= 0) {
		print "WARNING:";
	} else {
		print "OK:";
	}

	foreach $i (@critical) {
		print " $i";
	}

	if (($#critical >= 0) &&
	    ($#warning >= 0)) {
		print "  WARNING:";
	}

	foreach $i (@warning) {
		print " $i";
	}

	print "\n";

	return $exit;
}


#
sub get_running_procs {

	my($regex, $name, $process_list) = @_;
	my $count = 0;
	my $process;

	$count = 0;

	if ($regex) {

		foreach $process (keys %{$process_list}) {

			if ($process =~ /$name/) {
				$count += $$process_list{$process};
			}
		}


	} else {

		if (!defined($count = $$process_list{$name})) {
			$count = 0;
		}
	}

	return $count;
}


#
sub get_process_list {

	my($verbose, $host, $username, $privpassword, $authprotocol, $authpassword, $community, $port, $oidname, $snmp_version) = @_;
	my(%process_list, %process_pid_list, $result);
	my ($process_list_longest, $not_done) = (1, 1);
	my(@args, @oids, $oid, $name);

	($session, $error) = Net::SNMP->session(
		-hostname      =>  $host,
		-community     =>  $community,
		-port	       =>  $port,
		-version       =>  $snmp_version,
		defined($privpassword) ? (-privpassword  =>  $privpassword) : (),
		defined($authpassword) ? (-authpassword  =>  $authpassword) : (),
		defined($authprotocol) ? (-authprotocol  =>  $authprotocol) : (),
		defined($username)     ? (-username      =>  $username) : ());

	if (!defined($session)) {
		print ("UNKNOWN: $error\n");
		exit $ERRORS{'UNKNOWN'};
	}

	@args = (-varbindlist => [$OIDS{$oidname}]);

	if ($session->version == SNMP_VERSION_1) {

		while (defined($session->get_next_request(@args))) {

			$oid = (keys(%{$session->var_bind_list}))[0];

			last if (!oid_base_match($OIDS{$oidname}, $oid));

			$name = $session->var_bind_list->{$oid};
			$process_list{$name}++;

			if ($verbose && ($process_list_longest < length($name))) {
				$process_list_longest = length($name);
			}

			@args = (-varbindlist => [$oid]);
		}

	} else {

		push(@args, -maxrepetitions => 25);

		while ($not_done && defined($session->get_bulk_request(@args))) {

			@oids = oid_lex_sort(keys(%{$session->var_bind_list}));

			foreach $oid (@oids) {
				if (!oid_base_match($OIDS{$oidname}, $oid)) {

					$not_done = 0;

				} else {

					$name = $session->var_bind_list->{$oid};
					$process_list{$name}++;

					if ($verbose && ($process_list_longest < length($name))) {
						$process_list_longest = length($name);
					}

					if ($session->var_bind_list->{$oid} eq 'endOfMibView') {
						$not_done = 0;
					}
				}
			}

			if ($not_done) {
				@args = (-maxrepetitions => 25, -varbindlist => [pop(@oids)]);
			}
		}
	}

	if ($session->error() ne '') {
		print ("UNKNOWN: " . $session->error() . "\n");
		exit $ERRORS{'UNKNOWN'};
	}

	$session->close;

	return($process_list_longest, %process_list);
}


#
sub parse_wanted_procs {

	my($verbose, $wanted_procs, $warning, $critical) = @_;

	my(@procs, $process, $i, $critical_low, $critical_high, $warn_low, $warn_high, $process_name, $process_min);
	my(@process_array, @warn_array, @critical_array);
	my $exit = 0;
	my $longest_name = 1;

	if (defined($wanted_procs)) {
		@process_array = split(/,/, $wanted_procs);
	}

	if (defined($warning)) {
		@warn_array = split(/,/, $warning);
	}

	if (defined($critical)) {
		@critical_array = split(/,/, $critical);
	}

	if( defined($warning) && $#process_array != $#warn_array ) {

		print "$PROGNAME UNKNOWN: Error: Number of entries in process list($#process_array) and warn list($#warn_array) don't match\n";
		exit $ERRORS{'UNKNOWN'};
	}

	if( defined($critical) && $#process_array != $#critical_array ) {

		print "$PROGNAME UNKNOWN Error: Number of entries in process list and critical list don't match\n";
		exit $ERRORS{'UNKNOWN'};
	}

	for ($i = 0; $i <= $#process_array; $i++) {

		if ((($process_name, $process_min) = split(/:/, $process_array[$i])) != 2) {

			$process_min = 1;
		}

		if ($verbose && ($longest_name < length($process_name))) {

			$longest_name = length($process_name);
		}

		if (defined($critical_array[$i])) {
			if ((($critical_low, $critical_high) = split(/:/, $critical_array[$i])) != 2) {

				$critical_high = $critical_low;

			} else {

				if ($critical_high eq "") {
					$critical_high = $max_no_processes;
				}

				if ($critical_low eq "") {
					$critical_low = 0;
				}
			}
		} else {

			$critical_low = -1;
			$critical_high = -1;
		}

		if (defined($warn_array[$i])) {
			if ((($warn_low, $warn_high) = split(/:/, $warn_array[$i])) != 2) {

				$warn_high = $warn_low;

			} else {

				if ($warn_high eq "") {
					$warn_high = $max_no_processes;
				}

				if ($warn_low eq "") {
					$warn_low = 0;
				}
			}
		} else {

			$warn_low = -1;
			$warn_high = -1;
		}

		if ($critical_low > $critical_high) {
			print "Error: $process_name critical low($critical_low) is larger than high($critical_high)\n";
			$exit = 1;
		}

		if ($warn_low > $warn_high) {
			print "Error: $process_name warn low($warn_low) is larger than high($warn_high)\n";
			$exit = 1;
		}

		if (@critical_array &&
		    ($process_min > $critical_low)) {
			print "Error: $process_name minimum($process_min) is larger than critical low($critical_low)\n";
			$exit = 1;
		}

		if (@warn_array &&
		    ($process_min > $warn_low)) {
			print "Error: $process_name minimum($process_min) is larger than warn low($warn_low)\n";
			$exit = 1;
		}

		if (@warn_array && @critical_array &&
		    ((($warn_low >= $critical_low) && ($warn_low <= $critical_high)) ||
		     (($warn_high >= $critical_low) && ($warn_high <= $critical_high)))) {

			print "Error: $process_name warn levels($warn_low:$warn_high) overlap with critical levels($critical_low:$critical_high)\n";
			$exit = 1;
		}

		push(@procs,{
			name          => $process_name,
			critical      => defined($critical),
			critical_low  => $critical_low,
			critical_high => $critical_high,
			minimum	      => $process_min,
			warning       => defined($warning),
			warn_low      => $warn_low,
			warn_high     => $warn_high});
	}

	if ($exit) {
		exit $ERRORS{'UNKNOWN'};
	}

	return($longest_name, @procs);
}


#
sub print_info {

	my ($longest_current, $current_process_list, $longest_wanted, @wanted_procs) = @_;

	if ($longest_wanted < 7) {
		$longest_wanted = 7;
	} else {
		$longest_wanted++;
	}

	printf("%s---------------------------------------------\n", "-" x $longest_wanted);
	printf("|%-" . $longest_wanted . "s |      |  Min |     Warn    |   Critical  |\n", "Process");
	printf("|%-" . $longest_wanted . "s | Qty  | Procs|  Low | High |  Low | High |\n", "Name");
	printf("%s---------------------------------------------\n", "-" x $longest_wanted);

	for (my $temp=0; $temp <= $#wanted_procs; $temp++) {

		printf("|%-" . $longest_wanted . "s |%6d|%6d|%6d|%6d|%6d|%6d|\n",
			$wanted_procs[$temp]{name},
			$wanted_procs[$temp]{no_matches},
			$wanted_procs[$temp]{minimum},
			$wanted_procs[$temp]{critical_low},
			$wanted_procs[$temp]{critical_high},
			$wanted_procs[$temp]{warn_low},
			$wanted_procs[$temp]{warn_high});
	}

	printf("%s---------------------------------------------\n\n", "-" x $longest_wanted);

	if ($longest_current < 7) {
		$longest_current = 7;
	} else {
		$longest_current++;
	}

	printf("%s----------\n", "-" x $longest_current);
	printf("|%-" . $longest_current . "s |  Qty |\n", "Process");
	printf("%s----------\n", "-" x $longest_current);

	foreach my $result (sort keys %{$current_process_list}) {

		printf("|%-" . $longest_current . "s |%6d|\n", $result,
			$current_process_list{$result});
	}
	printf("%s----------\n", "-" x $longest_current);

	return;
}
