#!/usr/bin/env php
<?php

/*

  check_nems_php_agent
  Written by Robbie Ferguson for NEMS Linux
  -----
  1.9 - Jun 30, 2024 - Fix warn/crit values to float (to accommodate, for example,
                       load warning at 0.6). Round output to appropriate number of
                       decimal places for several checks.

  1.8 - May 13, 2024 - Improve output of CPU Load, add perfdata, improve error
                       output if response code is not 200, increased timeout.
                       Optimize CURL call to only execute once.

  1.7 - Apr 29, 2024 - Improve output of disk, including space free in GB.

  1.6 - Apr 1, 2024  - Remove redundant state output and test names.
                       Added perfdata to storage.

  1.5 - Aug 16, 2022 - If a disk check is performed and the target disk is not
                       mounted, give CRIT rather than parent disk usage.
                       Suppress CLI errors.

  1.4 - Jul 17, 2022 - Improve disk checking by allowing user to specify the
                       location to check. This can be / for the root disk,
                       . for the running folder of the PHP agent, /var or even
                       a mountpoint such as /mnt/backup

  1.2 - Dec 10, 2020 - Network usage now uses ifstat and generates a more
                       accurate average usage number based on all network
                       interfaces on the server with a 5 second average.
                       New agent will only run the equations and tests
                       for the requested check. For example, don't run
                       a 5 second network bandwidth test when the requested
                       check is for the load average. Fixed bug where nettx
                       was in fact reporting netrx.

  1.1 - Nov 19, 2020 - Key is now encrypted (not just base64 encoded)
                       Added "." disk feature to load stats on the web dir
                       Several bug fixes for disk and var feature
                       Added output for open_basedir permissions problem

  1.0 - Jul 29, 2019 - Initial release

*/

  $nemsagentver = '1.6';

  if (count($argv) >=5) {
    array_shift($argv);
    $data['warn'] = floatval(array_shift($argv));
    $data['crit'] = floatval(array_shift($argv));
    $data['url'] = trim(array_shift($argv));
    $data['check'] = trim(array_shift($argv));
    $data['switch'] = trim(array_shift($argv)); // a final switch variable
  } else {
    exit_usage();
  }

  $php_agent_key = '';
  $nemsconffile = '/usr/local/share/nems/nems.conf'; // www-admin must have access to read/write
  if (!file_exists($nemsconffile)) {
    die('NEMS Linux is required to execute this command.' . PHP_EOL);
  }
  $conf = file($nemsconffile);
  if (is_array($conf)) { // Load the existing conf data
        foreach ($conf as $line) {
                $tmp = explode('=',$line);
                if (trim($tmp[0]) == 'php_agent_key') $php_agent_key = trim($tmp[1]);
        }
  }
  if ($php_agent_key == '') {
    echo 'Missing passphrase. Did you set one in NEMS SST?';
    exit(3);
  }

  $decryptionkey = openssl_encrypt($php_agent_key,"AES-128-ECB",base64_encode(':' . $php_agent_key . ':'));

  // Set connection timeout
    $timeout = 45;

  // Get the remote header and response
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_HEADER, false); // Don't include header in output
    curl_setopt($ch, CURLOPT_URL, $data['url']);
    curl_setopt($ch, CURLOPT_POST, true);
    $postfields = array('check' => $data['check'], 'switch' => $data['switch']);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $postfields);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    $response = curl_exec($ch);
    $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); // Get HTTP code
    curl_close($ch);

  // Stop if header is anything other than 'found'
    if ($httpcode != 200) {
        switch ($httpcode) {
            case 0:
                $httpresponse = "No response received";
                break;
            case 100:
                $httpresponse = "Continue";
                break;
            case 101:
                $httpresponse = "Switching Protocols";
                break;
            case 200:
                $httpresponse = "OK";
                break;
            case 201:
                $httpresponse = "Created";
                break;
            case 202:
                $httpresponse = "Accepted";
                break;
            case 203:
                $httpresponse = "Non-Authoritative Information";
                break;
            case 204:
                $httpresponse = "No Content";
                break;
            case 205:
                $httpresponse = "Reset Content";
                break;
            case 206:
                $httpresponse = "Partial Content";
                break;
            case 300:
                $httpresponse = "Multiple Choices";
                break;
            case 301:
                $httpresponse = "Moved Permanently";
                break;
            case 302:
                $httpresponse = "Found";
                break;
            case 303:
                $httpresponse = "See Other";
                break;
            case 304:
                $httpresponse = "Not Modified";
                break;
            case 305:
                $httpresponse = "Use Proxy";
                break;
            case 307:
                $httpresponse = "Temporary Redirect";
                break;
            case 400:
                $httpresponse = "Bad Request";
                break;
            case 401:
                $httpresponse = "Unauthorized";
                break;
            case 402:
                $httpresponse = "Payment Required";
                break;
            case 403:
                $httpresponse = "Forbidden";
                break;
            case 404:
                $httpresponse = "Not Found";
                break;
            case 405:
                $httpresponse = "Method Not Allowed";
                break;
            case 406:
                $httpresponse = "Not Acceptable";
                break;
            case 407:
                $httpresponse = "Proxy Authentication Required";
                break;
            case 408:
                $httpresponse = "Request Timeout";
                break;
            case 409:
                $httpresponse = "Conflict";
                break;
            case 410:
                $httpresponse = "Gone";
                break;
            case 411:
                $httpresponse = "Length Required";
                break;
            case 412:
                $httpresponse = "Precondition Failed";
                break;
            case 413:
                $httpresponse = "Request Entity Too Large";
                break;
            case 414:
                $httpresponse = "Request-URI Too Long";
                break;
            case 415:
                $httpresponse = "Unsupported Media Type";
                break;
            case 416:
                $httpresponse = "Requested Range Not Satisfiable";
                break;
            case 417:
                $httpresponse = "Expectation Failed";
                break;
            case 500:
                $httpresponse = "Internal Server Error";
                break;
            case 501:
                $httpresponse = "Not Implemented";
                break;
            case 502:
                $httpresponse = "Bad Gateway";
                break;
            case 503:
                $httpresponse = "Service Unavailable";
                break;
            case 504:
                $httpresponse = "Gateway Timeout";
                break;
            case 505:
                $httpresponse = "HTTP Version Not Supported";
                break;
            default:
                $httpresponse = "Unknown response code: " . $httpcode;
                break;
        }
      echo 'NEMS PHP Server Agent not found at provided URL (' . $httpresponse . ').';
      exit(3);
    }

  $data['response'] = json_decode($response);
  if (!is_object($data['response'])) {
    echo 'Invalid response from nems-agent.php at that URL.';
    exit(3);
  }

  $nemsver = shell_exec('/usr/local/bin/nems-info nemsver');

  $authLocal = hash('sha256', $decryptionkey);

  if ($authLocal != $data['response']->auth) {
    echo 'Local PHP Agent encryption passphrase differs from remote.';
    exit(3);
  }

  // Decrypt the data
    $data['response']->data = json_decode(openssl_decrypt($data['response']->data,"AES-128-ECB",$decryptionkey));

  // Make sure it decrypted okay
    if (!is_object($data['response']->data)) {
      echo 'Invalid decrypted response from nems-agent.php at that URL.';
      exit(3);
    }

  // Run the checks against the received data
    switch ($data['check']) {
      case 'load':
        if ($data['response']->data->cpu->loadaverage->{15} > $data['crit']) {
          echo round($data['response']->data->cpu->loadaverage->{15},2) . "%|load15=" . $data['response']->data->cpu->loadaverage->{15} . "%;{$data['warn']};{$data['crit']} load5=" . $data['response']->data->cpu->loadaverage->{5} . "% load1=" . $data['response']->data->cpu->loadaverage->{1} . "%";
          exit(2);
        } else if ($data['response']->data->cpu->loadaverage->{15} > $data['warn']) {
          echo round($data['response']->data->cpu->loadaverage->{15},2) . "%|load15=" . $data['response']->data->cpu->loadaverage->{15} . "%;{$data['warn']};{$data['crit']} load5=" . $data['response']->data->cpu->loadaverage->{5} . "% load1=" . $data['response']->data->cpu->loadaverage->{1} . "%";
          exit(1);
        } else {
          echo round($data['response']->data->cpu->loadaverage->{15},2) . "%|load15=" . $data['response']->data->cpu->loadaverage->{15} . "%;{$data['warn']};{$data['crit']} load5=" . $data['response']->data->cpu->loadaverage->{5} . "% load1=" . $data['response']->data->cpu->loadaverage->{1} . "%";
          exit(0);
        }
        break;

      case 'mem':
        if ($data['response']->data->mem->percent > $data['crit']) {
          echo round($data['response']->data->mem->percent,1) . '%';
          exit(2);
        } else if ($data['response']->data->mem->percent > $data['warn']) {
          echo round($data['response']->data->mem->percent,1) . '%';
          exit(1);
        } else {
          echo round($data['response']->data->mem->percent,1) . '%';
          exit(0);
        }
        break;

      case 'disk':
        $mountpoint = '/';
        if (isset($data['switch']) && strlen($data['switch']) > 0) $mountpoint = $data['switch'];
        if (isset($data['response']->data->storage->$mountpoint->mounted) && $data['response']->data->storage->$mountpoint->mounted == 0) {
            echo 'NOT MOUNTED (' . $mountpoint . ')';
            exit(2);
        } else {
          if (!isset($data['response']->data->storage->$mountpoint->locked)) {
            $perfdata = ' | Used=' . $data['response']->data->storage->$mountpoint->used . 'GB;' . round(($data['response']->data->storage->$mountpoint->used * ($data['warn']/100)),2) . ';' . round(($data['response']->data->storage->$mountpoint->used * ($data['crit']/100)),2) . '; ';
            $perfdata .= 'Capacity=' . $data['response']->data->storage->$mountpoint->total . 'GB; ';
            $perfdata .= 'Usage=' . $data['response']->data->storage->$mountpoint->percent . '%;' . $data['warn'] . ';' . $data['crit'] . '; ';
            $perfdata .= 'Free=' . $data['response']->data->storage->$mountpoint->free . 'GB;' . round( $data['response']->data->storage->$mountpoint->total - ($data['response']->data->storage->$mountpoint->total * ($data['warn']/100) ),2) . ';' . round( $data['response']->data->storage->$mountpoint->total - ($data['response']->data->storage->$mountpoint->total * ($data['crit']/100)),2) . '; ';
            if ($data['response']->data->storage->$mountpoint->percent > $data['crit']) {
              echo round($data['response']->data->storage->$mountpoint->percent,2) . '% Used / ' . round($data['response']->data->storage->$mountpoint->free,2) . ' GB Free (' . $mountpoint . ')' . $perfdata;
              exit(2);
            } else if ($data['response']->data->storage->$mountpoint->percent > $data['warn']) {
              echo round($data['response']->data->storage->$mountpoint->percent,2) . '% Used / ' . round($data['response']->data->storage->$mountpoint->free,2) . ' GB Free (' . $mountpoint . ')' . $perfdata;
              exit(1);
            } else {
              echo round($data['response']->data->storage->$mountpoint->percent,2) . '% Used / ' . round($data['response']->data->storage->$mountpoint->free,2) . ' GB Free (' . $mountpoint . ')' . $perfdata;
              exit(0);
            }
          } else {
            echo "open_basedir in effect: Cannot access $mountpoint";
            exit(3);
          }
        }
        break;

      case 'net':
        if (isset($data['response']->data->network->none)) {
          echo "You need to install ifstat on remote server";
          exit(3);
        }
        if ($data['response']->data->network->total->rx > $data['crit'] || $data['response']->data->network->total->tx > $data['crit']) {
          echo $data['response']->data->network->total->rx . ' Mb/s Down / ' . $data['response']->data->network->total->tx . ' Mb/s Up';
          exit(2);
        } else if ($data['response']->data->network->total->rx > $data['warn'] || $data['response']->data->network->total->tx > $data['warn']) {
          echo $data['response']->data->network->total->rx . ' Mb/s Down / ' . $data['response']->data->network->total->tx . ' Mb/s Up';
          exit(1);
        } else {
          echo $data['response']->data->network->total->rx . ' Mb/s Down / ' . $data['response']->data->network->total->tx . ' Mb/s Up';
          exit(0);
        }
        break;

      case 'netrx':
        if ($data['response']->data->network->total->rx > $data['crit']) {
          echo $data['response']->data->network->total->rx . ' Mb/s Down';
          exit(2);
        } else if ($data['response']->data->network->total->rx > $data['warn']) {
          echo $data['response']->data->network->total->rx . ' Mb/s Down';
          exit(1);
        } else {
          echo $data['response']->data->network->total->rx . ' Mb/s Down';
          exit(0);
        }
        break;

      case 'nettx':
        if ($data['response']->data->network->total->tx > $data['crit']) {
          echo $data['response']->data->network->total->tx . ' Mb/s Up';
          exit(2);
        } else if ($data['response']->data->network->total->tx > $data['warn']) {
          echo $data['response']->data->network->total->tx . ' Mb/s Up';
          exit(1);
        } else {
          echo $data['response']->data->network->total->tx . ' Mb/s Up';
          exit(0);
        }
        break;

      default:
        exit_usage();
        break;

    }


function exit_usage() {
  global $argv;
    echo 'NEMS PHP Server Agent Check' . PHP_EOL;
    echo PHP_EOL . 'Usage: ' . $argv[0] . ' warn crit url [mem|disk|var|.|net|netrx|nettx|load]' . PHP_EOL;
    echo 'Example: ' . $argv[0] . ' 3 9 https://example.com/nems-agent.php load' . PHP_EOL . PHP_EOL;
    exit(3);
}
