#!/usr/bin/perl -w
# nagios: -epn
#
# Copyright (c) 2012, SUSE Linux Products GmbH
# Author: Lars Vogdt <lrupp@suse.de>
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# * Neither the name of the Novell nor the names of its contributors may be
#   used to endorse or promote products derived from this software without
#   specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#

use strict;
use Net::SNMP;
use Getopt::Long;
use Net::Ping;
use Pod::Usage;
use bignum qw/hex/;
Getopt::Long::Configure('bundling');

# cleanup the environment
$ENV{'PATH'}     = '/bin:/usr/bin:/sbin:/usr/sbin:';
$ENV{'BASH_ENV'} = '';
$ENV{'ENV'}      = '';

our $conf = {
        'VERSION'       => '1.1',
        'PROGNAME'      => 'check_qlogic_sanbox',
        'timeout'       => '60',
        'critical'      => '45',
        'warning'       => '35',
        'debug'         => 0,
    	'hostname'	=> 'localhost',
        'community'     => 'public',
        'snmp_version'  => '2c',
        'snmp_port'     => '161',
};

our $print_version = 0;
our $print_help    = 0;
our $exitcode      = 0;
our $output        = '';

# The Name_Identifier of the Fabric Element
our $fcFeElementName = '.1.3.6.1.2.1.75.1.1.2.0';
# The basic information about the Fabric Element
our $base_table_query = '.1.3.6.1.2.1.1';

our %ERRORS        = (
    'OK'        => 0,
    'WARNING'   => 1,
    'CRITICAL'  => 2,
    'UNKNOWN'   => 3,
    'DEPENDENT' => 4
);

our %BASE_OIDs = (
	'sysDescr' 	=> '.1.3.6.1.2.1.1.1.0',
	'sysUpTime'	=> '.1.3.6.1.2.1.1.3.0',
	'sysName'	=> '.1.3.6.1.2.1.1.5.0',
	'sysContact'	=> '.1.3.6.1.2.1.1.4.0',
	'sysLocation'	=> '.1.3.6.1.2.1.1.6.0',
);

#######################################################################
# Functions
#######################################################################

sub snmp_get_table($$$){
	my ($conf,$session,$query) = @_;
	my ($answer,$response);
	if ( ! defined ( $response = $session->get_table($query))){
       		$answer=$session->error;
	        $session->close;
	        print "CRITICAL: $response with SNMP version $conf->{'snmp_version'}\n";
	        exit $ERRORS{'CRITICAL'};
	}
	return $response;
}

sub snmp_get_request($$$){
	my ($conf,$session,$query) = @_;
	my ($answer,$response);
	if ( ! defined ( $response = $session->get_request($query))){
		$answer=$session->error;
		$session->close;
		print "CRITICAL: $response with SNMP version $conf->{'snmp_version'}\n";
		exit $ERRORS{'CRITICAL'};
	}
	return($response->{$query});
}

sub DEBUG($) {
    my ($output) = @_;
    print "DEBUG:  $output\n" if ( $conf->{'debug'} );
}

sub print_myrevision ($$) {
    my ($commandName,$pluginRevision) = @_;
    print "$commandName v$pluginRevision\n";
}

#######################################################################
# Main
#######################################################################

Getopt::Long::Configure('bundling');
GetOptions(
    "H=s"           => \$conf->{'hostname'},
    "hostname=s"    => \$conf->{'hostname'},
    "f=s"           => \$conf->{'filename'},
    "filename=s"    => \$conf->{'filename'},
    "v"             => \$print_version,
    "version"       => \$print_version,
    "h"             => \$print_help,
    "help"          => \$print_help,
    "d"             => \$conf->{'debug'},
    "debug"         => \$conf->{'debug'},
    "w=f"           => \$conf->{'warning'},
    "warning=f"     => \$conf->{'warning'},
    "c=f"           => \$conf->{'critical'},
    "critical=f"    => \$conf->{'critical'},
    "t=i"           => \$conf->{'timeout'},
    "timeout=i"     => \$conf->{'timeout'},
    "C=s"           => \$conf->{'community'},
    "community=s"   => \$conf->{'community'},
    "p=i"           => \$conf->{'snmp_port'},
    "port=i"        => \$conf->{'snmp_port'},
    "V=s"           => \$conf->{'snmp_version'},
    "snmp-version=s"=> \$conf->{'snmp_version'},
) or pod2usage(2);

pod2usage(
    -exitstatus => 0,
    -verbose    => 2,    # 2 to print full pod
) if $print_help;

# Just in case of problems, let's not hang Nagios
$SIG{'ALRM'} = sub {
    print "UNKNOWN: No snmp response from ".$conf->{'hostname'}." (alarm timeout)\n";
    exit $ERRORS{'UNKNOWN'};
};
alarm( $conf->{'timeout'} );

if ($print_version) {
    print_myrevision( $conf->{'PROGNAME'}, $conf->{'VERSION'} );
    exit $ERRORS{'OK'};
}

#
# Check the given options...
#
if (defined($conf->{'filename'})){
    use Config::IniFiles;
    my $ini = new Config::IniFiles( -file => "$conf->{'filename'}");
    if( ! $ini ){
        print "ERROR: Could not open $conf->{'filename'} : $!\n";
        exit $ERRORS{'UNKNOWN'};
    }
    if (! $ini->SectionExists($conf->{'hostname'})){
        print "ERROR: Could not find section [$conf->{'hostname'}] in $conf->{'filename'}\n";
        exit $ERRORS{'UNKNOWN'};
    }
    $conf->{'community'}    = $ini->val($conf->{'hostname'}, 'community')    if (defined($ini->val($conf->{'hostname'},'community')));
    $conf->{'snmp_version'} = $ini->val($conf->{'hostname'}, 'snmp_version') if (defined($ini->val($conf->{'hostname'},'snmp_version')));
    $conf->{'snmp_port'}    = $ini->val($conf->{'hostname'}, 'snmp_port')    if (defined($ini->val($conf->{'hostname'},'snmp_port')));
    $conf->{'warning'}      = $ini->val($conf->{'hostname'}, 'warning')      if (defined($ini->val($conf->{'hostname'},'warning')));
    $conf->{'critical'}     = $ini->val($conf->{'hostname'}, 'critical')     if (defined($ini->val($conf->{'hostname'},'critical')));
}

if ( !defined($conf->{'hostname'}) ) {
    print "ERROR - no hostname given\n";
    pod2usage(2);
    alarm(0);
    exit $ERRORS{'UNKNOWN'};
}
if ($conf->{'warning'} gt $conf->{'critical'}){
    print "ERROR: warning level ($conf->{'warning'}) should not be greater than critical level ($conf->{'critical'})\n";
    pod2usage(2);
    alarm(0);
    exit $ERRORS{'UNKNOWN'};
}

if ($conf->{'debug'}){
    use Data::Dumper;
    print STDERR "Internal configuration:\n".Data::Dumper->Dump([$conf]);
}

#
# Basic defines / checks
#
my $ret_str='UNKNOWN';
my $error='UNKNOWN';
my $session;

# First try to ping the Sanbox, this gives faster results
my $p = Net::Ping->new();
if (! $p->ping($conf->{'hostname'}) ){
    print "CRITICAL: could not ping your Sanbox ($conf->{'hostname'})\n";
    exit $ERRORS{'CRITICAL'};
}
$p->close();

#
# Prepare SNMP Session
#
($session, $error) = Net::SNMP->session(
    -hostname  => $conf->{'hostname'},
    -community => $conf->{'community'},
    -port      => $conf->{'snmp_port'},
    -version   => $conf->{'snmp_version'},
);
if (!defined($session)){
    print "ERROR: $error";
    exit $ERRORS{'UNKNOWN'};
}

#
# Query SNMP informations from the single switch
#
# first get the WWPN of the SANBox and convert it into octal number
my $WWPN=snmp_get_request($conf,$session,$fcFeElementName);
my @array = unpack ("C*", pack ("H*", $WWPN));
my $sanbox_id=join(".",splice(@array,-4,4));
my $snmp_status_query=".1.3.6.1.3.94.1.8.1.6.16.0.0.192.$sanbox_id.0.0.0.0.0.0.0.0";
# The installed Firmware of the Fabric Element
our $firmware_oid = ".1.3.6.1.3.94.1.7.1.3.16.0.0.192.$sanbox_id.0.0.0.0.0.0.0.0.1";

# Now get some basic parameters 
my $baseinfo=snmp_get_table($conf,$session,$base_table_query);

# and also the currently running Firmware
my $firmware=snmp_get_request($conf,$session,$firmware_oid);

# Get the status output from the switch
my $status=snmp_get_table($conf,$session,$snmp_status_query);

# Get the names for the status output
my $snmp_status_names='.1.3.6.1.3.94.1.8.1.3.16.0.0.192.'.$sanbox_id.'.0.0.0.0.0.0.0.0';
my $status_names_ref=snmp_get_table($conf,$session,$snmp_status_names);
my %status_names=%$status_names_ref;

#
# Close the session and reset the alarm
#
$session->close;
alarm(0);

if ($conf->{'debug'}){
	print STDERR "\nSanbox ID: $sanbox_id ($WWPN)\n";
	print STDERR "Firmware : $firmware\n";
	print STDERR "Basic information:\n";
	print STDERR Data::Dumper->Dump([$baseinfo]);
	print STDERR "\nQuerying : $snmp_status_query";
	print STDERR "\nStatus Names Table:\n";
	print STDERR Data::Dumper->Dump([$status_names_ref]);
	print STDERR "\nStatus Table:\n";
	print STDERR Data::Dumper->Dump([$status]);
}

#
# Analyze the tables...
#
my $perfdata='';
foreach my $value (sort(keys(%status_names))){
	my ($is_temp, $is_temp_status, $is_status)=0;
	my ($sensor_number,$sensor_name);
	$output.="$status_names{$value}: ";
	if ( $status_names{$value} =~ /.*Temperature Sensor (.*) Value/ ){
		$perfdata.="'Temp ".$1."'=";
		$sensor_number=$1;
		$is_temp=1;
	}
	elsif ( $status_names{$value} =~ /Temperature Status/ ){
		$is_temp_status=1;
		$sensor_name=$status_names{$value};
	}
	elsif ( $status_names{$value} =~ /Power supply (.*) Status/ ){
		$is_status=1;
		$sensor_number=$1;
		$sensor_name=$status_names{$value};
	}
	elsif ( $status_names{$value} =~ /Fan (.*) Status/ ){
		$is_status=1;
		$sensor_number=$1;
		$sensor_name=$status_names{$value};
	}
	# convert the OID for the name to match the OID for the value:
	$value =~ s/.1.3.6.1.3.94.1.8.1.3.16.0.0.192/.1.3.6.1.3.94.1.8.1.6.16.0.0.192/;
	$output.="$status->{$value}; ";
	# now check the results depending on the sensors:
	if ($is_temp){
		my $temp="$status->{$value}";
		$temp=~ s/\D//g;
		$perfdata.="$temp;$conf->{'warning'};$conf->{'critical'};; ";
		if ($temp gt $conf->{'critical'}){
			$exitcode=$ERRORS{'CRITICAL'};
			$output="CRITICAL: temperatur $status->{$value} exceeds critical level ($conf->{'critical'}); ".$output;
		}
		elsif ($temp gt $conf->{'warning'}){
			$exitcode=$ERRORS{'WARNING'};
			$output="WARNING: temperatur $status->{$value} exceeds warning level ($conf->{'warning'}); ".$output;
		}
	}
	if ($is_temp_status){
		if ($status->{$value} !~ /Normal/){
			$exitcode=$ERRORS{'CRITICAL'};
			$output="CRITICAL: $sensor_name is $status->{$value}; ".$output;
		}
	}
	if ($is_status){
		if ($status->{$value} !~ /Good/){
			$exitcode=$ERRORS{'CRITICAL'};
			$output="CRITICAL: $sensor_name is $status->{$value}; ".$output;
		}
	}
}

foreach my $value (sort(keys(%BASE_OIDs))){
    $output.="$value: $baseinfo->{$BASE_OIDs{$value}}; ";
}
$output.="Firmware: $firmware; ";

#
# Finished : output what we have so far...
#
print "$output | $perfdata\n";
exit $exitcode;

__END__

=head1 check_qlogic_sanbox

check_qlogic_sanbox is a Nagios plugin, allowing to check QLogic FC Sanboxes.

This plugin has been tested with the following QLogic switches:

=over 4

=item SANbox 5200 FC Switch

=item SANbox 5202 FC Switch

=item SANbox 5600 FC Switch

=item SANbox 5602 FC Switch

=item SANbox 5800 FC Switch

=back

=head1 SYNOPSIS

./check_qlogic_sanbox -H $HOSTNAME$ --community public

 Options:
    -H <HOSTNAME>    | --hostname <HOSTNAME>
    -w <int>         | --warning <int>
    -c <int>         | --critical <int>

    -t <int>         | --timeout <int>
    -C <string>      | --community <string>
    -V <string>      | --snmp-version <string>
    -p <int>         | --port <int>

    -h               | --help
    -d               | --debug

=head1 OPTIONS

=over 8

=item B<--hostname> F<hostname>

The name of the Sanbox. Default: localhost (which makes no sense).

=item B<--critical> F<int>

Critical temperature level. Default is 45 degrees Celsius.

=item B<--warning> F<int>

Warning temperature level. Default is 35 degrees Celsius.

=item B<--timeout> F<int>

Timeout until the plugin times out with result UNKNOWN.

=item B<--community> F<string>

SNMP community string to use. Default: public.

=item B<--port> F<int>

SNMP port to use. Default: 161.

=item B<--snmp-version> F<string>

SNMP version to use. Default: 2c.

=item B<--help>

Produces this output.

=item B<--debug>

Print debug output on STDERR.

=back

=head1 DESCRIPTION

=head1 AUTHORS

Written by Lars Vogdt <lrupp@suse.de>

=head1 SUPPORT

Please use https://bugzilla.novell.com to submit patches or suggest improvements.

Include version information with all correspondence (when possible use output from 
the --version option of the plugin itself).

