#!/usr/local/bin/php
<?php
// Nagios Exit Errorlevels, 0=OK, 1=Warning, 2=Critical, 3=Unknown

// Script info and variables
$VERSION = '$Rev: 2252 $';
$AUTHOR = 'bseklecki-mastrboy';

# Note: This script was modified by Brian A. Seklecki <bseklecki@collaborativefusion.com> 
# to perform additional certificate validation
# This script had no initial lisence
# so we're re-releasing' it dual-licensed under BSD* and mastrboy*

// function related variables
$OPENSSL = '/usr/bin/openssl';
$CAPATH = "/usr/local/etc/nagios/scripts/checks/ff_rootCA_Extr_hash";
$TMPDIR = "/tmp";

# Note: cd $! && perl c_rehash.pl

# Try to load library
$lib = dirname($argv[0]) . '/nagios_php.inc';
if ( is_file($lib) && is_readable($lib) ) {
  include $lib;
} else {
  echo "Unable to load CFI Shared Nagios Library";
  exit(255);
}

# global
$ocsp = true;
$nagios_separator = "\n";

// usage help
function usage() {
  global $VERSION;
  global $AUTHOR;
  $usagetext = "".$_SERVER['SCRIPT_FILENAME']." $VERSION by $AUTHOR
  
  Usage:    ".$_SERVER['SCRIPT_FILENAME']." [-v] Hostname/IP port WarningDays CriticalDays
  Example:  ".$_SERVER['SCRIPT_FILENAME']." [-v] gmail.com 443 30 7\n";

  fwrite(STDOUT,"$usagetext");
}

function error($str) {
  nagios_diode("UNKNOWN", "$str\n");
  cane_one();
}

function wildcard_compare($a, $b) {
  if ($a == '*' || $b == '*' || strcasecmp($a, $b) == 0) {
    return 0;
  } else {
    return -1;
  }
}

// command line argument stuff
if (!$argv[4]) {
  // show usage info if no argument is passed
  nagios_diode("UNKNOWN", "Bad Usage Syntax");
  usage();
  cane_one();
}

if (sizeof($argv) > 4 && $argv[1] == "-v") {
  // check if we're in "validate mode"
  // assume the user has a clue about valid argv, otherwise complicate this code greatly
  $VALIDATE=1;
  // run the process
  $HOSTNAME = $argv[2];
  $PORT = $argv[3];
  $WDAYS = $argv[4];
  $CDAYS = $argv[5];

} else {
  if (!is_numeric($argv[2])) {
    // check to see if the port argument is numeric or not
    nagios_diode("UNKNOWN", "Port argument is not a numeric value");
    cane_one();
  } elseif (!is_numeric($argv[3])) {
    nagios_diode("UNKNOWN","Warning days argument is not a numeric value");
    cane_one();
  } elseif (!is_numeric($argv[4])) {
    nagios_diode("UNKNOWN","Critical days argument is not a numeric value");
    cane_one();
  }
  // run the process
  $HOSTNAME = $argv[1];
  $PORT = $argv[2];
  $WDAYS = $argv[3];
  $CDAYS = $argv[4];
}
  
$descriptorspec = array(
  0 => array("pipe", "r"),  // stdin
  1 => array("pipe", "w"),  // stdout
  2 => array("pipe", "w")   // stderr
);
 
if ($VALIDATE) {
  $process = proc_open("$OPENSSL s_client -showcerts -verify 99 -CApath $CAPATH -connect $HOSTNAME:$PORT |
                        awk -v c=-1 '/-----BEGIN CERTIFICATE-----/{inc=1;c++}
                                     inc {print }
                                     /---END CERTIFICATE-----/{inc=0}'", $descriptorspec, $pipes);
} else { 
  $process = proc_open("$OPENSSL s_client -connect $HOSTNAME:$PORT 2>/dev/null | $OPENSSL x509 -text -noout -enddate -serial", $descriptorspec, $pipes);
}

if (is_resource($process)) {
  // $pipes now looks like this:
  // 0 => writeable handle connected to child stdin
  // 1 => readable handle connected to child stdout
  // 2 => error output will be appended to child stderr

  fclose($pipes[0]);
  
  $output = stream_get_contents($pipes[1]);
  fclose($pipes[1]);
  $error = stream_get_contents($pipes[2]);
  fclose($pipes[2]);
  
  // It is important that you close any pipes before calling
  // proc_close in order to avoid a deadlock
  $return_value = proc_close($process);

  // check to see if there are any errors and exit if it is
  // usually this happens when openssl can't reach the host/port or the host is down
  if ($error && $VALIDATE) {
    if ($certificate_errors = eregi('verify\ error.*', wsstrip($error), $matches)) { ;
      nagios_diode("CRITICAL", "Error in PKI chain validation: $matches[0]");
    }
  } elseif ($error && !$VALIDATE) {
    nagios_diode("UNKNOWN","Unable to exec openssl (check args,hostname): $error");
    cane_one();
  }
  
  // continue if there are any output
  if ($output) {
    $x509_header = "-----BEGIN CERTIFICATE-----\n";
    $x509_trailer = "-----END CERTIFICATE-----";
    $certs = preg_split("/-----(BEGIN|END) CERTIFICATE-----\n/", $output, -1, PREG_SPLIT_NO_EMPTY);
    $num_certs = count($certs);
   
    // Get information about the server's certificate in particular
    $server_pem = $x509_header . $certs[0] . $x509_trailer;
    exec("echo \"$server_pem\" | $OPENSSL x509 -noout -enddate -serial -text", $output);
    $output = implode($output, "\n");
    preg_match("/notAfter=.*/", $output, $matches) ? $expire_date = preg_replace("/notAfter=/", "", $matches[0]) : error("Could not find expiration date");
    preg_match("/serial=.*/", $output, $matches) ? $serial = preg_replace("/serial=/", "", $matches[0]) : error("Could not find certificate serial number");
    preg_match("/OCSP - URI:.*/", $output, $matches) ? $ocsp_uri = preg_replace("/OCSP - URI:/", "", $matches[0]) : $ocsp = false;
    preg_match("/DNS:.*/", $output, $matches) ? $saltname = $matches[0] : $saltname = false;

    // Validate that the hostname matches the certificates CN or subjetAltNames
    if ($saltname) {
      $saltname = preg_replace("/DNS:/", "", $saltname);
      $allowed_names = explode(", ", $saltname);
    } else { 
      if (preg_match("/Subject: .*/", $output, $matches)) {
        $cn = preg_replace("/.*CN=/", "", $matches[0]);
        // Get rid of the email field if it exists
        $allowed_names = array(preg_replace("/\/.*/", "", $cn));
      } else {
        error("Could not determine certificate CN");
      }
    }
    $match = false;
    foreach($allowed_names as $name) { 
      $name_split = explode(".", $name);
      $hostname_split = explode(".", $HOSTNAME);
      $diff = array_udiff_assoc($name_split, $hostname_split, "wildcard_compare");
      if (count($diff) == 0) {
        $match = true;
      }
    }
    if (!$match) {
      nagios_diode("CRITICAL", "SSL Certificate is not a match for the requested hostname");
    }
    
    // calculate days before the ssl cert expires
    $expire = strtotime($expire_date);
    $now = time();
    $certexpire = intval(($expire - $now) / 86400);
    if ($certexpire < $CDAYS) {
      nagios_diode("CRITICAL", "SSL Certificate expires in $certexpire days");
    } elseif ($certexpire < $WDAYS) {
      nagios_diode("WARNING","SSL Certificate expires in $certexpire days");
    } else {
      nagios_diode("OK","SSL Certificate expires in $certexpire days");
    }

    // Hack for various verisign.com unauthorized responses
    if ($ocsp && ( $ocsp_uri == "http://ocsp.verisign.com" || $ocsp_uri == "http://EVSecure-ocsp.verisign.com" ))
      $ocsp = false;

    // Check OCSP if the certificate lists a URL
    if ($ocsp) {
      // Dump the issuer's certificate to a file
      $fname = tempnam($TMPDIR, "{$argv[0]}_{$HOSTNAME}_");
      file_put_contents($fname, $x509_header . $certs[1] . $x509_trailer);
      exec("$OPENSSL ocsp -issuer $fname -nonce -CApath $CAPATH -url $ocsp_uri -serial 0x$serial 2>/dev/null", $output, $return);
      unlink($fname);
      if ($return) {
        nagios_diode("UNKNOWN", "Unknown OpenSSL OCSP error");
        cane_one();
      }
      $output = implode($output, "\n");
      if (preg_match("/0x$serial: good/", $output)) {
        nagios_diode("OK", "SSL Certificate passed OCSP Validation");
      } else {
        nagios_diode("CRITICAL", "SSL Certificate failed OCSP Validation", $output);
      }
    }
  } else {
    // we exit if there is no output to process
    nagios_diode("UNKNOWN","no output to process");
  } 
} else {
  nagios_diode("UNKNOWN","no output resources -- cant find $OPENSSL maybe ");
}
cane_one();

?>
