#!/usr/bin/perl
# check_ipmi_sensor: Nagios/Icinga plugin to check IPMI sensors
#
# Copyright (C) 2009-2016 Thomas-Krenn.AG,
# additional contributors see changelog.txt
#
# 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 3 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, see <http://www.gnu.org/licenses/>.
#
################################################################################
# The following guides provide helpful information if you want to extend this
# script:
#   http://tldp.org/LDP/abs/html/ (Advanced Bash-Scripting Guide)
#   http://www.gnu.org/software/gawk/manual/ (Gawk: Effective AWK Programming)
#   http://de.wikibooks.org/wiki/Awk (awk Wikibook, in German)
#   http://nagios.sourceforge.net/docs/3_0/customobjectvars.html (hints on
#                  custom object variables)
#   http://nagiosplug.sourceforge.net/developer-guidelines.html (plug-in
#                  development guidelines)
#   http://nagios.sourceforge.net/docs/3_0/pluginapi.html (plugin API)
################################################################################
use strict;
use warnings;
use Getopt::Long qw(:config no_ignore_case);
use IPC::Run qw( run ); #interact with processes
################################################################################
# set text variables
our $check_ipmi_sensor_version = "3.12";

sub get_version{
	return <<EOT;
check_ipmi_sensor version $check_ipmi_sensor_version
Copyright (C) 2009-2016 Thomas-Krenn.AG
Current updates at https://github.com/thomas-krenn/check_ipmi_sensor_v3.git
EOT
}

sub get_usage{
	return <<EOT;
Usage:
check_ipmi_sensor -H <hostname>
  [-f <FreeIPMI config file> | -U <username> -P <password> -L <privilege level>]
  [-O <FreeIPMI options>] [-b] [-T <sensor type(s)>] [-x <sensor id>]
  [-xT <sensor type(s)>] [-xST <SEL sensor type(s)]
  [-i <sensor id>] [-o zenoss] [-D <protocol LAN version>] [-h] [-V]
  [-fc <num_fans>] [--fru] [--nosel] [--nothresholds] [--nosudo]
  [-v|-vv|-vvv]
EOT
}

sub get_help{
	return <<EOT;
  [-H <hostname>]
       hostname or IP of the IPMI interface.
       For \"-H localhost\" or if no host is specified (local computer) the
       Nagios/Icinga user must be allowed to run
       ipmimonitoring/ipmi-sensors/ipmi-sel/[ipmi-fru] with root privileges
       or via sudo (ipmimonitoring/ipmi-sensors/ipmi-sel/[ipmi-fru] must be
       able to access the IPMI devices via the IPMI system interface).
  [-f <FreeIPMI config file>]
       path to the FreeIPMI configuration file.
       Only neccessary for communication via network.
       Not neccessary for access via IPMI system interface (\"-H localhost\").
       It should contain IPMI username, IPMI password, and IPMI privilege-level,
       for example:
         username monitoring
         password yourpassword
         privilege-level user
       As alternative you can use -U/-P/-L instead (see below).
  [-U <username> -P <password> -L <privilege level>]
       IPMI username, IPMI password and IPMI privilege level, provided as
       parameters and not by a FreeIPMI configuration file. Useful for RHEL/
       Centos 5.* with FreeIPMI 0.5.1 (this elder FreeIPMI version does not
       support config files).
       Warning: with this method the password is visible in the process list.
                So whenever possible use a FreeIPMI confiugration file instead.
  [-O <FreeIPMI options>]
       additional options for FreeIPMI. Useful for RHEL/CentOS 5.* with
       FreeIPMI 0.5.1 (this elder FreeIPMI version does not support config
       files).
  [-b]
       backward compatibility mode for FreeIPMI 0.5.* (this omits the FreeIPMI
       caching options --quiet-cache and --sdr-cache-recreate)
  [-T <sensor type(s)>]
       limit sensors to query based on IPMI sensor type.
       Examples for IPMI sensor types are 'Fan', 'Temperature', 'Voltage', ...
       See the output of the FreeIPMI command 'ipmi-sensors -L' and chapter
       '42.2 Sensor Type Codes and Data' of the IPMI 2.0 spec for a full list
       of possible sensor types. The available types depend on your particular
       server and the available sensors there.
       Multiple sensor types can be specified as a comma-separated list.
  [-ST <SEL sensor type(s)>]
       limit SEL entries to specific types, run 'ipmi-sel -L' for a list of
       types. All sensors are populated to the SEL and per default all sensor
       types are monitored. E.g. to limit the sensor SEL types to Memory and
       Processsor use -ST 'Memory,Processor'.
  [-x <sensor id>]
       exclude sensor matching <sensor id>. Useful for cases when unused
       sensors cannot be deleted from SDR and are reported in a non-OK state.
       Option can be specified multiple times. The <sensor id> is a numeric
       value (sensor names are not used as some servers have multiple sensors
       with the same name). Use -vvv option to query the <sensor ids>.
  [-xT <sensor type(s)>]
       exclude sensors based on IPMI sensor type.
       Multiple sensor types can be specified as a comma-separated list.
  [-xST <SEL sensor type(s)]
       exclude SEL entries of specific sensor types.
       Multiple sensor types can be specified as a comma-separated list.
  [-i <sensor id>]
       include only sensor matching <sensor id>. Useful for cases when only
       specific sensors should be monitored. Be aware that only for the
       specified sensor errors/warnings are generated. Use -vvv option to query
       the <sensor ids>.
  [-v|-vv|-vvv]
       be verbose
         (no -v) .. single line output
         -v   ..... single line output with additional details for warnings
         -vv  ..... multi line output, also with additional details for warnings
         -vvv ..... debugging output, followed by normal multi line output
  [-o]
       change output format. Useful for using the plugin with other monitoring
       software than Nagios or Icinga.
         -o zenoss .. create ZENOSS compatible formatted output (output with
                      underscores instead of whitespaces and no single quotes)
  [-D]
       change the protocol LAN version. Normally LAN_2_0 is used as protocol
       version if not overwritten with this option. Use 'default' here if you
       don't want to use LAN_2_0.
  [-fc <num fans>]
       number of fans that should be active. If the number of current active
       fans reported by IPMI is smaller than <num fans> then a Warning state
       is returned.
  [--fru]
       print the product serial number if it is available in the IPMI FRU data.
       For this purpose the tool 'ipmi-fru' is used. E.g.:
         IPMI Status: OK (9000096781)
  [--nosel]
       turn off system event log checking via ipmi-sel. If there are
       unintentional entries in SEL, use 'ipmi-sel --clear' or the -sx or -xST
       option.
  [-sx|--selexclude <sel exclude file>]
       use a sel exclude file to exclude entries from the system event log.
       Specify name and type pipe delimitered in this file to exclude an entry,
       for example: System Chassis Chassis Intru|Physical Security
       To get valid names and types use the -vvv option and take a look at:
       debug output for sel (-vvv is set). Don't use name and type from the
       web interface as sensor descriptions are not complete there.
  [-xx|--sexclude <exclude file>]
       use an exclude file to exclude sensors.
       Specify name and type pipe delimitered in this file to exclude a sensor,
       To get valid names and types use the -vvv option.
  [--nosudo]
       turn off sudo usage on localhost or if ipmi host is ommited.
  [--nothresholds]
       turn off performance data thresholds from output-sensor-thresholds.
  [--noentityabsent]
       skip sensor checks for sensors that have 'noentityabsent' as event state
  [-s <ipmi-sensor output file>]
       simulation mode - test the plugin with an ipmi-sensor output redirected
       to a file.
  [-h]
       show this help
  [-V]
       show version information

Examples:
  \$ check_ipmi_sensor -H 192.0.2.1 -U monitor -P monitor -L user
    IPMI Status: OK | 'System Temp'=30.00 'Peripheral Temp'=32.00
    'FAN 1'=2775.00 [...]
  \$ check_ipmi_sensor -H 192.0.2.1 -U monitor -P monitor -L user -x 205
    IPMI Status: OK | 'System Temp'=30.00 'Peripheral Temp'=32.00
    'FAN 2'=2775.00 [...]
  \$ check_ipmi_sensor -H 192.0.2.1 -U monitor -P monitor -L user -i 4,71
    IPMI Status: OK | 'System Temp'=30.00 'Peripheral Temp'=32.00
  \$ check_ipmi_sensor -H 192.0.2.1 -U monitor -P monitor -L user -i 4 --fru
    IPMI Status: OK (0000012345) | 'System Temp'=30.00

Further information about this plugin can be found at
http://www.thomas-krenn.com/en/wiki/IPMI_Sensor_Monitoring_Plugin

Use the github repo at https://github.com/thomas-krenn/check_ipmi_sensor_v3.git
to submit patches, or suggest improvements.

Send email to the IPMI-plugin-user mailing list if you have questions regarding
use of this software. The mailing list is available at
http://lists.thomas-krenn.com/
EOT
}

sub usage{
	my ($arg) = @_; #the list of inputs
	my ($exitcode);
	if ( defined $arg ){
		if ( $arg =~ m/^\d+$/ ){
			$exitcode = $arg;
		}
		else{
			print STDOUT $arg, "\n";
			$exitcode = 1;
		}
	}
	print STDOUT get_usage();
	exit($exitcode) if defined $exitcode;
}
################################################################################
# set ipmimonitoring path
our $MISSING_COMMAND_TEXT = '';
our $IPMICOMMAND ="";
if(-x "/usr/sbin/ipmimonitoring"){
	$IPMICOMMAND = "/usr/sbin/ipmimonitoring";
}
elsif (-x "/usr/bin/ipmimonitoring"){
	$IPMICOMMAND = "/usr/bin/ipmimonitoring";
}
elsif (-x "/usr/local/sbin/ipmimonitoring"){
	$IPMICOMMAND = "/usr/local/sbin/ipmimonitoring";
}
elsif (-x "/usr/local/bin/ipmimonitoring"){
	$IPMICOMMAND = "/usr/local/bin/ipmimonitoring";
}
else{
	$MISSING_COMMAND_TEXT = " ipmimonitoring/ipmi-sensors command not found!\n";
}

# Identify the version of the ipmi-tool
sub get_ipmi_version{
	my @ipmi_version_output = '';
	my $ipmi_version = '';
	@ipmi_version_output = `$IPMICOMMAND -V`;
	$ipmi_version = shift(@ipmi_version_output);
	$ipmi_version =~ /(\d+)\.(\d+)\.(\d+)/;
	@ipmi_version_output = ();
	push @ipmi_version_output,$1,$2,$3;
	return @ipmi_version_output;
}

sub simulate{
	my $output = '';
	my $simul_file = $_[0];
	if( !defined $simul_file || (-x '\"'.$simul_file.'\"')){
		print "DEBUG: Using simulation file: $simul_file\n";
		print "Error: Simulation file with ipmi output not found.\n";
		exit(3);
	}
	return ($output = `cat $simul_file`);
}

sub get_fru{
	my @frucmd = @{(shift)};
	my $verbosity = shift;
	my $fru;
	if(-e '/usr/sbin/ipmi-fru'){
		$fru = '/usr/sbin/ipmi-fru';
	}
	else{
		chomp($fru = `which ipmi-fru`);
	}
	#if sudo is used the command is the second element
	if($frucmd[0] eq 'sudo'){
		$frucmd[1] = $fru;
	}
	else{
		$frucmd[0] = $fru;
	}
	#skip checksum validation
	push @frucmd,'-s';
	my $fruoutput;
	my $returncode;
	run \@frucmd, '>&', \$fruoutput;
	#the upper eight bits contain the error condition (exit code)
	#see http://perldoc.perl.org/perlvar.html#Error-Variables
	$returncode = $? >> 8;
	if ( $returncode != 0 ){
		print "$fruoutput\n";
		print "-> Execution of $fru failed with return code $returncode.\n";
		print "-> $fru was executed with the following parameters:\n";
		print "   ", join(' ', @frucmd), "\n";
		exit(3);
	}
	if($verbosity == 3){
		print "------------- debug output for fru (-vvv is set): ------------\n";
		print "  $fru was executed with the following parameters:\n";
		print "    ", join(' ', @frucmd), "\n";
		print "  output of FreeIPMI:\n";
		print "$fruoutput";
	}
	return split('\n', $fruoutput);
}

sub get_sel{
	my @selcmd = @{(shift)};
	my $verbosity = shift;
	my @sel_sensor_types = @{(shift)};
	my @exclude_sel_sensor_types = @{(shift)};
	my $sel;
	if(-e '/usr/sbin/ipmi-sel'){
		$sel = '/usr/sbin/ipmi-sel';
	}
	else{
		chomp($sel = `which ipmi-sel`);
	}
	#if sudo is used the command is the second element
	if($selcmd[0] eq 'sudo'){
		$selcmd[1] = $sel;
	}
	else{
		$selcmd[0] = $sel;
	}
	push @selcmd, '--output-event-state', '--interpret-oem-data', '--entity-sensor-names';
	push @selcmd, '--sensor-types=' . join(',', @sel_sensor_types);
	push @selcmd, '--exclude-sensor-types=' . join(',', @exclude_sel_sensor_types);
	my $seloutput;
	my $returncode;
	run \@selcmd, '>&', \$seloutput;
	$returncode = $? >> 8;
	if ( $returncode != 0 ){
		print "$seloutput\n";
		print "-> Execution of $sel failed with return code $returncode.\n";
		print "-> $sel was executed with the following parameters:\n";
		print "   ", join(' ', @selcmd), "\n";
		exit(3);
	}
	if($verbosity == 3){
		print "------------- debug output for sel (-vvv is set): ------------\n";
		print "  $sel was executed with the following parameters:\n";
		print "    ", join(' ', @selcmd), "\n";
		print "  output of FreeIPMI:\n";
		print "$seloutput";
	}
	return split('\n', $seloutput);
}

sub parse_sel{
	my $selcmd = shift;
	my $verbosity = shift;
	my $sel_xfile = shift;
	my $sel_sensor_types = shift;
	my $exclude_sel_sensor_types = shift;
	my @seloutput = get_sel($selcmd, $verbosity, $sel_sensor_types, $exclude_sel_sensor_types);
	@seloutput = map { [ map { s/^\s*//; s/\s*$//; $_; } split(m/\|/, $_) ] } @seloutput;
	my $header = shift(@seloutput);

	my @sel_rows;
	foreach my $row (@seloutput){
		my %curr_row;
		for(my $i = 0; $i < scalar(@{$header}); $i++){
			my $key = lc $header->[$i];
			$curr_row{$key} = $row->[$i];
		}
		if(!(exclude_with_file($sel_xfile, $curr_row{'name'}, $curr_row{'type'}))){
			push @sel_rows, \%curr_row;
		}
	}
	return \@sel_rows;
}

# Excludes a name and type pair if it is present in the given file, pipe
# delimitered.
# @return 1 if name should be skipped, 0 if not
sub exclude_with_file{
	my $file_name = shift;
	my $name = shift;
	my $type = shift;
	my @xlist;
	my $skip = 0;
	if($file_name){
		if(!(open (FH, "< $file_name"))){
			print "-> Reading exclude file $file_name failed with: $!.\n";
			exit(3);
		};
		@xlist = <FH>;
	}
	foreach my $exclude (@xlist){
		my @curr_exclude = map { s/^\s*//; s/\s*$//; $_; } split(/\|/,$exclude);
		if($curr_exclude[0] eq $name &&
		$curr_exclude[1] eq $type){
			$skip = 1;
		}
	}
	close FH;
	return $skip;
}

#define entire hashes
our %hdrmap = (
	'Record_ID'		=> 'id',	# FreeIPMI ...,0.7.x
	'Record ID'		=> 'id',	# FreeIPMI 0.8.x,... with --legacy-output
	'ID'			=> 'id',	# FreeIPMI 0.8.x
	'Sensor Name'		=> 'name',
	'Name'			=> 'name',	# FreeIPMI 0.8.x
	'Sensor Group'		=> 'type',
	'Type'			=> 'type',	# FreeIPMI 0.8.x
	'Monitoring Status'	=> 'state',
	'State'			=> 'state',	# FreeIPMI 0.8.x
	'Sensor Units'		=> 'units',
	'Units'			=> 'units',	# FreeIPMI 0.8.x
	'Sensor Reading'	=> 'reading',
	'Reading'		=> 'reading',	# FreeIPMI 0.8.x
	'Event'			=> 'event',	# FreeIPMI 0.8.x
	'Lower C'		=> 'lowerC',
	'Lower NC'		=> 'lowerNC',
	'Upper C'		=> 'upperC',
	'Upper NC'		=> 'upperNC',
	'Lower NR'		=> 'lowerNR',
	'Upper NR'		=> 'upperNR',
);

our $verbosity = 0;

MAIN: {
	$| = 1; #force a flush after every write or print
	my @ARGV_SAVE = @ARGV;#keep args for verbose output
	my ($show_help, $show_version);
	my ($ipmi_host, $ipmi_user, $ipmi_password, $ipmi_privilege_level, $ipmi_config_file, $ipmi_outformat);
	my (@freeipmi_options, $freeipmi_compat);
	my (@ipmi_sensor_types, @ipmi_exclude_sensor_types, @ipmi_xlist, @ipmi_ilist);
	my (@ipmi_version);
	my $ipmi_sensors = 0;#states to use ipmi-sensors instead of ipmimonitoring
	my $fan_count;#number of fans that should be installed in unit
	my $lanVersion;#if desired use a different protocol version
	my $abort_text = '';
	my $zenoss = 0;
	my @sel_sensor_types;
	my @exclude_sel_sensor_types;
	my $sel_issues_present = 0;
	my $simulate = '';
	my ($use_fru, $no_sel, $no_sudo, $use_thresholds, $no_thresholds, $sel_xfile, $s_xfile, $no_entity_absent);

	#read in command line arguments and init hash variables with the given values from argv
	if ( !( GetOptions(
		'H|host=s'			=> \$ipmi_host,
		'f|config-file=s'	=> \$ipmi_config_file,
		'U|user=s'			=> \$ipmi_user,
		'P|password=s'  	=> \$ipmi_password,
		'L|privilege-level=s'	=> \$ipmi_privilege_level,
		'O|options=s'		=> \@freeipmi_options,
		'b|compat'			=> \$freeipmi_compat,
		'T|sensor-types=s'	=> \@ipmi_sensor_types,
		'xT|exclude-sensor-types=s'	=> \@ipmi_exclude_sensor_types,
		'ST|sel-sensor-types=s'	=> \@sel_sensor_types,
		'xST|exclude-sel-sensor-types=s'	=> \@exclude_sel_sensor_types,
		'fru'				=> \$use_fru,
		'nosel'				=> \$no_sel,
		'nosudo'			=> \$no_sudo,
		'nothresholds'			=> \$no_thresholds,
		'noentityabsent'	=> \$no_entity_absent,
		'v|verbosity'		=> \$verbosity,
		'vv'				=> sub{$verbosity=2},
		'vvv'				=> sub{$verbosity=3},
		'x|exclude=s'		=> \@ipmi_xlist,
		'sx|selexclude=s'	=> \$sel_xfile,
		'xx|sexclude=s'		=> \$s_xfile,
		'i|include=s'		=> \@ipmi_ilist,
		'o|outformat=s'		=> \$ipmi_outformat,
		'fc|fancount=i'		=> \$fan_count,
		'D=s'				=> \$lanVersion,
		's=s'				=> \$simulate,
		'h|help'			=>
			sub{print STDOUT get_version();
				print STDOUT "\n";
				print STDOUT get_usage();
				print STDOUT "\n";
				print STDOUT get_help();
				exit(0)
			},
		'V|version'			=>
			sub{
				print STDOUT get_version();
				exit(0);
			},
		'usage|?'			=>
			sub{print STDOUT get_usage();
				exit(3);
			}
	) ) ){
		usage(1);#call usage if GetOptions failed
	}
	usage(1) if @ARGV;#print usage if unknown arg list is left

################################################################################
# check for ipmimonitoring or ipmi-sensors. Since version > 0.8 ipmi-sensors is used
# if '--legacy-output' is given ipmi-sensors cannot be used
	if( $MISSING_COMMAND_TEXT ne "" ){
		print STDOUT "Error:$MISSING_COMMAND_TEXT";
		exit(3);
	}
	else{
		@ipmi_version = get_ipmi_version();
		if( $ipmi_version[0] > 0 && (grep(/legacy\-output/,@freeipmi_options)) == 0){
			$IPMICOMMAND =~ s/ipmimonitoring/ipmi-sensors/;
			$ipmi_sensors = 1;
		}
		if( $ipmi_version[0] > 0 && (grep(/legacy\-output/,@freeipmi_options)) == 1){
			print "Error: Cannot use ipmi-sensors with option \'--legacy-output\'. Remove it to work correctly.\n";
			exit(3);
		}
		# check if output-sensor-thresholds can be used, this is supported
		# since 1.2.1. Version 1.2.0 was not released, so skip the third minor
		# version number
		if($ipmi_version[0] > 1 || ($ipmi_version[0] == 1 && $ipmi_version[1] >= 2)){
			$use_thresholds = 1;
		}
		else{
			$use_thresholds = 0;
		}
	}
###############################################################################
# verify if all mandatory parameters are set and initialize various variables
	#\s defines any whitespace characters
	#first join the list, then split it at whitespace ' '
	#also cf. http://perldoc.perl.org/Getopt/Long.html#Options-with-multiple-values
	@freeipmi_options = split(/\s+/, join(' ', @freeipmi_options)); # a bit hack, shell word splitting should be implemented...
	@ipmi_sensor_types = split(/,/, join(',', @ipmi_sensor_types));
	@ipmi_exclude_sensor_types = split(/,/, join(',', @ipmi_exclude_sensor_types));
	@sel_sensor_types = split(/,/, join(',', @sel_sensor_types));
	@exclude_sel_sensor_types = split(/,/, join(',', @exclude_sel_sensor_types));
	@ipmi_xlist = split(/,/, join(',', @ipmi_xlist));
	@ipmi_ilist = split(/,/, join(',', @ipmi_ilist));

	#check for zenoss output
	if(defined $ipmi_outformat && $ipmi_outformat eq "zenoss"){
		$zenoss = 1;
	}

	# Per default monitor all sensor types, use -ST to specify your sensor types
	if(!@sel_sensor_types){
		@sel_sensor_types = ('all');
	}
    # If -xST has not been set, set this array to empty.
	if(!@exclude_sel_sensor_types){
		@exclude_sel_sensor_types = ('');
	}

	# Define basic ipmi command
	my @basecmd = $IPMICOMMAND;
	# If host is omitted localhost is assumed, if not turned off sudo is used
	if(!(defined $ipmi_host) || ($ipmi_host eq 'localhost')){
		if(!defined($no_sudo)){
			# Only add sudo if not already root
			@basecmd = ($> != 0 ? 'sudo' : (), $IPMICOMMAND);
		}
	}
	# If we are not local, we need authentication credentials
	else{
		# Add the ipmi desired host
		push @basecmd, '-h', $ipmi_host;
		if(defined $ipmi_config_file){
			push @basecmd, '--config-file', $ipmi_config_file;
		}
		elsif(defined $ipmi_user && defined $ipmi_password && defined $ipmi_privilege_level ){
			push @basecmd, '-u', $ipmi_user, '-p', $ipmi_password, '-l', $ipmi_privilege_level;
		}
		else{
			$abort_text = $abort_text . " -f <FreeIPMI config file> or -U <username> -P <password> -L <privilege level>";
		}
		if( $abort_text ne ""){
			print STDOUT "Error: " . $abort_text . " missing.";
			print STDOUT get_usage();
			exit(3);
		}
	}
	# copy command for fru usage
	my @frucmd;
	if($use_fru){
		@frucmd = @basecmd
	}
	my @selcmd = @basecmd;

	if(@ipmi_sensor_types){
		# , is the seperator in the new string
    	# -g option is older name for ipmi-sensors -t or --sensor-types and
    	# compatible with both older and newer version of FreeIPMI
		push @basecmd, '-g', join(',', @ipmi_sensor_types);
	}

	# add sensor types to exclude
	if(@ipmi_exclude_sensor_types){
		push @basecmd, '--exclude-sensor-types', join(',', @ipmi_exclude_sensor_types);
	}


	if(@freeipmi_options){
		push @basecmd, @freeipmi_options;
	}

	#keep original basecmd for later usage
	my @getstatus = @basecmd;

	#if -b is not defined, caching options are used
	if( !(defined $freeipmi_compat) ){
		push @getstatus, '--quiet-cache', '--sdr-cache-recreate';
	}
	#since version 0.8 it is possible to interpret OEM data
	if( ($ipmi_version[0] == 0 && $ipmi_version[1] > 7) ||
			$ipmi_version[0] > 0){
				push @getstatus, '--interpret-oem-data';
	}
	#since version 0.8 it is necessary to add the legacy option
	if( ($ipmi_version[0] == 0 && $ipmi_version[1] > 7) && (grep(/legacy\-output/,@freeipmi_options) == 0)){
		push @getstatus, '--legacy-output';
	}
	#if ipmi-sensors is used show the state of sensors and ignore N/A
	if($ipmi_sensors){
		push @getstatus, '--output-sensor-state', '--ignore-not-available-sensors';
	}
	#if not stated otherwise we use protocol lan version 2 per default
	if(!defined($lanVersion)){
		$lanVersion = 'LAN_2_0';
	}
	if($lanVersion ne 'default' && defined $ipmi_host && $ipmi_host ne 'localhost'){
		push @getstatus, "--driver-type=$lanVersion";
		if(!$no_sel){
			push @selcmd, "--driver-type=$lanVersion";
		}
		if($use_fru){
			push @frucmd, "--driver-type=$lanVersion";
		}
	}
	if($use_thresholds && !$no_thresholds){
		push @getstatus, '--output-sensor-thresholds';
	}

################################################################################
	#execute status command and redirect stdout and stderr to ipmioutput
	my $ipmioutput;
	my $returncode;
	if(!$simulate){
		run \@getstatus, '>&', \$ipmioutput;
		#the upper eight bits contain the error condition (exit code)
		#see http://perldoc.perl.org/perlvar.html#Error-Variables
		$returncode = $? >> 8;
	}
	else{
		$ipmioutput = simulate($simulate);
		print "DEBUG: Using simulation mode\n";
		$returncode = 0;
	}
	my @fruoutput;
	if($use_fru){
		@fruoutput = get_fru(\@frucmd, $verbosity);
	}
	my $seloutput;
	if(!$no_sel){
		$seloutput = parse_sel(\@selcmd, $verbosity, $sel_xfile, \@sel_sensor_types, \@exclude_sel_sensor_types);
	}
################################################################################
# print debug output when verbosity is set to 3 (-vvv)
	if ( $verbosity == 3 ){
		my $ipmicommandversion;
		run [$IPMICOMMAND, '-V'], '2>&1', '|', ['head', '-n', 1], '&>', \$ipmicommandversion;
		#remove trailing newline with chomp
		chomp $ipmicommandversion;
		print "------------- debug output for sensors (-vvv is set): ------------\n";
		print "  script was executed with the following parameters:\n";
		print "    $0 ", join(' ', @ARGV_SAVE), "\n";
		print "  check_ipmi_sensor version:\n";
		print "    $check_ipmi_sensor_version\n";
		print "  FreeIPMI version:\n";
		print "    $ipmicommandversion\n";
		print "  FreeIPMI was executed with the following parameters:\n";
		print "    ", join(' ', @getstatus), "\n";
		print "  FreeIPMI return code: $returncode\n";
		print "  output of FreeIPMI:\n";
		print "$ipmioutput\n";
		print "--------------------- end of debug output ---------------------\n";
	}

################################################################################
# generate main output
	if ( $returncode != 0 ){
		print "$ipmioutput\n";
		print "-> Execution of $IPMICOMMAND failed with return code $returncode.\n";
		print "-> $IPMICOMMAND was executed with the following parameters:\n";
		print "   ", join(' ', @getstatus), "\n";
		exit(3);
	}
	else{
		my @outputRows;
		if(defined($ipmioutput)){
			@outputRows = split('\n', $ipmioutput);
		}
		if(!defined($ipmioutput) || scalar(@outputRows) == 1){
			print "-> Your server seems to be powered off.";
			print " (Execution of FreeIPMI returned an empty output or only 1 header row!)\n";
			print "-> $IPMICOMMAND was executed with the following parameters:\n";
			print "   ", join(' ', @getstatus), "\n";
			exit(3);
		}
		#print desired filter types
		if ( @ipmi_sensor_types ){
			print "Sensor Type(s) ", join(', ', @ipmi_sensor_types), " Status: ";
		}
		else{
			print "IPMI Status: ";
		}
		#split at newlines, fetch array with lines of output
		my @ipmioutput = split('\n', $ipmioutput);

		#remove sudo errors and warnings like they appear on dns resolving issues
		@ipmioutput = map { /^sudo:/ ? () : $_ } @ipmioutput;

		#remove leading and trailing whitespace characters, split at the pipe delimiter
		@ipmioutput = map { [ map { s/^\s*//; s/\s*$//; $_; } split(m/\|/, $_) ] } @ipmioutput;

		#shift out the header as it is the first line
		my $header = shift @ipmioutput;
		if(!defined($header)){
			print "$ipmioutput\n";
			print " FreeIPMI returned an empty header map (first line)";
			if(@ipmi_sensor_types){
				print " FreeIPMI could not find any sensors for the given sensor type (option '-T').\n";
			}
			exit(3);
		}
		my %header;
		for(my $i = 0; $i < @$header; $i++)
		{
			#assigning %header with (key from hdrmap) => $i
			#checking at which position in the header is which key
			$header{$hdrmap{$header->[$i]}} = $i;
		}
		my @ipmioutput2;
		foreach my $row ( @ipmioutput ){
			my %row;
			#fetch keys from header and assign existent values to row
			#this maps the values from row(ipmioutput) to the header values
			while ( my ($key, $index) = each %header ){
				$row{$key} = $row->[$index];
			}
			if(!(exclude_with_file($s_xfile, $row{'name'}, $row{'type'}))){
				push @ipmioutput2, \%row;
			}
		}
		#create hash with sensor name an 1
		my %ipmi_xlist = map { ($_, 1) } @ipmi_xlist;
		#filter out the desired sensor values
		@ipmioutput2 = grep(!exists $ipmi_xlist{$_->{'id'}}, @ipmioutput2);
		#check for an include list
		if(@ipmi_ilist){
			my %ipmi_ilist = map { ($_, 1) } @ipmi_ilist;
			#only include sensors from include list
			@ipmioutput2 = grep(exists $ipmi_ilist{$_->{'id'}}, @ipmioutput2);
		}
		#start with main output
		my $exit = 0;
		my $w_sensors = '';#sensors with warnings
		my $sel_w_sensors = '';#verbose output for sel entries with warnings
		my $perf = '';#performance sensor
		my $curr_fans = 0;
		foreach my $row ( @ipmioutput2 ){
			if( $zenoss ){
				$row->{'name'} =~ s/ /_/g;
			}
			my $check_sensor_state = 1;
			if($no_entity_absent && ($row->{'event'} eq '\'Entity Absent\'')){
				$check_sensor_state = 0;
			}
			#check for warning sensors
			if($check_sensor_state && ($row->{'state'} ne 'Nominal' && $row->{'state'} ne 'N/A')){
				$exit = 1 if $exit < 1;
				$exit = 2 if $exit < 2 && $row->{'state'} ne 'Warning';
				#don't insert a , the first time
				$w_sensors .= ", " unless $w_sensors eq '';
				$w_sensors .= "$row->{'name'} = $row->{'state'}";
				if( $verbosity ){
					if( $row->{'reading'} ne 'N/A'){
						$w_sensors .= " ($row->{'reading'})" ;
					}
					else{
						$w_sensors .= " ($row->{'event'})";
					}
				}
			}
			if($check_sensor_state && ($row->{'units'} ne 'N/A')){
				my $val = $row->{'reading'};
				my $perf_data;
				my $perf_thresholds;
				if($zenoss){
					$perf_data = $row->{'name'}."=".$val;
				}
				else{
					$perf_data = "'".$row->{'name'}."'=".$val;
				}
				if($use_thresholds && !$no_thresholds){
					if(($row->{'lowerNC'} ne 'N/A') && ($row->{'upperNC'} ne 'N/A')){
						$perf_thresholds = $row->{'lowerNC'}.":".$row->{'upperNC'}.";";
					}
					elsif(($row->{'lowerNC'} ne 'N/A') && ($row->{'upperNC'} eq 'N/A')){
						$perf_thresholds = $row->{'lowerNC'}.":;";
					}
					elsif(($row->{'lowerNC'} eq 'N/A') && ($row->{'upperNC'} ne 'N/A')){
						$perf_thresholds = "~:".$row->{'upperNC'}.";";
					}
					elsif(($row->{'lowerNC'} eq 'N/A') && ($row->{'upperNC'} eq 'N/A')){
						$perf_thresholds = ";";
					}
					if(($row->{'lowerC'} ne 'N/A') && ($row->{'upperC'} ne 'N/A')){
						$perf_thresholds .= $row->{'lowerC'}.":".$row->{'upperC'};
					}
					elsif(($row->{'lowerC'} ne 'N/A') && ($row->{'upperC'} eq 'N/A')){
						$perf_thresholds .= $row->{'lowerC'}.":";
					}
					elsif(($row->{'lowerC'} eq 'N/A') && ($row->{'upperC'} ne 'N/A')){
						$perf_thresholds .= "~:".$row->{'upperC'};
					}
					# Add thresholds to performance data
					if(($row->{'lowerNC'} ne 'N/A') || ($row->{'upperNC'} ne 'N/A') ||
					($row->{'lowerC'} ne 'N/A') || ($row->{'upperC'} ne 'N/A')){
						$perf_data .= ";".$perf_thresholds;
					}
				}
				$perf .= $perf_data." ";
			}
			if( $row->{'type'} eq 'Fan' && $row->{'reading'} ne 'N/A' ){
				$curr_fans++;
			}
		}
		foreach my $row (@{$seloutput}){
			if( $zenoss ){
				$row->{'name'} =~ s/ /_/g;
			}
			if ($row->{'state'} ne 'Nominal'){
				$sel_issues_present += 1;
				$exit = 1 if $exit < 1;
				$exit = 2 if $exit < 2 && $row->{'state'} ne 'Warning';
				if( $verbosity ){
					$sel_w_sensors .= ", " unless $sel_w_sensors eq '';
					$sel_w_sensors .= "($row->{'name'} = $row->{'state'},";
					$sel_w_sensors .= " $row->{'type'}," ;
					$sel_w_sensors .= " $row->{'event'})" ;
				}
			}
		}
		if ( $sel_issues_present ){
			$w_sensors .= ", " unless $w_sensors eq '';
			if ( $sel_issues_present == 1 ){
				$w_sensors .= "1 system event log (SEL) entry present";
			}else{
				$w_sensors .= $sel_issues_present." system event log (SEL) entries present";
			}
			if( $verbosity ){
				$w_sensors .= " - details: ";
				$w_sensors .= $sel_w_sensors;
				$w_sensors .= " - fix the reported issues and clear your SEL";
				$w_sensors .= " or exclude specific SEL entries using the -sx or -xST option";
			}
		}
		#now check if num fans equals desired unit fans
		if( $fan_count ){
			if( $curr_fans < $fan_count ){
				$exit = 1 if $exit < 1;
				$w_sensors .= ", " unless $w_sensors eq '';
				$w_sensors .= "Fan = Warning";
				if( $verbosity ){
					$w_sensors .= " ($curr_fans)" ;
				}
			}
		}
		#check for the FRU serial number
		my @server_serial;
		my $serial_number;
		if( $use_fru ){
			@server_serial = grep(/Product Serial Number/,@fruoutput);
			if(@server_serial){
				$server_serial[0] =~ m/(\d+)/;
				$serial_number = $1;
			}
		}
		$perf = substr($perf, 0, -1);#cut off the last chars
		if ( $exit == 0 ){
			print "OK";
		}
		elsif ( $exit == 1 ){
			print "Warning [$w_sensors]";
		}
		else{
			print "Critical [$w_sensors]";
		}
		if( $use_fru && defined($serial_number)){
			print " ($serial_number)";
		}
		print " | ", $perf if $perf ne '';
		print "\n";

		if ( $verbosity > 1 ){
			foreach my $row (@ipmioutput2){
				if( $row->{'state'} eq 'N/A'){
					next;
				}
				elsif( $row->{'reading'} ne 'N/A'){
					print "$row->{'name'} = $row->{'reading'} ";
				}
				elsif( $row->{'event'} ne 'N/A'){
					print "$row->{'name'} = $row->{'event'} ";
				}
				else{
					next;
				}
				print "(Status: $row->{'state'})\n";
			}
		}
		exit $exit;
	}
};
