<?php
//
//  Nagios Core Config Manager
//  Copyright (c) 2010-2021 Nagios Enterprises, LLC
//
//  File: command_test.php
//  Desc: Script that runs a test check command in the shell for a host/service and returns the value
//        out to the user in the user interface.
//

// Include the Nagios XI helper functions through the component helper file and initialize
// anything we will need to authenticate ourselves to the CCM
require_once(dirname(__FILE__).'/../componenthelper.inc.php');

// Initialization stuff
pre_init();
init_session();

// Grab GET or POST variables and check pre-reqs
grab_request_vars();
check_prereqs();
check_authentication(false);

// Verify access
if (!user_can_access_ccm()) {
    die(_('You do not have access to this page.'));
}

// Set the location of the CCM root directory
define('BASEDIR', dirname(__FILE__).'/');
require_once('includes/constants.inc.php');
require_once('includes/session.inc.php');

// Check authentication and grab the token and command (there shouldn't be one
// but we check this anyway...)
$cmd = ccm_grab_request_var('cmd', '');
$token = ccm_grab_request_var('token', '');
if ($AUTH !== true) { $cmd = 'login'; }

// Verify that the command was submitted from the form and if it's not then the user will be
// routed to the login page if it's an illegal operation otherwise route the request for download
verify_token($cmd, $token);
route_request($cmd);

/**
 * Directs page navigation and input requests for command tests and verifies user authentication
 *
 * @param string $cmd requires a valid command to do anything, if auth it bad this will be '' or 'login'
 */
function route_request($cmd='')
{
    // Bail on no authentication
    if ($cmd == 'login') {
        header('Location: index.php?cmd=login');
        return;
    }

    $mode = ccm_grab_request_var('mode', '');
    switch ($mode)
    {
        case 'help':
            $plugin = escapeshellcmd(ccm_grab_request_var('plugin', ''));

            // Validate plugin name to be alphanumeric with underscores or dashes and optionally a file extension.
            if (!preg_match('/^[a-zA-Z0-9_\-]+(\.[a-zA-Z0-9_\-]+)?$/', $plugin)) {
                echo "Invalid plugin name!";
                return;
            }

            // Print plugin help output
            get_plugin_doc($plugin);
            break;

        case 'test':
            test_command(); 
            break; 
        
        default:
            // Don't do anything, page will just exit
            break; 
    }
}

/**
 * Walks a template-inheritance chain depth-first, collecting custom variables into
 * $resolved (keyed by uppercased name with any leading underscores stripped).
 * Only idTable=1 (id-based) template links are followed. Cycles are guarded via the
 * $visited set. All IDs are intval()'d before being interpolated into SQL.
 *
 * @param string $tpl_link_table e.g. 'tbl_lnkHosttemplateToHosttemplate'
 * @param string $var_link_table e.g. 'tbl_lnkHosttemplateToVariabledefinition'
 * @param int    $tid            template id to descend from
 * @param array  &$resolved      accumulator (name => value); populated in-place
 * @param array  &$visited       set of already-walked template ids (cycle guard)
 */
function ccm_walk_template_vars($tpl_link_table, $var_link_table, $tid, &$resolved, &$visited)
{
    global $ccm;

    $tid = intval($tid);
    if ($tid <= 0 || isset($visited[$tid])) { return; }
    $visited[$tid] = true;

    // Collect custom variables defined directly on this template.
    $sql = "SELECT v.name, v.value
              FROM `".$var_link_table."` l
              JOIN tbl_variabledefinition v ON v.id = l.idSlave
             WHERE l.idMaster = ".$tid;
    $rows = $ccm->db->query($sql);
    if (is_array($rows)) {
        foreach ($rows as $r) {
            $key = strtoupper(ltrim((string)$r['name'], '_'));
            if ($key === '' || isset($resolved[$key])) { continue; }
            $resolved[$key] = (string)$r['value'];
        }
    }

    // Recurse into this template's own `use` list, in idSort order.
    $sql = "SELECT idSlave
              FROM `".$tpl_link_table."`
             WHERE idMaster = ".$tid." AND idTable = 1
             ORDER BY idSort ASC";
    $rows = $ccm->db->query($sql);
    if (is_array($rows)) {
        foreach ($rows as $r) {
            ccm_walk_template_vars($tpl_link_table, $var_link_table, $r['idSlave'], $resolved, $visited);
        }
    }
}

/**
 * Collects custom variables for a host (by id), including those inherited from its
 * host template chain, and merges them into $resolved. Direct host vars beat
 * inherited template vars.
 *
 * @param int   $host_id
 * @param array &$resolved
 */
function ccm_collect_host_vars($host_id, &$resolved)
{
    global $ccm;

    $host_id = intval($host_id);
    if ($host_id <= 0) { return; }

    // Direct vars on the host.
    $sql = "SELECT v.name, v.value
              FROM tbl_lnkHostToVariabledefinition l
              JOIN tbl_variabledefinition v ON v.id = l.idSlave
             WHERE l.idMaster = ".$host_id;
    $rows = $ccm->db->query($sql);
    if (is_array($rows)) {
        foreach ($rows as $r) {
            $key = strtoupper(ltrim((string)$r['name'], '_'));
            if ($key === '' || isset($resolved[$key])) { continue; }
            $resolved[$key] = (string)$r['value'];
        }
    }

    // Walk the host's template chain for inherited vars.
    $sql = "SELECT idSlave FROM tbl_lnkHostToHosttemplate
             WHERE idMaster = ".$host_id." AND idTable = 1
             ORDER BY idSort ASC";
    $rows = $ccm->db->query($sql);
    if (is_array($rows)) {
        $visited = array();
        foreach ($rows as $r) {
            ccm_walk_template_vars(
                'tbl_lnkHosttemplateToHosttemplate',
                'tbl_lnkHosttemplateToVariabledefinition',
                $r['idSlave'],
                $resolved,
                $visited
            );
        }
    }
}

/**
 * Cleans input variables and executes them from the command-line and returns output to browser
 */
function test_command()
{
    // check against the session if we are in nagios xi
    check_nagios_session_protector();
   
    global $cfg;
    global $ccm;

    // Get all necessary parameters for a check command
    $cid  = intval(ccm_grab_request_var('cid')); 
    $address = ccm_grab_request_var('address', ''); 
    $host = ccm_grab_array_var($_REQUEST, 'host', '');
    $arg1 = ccm_grab_array_var($_REQUEST, 'arg1', '');
    $arg2 = ccm_grab_array_var($_REQUEST, 'arg2', '');
    $arg3 = ccm_grab_array_var($_REQUEST, 'arg3', '');
    $arg4 = ccm_grab_array_var($_REQUEST, 'arg4', '');
    $arg5 = ccm_grab_array_var($_REQUEST, 'arg5', '');
    $arg6 = ccm_grab_array_var($_REQUEST, 'arg6', '');
    $arg7 = ccm_grab_array_var($_REQUEST, 'arg7', '');
    $arg8 = ccm_grab_array_var($_REQUEST, 'arg8', '');
    $arg9 = ccm_grab_array_var($_REQUEST, 'arg9', '');
    $arg10 = ccm_grab_array_var($_REQUEST, 'arg10', '');
    $arg11 = ccm_grab_array_var($_REQUEST, 'arg11', '');
    $arg12 = ccm_grab_array_var($_REQUEST, 'arg12', '');
    $arg13 = ccm_grab_array_var($_REQUEST, 'arg13', '');
    $arg14 = ccm_grab_array_var($_REQUEST, 'arg14', '');
    $arg15 = ccm_grab_array_var($_REQUEST, 'arg15', '');
    $arg16 = ccm_grab_array_var($_REQUEST, 'arg16', '');
    $arg17 = ccm_grab_array_var($_REQUEST, 'arg17', '');
    $arg18 = ccm_grab_array_var($_REQUEST, 'arg18', '');
    $arg19 = ccm_grab_array_var($_REQUEST, 'arg19', '');
    $arg20 = ccm_grab_array_var($_REQUEST, 'arg20', '');
    $arg21 = ccm_grab_array_var($_REQUEST, 'arg21', '');
    $arg22 = ccm_grab_array_var($_REQUEST, 'arg22', '');
    $arg23 = ccm_grab_array_var($_REQUEST, 'arg23', '');
    $arg24 = ccm_grab_array_var($_REQUEST, 'arg24', '');
    $arg25 = ccm_grab_array_var($_REQUEST, 'arg25', '');
    $arg26 = ccm_grab_array_var($_REQUEST, 'arg26', '');
    $arg27 = ccm_grab_array_var($_REQUEST, 'arg27', '');
    $arg28 = ccm_grab_array_var($_REQUEST, 'arg28', '');
    $arg29 = ccm_grab_array_var($_REQUEST, 'arg29', '');
    $arg30 = ccm_grab_array_var($_REQUEST, 'arg30', '');
    $arg31 = ccm_grab_array_var($_REQUEST, 'arg31', '');
    $arg32 = ccm_grab_array_var($_REQUEST, 'arg32', '');

    // Custom variables posted from the edit form (parallel arrays of names and values).
    $custom_var_names = ccm_grab_array_var($_REQUEST, 'variables', array());
    $custom_var_defs  = ccm_grab_array_var($_REQUEST, 'variabledefs', array());
    $objecttype       = ccm_grab_request_var('objecttype', '');

    // Selected templates / hosts from the form, used to resolve inherited custom variables.
    $form_templates        = ccm_grab_array_var($_REQUEST, 'templates', array());
    $form_contacttemplates = ccm_grab_array_var($_REQUEST, 'contacttemplates', array());
    $form_hosts            = ccm_grab_array_var($_REQUEST, 'hosts', array());

    if (!is_array($custom_var_names))      { $custom_var_names = array(); }
    if (!is_array($custom_var_defs))       { $custom_var_defs  = array(); }
    if (!is_array($form_templates))        { $form_templates = array(); }
    if (!is_array($form_contacttemplates)) { $form_contacttemplates = array(); }
    if (!is_array($form_hosts))            { $form_hosts = array(); }

    // Validate that address is provided and is a valid IP address or hostname
    if (empty($address)) {
        print "ERROR: Host address is required. Every service is assigned to a host.";
        exit();
    }
    
    if (!filter_var($address, FILTER_VALIDATE_IP) && !filter_var($address, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
        print "ERROR: Invalid host address";
        print "This is the host address you entered: ".encode_form_val($address);
        exit();
    }

    // Grab the command we were sent from the database
    $query = "SELECT `command_name`, `command_line` FROM tbl_command WHERE `id` = '$cid' && command_type = 1 LIMIT 1";
    $command = $ccm->db->query($query); 
    
    // If the command isn't in the database then fail now
    if (!isset($command[0]['command_name'])) {
        print "ERROR: Unable to locate the command in the database";
        exit(); 
    }

    // Create all the database variables and then replace all variables that need to be replaced
    // before running the actual command and echoing the output to the screen.
    $name = $command[0]['command_name'];
    $cmd_line = $command[0]['command_line'];
    $macro_values = array($cfg['component_info']['nagioscore']['plugin_dir'], $address, $arg1, $arg2, $arg3, $arg4, $arg5, $arg6, $arg7, $arg8, $arg9, $arg10, $arg11, $arg12, $arg13, $arg14, $arg15, $arg16, $arg17, $arg18, $arg19, $arg20, $arg21, $arg22, $arg23, $arg24, $arg25, $arg26, $arg27, $arg28, $arg29, $arg30, $arg31, $arg32);
    $macro_names  = array('$USER1$', '$HOSTADDRESS$', '$ARG1$', '$ARG2$', '$ARG3$', '$ARG4$', '$ARG5$', '$ARG6$', '$ARG7$', '$ARG8$', '$ARG9$', '$ARG10$', '$ARG11$', '$ARG12$', '$ARG13$', '$ARG14$', '$ARG15$', '$ARG16$', '$ARG17$', '$ARG18$', '$ARG19$', '$ARG20$', '$ARG21$', '$ARG22$', '$ARG23$', '$ARG24$', '$ARG25$', '$ARG26$', '$ARG27$', '$ARG28$', '$ARG29$', '$ARG30$', '$ARG31$', '$ARG32$');

    // Expand Nagios custom object variables ($_HOST<NAME>$ / $_SERVICE<NAME>$ / $_CONTACT<NAME>$).
    switch ($objecttype) {
        case 'service':
        case 'servicetemplate':
            $cv_prefix      = '$_SERVICE';
            $tpl_link_table = 'tbl_lnkServicetemplateToServicetemplate';
            $var_link_table = 'tbl_lnkServicetemplateToVariabledefinition';
            $seed_templates = $form_templates;
            break;
        case 'contact':
        case 'contacttemplate':
            $cv_prefix      = '$_CONTACT';
            $tpl_link_table = 'tbl_lnkContacttemplateToContacttemplate';
            $var_link_table = 'tbl_lnkContacttemplateToVariabledefinition';
            $seed_templates = $form_contacttemplates;
            break;
        case 'host':
        case 'hosttemplate':
        default:
            $cv_prefix      = '$_HOST';
            $tpl_link_table = 'tbl_lnkHosttemplateToHosttemplate';
            $var_link_table = 'tbl_lnkHosttemplateToVariabledefinition';
            $seed_templates = $form_templates;
            break;
    }

    // Step 1: direct vars from the form
    $resolved_self = array();
    $cv_count = min(count($custom_var_names), count($custom_var_defs));
    for ($i = 0; $i < $cv_count; $i++) {
        $key = strtoupper(ltrim((string)$custom_var_names[$i], '_'));
        if ($key === '' || isset($resolved_self[$key])) { continue; }
        $resolved_self[$key] = html_entity_decode((string)$custom_var_defs[$i], ENT_QUOTES);
    }

    // Step 2: walk this object's template chain for inherited vars
    if (is_array($seed_templates) && count($seed_templates) > 0) {
        $visited = array();
        foreach ($seed_templates as $tid) {
            ccm_walk_template_vars($tpl_link_table, $var_link_table, $tid, $resolved_self, $visited);
        }
    }

    foreach ($resolved_self as $key => $value) {
        $macro_names[]  = $cv_prefix . $key . '$';
        $macro_values[] = $value;
    }

    // Step 3: for services, also resolve $_HOST<NAME>$ macros from the attached host(s)
    if ($objecttype === 'service' || $objecttype === 'servicetemplate') {
        $resolved_host = array();
        foreach ($form_hosts as $hid) {
            ccm_collect_host_vars($hid, $resolved_host);
        }
        foreach ($resolved_host as $key => $value) {
            $macro_names[]  = '$_HOST' . $key . '$';
            $macro_values[] = $value;
        }
    }

    $fullcommand = str_ireplace($macro_names, $macro_values, $cmd_line);

    // break apart everything after an unescaped semi-colon
    $fullcommand = str_replace("\;", "%%%%%", $fullcommand);
    $fullcommand_array = explode(';', $fullcommand);
    $fullcommand = $fullcommand_array[0];
    $fullcommand = str_replace("%%%%%", "\;", $fullcommand);    

    // Unescape Nagios config escapes that Nagios Core would normally escape
    $fullcommand = str_replace('\\;', ';', $fullcommand);
    $fullcommand = str_replace('\\!', '!', $fullcommand);

    // Grab the current value of $fullcommand and store it to display in the run check command results box in order to maintain obfuscation
    $displaycommand = $fullcommand;

    $fullcommand = nagiosccm_replace_user_macros($fullcommand);

    $id = submit_command(COMMAND_RUN_CHECK_CMD, $fullcommand);

    if ($id <= 0) {
        $output = _("Error submitting command.");
    }
    else {
        for ($x = 0; $x < 40; $x++) {
            usleep(500000);
            $args = array(
                "command_id" => $id
            );
            $xml = get_command_status_xml($args);
            if ($xml) {
                if ($xml->command[0]) {
                    if (intval($xml->command[0]->status_code) == 2) {
                        $output = $xml->command[0]->result;
                        break;
                    }
                }
            }
        }
    }

    $hn = (function_exists('gethostname')) ? gethostname() : php_uname('n');
    if (stripos('.', $hn)) {
        $hna = explode('.', $hn);
        $hn = $hna[0];
    }

    // $output is already encoded so we don't need to do encode_form_val()
    echo "<pre class='monospace-textarea'>[nagios@{$hn} ~]$ ".encode_form_val($displaycommand)."\n"; 
    print $output."\n";
    echo "</pre>";
}

/**
 * Executes plugin from the command-line with -h flag and prints output between pre tags
 *
 * @param string $plugin The plugin name
 */
function get_plugin_doc($plugin)
{
    global $cfg;
    $output = array();
    exec($cfg['component_info']['nagioscore']['plugin_dir'].'/'.$plugin.' -h', $output);
    print "<pre>";
    if (!empty($output)) {
        foreach ($output as $line) {
            print encode_form_val($line)."\n";
        }
    }
    print "</pre>";
}