#!/usr/bin/env perl

use warnings;
use strict;

use Getopt::Long;
use Pod::Usage;
use SOAP::Lite;

# global variables
my $warning  = 90;
my $critical = 30;
my $version  = 1;
my $help     = 0;
my $host;
my $revision;
my $debug;
my $tag;
my $community;
my $esx;
my $pwdfile;
my $username;
my $password;

# variables needed for xserv.dell.com
my $soap_url = "http://xserv.dell.com/services/assetservice.asmx";
my $soap_uri = "http://support.dell.com/WebServices/";
my $guid     = "f1d49b55-a55c-44c1-baae-a244e1ae57d8";
my $appname  = "nagios warranty check";

#-------------------------------------------------------------------------------
# cli options
#-------------------------------------------------------------------------------
Getopt::Long::Configure( "no_ignore_case", "bundling" );
GetOptions(
    'H|host=s'       => \$host,
    't|tag=s'        => \$tag,
    'h|help|?'       => \$help,
    'v|verbose'      => \$debug,
    'V|version'      => \$revision,
    'w|warning=i'    => \$warning,
    'c|critical=i'   => \$critical,
    'C|community=s'  => \$community,
    'E|esx=s'        => \$esx,
    'p|passwdfile=s' => \$pwdfile,
);

# get version info if requested and exit
if ($revision) {
    print "Version: $version\n";
    exit 0;
}

pod2usage( -verbose => 2, -noperldoc => 1, ) if $help;

pod2usage(1) unless $host;

#-------------------------------------------------------------------------------
#  process cli switches
#-------------------------------------------------------------------------------

if ( defined $debug ) {
    SOAP::Lite->import( trace => "debug" );
}

# if no tag is given from the cli and the check is run against localhost
# try getting it from dmidecode

if ( !defined $tag and $host eq "localhost" ) {
    dbg("Getting tag from dmidecode");
    _get_delltag_dmidecode();
    dbg("tag is $tag");
}

# try getting the $tag with snmp if we get a community cli switch
if ( defined $community ) {
    _get_tag_snmp( $host, $community );
}

# try getting the $tag using the VMware api
if ( !defined $pwdfile and defined $esx ) {
    print "We need a config file for ESXi credentials.\n";
    print "Please see the help for details.\n";
    exit 1;
}
if ( defined $esx and defined $pwdfile ) {
    dbg("ok, both switches for ESXi and password file have values");
    _get_pwdfile_mod($pwdfile);
    _process_password_file($pwdfile);
    _get_tag_esx();
}

# If we cannot get a $tag either from the cli options or dmidecode or
# snmp, then we cannot go on. End script then.
unless ( defined $tag ) {
    print
"We could not find an appropriate dell tag string. Without one we cannot use this plugin.\n";
    exit 3;
}

#-------------------------------------------------------------------------------
# main
#-------------------------------------------------------------------------------

# create soap agent
my $client = SOAP::Lite->new(
    proxy => $soap_url,
    uri   => $soap_uri,
);

# we need this, or it won't work
$client->on_action(
    sub {
        "http://support.dell.com/WebServices/GetAssetInformation";
    }
);

# poll the dell service with the requested info
my $call = $client->call(
    'GetAssetInformation',
    SOAP::Data->name('guid')->value($guid),
    SOAP::Data->name('applicationName')->value($appname),
    SOAP::Data->name('serviceTags')->value($tag),
);

die $call->faultstring if ( $call->fault );

# to see all the whole response uncomment these 2 lines
# use Data::Dumper;
# print Dumper $call->result;

# get some answers from the soap service:

# until when is our warranty valid?
my $end_date = $call->valueof('//Asset/Entitlements/EntitlementData/EndDate');

# is our warranty active now?
my $entitlement_type =
  $call->valueof('//Asset/Entitlements/EntitlementData/EntitlementType');

# how many days left do we have?
my $warranty_left =
  $call->valueof('//Asset/Entitlements/EntitlementData/DaysLeft');

if ($debug) {
    print "$tag still $warranty_left days left\n";
    print "entitlement type: \t $entitlement_type\n";
    print "end warranty date: \t $end_date\n";
}

# nagios logic/end of script
if ( $warranty_left > $warning ) {
    print
"OK: we have $warranty_left days. Warranty ends $end_date|days:$warranty_left\n";
    exit 0;
}
elsif ( $warranty_left <= $warning && $warranty_left >= $critical ) {
    print
"WARNING: we have $warranty_left days. Warranty ends $end_date|days:$warranty_left\n";
    exit 1;
}
elsif ( $warranty_left < $critical ) {
    print
"CRITICAL: we have $warranty_left days. Warranty ends $end_date|days:$warranty_left\n";
    exit 2;
}
else {
    print "UNKNOWN: run $0 with --verbose flag to see what has gone wrong\n";
    exit 3;
}

#-------------------------------------------------------------------------------
# subroutines
#-------------------------------------------------------------------------------

sub dbg {
    print STDERR "--", shift, "\n" if $debug;
}    # ----------  end of subroutine dbg  ----------

#===  FUNCTION  ================================================================
#         NAME:  _get_delltag_dmidecode
#      PURPOSE:  get the dell tag using dmidecode
#   PARAMETERS:
#      RETURNS:  dell tag string
#  DESCRIPTION:  when run on the localhost, we can get the dell tag
#                with dmidecode --type system
#       THROWS:  no exceptions
#     COMMENTS:  as this plugin will probably run as user nagios, we
#                need to use sudo. dmidecode can only run as root
#                To enable sudo dmidecode for the user nagios, edit the
#                sudoers file with visudo and set something like this:
#                nagios     ALL = NOPASSWD: /usr/sbin/dmidecode
#     SEE ALSO:  n/a
#===============================================================================
sub _get_delltag_dmidecode {
    my $dmidecode = "sudo dmidecode --type system";
    dbg("running and parsing $dmidecode");
    open my $outputdmidecode, '-|', $dmidecode or die "$!\n";
    while (<$outputdmidecode>) {
        chomp;    # dump hidden new lines please

        # we need to match Serial Number: *****, we save everything
        # after the : until a space in $1 which later becomes $tag
        # update: now we return the lower case tag after an update of
        # Dell's site
        if ( $_ =~ m/^.*Serial Number: (.*)\s*$/ ) {
            $tag = lc $1;
        }
    }

    close $outputdmidecode;

    dbg("this system\'s dell tag is $tag");

    return $tag;
}    # ----------  end of subroutine _get_delltag_dmidecode  ----------

#===  FUNCTION  ================================================================
#         NAME:  _get_tag_snmp
#      PURPOSE:  get the dell tag using snmp
#   PARAMETERS:  $host, $community
#      RETURNS:  dell tag string
#  DESCRIPTION:  get the dell tag using snmp
#       THROWS:  no exceptions
#     COMMENTS:  we use snmp version 1
#     SEE ALSO:  n/a
#===============================================================================
sub _get_tag_snmp {
    ( $host, $community ) = @_;

    dbg( "polling $host with community $community using SNMP to get asset tag\n"
    );

    # create snmp object
    require Net::SNMP;

    my $oid_dell_tag = "1.3.6.1.4.1.674.10892.1.300.10.1.11.1";

    # start snmp session
    my ( $session, $error ) = Net::SNMP->session(
        -hostname  => $host,
        -community => $community,
        -version   => 1,

        #-debug     => 255,
    );

    # if the snmp session fails, exit check with error message
    if ( !defined $session ) {
        printf "ERROR: %s.\n", $error;
        exit 1;
    }

    # get the value in our wanted oid
    my $result = $session->get_request( -varbindlist => [$oid_dell_tag], );

    # if we cannot get a result, exit check with error message
    if ( !defined $result ) {
        printf "ERROR: %s.\n", $error;
        $session->close();
        exit 1;
    }

    # close snmp session, after this we parse the results we get
    $session->close();

    # uncomment this to see the snmp status
    $tag = $result->{$oid_dell_tag};
    dbg("got service tag [$tag] from snmp\n");

    return $tag;
}    # ----------  end of subroutine _get_tag_snmp  ----------

#===  FUNCTION  ================================================================
#         NAME: _get_tag_esx
#      PURPOSE: retrieve the dell service tag of a ESXi host using the VMware
#               api
#   PARAMETERS: none
#      RETURNS: $tag
#  DESCRIPTION: see purpose
#       THROWS: no exceptions
#     COMMENTS: none
#     SEE ALSO: n/a
#===============================================================================
sub _get_tag_esx {

    # adapted from http://www.virtuallyghetto.com/
    # http://communities.vmware.com/docs/DOC-14652
    dbg("got esx usernme: $username");
    dbg("got esx password: $password");

    require VMware::VILib;
    require VMware::VIRuntime;
    Opts::set_option( 'server',   $host );
    Opts::set_option( 'username', $username );
    Opts::set_option( 'password', $password );
    Opts::parse();
    Opts::validate();
    Util::connect();

    my $host_view = Vim::find_entity_view( view_type => 'HostSystem' );
    my $additional_vendor_info = "";

    if ( $host_view->summary->hardware->otherIdentifyingInfo ) {
        my $add_info = $host_view->summary->hardware->otherIdentifyingInfo;
        foreach (@$add_info) {
            if ( $_->identifierType->key eq "ServiceTag" ) {
                $tag = $_->identifierValue;
                dbg("tag from ESX API: [$tag]\n");
            }
        }
    }
    else {
        dbg(
"There is no Service Tag information configured by your Vendor/OEM\n"
        );
    }
    return $tag;
}    # ----------  end of subroutine _get_tag_esx  ----------

#===  FUNCTION  ================================================================
#         NAME: _process_password_file
#      PURPOSE: import the cli switch $pwdfile
#   PARAMETERS: $pwdfile
#      RETURNS: $username and $password
#  DESCRIPTION: see purpose
#       THROWS: no exceptions
#     COMMENTS: none
#     SEE ALSO: n/a
#===============================================================================
sub _process_password_file {
    ($pwdfile) = @_;

    # load the file
    require Config::Tiny;
    dbg("password file is $pwdfile");

    # create the config
    my $config = Config::Tiny->new;

    # open it
    $config = Config::Tiny->read($pwdfile);

    # get properties;
    $username = $config->{_}->{username};
    $password = $config->{_}->{password};
    dbg("esx username found in config file $pwdfile: $username");
    dbg("esx password found in config file $pwdfile: $password");
    return ( $username, $password );
}

#===  FUNCTION  ================================================================
#         NAME: _get_pwdfile_mod
#      PURPOSE: verify $pwdfile has 0600 permissions
#   PARAMETERS: $pwdfile
#      RETURNS: nothing
#  DESCRIPTION: if $pwdfile permissions are too lax, exit script
#       THROWS: no exceptions
#     COMMENTS: none
#     SEE ALSO: n/a
#===============================================================================
sub _get_pwdfile_mod {

    # thanks perlmonks
    # http://www.perlmonks.org/?node_id=882424
    ($pwdfile) = @_;

    # get the file permissions
    my $mode = sprintf '%04o', ( stat $pwdfile )[2] & 07777;
    dbg("permissions $pwdfile are $mode\n");

    if ( $mode == "0600" ) {
        dbg("permissions are ok, only the owner may read/write the file");
        return;
    }
    else {
        print "o o, the file's mode is $mode, it really shold be 0600\n";
        print "please chmod $pwdfile 0600 to fix it\n";
        exit 1;
    }
}

#-------------------------------------------------------------------------------
#  Plain Old Documentation
#-------------------------------------------------------------------------------

=head1 NAME

dellkit_warranty

=head1 SYNOPSIS

dellkit_warranty -H [hostname] -[tcwvVhCEp]

options -E and -C are mutually exclusive (-E is for polling ESXi hosts, -C is
for polling hosts using SNMP.

=head1 DESCRIPTION

This is the documentation for dellkit_warranty version 1.

dellkit_warranty is a Nagios plugin to check the remaining days of warranty left for Dell hardware.

The plugin requires the installation of the SOAP::Lite module,
available from your Perl distributor repositories or CPAN. 

For Linux hosts you can retrieve the Dell service tag information using
dmidecode (you need to allow the nagios user to run dmidecode in sudo without
a password. This assumes you run the check from NRPE, but then you need to
allow the linux hosts access to the internet (no proxy) to poll the host
xserv.dell.com (http).

Otherwise, for Linux and Windows hosts you may retrieve the service tag using
SNMP. This requires the installation of Open Managed Node from Dell and
configuring the snmp service/daemon. If you already use (and you should!) the
excellent L<check_openmange|http://folk.uio.no/trondham/software/check_openmanage.html> you have already this sorted out.

For ESXi hosts you need the Config::Tiny module and the VMware Perl SDK. If
you already use the excellent L<check_esx|http://goo.gl/5GS0X> by OP5 then you
are all set; follow the prerequisites instructions here L<http://goo.gl/Gjx9OV>
if you do not have it.

You also need to create a password file to not pass the ESX username/password
on the command line. This password file is a simple rhs = lhs ini format file

    username = your_esx_username
    password = yourpassword

That's it. After that, change the file's permissions to be 600, that is, only
read/write, for the nagios user.

=head1 ARGUMENTS

=for text

=begin text

-H | --host         Hostname/ip address of server to monitor (required)

-t | --tag          Dell service tag number of server to monitor; if you do
                    not specify one on the command line, the script will try
                    to get it from dmidecode (only localhost). If you want
                    to poll a Dell server using SNMP, use the -C switch;
                    if you want to poll a ESXi host, use the -E switch.

-V | --version      prints the version of this program

-v | --verbose      prints extra debugging information

-w | --warning      days before nagios gives a warning; default is 90

-c | --critical     days before nagios gives a critical alert; default is 30

-E | --esx          to indicate we want to poll a dell server running ESXi 
                    using the VMware api (requires installing the VMware
                    Perl SDK)

-p | --passwdfile   path to the file where the ESXi credentials are stored   

-C | --community    to indicate we want to poll a dell server using SNMP
                    (requires the Open Managed Node software on the host
                    and the Net::SNMP perl library). Right now only snmp
                    version 1 is implemented

-h | --help | -?    print the full help text

=end text

=for text

=head1 AUTHOR

natxo asenjo in his spare time

=head1 EXAMPLES

=over

=item poll an ESXi host

dellkit_warranty -H host -E true -p passwordfile

=item poll localhost

dellkit_warranty -H localhost

=item poll Windows host using SNMP

dellkit_warranty -H windowshost -C communityname

=item poll localhost and get debugging info (it shows the soap conversation
with the xserv.dell.com host)

dellkit_warranty -H localhost -v

=back

=head1 COPYRIGHT & LICENSE

Copyright Natxo Asenjo
This program is free software; you can redistribute it and/or
modify it under the terms of the Artistic License version 2.0.
