#!/usr/bin/perl -w

# Version:  0.6
# Date:     Nov 26, 2021
# Author:   ivan.stetka@live.com
# Summary:  This is a Nagios plugin to check SNMP OID time generally 
#           (based on plugin check_snmp_lastwritetime.pl)
#           for time format:    "2000/01/31_23:59:59" 
#                               "2000-01-31 23:59:59" 
#                               "949363199" UNIX Epoch time format
#                               "0x07d0011f173b3b00" Hex time format
#                               (SNMP MIB HOST-RESOURCES-MIB DateAndTime format)

use strict;
use Net::SNMP;
use Getopt::Long qw(:config no_ignore_case bundling);
use Time::Piece;
use POSIX qw(strftime);

my %STATES = (
    OK       => 0,
    WARNING  => 1,
    CRITICAL => 2,
    UNKNOWN  => 3
);

my %TIMEZONES = (
    "UTC"   => "date -u ",
    "UTC1"  => "date -u -d '+1 hour' ",
    "UTC2"  => "date -u -d '+2 hour' ",
    "CZ"    => "date ",
);

my $host = undef;
my $community = undef;
my $oid = undef;
my $protocolV = 1;
my $crit = 5;
my $warn = 2;
my $timeZone = 'UTC1';
my $timeout = 15;
my $labelEquipment = 'Equipment';
my $unit = 's';
my $version = 0;
my $help = 0;
my $oidTime;
my $systemTime;
my $zoneText = "";
my $diffSign = undef;
my $diffText = undef;
my $diffInUnit = undef;

GetOptions(
    'H|host:s'       => \$host,
    'C|community:s'  => \$community,
    'o|oid:s'        => \$oid,
    'P|protocolV:n'  => \$protocolV,
    'w|warning:n'    => \$warn,
    'c|critical:n'   => \$crit,
    'z|timeZone:s'   => \$timeZone,
    't|timeout:n'    => \$timeout,
    'l|labelE:s'     => \$labelEquipment,
    'U|unit:s'       => \$unit,   
    'V|version'      => \$version,
    'h|help'         => \$help
);

if ($help) {
    help();
    exit($STATES{OK});
}

if ($version) { 
    version();
    exit($STATES{OK});
}

# input control
if (!defined ($TIMEZONES{$timeZone} )) {
    print "Bad format of time zone '$timeZone'.\n";
    usage();
    exit($STATES{CRITICAL});
}

if (!defined $host || !defined $community || !defined $oid) {
    print "Missing required parameter (host, community, oid).\n";
    usage();
    exit($STATES{CRITICAL});
}

chomp($oid);
 if ($oid !~ /^\d+(\.\d+)*$/) {
    print "Bad format of OID number '$oid'.\n";
    usage();
    exit($STATES{CRITICAL});
}

if (defined $protocolV) {
    if ($protocolV != 1 && $protocolV != 2) {
        print "SNMP protocol version can be only 1 or 2.\n";
        exit($STATES{CRITICAL});
    }
}

# timeout control
$SIG{ALRM} = sub {
    print "Something is wrong, exceeded timeout parameter.\n";
    usage();
    exit($STATES{CRITICAL});
};
alarm $timeout;

# opening of the SNMP session
my ($session, $error) = Net::SNMP->session(
    -hostname  => $host,
    -community => $community,
    -version   => $protocolV
);

# check of the session
if (!defined $session) {
    print "Missing session: $error\n";
    exit($STATES{CRITICAL});
}

# getting of the OID data (status) from the remote agent (reference to hash)
my @oids = ($oid);
my $result = $session->get_request(
    -varbindlist => \@oids
);

# checking of the result (existence of the OID status)
if (!defined $result) {
    print "Error in fetching OID request: ", $session->error(), "\n";
    badChoice();
    exit($STATES{CRITICAL});
}

# dereference of result 
my %hash = %{$result};

# checking of the data existence in the return value
if (!exists $hash{$oid}) {
    print "OID '$oid' does not contain a value!\n";
    badChoice();
    $session->close();
    exit($STATES{CRITICAL});
}
my $valueOid = $hash{$oid};

# parsing time from OID value
if ($valueOid =~ /^(\d{4})\/(\d{2})\/(\d{2})_(\d{2}):(\d{2}):(\d{2})$/) {
    $oidTime = Time::Piece->strptime($valueOid, "%Y/%m/%d_%H:%M:%S");
} elsif ($valueOid =~ /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/) {
    $oidTime = Time::Piece->strptime($valueOid, "%Y-%m-%d %H:%M:%S");
} elsif ($valueOid =~ /^(\d{9,10})$/) {
    my $oidEpochTime = strftime("%Y-%m-%d %H:%M:%S", localtime($valueOid));
    $oidTime = Time::Piece->strptime($oidEpochTime, "%Y-%m-%d %H:%M:%S");
} elsif ($valueOid =~ /^0x((([a-f]|[0-9]){2}){8})$/) {
    $valueOid =~ s/^.{2}//s;
    my @hexTime = unpack 'n C6 a C2',(pack "H*", $valueOid); 
    my $hexTime = join ' ', @hexTime;
    if ($hexTime =~ /^(\d{4}) (\d{1,2}) (\d{1,2}) (\d{1,2}) (\d{1,2}) (\d{1,2})(.*)$/) {
        $hexTime =~ s/$7$//;
        $oidTime = Time::Piece->strptime($hexTime, "%Y %m %d %H %M %S");
    } else {
    print "Wrong hexTime format: '$hexTime'.\n";
    $session->close();
    exit($STATES{CRITICAL});
    }
} else {
    print "Wrong OID time format: '$valueOid'.\n";
    $session->close();
    exit($STATES{CRITICAL});
}

# retrieving time from the console (Nagios-Linux)
open CH, "$TIMEZONES{$timeZone} '+%F %T' |";
my $consoleTime = <CH>;
close CH;
$consoleTime =~ s/\n//;

# parsing time from the console
if ($consoleTime =~ /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/) {
    $systemTime = Time::Piece->strptime($consoleTime, "%Y-%m-%d %H:%M:%S");
} else {
    print "Wrong System time format: '$consoleTime'.\n";
    $session->close();
    exit($STATES{CRITICAL});
}

# calculation of time differences
my $diffInS = $oidTime - $systemTime;
my $diffInM = (int($diffInS / 60));
my $diffInAbsM = abs($diffInM);
my $diffInAbsH = (int($diffInAbsM / 60));
my $restInAbsM = $diffInAbsM - ($diffInAbsH * 60);
my $restInAbsS = abs($diffInS) - ($diffInAbsM * 60); 

# checking whether the time difference is exactly 1 or 2 hours
if ($diffInAbsM == 60 || $diffInAbsM == 120) { 
    $zoneText = ", maybe wrong time zone " . $timeZone;
} 

# evaluation of the time difference type
if ($diffInS < 0 ) {  
    $diffSign = " delayed";
} elsif ($diffInS > 0 ) {   
    $diffSign = " overflowed";
} else  {
    $diffSign = " the same";
}

# preparation diff output text
if ($diffInS == 0) {     
    $diffText = " as System time.";
} else {  
    $diffText = ". Difference is "
    . sprintf("%02d:%02d:%02d", $diffInAbsH, $restInAbsM, $restInAbsS);
} 

# check the unit of threshold and the performance data
if ($unit eq 'm') {     
    $diffInUnit = $diffInM;
} elsif ($unit eq 's') {  
    $diffInUnit = $diffInS;
} else {  
    print "Wrong threshold unit\n";
    usage();
    print "Check -h (help) for more information.\n";
    $session->close();
    exit($STATES{UNKNOWN});
} 

# text output, long text and perf data
my $outputText = $labelEquipment 
    . " time is" . $diffSign . $zoneText . $diffText
    . "\n$labelEquipment time: $oidTime"
    . "\nSystem time: $systemTime" 
    . "\nTime zone: $timeZone"
    . "|Difference=$diffInUnit" . $unit . ";" . $warn . ";" . $crit . "\n";

# evaluating states according to thresholds
if (abs($diffInUnit) >= $crit) {
    print "CRITICAL: " . $outputText;
    $session->close();
    exit($STATES{CRITICAL});
} elsif (abs($diffInUnit) >= $warn) {
    print "WARNING: " . $outputText;
    $session->close();
    exit($STATES{WARNING});
} else {
    print "OK: " . $outputText;
    $session->close();
    exit($STATES{OK});
}

$session->close();
print "CRITICAL: unexpected plugin error.\n";
exit($STATES{CRITICAL});

# # # SUBROUTINES # # #

# Bad choice text 
sub badChoice {
    print "Is your choice of the OID: '$oid',"
    . " SNMP protocol version: '$protocolV'"
    . " or the host IP address: '$host' correct?\n";
}

# Usage 
sub usage {
    print "Usage: $0 -H <host> -C <community> -o <oid>"
    . " [-P <protocol version>] [-w <warning>] [-c <critical>]"
    . " [-z <timeZone>] [-t <timeout>] [-l <label>] [-U <unit>]"
    . "\nFor more information, use the -h (help).\n";
}

# Help
sub help {
    print <<HELP;

This is a nagios plugin to check SNMP OID time generally 
based on plugin check_snmp_lastwritetime.pl)
for time format:    "2000/01/31_23:59:59" 
                    "2000-01-31 23:59:59" 
                    "949363199" UNIX Epoch time format
                    "0x07d0011f173b3b00" Hex time format
                    (SNMP MIB HOST-RESOURCES-MIB DateAndTime format)

Example Mobatime 
(mobatime.mobaNetClocks.mobaNetClocksV1.mbnscTime.mbnscTimeDeviceTime.0):
perl $0 -H 192.168.0.50 -C romobatime -P 2 -o 1.3.6.1.4.1.13842.6.1.2.7.0 
-w 20 -c 60 -z UTC1 -t 15 -l "Mobatime 1" -U s

-H, --host          # Hostname or IP - required value
-C, --community     # SNMP community for read - required value
-P, --protocolV     # SNMP protocol version - required value
-o, --oid           # OID - required value
-w, --warning       # warning threshold in unit -U [default 2]
-c, --critical      # critical threshold in unit -U [default 5]
-z, --timeZone      # time zone [default UTC1]
                    UTC  - Coordinated Universal Time 
                    UTC1 - UTC +1h, CZ winter time, Central European Time
                    UTC2 - UTC +2h, CZ summer time, Central European Summer Time
                    CZ   - respect CZ summer/winter time CET/CEST
-t, --timeout       # Timeout in seconds [default 15]
-l, --labelE        # Equipment label [Equipment]
-U, --unit          # Threshold and Performance data unit [default s (seconds)]
                    s - seconds
                    m - minutes
-V, --version       # Print plugin version history
-h, --help          # Print this help
HELP
}

# Version history 
sub version {
    print "\n$0\n";
    print <<VERSION;

Plugin to check SNMP OID time, for time format: 
"2000/01/31_23:59:59", "2000-01-31 23:59:59", 
Epoch "949363199" and Hex "07d0011f173b3b00" 

version 0.6 (26.11.2021)
 - fixed hex time problem
version 0.5 (30.04.2019)
 - without tabs
version 0.4 (09.03.2019)
 - code cleanup
version 0.3 (14.02.2018)
 - added Hex time format 
version 0.2 (05.02.2018)
 - fixed diff variables, added ability to change Threshold and Perfdata units 
version 0.1 (24.01.2018)
 - new plugin to check SNMP OID time based on plugin check_snmp_lastwritetime.pl
 - added epoch time format, equipment label and delayed/overflowed check
VERSION
}  