#!/usr/bin/perl
#!/usr/bin/env perl

#
# nagios: -epn
# disable embedded perl, works only under 3.x
# to avoid not preserving NAGIOS_* environment variables
#

#
#    Short description: Checks HTTP statuscode for URL (and measures Duration for get-Request),
#           checks for valid XML Code and for age of checked file
#    Copyright (C) 2012  Erik Lukacs
#
#    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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA

#
#    Contact details:
#               nagios(at)lukacs.lu
#		http://www.lukacs.lu

# version 2.0.2
# date Nov 07, 2012

# his young padawan mojoliciously thanks to rhaen
# Also many thanks to badri@diglinks.com for having found some bugs and code-review

######## Changelogs
#
# fixed from -> to		info
#######
# 2.0.0alpha -> 2.0.0beta	Bug: time calculation fixed
# 2.0.0stable-> 2.0.1		Bug: date-error (see bugs below) fixed. Thanks to 
# 2.0.1stable-> 2.0.2		changed Nagios-Message in function httpCheck for default from WARNING to UNKNOWN

######## Bugs
# V2.0.0 alpha	scheme https does not work
# V2.0.0 stable FIXED date-error: on every months with 31 days it throws errors thanks to badri@diglinks.com

####### ToDo
# v2.0.0 alpha	check for modules wehter avaivable or not
# v2.0.0 alpha	Print ModuleVersions if debug enabled
# v2.0.0 alpha	check for module-Versions
# v2.0.0 beta	implement a function which checks quickly wether all perl modules are avaivable


use warnings;
use strict;

#checking for perl version
# eval {
#   require v5.10;
#   v5.10->import();
# };
# unless ($@) {
#   print "Perl 5.10 is required\n";
#   exit 3;
# }
# eval {
#   require Getopt::Long;
#   Getopt::Long->import();
# };
# unless ($@) {
#   print "Getopt::Long is required\n";
#   exit 3;
# }
# eval {
#   require Mojo::URL;
#   Mojo::URL->import();
# };
# unless ($@) {
#   print "Mojo::URL is required\n";
#   exit 3;
# }
# eval {
#   require Time::HiRes;
#   Time::HiRes->import();
# };
# unless ($@) {
#   print "Time::HiRes is required\n";
#   exit 3;
# }
# eval {
#   require Mojo::UserAgent;
#   Mojo::UserAgent->import();
# };
# unless ($@) {
#   print "Mojo::UserAgent is required\n";
#   exit 3;
# }

use v5.10;
use Getopt::Long;
use Mojo::URL;
use Time::HiRes;
use Mojo::UserAgent;


###### for debugging
my $debug;

#use Perl::Critic;
#my $critic=Perl::Critic->new(-severity => 'harsh' -theme => 'bugs && pbp');
######


my %RETCODE = (
  'OK'        => 0,
  'WARNING'   => 1,
  'CRITICAL'  => 2,
  'UNKNOWN'   => 3,
  'DEPENDENT' => 4
);
my %STATUS = ('OK' => 0, 'WARNING' => 0, 'CRITICAL' => 0, 'UNKNOWN' => 0);
my %httpResults = (
  'status'         => "",
  'httpStatusCode' => 0,
  'message'        => "",
  'url'            => "",
  'duration'       => 0,
  'onemore'        => 0
);
my %xmlResults = (
  'status'          => "",
  'message'         => "",
  'url'             => "",
  'xmlCheckCounter' => 0,
  'errMsg'          => "",
  'twomore'         => 0
);
my %ageResults =
  ('status' => "", 'message' => "", 'olderThan' => 0, 'age' => 0);


my $url = Mojo::URL->new;

my $scheme = "http";
my $user;
my $pass;
my $host = "lemonparty.org";
my $port = 80;
my $urlPath;
my $maxRedirects = 0;
my $zeit_vorher  = 0;
my $zeit_nachher = 0;
my $getDuration;
my $global_status;
my $url_content;    #contains data, which GET-Request got back
my $known_checks = "(http_ok|age|xml)";    #to be deleted
my $xml;
my $age;
my $xmlMaxRetries = 3;
my $check;

my $ok       = 0;
my $warning  = 0;
my $critical = 0;
my $unknown  = 0;

my $message    = "";
my $xmlCounter = 0;
my $input      = "";    #should be removed when I can do better

GetOptions(
  "scheme=s"       => \$scheme,
  "user=s"         => \$user,
  "pass=s"         => \$pass,
  "host=s"         => \$host,
  "port=i"         => \$port,
  "path=s"         => \$urlPath,
  "maxRedirects=i" => \$maxRedirects,
  'X'              => \$xml,
  'A=i'            => \$age,
  'debug'          => \$debug,
  'check'          => \&check,
  'help'           => \&help
);

sub help {
  print
    "\nUsage: \n ./check_url_state [-scheme <scheme> -user <username> -pass <password>] -host <host> [-port <port> -path <urlPath> -X -A <sec> -followredirects <int> -debug -check -help]\n";
  print "-scheme [http|ftp]\t scheme. Either http or ftp.\n";
  print "-user username. Optional.\n";
  print "-pass password. Optional.\n";
  print
    "-host\t base url address i.e. Hostname. By default: see presets below\n";
  print "-port\t Port. By Default: see presets below\n";
  print "-path\t URL-Patht. Path to Document which is to be checked\n";
  print "-maxRedirects <int>. Number of Redirects to follow.\n";
  print "\n";
  print "check scenarios: \n";
  print
    "Standard behaviour: checks wether <scheme>://<user>:<key>\@<host>:<port>/<urlPath> throws http Status Code 200 or not.\n";
  print "-x checks wether URL is valid XML/HTML or not.\n";
  print "-age checks wether File is oder than <sec> seconds.\n";
  print "-help displays this help\n";
  print "-debug prints debug information\n";
  print
    "-check checks wether all modules avaivable on your system (to be implemented)\n";
  print "\n";
  print "Presets:\n";
  print "Scheme: http\n";
  print "Host: lemonparty.org\n";
  print "Port: 80\n";
  print "MaxRedirects: 0\n";
  print "\n";
  exit $RETCODE{"UNKNOWN"};
}


sub debugPrintInitVariables {

  print "[DEBUG] Scheme:\t\t$scheme\n";
  if (defined($user)) {
    print "[DEBUG] User:\t\t$user\n";
  }
  else { print "[DEBUG] User:\t\tundefined\n"; }
  if (defined($pass)) {
    print "[DEBUG] Passwd:\t\t$pass\n";
  }
  else { print "[DEBUG] Passwd:\t\tundefined\n"; }
  print "[DEBUG] Hostname:\t$host\n";

  print "[DEBUG] Port:\t\t$port\n";
  if (defined($urlPath)) {
    print "[DEBUG] urlPath:\t$urlPath\n";
  }
  else { print "[DEBUG] urlPath:\tundefined\n"; }
  if (defined($xml)) {
    print "[DEBUG] XML:\t\t$xml\n";
  }
  else { print "[DEBUG] XML:\t\tundefined\n"; }
  if (defined($age)) {
    print "[DEBUG] XAge:\t\t$age\n";
  }
  else { print "[DEBUG] Age:\t\tundefined\n"; }
  print "[DEBUG] maxRedirects:\t\t$maxRedirects\n";
}
############# I could do better
sub checkArguments {

#here maybe smarter: check wether host.domain.tld entry
  if (!(defined($host))) {
    argMissing("host");
  }
  if (!(defined($port))) {
    argMissing("port");
  }

#not neccessary
#  if (!(defined($urlPath))) {
#    argMissing("urlPath");
#  }
  if (defined($user && $pass)) {
    $url->scheme($scheme)->userinfo("$user:$pass")->host($host)->port($port)
      ->path($urlPath);
  }
  else {
    $url->scheme($scheme)->host($host)->port($port)->path($urlPath);

    #print "URL: $url\n";
#      checkScenario();
  }
  if (defined $debug) {
    print "[DEBUG] URL $url\n";
  }
}

#Function for throwing an error
sub argMissing {
  my $tmp = $_[0];
  if (defined $debug) {
    print "Error: Argument $tmp is missing\n";
  }
  $message = "Error: Argument $tmp is missing\n";
  help();
  quiet_exit("ERROR", $message);
}

#brauchma evtl nimma
#sub unknownCheck {
#  my $tmp = $_;
#  print "Error: check $tmp is not valid\n";
#  $message = "Error: check $tmp is not valid\n";
#  print "\n";
#  help();
#  quiet_exit("ERROR", $message);
#}


# does GET-Request
# output: HTTP-Response-Code; Duration for Check
sub httpCheck {
  if (defined $debug) {
    print "[DEBUG] ----------------\n";
    print "[DEBUG] Doing HTTP-Check\n";
  }
  my $ua = Mojo::UserAgent->new;
  $zeit_vorher  = Time::HiRes::time();
  $url_content  = $ua->max_redirects($maxRedirects)->get($url);
  $zeit_nachher = Time::HiRes::time();
  my $statusCode = $url_content->res->code;
  $getDuration = $zeit_nachher - $zeit_vorher;


  given ($statusCode) {
    when (/118/) {
      %httpResults = (
        'status'         => "CRITICAL",
        'httpStatusCode' => $statusCode,
        'message'        => "Connection timed out.",
        'url'            => $url,
        'duration'       => $getDuration
      );
      $critical++;
    }
    when (/200/) {
      %httpResults = (
        'status'         => "OK",
        'httpStatusCode' => $statusCode,
        'message'        => "is accessible.",
        'url'            => $url,
        'duration'       => $getDuration
      );
      $ok++;
    }
    when (/20[1-7]{1}/) {
      %httpResults = (
        'status'         => "WARNING",
        'httpStatusCode' => $statusCode,
        'message'        => "is okay, but weird.",
        'url'            => $url,
        'duration'       => $getDuration
      );
    }
    when (/3[0-7]{2}/) {
      %httpResults = (
        'status'         => "WARNING",
        'httpStatusCode' => $statusCode,
        'message'        => "is NOT okay.",
        'url'            => $url,
        'duration'       => $getDuration
      );
      $warning++;
    }
    when (/4[0-9]{2}/) {
      %httpResults = (
        'status'         => "CRITICAL",
        'httpStatusCode' => $statusCode,
        'message'        => "is NOT okay: Client-Error.",
        'url'            => $url,
        'duration'       => $getDuration
      );
      $critical++;
    }
    when (/5[0-9]{2}/) {
      %httpResults = (
        'status'         => "CRITICAL",
        'httpStatusCode' => $statusCode,
        'message'        => "is NOT okay: Server-Error.",
        'url'            => $url,
        'duration'       => $getDuration
      );
      $critical++;
    }
    default {
      %httpResults = (
        'status'         => "UNKNOWN",
        'httpStatusCode' => $statusCode,
        'message'        => "Unknown error.",
        'url'            => $url,
        'duration'       => $getDuration
      );
      $warning++;
    }


  }
  if (defined $debug) {
    print "[DEBUG] Status:\t\t$httpResults{'status'} \n";
    print "[DEBUG] StatusCd:\t$httpResults{'httpStatusCode'} \n";
    print "[DEBUG] Message:\t$httpResults{'message'} \n";
    print "[DEBUG] URL:\t\t" . $httpResults{'url'} . " \n";
    print "[DEBUG] Duratn:\t\t" . $httpResults{'duration'} . " \n";
    print "[DEBUG] Http-Check done.\n";
  }
}

# checks recieved Data for valid XML. retries several time (see $maxXmlRetries) if invalid Check with sleep inbetween
# Output: errormessage if invalid
sub xmlCheck {

#  my $xmlCounter = $xmlResults{'xmlCheckCounter'};
  if (defined $debug) {
    print "[DEBUG] ----------------\n";
    print "[DEBUG] Doing xml-Check.\n";
  }
  use XML::LibXML;    #TODO: Check, ob Paket
  my $parser = XML::LibXML->new();
  $xmlCounter = $xmlCounter + 1;

  eval { $parser->load_xml(location => $url); };
  if ($@) {
    %xmlResults = (
      'status'          => "CRITICAL",
      'message'         => "Invalid XML-File.",
      'url'             => $url,
      'errMsg'          => $@,
      'xmlCheckCounter' => $xmlCounter
    );
    $critical++;
  }
  else {
    %xmlResults = (
      'status'          => "OK",
      'message'         => "This File is a valid XML-File.",
      'url'             => $url,
      'errMsg'          => $@,
      'xmlCheckCounter' => $xmlCounter
    );
    $ok++;
  }

  if (defined $debug) {
    print "[DEBUG] Status:\t\t$xmlResults{'status'} \n";
    print "[DEBUG] Message:\t$xmlResults{'message'} \n";
    print "[DEBUG] URL:\t\t" . $xmlResults{'url'} . " \n";
    print "[DEBUG] MSG:\t\t" . $xmlResults{'errMsg'} . " \n";
    print "[DEBUG] Counts:\t\t" . $xmlResults{'xmlCheckCounter'} . " \n";
    print "[DEBUG] xml-Check done.\n";
  }
}


sub checkFileAge {
  if (defined $debug) {
    print "[DEBUG] ----------------\n";
    print "[DEBUG] Doing age-Check\n";
  }

  #
  use Time::Local;    # check, ob die verfuegbar sind


  my $httpTime;
  my $httpTimeASCII;

#  my $ua = Mojo::UserAgent->new;
  my $difference;

  if (defined $url_content) {
    $httpTimeASCII = $url_content->res->headers->date;
  }
  else {

# If no content avaivable GET it again
# relict from version 1.x. There was an age-check without HTTP-Status-Check

    my $ua = Mojo::UserAgent->new;
    $httpTimeASCII =
      $ua->max_redirects($maxRedirects)->get($url)->res->headers->date;
  }

  if (defined $debug) {
    print "[DEBUG] Webserver-Time\n";
    print "[DEBUG] handover for time-Calc: $httpTimeASCII \n";
  }
  $input = gmtime;
  my $timeNow = gimmeTime($input);
  if (defined $debug) {
    print "[DEBUG] local-Time\n";
    print "[DEBUG] handover for time-Calc: " . gmtime . " \n";
  }

  $input    = $httpTimeASCII;
  $httpTime = gimmeTime($input);


  if (defined $debug) {
    print "[DEBUG] Time on Webserver: $httpTime \n";
    print "[DEBUG] local time: " . $timeNow . "\n";
  }

  #hier $timeToCheck nach integer umwandeln
  $difference = $timeNow - $httpTime;
  if (($difference < $age) and ($difference > 0)) {
    %ageResults = (
      'status'    => "OK",
      'message'   => "File is younger than",
      'olderThan' => $age,
      'age'       => $difference
    );
    $ok++;
  }
  elsif (($difference) > $age or $difference == $age) {
    %ageResults = (
      'status'    => "WARNING",
      'message'   => "File hasn't been renewed inbetween ",
      'olderThan' => $age,
      'age'       => $difference
    );
    $warning++;
  }
  elsif (($difference) > 2 * $age) {
    %ageResults = (
      'status'    => "CRITICAL",
      'message'   => "File is twice older than ",
      'olderThan' => $age,
      'age'       => $difference
    );
    $critical++;
  }
  elsif ($difference == 0 ) {
    %ageResults = (
      'status'    => "OK",
      'message'   => "File is up-to-date and not older than",
      'olderThan' => $age,
      'age'       => $difference
    );
    $ok++;
  }
  else {
    %ageResults = (
      'status'    => "UNKNOWN",
      'message'   => "Checking Fileage failed.",
      'olderThan' => $age,
      'age'       => $difference
    );
    $unknown++;
  }
  if (defined $debug) {
    print "[DEBUG] Status:\t\t$ageResults{'status'} \n";
    print "[DEBUG] Message:\t$ageResults{'message'} \n";
    print "[DEBUG] YoungerThan:\t" . $ageResults{'olderThan'} . " \n";
    print "[DEBUG] age:\t\t" . $ageResults{'age'} . " \n";
    print "[DEBUG] age-Check done.\n";
  }
}

#helper-function for time calculation
#imput Time-String
#output epoch (unix-time)
sub gimmeTime {

#  my $input = $_; #why the hell does this not work?
  use DateTime::Format::HTTP;
  if (defined $debug) {
    print "[DEBUG] Time-Converter \n";
    print "[DEBUG] Input:\t\t$input \n";
  }
  my $output;
  my $timeString = 'DateTime::Format::HTTP'->parse_datetime($input);

  my @time  = split('T', $timeString);
  my @hours = split(':', $time[1]);
  my @date  = split('-', $time[0]);
  if (defined $debug) {
        #Date check: 51, 58, 08, 31, 10, 2012
        print "[DEBUG] Date check: $hours[2], $hours[1], $hours[0], $date[2], $date[1], $date[0]\n";
  }
  $output =
    timelocal($hours[2], $hours[1], $hours[0], $date[2], $date[1] - 1, $date[0]);

  if (defined $debug) {
    print "[DEBUG] Converted:\t\t$timeString \n";
    print "[DEBUG] Time $time[1], Date $time[0] \n";
    print "[DEBUG] Output $output\n";
  }

  return $output;
}

#calls Checks
# no output
sub doChecks {
  httpCheck();

#no get-message here because if I need another get-request I am setting values here
  if (defined $xml) {
    xmlCheck();
    while (($xmlResults{'status'} eq "CRITICAL")
      && ($xmlCounter < $xmlMaxRetries))

    {
      sleep(2);
      httpCheck();    #re-fetching file
      xmlCheck();
    }

#generating message. should be done here because we have three http-get trials and I only want to know wether successful or not
    $message = $message
      . "HTTP: $httpResults{'url'} $httpResults{'message'} Status Code: $httpResults{'httpStatusCode'}. ";
    $message = $message . "$xmlResults{'message'} ";
  }
  else {

#if xml-check is disabled the only output I get shall be my get-request.
    $message = $message
      . "HTTP: $httpResults{'url'} $httpResults{'message'} Status Code: $httpResults{'httpStatusCode'}. ";
  }
  if (defined $age) {
    checkFileAge();
    $message =
      $message . " AGE: $ageResults{'message'} $ageResults{'olderThan'}s. Age: $ageResults{'age'}";
  }
}


sub printSummaryAndExit {
  if (defined $debug) {
    print "[DEBUG] Summary --------\n";
  }

#$STATUS($httpResults{'status'})++;
#
#	if (defined $debug){
#print "[DEBUG] OK:\t$STATUS('OK')\n";
#print "[DEBUG] WARN:\t$STATUS('WARNING')\n";
#print "[DEBUG] CRIT:\t$STATUS('CRITICAL')\n";
#print "[DEBUG] NA:\t$STATUS('UNKNOWN')\n";
  #}

  if (defined $debug) {
    print "[DEBUG] OK:\t$ok\n";
    print "[DEBUG] WARN:\t$warning\n";
    print "[DEBUG] CRIT:\t$critical\n";
    print "[DEBUG] NA:\t$unknown\n";
  }

  if ($critical > 0) {
    quiet_exit($RETCODE{"CRITICAL"}, $message);
  }
  elsif ($warning > 0) {
    quiet_exit($RETCODE{"WARNING"}, $message);
  }
  elsif ($unknown > 0) {
    quiet_exit($RETCODE{"UNKNOWN"}, $message);
  }
  elsif ($ok > 0) {
    quiet_exit($RETCODE{"OK"}, $message);
  }
  else {
    quiet_exit($RETCODE{"UNKNOWN"}, "Ausnahmefehler");
  }
}

#standard exiting-method for nagios
sub quiet_exit {
  my ($exitcode, $exitmsg) = @_;
  my $globalMessage = "";

#generating general Summary
  my $sumFail   = $critical + $warning + $unknown;
  my $sumAll    = $sumFail + $ok;
  my $firstData = "$sumFail/$sumAll Checks Failed. ";

#generating measure-Data
  my $values = "getduration=$httpResults{'duration'}s ";
  if (defined $ageResults{'age'}) {
    $values = $values . "fileage=$ageResults{'age'}s";
  }

#generating global exitmessage
  $globalMessage = $firstData . $exitmsg . " | $values";


#print "$exitmsg\n";
  print $globalMessage . "\n";
  exit $exitcode;

#  $STATUS{$exitcode} = $STATUS{$exitcode} + 1;
}


#help if (>defined(@ARGV));
if (defined $debug) {
  print "[DEBUG] Debug turned on\n";
  debugPrintInitVariables();
}
checkArguments();
doChecks();
printSummaryAndExit();
