diff --git a/pandora_console/extras/mr/65.sql b/pandora_console/extras/mr/65.sql
index 623c0a36f0..67efb5b908 100644
--- a/pandora_console/extras/mr/65.sql
+++ b/pandora_console/extras/mr/65.sql
@@ -1,5 +1,49 @@
START TRANSACTION;
+CREATE TABLE IF NOT EXISTS `tdiscovery_apps` (
+ `id_app` int(10) auto_increment,
+ `short_name` varchar(250) NOT NULL DEFAULT '',
+ `name` varchar(250) NOT NULL DEFAULT '',
+ `section` varchar(250) NOT NULL DEFAULT 'custom',
+ `description` varchar(250) NOT NULL DEFAULT '',
+ `version` varchar(250) NOT NULL DEFAULT '',
+ PRIMARY KEY (`id_app`),
+ UNIQUE (`short_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8MB4;
+
+CREATE TABLE IF NOT EXISTS `tdiscovery_apps_scripts` (
+ `id_app` int(10),
+ `macro` varchar(250) NOT NULL DEFAULT '',
+ `value` text NOT NULL DEFAULT '',
+ PRIMARY KEY (`id_app`, `macro`),
+ FOREIGN KEY (`id_app`) REFERENCES tdiscovery_apps(`id_app`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8MB4;
+
+CREATE TABLE IF NOT EXISTS `tdiscovery_apps_executions` (
+ `id` int(10) unsigned NOT NULL auto_increment,
+ `id_app` int(10),
+ `execution` text NOT NULL DEFAULT '',
+ PRIMARY KEY (`id`, `id_app`),
+ FOREIGN KEY (`id_app`) REFERENCES tdiscovery_apps(`id_app`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8MB4;
+
+CREATE TABLE IF NOT EXISTS `tdiscovery_apps_tasks_macros` (
+ `id_task` int(10) unsigned NOT NULL,
+ `macro` varchar(250) NOT NULL DEFAULT '',
+ `type` varchar(250) NOT NULL DEFAULT 'custom',
+ `value` text NOT NULL DEFAULT '',
+ `temp_conf` tinyint unsigned NOT NULL DEFAULT 0,
+ PRIMARY KEY (`id_task`, `macro`),
+ FOREIGN KEY (`id_task`) REFERENCES trecon_task(`id_rt`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8MB4;
+
+
+ALTER TABLE `trecon_task`
+ ADD COLUMN `id_app` int(10),
+ ADD COLUMN `setup_complete` tinyint unsigned NOT NULL DEFAULT 0,
+ ADD COLUMN `executions_timeout` int unsigned NOT NULL DEFAULT 60,
+ ADD FOREIGN KEY (`id_app`) REFERENCES tdiscovery_apps(`id_app`) ON DELETE CASCADE ON UPDATE CASCADE;
+
CREATE TABLE IF NOT EXISTS `tnetwork_explorer_filter` (
`id` INT NOT NULL,
`filter_name` VARCHAR(45) NULL,
@@ -26,6 +70,7 @@ ALTER TABLE `tlayout_template`
ADD COLUMN `grid_color` VARCHAR(45) NOT NULL DEFAULT '#cccccc' AFTER `maintenance_mode`,
ADD COLUMN `grid_size` VARCHAR(45) NOT NULL DEFAULT '10' AFTER `grid_color`;
+
DELETE FROM tconfig WHERE token = 'refr';
INSERT INTO `tmodule_inventory` (`id_module_inventory`, `id_os`, `name`, `description`, `interpreter`, `data_format`, `code`, `block_mode`,`script_mode`) VALUES (37,2,'CPU','CPU','','Brand;Clock;Model','',0,2);
diff --git a/pandora_console/godmode/menu.php b/pandora_console/godmode/menu.php
index 3d04b230b0..5d95b25332 100644
--- a/pandora_console/godmode/menu.php
+++ b/pandora_console/godmode/menu.php
@@ -30,6 +30,7 @@
// Begin.
require_once 'include/config.php';
require_once 'include/functions_menu.php';
+require_once $config['homedir'].'/godmode/wizards/ManageExtensions.class.php';
check_login();
@@ -78,15 +79,97 @@ if ((bool) check_acl($config['id_user'], 0, 'AR') === true
}
if ((bool) check_acl($config['id_user'], 0, 'AW') === true) {
- enterprise_hook('applications_menu');
- enterprise_hook('cloud_menu');
- }
+ // Applications.
+ $sub2 = [];
+ if (enterprise_installed() === true) {
+ $sub2['godmode/servers/discovery&wiz=app&mode=MicrosoftSQLServer']['text'] = __('Microsoft SQL Server');
+ $sub2['godmode/servers/discovery&wiz=app&mode=mysql']['text'] = __('Mysql');
+ $sub2['godmode/servers/discovery&wiz=app&mode=oracle']['text'] = __('Oracle');
+ $sub2['godmode/servers/discovery&wiz=app&mode=vmware']['text'] = __('VMware');
+ $sub2['godmode/servers/discovery&wiz=app&mode=SAP']['text'] = __('SAP');
+ $sub2['godmode/servers/discovery&wiz=app&mode=DB2']['text'] = __('DB2');
+ }
- if ((bool) check_acl($config['id_user'], 0, 'RW') === true
- || (bool) check_acl($config['id_user'], 0, 'RM') === true
- || (bool) check_acl($config['id_user'], 0, 'PM') === true
- ) {
- enterprise_hook('console_task_menu');
+ $extensions = ManageExtensions::getExtensionBySection('app');
+ if ($extensions !== false) {
+ foreach ($extensions as $key => $extension) {
+ $url = sprintf(
+ 'godmode/servers/discovery&wiz=app&mode=%s',
+ $extension['short_name']
+ );
+ $sub2[$url]['text'] = __($extension['name']);
+ }
+ }
+
+ if ($extensions !== false || enterprise_installed() === true) {
+ $sub['godmode/servers/discovery&wiz=app']['text'] = __('Applications');
+ $sub['godmode/servers/discovery&wiz=app']['id'] = 'app';
+ $sub['godmode/servers/discovery&wiz=app']['type'] = 'direct';
+ $sub['godmode/servers/discovery&wiz=app']['subtype'] = 'nolink';
+ $sub['godmode/servers/discovery&wiz=app']['sub2'] = $sub2;
+ }
+
+ // Cloud.
+ $sub2 = [];
+ if (enterprise_installed() === true) {
+ $sub2['godmode/servers/discovery&wiz=cloud&mode=amazonws']['text'] = __('Amazon Web Services');
+ $sub2['godmode/servers/discovery&wiz=cloud&mode=azure']['text'] = __('Microsoft Azure');
+ $sub2['godmode/servers/discovery&wiz=cloud&mode=gcp']['text'] = __('Google Compute Platform');
+ }
+
+
+ $extensions = ManageExtensions::getExtensionBySection('cloud');
+ if ($extensions !== false) {
+ foreach ($extensions as $key => $extension) {
+ $url = sprintf(
+ 'godmode/servers/discovery&wiz=cloud&mode=%s',
+ $extension['short_name']
+ );
+ $sub2[$url]['text'] = __($extension['name']);
+ }
+ }
+
+ if ($extensions !== false || enterprise_installed() === true) {
+ $sub['godmode/servers/discovery&wiz=cloud']['text'] = __('Cloud');
+ $sub['godmode/servers/discovery&wiz=cloud']['id'] = 'cloud';
+ $sub['godmode/servers/discovery&wiz=cloud']['type'] = 'direct';
+ $sub['godmode/servers/discovery&wiz=cloud']['subtype'] = 'nolink';
+ $sub['godmode/servers/discovery&wiz=cloud']['sub2'] = $sub2;
+ }
+
+ // Custom.
+ $sub2 = [];
+ $extensions = ManageExtensions::getExtensionBySection('custom');
+ if ($extensions !== false) {
+ foreach ($extensions as $key => $extension) {
+ $url = sprintf(
+ 'godmode/servers/discovery&wiz=custom&mode=%s',
+ $extension['short_name']
+ );
+ $sub2[$url]['text'] = __($extension['name']);
+ }
+
+ $sub['godmode/servers/discovery&wiz=custom']['text'] = __('Custom');
+ $sub['godmode/servers/discovery&wiz=custom']['id'] = 'customExt';
+ $sub['godmode/servers/discovery&wiz=custom']['type'] = 'direct';
+ $sub['godmode/servers/discovery&wiz=custom']['subtype'] = 'nolink';
+ $sub['godmode/servers/discovery&wiz=custom']['sub2'] = $sub2;
+ }
+
+ if (check_acl($config['id_user'], 0, 'RW')
+ || check_acl($config['id_user'], 0, 'RM')
+ || check_acl($config['id_user'], 0, 'PM')
+ ) {
+ $sub['godmode/servers/discovery&wiz=magextensions']['text'] = __('Manage disco packages');
+ $sub['godmode/servers/discovery&wiz=magextensions']['id'] = 'mextensions';
+ }
+
+ if ((bool) check_acl($config['id_user'], 0, 'RW') === true
+ || (bool) check_acl($config['id_user'], 0, 'RM') === true
+ || (bool) check_acl($config['id_user'], 0, 'PM') === true
+ ) {
+ enterprise_hook('console_task_menu');
+ }
}
}
diff --git a/pandora_console/godmode/servers/discovery.php b/pandora_console/godmode/servers/discovery.php
index 0fb36b4cfb..9af4343a84 100755
--- a/pandora_console/godmode/servers/discovery.php
+++ b/pandora_console/godmode/servers/discovery.php
@@ -53,6 +53,12 @@ function get_wiz_class($str)
case 'deploymentCenter':
return 'DeploymentCenter';
+ case 'magextensions':
+ return 'ManageExtensions';
+
+ case 'custom':
+ return 'Custom';
+
default:
// Main, show header.
ui_print_standard_header(
@@ -169,6 +175,12 @@ if ($classname_selected === null) {
$classname = basename($classpath, '.class.php');
$obj = new $classname();
+ if (method_exists($obj, 'isEmpty') === true) {
+ if ($obj->isEmpty() === true) {
+ continue;
+ }
+ }
+
$button = $obj->load();
if ($button === false) {
diff --git a/pandora_console/godmode/wizards/Applications.class.php b/pandora_console/godmode/wizards/Applications.class.php
new file mode 100644
index 0000000000..2237fdbe73
--- /dev/null
+++ b/pandora_console/godmode/wizards/Applications.class.php
@@ -0,0 +1,221 @@
+setBreadcrum([]);
+
+ $this->access = 'AW';
+ $this->task = [];
+ $this->msg = $msg;
+ $this->icon = $icon;
+ $this->class = $class_style;
+ $this->label = $label;
+ $this->page = $page;
+ $this->url = ui_get_full_url(
+ 'index.php?sec=gservers&sec2=godmode/servers/discovery&wiz=app'
+ );
+
+ return $this;
+ }
+
+
+ /**
+ * Run wizard manager.
+ *
+ * @return mixed Returns null if wizard is ongoing. Result if done.
+ */
+ public function run()
+ {
+ global $config;
+
+ // Load styles.
+ parent::run();
+
+ // Load current wiz. sub-styles.
+ ui_require_css_file(
+ 'application',
+ ENTERPRISE_DIR.'/include/styles/wizards/'
+ );
+
+ $mode = get_parameter('mode', null);
+
+ // Load application wizards.
+ $enterprise_classes = glob(
+ $config['homedir'].'/'.ENTERPRISE_DIR.'/include/class/*.app.php'
+ );
+ $extensions = new ExtensionsDiscovery('app', $mode);
+
+ foreach ($enterprise_classes as $classpath) {
+ enterprise_include_once(
+ 'include/class/'.basename($classpath)
+ );
+ }
+
+ switch ($mode) {
+ case 'DB2':
+ $classname_selected = 'DB2';
+ break;
+
+ case 'SAP':
+ $classname_selected = 'SAP';
+ break;
+
+ case 'vmware':
+ $classname_selected = 'VMware';
+ break;
+
+ case 'mysql':
+ $classname_selected = 'MySQL';
+ break;
+
+ case 'oracle':
+ $classname_selected = 'Oracle';
+ break;
+
+ case 'MicrosoftSQLServer':
+ $classname_selected = 'MicrosoftSQLServer';
+ break;
+
+ default:
+ $classname_selected = null;
+ break;
+ }
+
+ // Else: class not found pseudo exception.
+ if ($classname_selected !== null) {
+ $wiz = new $classname_selected($this->page);
+ $result = $wiz->run();
+ if (is_array($result) === true) {
+ return $result;
+ }
+ }
+
+ if ($classname_selected === null) {
+ if ($mode !== null) {
+ // Load extension if exist.
+ $extensions->run();
+ return;
+ }
+
+ // Load classes and print selector.
+ $wiz_data = [];
+ foreach ($enterprise_classes as $classpath) {
+ $classname = basename($classpath, '.app.php');
+ $obj = new $classname();
+ $wiz_data[] = $obj->load();
+ }
+
+ $wiz_data = array_merge($wiz_data, $extensions->loadExtensions());
+
+ $this->prepareBreadcrum(
+ [
+ [
+ 'link' => ui_get_full_url(
+ 'index.php?sec=gservers&sec2=godmode/servers/discovery'
+ ),
+ 'label' => __('Discovery'),
+ ],
+ [
+ 'link' => ui_get_full_url(
+ 'index.php?sec=gservers&sec2=godmode/servers/discovery&wiz=app'
+ ),
+ 'label' => __('Applications'),
+ 'selected' => true,
+ ],
+ ]
+ );
+
+ // Header.
+ ui_print_page_header(
+ __('Applications'),
+ '',
+ false,
+ '',
+ true,
+ '',
+ false,
+ '',
+ GENERIC_SIZE_TEXT,
+ '',
+ $this->printHeader(true)
+ );
+
+ Wizard::printBigButtonsList($wiz_data);
+
+ echo '
*'.__('All company names used here are for identification purposes only. Use of these names, logos, and brands does not imply endorsement.').'
';
+ }
+
+ return $result;
+ }
+
+
+ /**
+ * Check if section have extensions.
+ *
+ * @return boolean Return true if section is empty.
+ */
+ public function isEmpty()
+ {
+ $extensions = new ExtensionsDiscovery('app');
+ $listExtensions = $extensions->getExtensionsApps();
+ if ($listExtensions > 0 || enterprise_installed() === true) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+
+}
diff --git a/pandora_console/godmode/wizards/Cloud.class.php b/pandora_console/godmode/wizards/Cloud.class.php
new file mode 100644
index 0000000000..4664b1a566
--- /dev/null
+++ b/pandora_console/godmode/wizards/Cloud.class.php
@@ -0,0 +1,661 @@
+setBreadcrum([]);
+
+ $this->access = 'AW';
+ $this->task = [];
+ $this->msg = $msg;
+ $this->icon = $icon;
+ $this->label = $label;
+ $this->page = $page;
+ $this->url = ui_get_full_url(
+ 'index.php?sec=gservers&sec2=godmode/servers/discovery&wiz=cloud'
+ );
+
+ return $this;
+ }
+
+
+ /**
+ * Run wizard manager.
+ *
+ * @return mixed Returns null if wizard is ongoing. Result if done.
+ */
+ public function run()
+ {
+ global $config;
+
+ // Load styles.
+ parent::run();
+
+ // Load current wiz. sub-styles.
+ ui_require_css_file(
+ 'cloud',
+ ENTERPRISE_DIR.'/include/styles/wizards/'
+ );
+
+ $mode = get_parameter('mode', null);
+
+ // Load cloud wizards.
+ $enterprise_classes = glob(
+ $config['homedir'].'/'.ENTERPRISE_DIR.'/include/class/*.cloud.php'
+ );
+ $extensions = new ExtensionsDiscovery('cloud', $mode);
+
+ foreach ($enterprise_classes as $classpath) {
+ enterprise_include_once(
+ 'include/class/'.basename($classpath)
+ );
+ }
+
+ switch ($mode) {
+ case 'amazonws':
+ $classname_selected = 'Aws';
+ break;
+
+ case 'azure':
+ $classname_selected = 'Azure';
+ break;
+
+ case 'gcp':
+ $classname_selected = 'Google';
+ break;
+
+ default:
+ $classname_selected = null;
+ break;
+ }
+
+ // Else: class not found pseudo exception.
+ if ($classname_selected !== null) {
+ $wiz = new $classname_selected($this->page);
+ $result = $wiz->run();
+ if (is_array($result) === true) {
+ return $result;
+ }
+ }
+
+ if ($classname_selected === null) {
+ if ($mode !== null) {
+ // Load extension if exist.
+ $extensions->run();
+ return;
+ }
+
+ // Load classes and print selector.
+ $wiz_data = [];
+ foreach ($enterprise_classes as $classpath) {
+ $classname = basename($classpath, '.cloud.php');
+ $obj = new $classname();
+ $wiz_data[] = $obj->load();
+ }
+
+ $wiz_data = array_merge($wiz_data, $extensions->loadExtensions());
+
+ $this->prepareBreadcrum(
+ [
+ [
+ 'link' => ui_get_full_url(
+ 'index.php?sec=gservers&sec2=godmode/servers/discovery'
+ ),
+ 'label' => __('Discovery'),
+ ],
+ [
+ 'link' => $this->url,
+ 'label' => __('Cloud'),
+ 'selected' => true,
+ ],
+ ],
+ true
+ );
+
+ // Header.
+ ui_print_page_header(
+ __('Cloud'),
+ '',
+ false,
+ '',
+ true,
+ '',
+ false,
+ '',
+ GENERIC_SIZE_TEXT,
+ '',
+ $this->printHeader(true)
+ );
+
+ Wizard::printBigButtonsList($wiz_data);
+
+ echo '*'.__('All company names used here are for identification purposes only. Use of these names, logos, and brands does not imply endorsement.').'
';
+ }
+
+ return $result;
+ }
+
+
+ /**
+ * Run credentials wizard.
+ *
+ * @return boolean True if credentials wizard is displayed and false if not.
+ */
+ public function runCredentials()
+ {
+ global $config;
+
+ if ($this->status === false) {
+ $empty_account = true;
+ }
+
+ // Checks credentials. If check not passed. Show the form to fill it.
+ if ($this->checkCredentials()) {
+ return true;
+ }
+
+ // Add breadcrum and print header.
+ $this->prepareBreadcrum(
+ [
+ [
+ 'link' => $this->url.'&credentials=1',
+ 'label' => __('%s credentials', $this->product),
+ 'selected' => true,
+ ],
+ ],
+ true
+ );
+ // Header.
+ ui_print_page_header(
+ __('%s credentials', $this->product),
+ '',
+ false,
+ $this->product.'_credentials_tab',
+ true,
+ '',
+ false,
+ '',
+ GENERIC_SIZE_TEXT,
+ '',
+ $this->printHeader(true)
+ );
+
+ if ($this->product === 'Aws') {
+ ui_print_warning_message(
+ __(
+ 'If a task with the selected credentials is already running, it will be edited. To create a new one, another account from the credential store must be selected.'
+ )
+ );
+ }
+
+ if ($this->status === true) {
+ ui_print_success_message($this->msg);
+ } else if ($this->status === false) {
+ ui_print_error_message($this->msg);
+ }
+
+ if ($empty_account === true) {
+ ui_print_error_message($this->msg);
+ }
+
+ $link_to_cs = '';
+ if (check_acl($config['id_user'], 0, 'UM')) {
+ $link_to_cs = '';
+ $link_to_cs .= __('Manage accounts').'';
+ }
+
+ $this->getCredentials();
+ $this->printFormAsList(
+ [
+ 'form' => [
+ 'action' => $this->url,
+ 'method' => 'POST',
+ 'id' => 'form-credentials',
+ ],
+ 'inputs' => [
+ [
+ 'label' => __('Cloud tool full path'),
+ 'arguments' => [
+ 'name' => 'cloud_util_path',
+ 'value' => isset($config['cloud_util_path']) ? io_safe_output($config['cloud_util_path']) : '/usr/bin/pandora-cm-api',
+ 'type' => 'text',
+ ],
+ ],
+ [
+ 'label' => __('Account'),
+ 'extra' => $link_to_cs,
+ 'arguments' => [
+ 'name' => 'account_identifier',
+ 'type' => 'select',
+ 'fields' => CredentialStore::getKeys($this->keyStoreType),
+ 'selected' => $this->keyIdentifier,
+ 'return' => true,
+ ],
+ ],
+ [
+ 'arguments' => [
+ 'name' => 'parse_credentials',
+ 'value' => 1,
+ 'type' => 'hidden',
+ 'return' => true,
+ ],
+ ],
+ ],
+ ]
+ );
+
+ $buttons_form = $this->printInput(
+ [
+ 'name' => 'submit',
+ 'label' => __('Validate'),
+ 'type' => 'submit',
+ 'attributes' => [
+ 'icon' => 'wand',
+ 'form' => 'form-credentials',
+ ],
+ 'return' => true,
+ 'width' => 'initial',
+ ]
+ );
+
+ $buttons_form .= $this->printGoBackButton(
+ ui_get_full_url(
+ 'index.php?sec=gservers&sec2=godmode/servers/discovery&wiz=cloud'
+ ),
+ true
+ );
+
+ html_print_action_buttons($buttons_form);
+ return false;
+ }
+
+
+ /**
+ * Check credentials.
+ *
+ * @return boolean True if credentials are OK.
+ */
+ public function checkCredentials()
+ {
+ global $config;
+
+ $pandora = io_safe_output($config['cloud_util_path']);
+
+ if (isset($pandora) === false) {
+ config_update_value('cloud_util_path', '/usr/bin/pandora-cm-api');
+ }
+
+ if ((bool) get_parameter('disconnect_account', false) === true) {
+ $this->status = null;
+ return false;
+ }
+
+ if ($this->keyIdentifier === null) {
+ // Ask user for available credentials.
+ $this->msg = __('Select a set of credentials from the list');
+ $this->status = null;
+ return false;
+ }
+
+ $credentials = $this->getCredentials($this->keyIdentifier);
+
+ if (empty($credentials['username']) === true
+ || empty($credentials['password']) === true
+ || isset($pandora) === false
+ || is_executable($pandora) === false
+ ) {
+ if (is_executable($pandora) === false) {
+ $this->msg = (__('Path %s is not executable.', $pandora));
+ $this->status = false;
+ } else {
+ $this->msg = __('Invalid username or password');
+ $this->status = false;
+ }
+
+ return false;
+ }
+
+ try {
+ $value = $this->executeCMCommand('--get availability');
+ } catch (Exception $e) {
+ $this->msg = $e->getMessage();
+ $this->status = false;
+ return false;
+ }
+
+ if ($value == '1') {
+ return true;
+ }
+
+ $this->status = false;
+
+ // Error message directly from pandora-cm-api.
+ $this->msg = str_replace('"', '', $value);
+
+ return false;
+ }
+
+
+ /**
+ * Handle the click on disconnect account link.
+ *
+ * @return void But it prints some info to user.
+ */
+ protected function parseDisconnectAccount()
+ {
+ // Check if disconection account link is pressed.
+ if ((bool) get_parameter('disconnect_account') === false) {
+ return;
+ }
+
+ $ret = $this->setCredentials(null);
+ if ($ret) {
+ $this->msg = __('Account disconnected');
+ } else {
+ $this->msg = __('Failed disconnecting account');
+ }
+
+ $this->status = $ret;
+ $this->page = 0;
+ }
+
+
+ /**
+ * Build an array with Product credentials.
+ *
+ * @return array with credentials (pass and id).
+ */
+ public function getCredentials()
+ {
+ return CredentialStore::getKey($this->keyIdentifier);
+ }
+
+
+ /**
+ * Set Product credentials.
+ *
+ * @param string|null $identifier Credential store identifier.
+ *
+ * @return boolean True if success.
+ */
+ public function setCredentials($identifier)
+ {
+ if ($identifier === null) {
+ unset($this->keyIdentifier);
+ return true;
+ }
+
+ if (isset($identifier) === false) {
+ return false;
+ }
+
+ $all = CredentialStore::getKeys($this->type);
+
+ if (in_array($identifier, $all) === true) {
+ $this->keyIdentifier = $identifier;
+ return true;
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Parse credentials form.
+ *
+ * @return void But it prints a message.
+ */
+ protected function parseCredentials()
+ {
+ global $config;
+
+ if (!$this->keyIdentifier) {
+ $this->setCredentials(get_parameter('ki', null));
+ }
+
+ // Check if credentials form is submitted.
+ if ((bool) get_parameter('parse_credentials') === false) {
+ return;
+ }
+
+ $this->page = 0;
+ $ret = $this->setCredentials(
+ get_parameter('account_identifier')
+ );
+
+ $path = get_parameter('cloud_util_path');
+ $ret_path = config_update_value('cloud_util_path', $path);
+ if ($ret_path) {
+ $config['cloud_util_path'] = $path;
+ }
+
+ if ($ret && $ret_path) {
+ $this->msg = __('Credentials successfully updated');
+ } else {
+ $this->msg = __('Failed updating credentials process');
+ }
+
+ $this->status = ($ret && $ret_path);
+ }
+
+
+ /**
+ * This method must be implemented.
+ *
+ * Execute a pandora-cm-api request.
+ *
+ * @param string $command Command to execute.
+ *
+ * @return void But must return string STDOUT of executed command.
+ * @throws Exception If not implemented.
+ */
+ protected function executeCMCommand($command)
+ {
+ throw new Exception('executeCMCommand must be implemented.');
+ }
+
+
+ /**
+ * Get a recon token value
+ *
+ * @param string $token The recon key to retrieve.
+ *
+ * @return string String with the value.
+ */
+ protected function getConfigReconElement($token)
+ {
+ if ($this->reconConfig === false
+ || isset($this->reconConfig[0][$token]) === false
+ ) {
+ if (is_array($this->task) === true
+ && isset($this->task[$token]) === true
+ ) {
+ return $this->task[$token];
+ } else {
+ return '';
+ }
+ } else {
+ return $this->reconConfig[0][$token];
+ }
+ }
+
+
+ /**
+ * Print global inputs
+ *
+ * @param boolean $last True if is last element.
+ *
+ * @return array Array with all global inputs.
+ */
+ protected function getGlobalInputs(bool $last=false)
+ {
+ $task_id = $this->task['id_rt'];
+ if (!$task_id) {
+ $task_id = $this->getConfigReconElement('id_rt');
+ }
+
+ return [
+ [
+ 'arguments' => [
+ 'name' => 'page',
+ 'value' => ($this->page + 1),
+ 'type' => 'hidden',
+ 'return' => true,
+ ],
+ ],
+ [
+ 'arguments' => [
+ 'name' => 'submit',
+ 'label' => ($last) ? __('Finish') : __('Next'),
+ 'type' => 'submit',
+ 'attributes' => 'class="sub '.(($last) ? 'wand' : 'next').'"',
+ 'return' => true,
+ ],
+ ],
+ [
+ 'arguments' => [
+ 'name' => 'task',
+ 'value' => $task_id,
+ 'type' => 'hidden',
+ 'return' => true,
+ ],
+ ],
+ [
+ 'arguments' => [
+ 'name' => 'parse_form',
+ 'value' => 1,
+ 'type' => 'hidden',
+ 'return' => true,
+ ],
+ ],
+ ];
+ }
+
+
+ /**
+ * Print required css in some points.
+ *
+ * @return string With js code.
+ */
+ protected function cloudJS()
+ {
+ return '
+ function toggleCloudSubmenu(curr_elem, id_csm){
+ if (document.getElementsByName(curr_elem)[0].checked){
+ $("#li-"+id_csm).show();
+ } else {
+ $("#li-"+id_csm).hide();
+ }
+ };
+ ';
+ }
+
+
+ /**
+ * Check if section have extensions.
+ *
+ * @return boolean Return true if section is empty.
+ */
+ public function isEmpty()
+ {
+ $extensions = new ExtensionsDiscovery('cloud');
+ $listExtensions = $extensions->getExtensionsApps();
+ if ($listExtensions > 0 || enterprise_installed() === true) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+
+}
diff --git a/pandora_console/godmode/wizards/Custom.class.php b/pandora_console/godmode/wizards/Custom.class.php
new file mode 100644
index 0000000000..41a177b3e3
--- /dev/null
+++ b/pandora_console/godmode/wizards/Custom.class.php
@@ -0,0 +1,160 @@
+setBreadcrum([]);
+
+ $this->access = 'AW';
+ $this->task = [];
+ $this->msg = $msg;
+ $this->icon = $icon;
+ $this->class = $class_style;
+ $this->label = $label;
+ $this->page = $page;
+ $this->url = ui_get_full_url(
+ 'index.php?sec=gservers&sec2=godmode/servers/discovery&wiz=custom'
+ );
+
+ return $this;
+ }
+
+
+ /**
+ * Run wizard manager.
+ *
+ * @return mixed Returns null if wizard is ongoing. Result if done.
+ */
+ public function run()
+ {
+ global $config;
+
+ // Load styles.
+ parent::run();
+
+ // Load current wiz. sub-styles.
+ ui_require_css_file(
+ 'custom',
+ ENTERPRISE_DIR.'/include/styles/wizards/'
+ );
+
+ $mode = get_parameter('mode', null);
+ $extensions = new ExtensionsDiscovery('custom', $mode);
+ if ($mode !== null) {
+ // Load extension if exist.
+ $extensions->run();
+ return;
+ }
+
+ // Load classes and print selector.
+ $wiz_data = $extensions->loadExtensions();
+
+ $this->prepareBreadcrum(
+ [
+ [
+ 'link' => ui_get_full_url(
+ 'index.php?sec=gservers&sec2=godmode/servers/discovery'
+ ),
+ 'label' => __('Discovery'),
+ ],
+ [
+ 'link' => ui_get_full_url(
+ 'index.php?sec=gservers&sec2=godmode/servers/discovery&wiz=custom'
+ ),
+ 'label' => __('Custom'),
+ 'selected' => true,
+ ],
+ ]
+ );
+
+ // Header.
+ ui_print_page_header(
+ __('Custom'),
+ '',
+ false,
+ '',
+ true,
+ '',
+ false,
+ '',
+ GENERIC_SIZE_TEXT,
+ '',
+ $this->printHeader(true)
+ );
+
+ Wizard::printBigButtonsList($wiz_data);
+
+ echo '*'.__('All company names used here are for identification purposes only. Use of these names, logos, and brands does not imply endorsement.').'
';
+ return $result;
+ }
+
+
+ /**
+ * Check if section have extensions.
+ *
+ * @return boolean Return true if section is empty.
+ */
+ public function isEmpty()
+ {
+ $extensions = new ExtensionsDiscovery('custom');
+ $listExtensions = $extensions->getExtensionsApps();
+ if ($listExtensions > 0) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+
+}
diff --git a/pandora_console/godmode/wizards/DiscoveryTaskList.class.php b/pandora_console/godmode/wizards/DiscoveryTaskList.class.php
index 0fefe0739e..1c7ffe5526 100644
--- a/pandora_console/godmode/wizards/DiscoveryTaskList.class.php
+++ b/pandora_console/godmode/wizards/DiscoveryTaskList.class.php
@@ -168,7 +168,10 @@ class DiscoveryTaskList extends HTML
}
if (is_reporting_console_node() === false) {
- $ret2 = $this->showList();
+ $ret2 = $this->showList(__('Host & devices tasks'), [0, 1]);
+ $ret2 .= $this->showList(__('Applications tasks'), [3, 4, 5, 10, 11, 12], 'app');
+ $ret2 .= $this->showList(__('Cloud tasks'), [6, 7, 8, 13, 14], 'cloud');
+ $ret2 .= $this->showList(__('Custom tasks'), [-1], 'custom');
}
if ($ret === false && $ret2 === false) {
@@ -518,9 +521,13 @@ class DiscoveryTaskList extends HTML
/**
* Show complete list of running tasks.
*
+ * @param string $titleTable Title of section.
+ * @param array $filter Ids array from apps for filter.
+ * @param boolean $extension_section Extension to add in table.
+ *
* @return boolean Success or not.
*/
- public function showList()
+ public function showList($titleTable, $filter, $extension_section=false)
{
global $config;
@@ -544,7 +551,16 @@ class DiscoveryTaskList extends HTML
include_once $config['homedir'].'/include/functions_network_profiles.php';
if (users_is_admin()) {
- $recon_tasks = db_get_all_rows_sql('SELECT * FROM trecon_task');
+ $recon_tasks = db_get_all_rows_sql(
+ sprintf(
+ 'SELECT tasks.*, apps.section AS section, apps.short_name AS short_name
+ FROM trecon_task tasks
+ LEFT JOIN tdiscovery_apps apps ON tasks.id_app = apps.id_app
+ WHERE type IN (%s) OR section = "%s"',
+ implode(',', $filter),
+ $extension_section
+ )
+ );
} else {
$user_groups = implode(
',',
@@ -552,9 +568,14 @@ class DiscoveryTaskList extends HTML
);
$recon_tasks = db_get_all_rows_sql(
sprintf(
- 'SELECT * FROM trecon_task
- WHERE id_group IN (%s)',
- $user_groups
+ 'SELECT tasks.*, apps.section AS section, apps.short_name AS short_name
+ FROM trecon_task
+ LEFT JOIN tdiscovery_apps apps ON tasks.id_app = apps.id_app
+ WHERE id_group IN (%s) AND
+ (type IN (%s) OR section = "%s")',
+ $user_groups,
+ implode(',', $filter),
+ $extension_section
)
);
}
@@ -671,7 +692,9 @@ class DiscoveryTaskList extends HTML
$recon_script_name = false;
}
- if ($task['disabled'] == 0 && $server_name !== '') {
+ if (($task['disabled'] == 0 && $server_name !== '' && (int) $task['type'] !== DISCOVERY_EXTENSION)
+ || ((int) $task['type'] === DISCOVERY_EXTENSION && (int) $task['setup_complete'] === 1)
+ ) {
if (check_acl($config['id_user'], 0, 'AW')) {
$data[0] = '';
$data[9] .= html_print_image(
'images/web@groups.svg',
@@ -1012,13 +1050,24 @@ class DiscoveryTaskList extends HTML
).'';
}
} else {
+ $url_edit = sprintf(
+ 'index.php?sec=gservers&sec2=godmode/servers/discovery&%s&task=%d',
+ $this->getTargetWiz($task, $recon_script_data),
+ $task['id_rt']
+ );
+
+ if ((int) $task['type'] === DISCOVERY_EXTENSION) {
+ $url_edit = sprintf(
+ 'index.php?sec=gservers&sec2=godmode/servers/discovery&wiz=%s&mode=%s&id_task=%s',
+ $task['section'],
+ $task['short_name'],
+ $task['id_rt'],
+ );
+ }
+
// Check if is a H&D, Cloud or Application or IPAM.
$data[9] .= ''.html_print_image(
'images/edit.svg',
true,
@@ -1082,7 +1131,7 @@ class DiscoveryTaskList extends HTML
$return = true;
}
- ui_toggle($content, __('Server Tasks'), '', '', false);
+ ui_toggle($content, $titleTable, '', '', false);
// Div neccesary for modal map task.
echo '';
@@ -1240,7 +1289,7 @@ class DiscoveryTaskList extends HTML
($task['status'] < 0) ? 100 : $task['status'],
150,
150,
- '#3A3A3A',
+ '#14524f',
'%',
'',
'#ececec',
@@ -1310,7 +1359,7 @@ class DiscoveryTaskList extends HTML
$task['stats']['c_network_percent'],
150,
150,
- '#3A3A3A',
+ '#14524f',
'%',
'',
'#ececec',
@@ -1353,14 +1402,14 @@ class DiscoveryTaskList extends HTML
$output = '';
- if (is_array($task['stats']) === false) {
- $task['stats'] = json_decode($task['summary'], true);
+ if (is_array($task['stats']) === false && (int) $task['type'] !== DISCOVERY_EXTENSION) {
+ $task['stats'] = json_decode(io_safe_output($task['summary']), true);
if (json_last_error() !== JSON_ERROR_NONE) {
return $task['summary'];
}
}
- if (is_array($task['stats'])) {
+ if (is_array($task['stats']) || (int) $task['type'] === DISCOVERY_EXTENSION) {
$i = 0;
$table = new StdClasS();
$table->class = 'databox data';
@@ -1418,6 +1467,65 @@ class DiscoveryTaskList extends HTML
$table->data[$i][1] = '';
$table->data[$i][1] .= ($total - $agents);
$table->data[$i++][1] .= '';
+ } else if ((int) $task['type'] === DISCOVERY_EXTENSION) {
+ // Content.
+ $countSummary = 1;
+ if (is_array($task['stats']) === true && count(array_filter(array_keys($task['stats']), 'is_numeric')) === count($task['stats'])) {
+ foreach ($task['stats'] as $key => $summary) {
+ $table->data[$i][0] = ''.__('Summary').' '.$countSummary.'';
+ $table->data[$i][1] = '';
+ $countSummary++;
+ $i++;
+ if (is_array($summary) === true) {
+ if (empty($summary['summary']) === true && empty($summary['info']) === true) {
+ $table->data[$i][0] = json_encode($summary, JSON_PRETTY_PRINT);
+ $table->data[$i][1] = '';
+ $i++;
+ continue;
+ }
+
+ $unknownJson = $summary;
+ foreach ($summary as $k2 => $v) {
+ if (is_array($v) === true) {
+ if ($k2 === 'summary') {
+ foreach ($v as $k3 => $v2) {
+ $table->data[$i][0] = $k3;
+ $table->data[$i][1] = $v2;
+ $i++;
+ }
+
+ unset($unknownJson[$k2]);
+ }
+ } else {
+ if ($k2 === 'info') {
+ $table->data[$i][0] = $v;
+ $table->data[$i][1] = '';
+ $i++;
+
+ unset($unknownJson[$k2]);
+ }
+ }
+ }
+
+ if (empty($unknownJson) === false) {
+ $table->data[$i][0] = json_encode($unknownJson, JSON_PRETTY_PRINT);
+ $table->data[$i][1] = '';
+ $i++;
+ }
+ } else {
+ $table->data[$i][0] = $summary;
+ $table->data[$i][1] = '';
+ $i++;
+ }
+ }
+ } else {
+ $table->data[$i][0] = ''.__('Summary').'';
+ $table->data[$i][1] = '';
+ $i++;
+ $table->data[$i][0] = $task['summary'];
+ $table->data[$i][1] = '';
+ $i++;
+ }
} else {
// Content.
if (is_array($task['stats']['summary']) === true) {
@@ -1479,7 +1587,7 @@ class DiscoveryTaskList extends HTML
}
$task = db_get_row('trecon_task', 'id_rt', $id_task);
- $task['stats'] = json_decode($task['summary'], true);
+ $task['stats'] = json_decode(io_safe_output($task['summary']), true);
$summary = $this->progressTaskSummary($task);
$output = '';
@@ -1872,7 +1980,11 @@ class DiscoveryTaskList extends HTML
if ($task['status'] <= 0
&& empty($task['summary']) === false
) {
- $status = __('Done');
+ if ($task['status'] == -2) {
+ $status = __('Failed');
+ } else {
+ $status = __('Done');
+ }
} else if ($task['utimestamp'] == 0
&& empty($task['summary'])
) {
diff --git a/pandora_console/godmode/wizards/ManageExtensions.class.php b/pandora_console/godmode/wizards/ManageExtensions.class.php
new file mode 100644
index 0000000000..03b1bdee7b
--- /dev/null
+++ b/pandora_console/godmode/wizards/ManageExtensions.class.php
@@ -0,0 +1,1054 @@
+ajaxController = $config['homedir'].'/include/ajax/manage_extensions.ajax';
+ $this->url = ui_get_full_url(
+ 'index.php?sec=gservers&sec2=godmode/servers/discovery&wiz=magextensions'
+ );
+ }
+
+
+ /**
+ * Checks if target method is available to be called using AJAX.
+ *
+ * @param string $method Target method.
+ *
+ * @return boolean True allowed, false not.
+ */
+ public function ajaxMethod($method)
+ {
+ // Check access.
+ check_login();
+
+ return in_array($method, $this->AJAXMethods);
+ }
+
+
+ /**
+ * Implements load method.
+ *
+ * @return mixed Skeleton for button.
+ */
+ public function load()
+ {
+ return [
+ 'icon' => $this->icon,
+ 'label' => $this->label,
+ 'url' => $this->url,
+
+ ];
+
+ }
+
+
+ /**
+ * Generates a JSON error.
+ *
+ * @param string $msg Error message.
+ *
+ * @return void
+ */
+ public function errorAjax(string $msg)
+ {
+ echo json_encode(
+ ['error' => $msg]
+ );
+ }
+
+
+ /**
+ * Implements run method.
+ *
+ * @return void
+ */
+ public function run()
+ {
+ global $config;
+ // Load styles.
+ parent::run();
+
+ $uploadDisco = get_parameter('upload_disco', '');
+ $action = get_parameter('action', '');
+ $shortName = get_parameter('short_name', '');
+
+ if (empty($uploadDisco) === false) {
+ if ($_FILES['file']['error'] == 0) {
+ $result = $this->uploadExtension($_FILES['file']);
+ if ($result === true) {
+ ui_print_success_message(
+ __('Uploaded extension')
+ );
+ } else {
+ if (is_string($result)) {
+ echo $this->error($result);
+ } else {
+ echo $this->error(__('Failed to upload extension'));
+ }
+ }
+ } else {
+ echo $this->error(__('Failed to upload extension'));
+ }
+ }
+
+ if (empty($action) === false && empty($shortName) === false) {
+ switch ($action) {
+ case 'delete':
+ $result = $this->uninstallExtension($shortName);
+ if ($result === true) {
+ ui_print_success_message(
+ __('Deleted extension')
+ );
+ } else {
+ echo $this->error(__('Fail delete extension'));
+ }
+
+ case 'sync_server':
+ $syncAction = get_parameter('sync_action', '');
+ if ($syncAction === 'refresh') {
+ $installationFolder = $config['homedir'].'/'.$this->path.'/'.$shortName;
+ $result = $this->copyExtensionToServer($installationFolder, $shortName);
+ if ($result === true) {
+ ui_print_success_message(
+ __('Extension folder created successfully')
+ );
+ } else {
+ echo $this->error(__('Fail created extension folder'));
+ }
+ }
+ break;
+
+ default:
+ continue;
+ }
+ }
+
+ $this->prepareBreadcrum(
+ [
+ [
+ 'link' => ui_get_full_url(
+ 'index.php?sec=gservers&sec2=godmode/servers/discovery'
+ ),
+ 'label' => __('Discovery'),
+ ],
+ [
+ 'link' => '',
+ 'label' => _('Manage disco packages'),
+ 'selected' => 1,
+ ],
+ ]
+ );
+
+ // Header.
+ ui_print_page_header(
+ __('Manage disco packages'),
+ '',
+ false,
+ '',
+ true,
+ '',
+ false,
+ '',
+ GENERIC_SIZE_TEXT,
+ '',
+ $this->printHeader(true)
+ );
+
+ $table = new stdClass();
+ $table->width = '100%';
+ $table->class = 'databox filters';
+ $table->size = [];
+ $table->size[0] = '80%';
+ $table->align[3] = 'right';
+ $table->data = [];
+ $table->data[0][0] = html_print_label_input_block(
+ __('Load DISCO'),
+ html_print_div(
+ [
+ 'id' => 'upload_file',
+ 'content' => html_print_input_file(
+ 'file',
+ true,
+ ['style' => 'width:100%']
+ ),
+ 'class' => 'mrgn_top_15px',
+ ],
+ true
+ )
+ );
+ $table->data[0][3] = html_print_submit_button(
+ __('Upload DISCO'),
+ 'upload_button',
+ false,
+ [
+ 'class' => 'sub ok float-right',
+ 'icon' => 'next',
+ ],
+ true
+ );
+
+ echo '';
+
+ echo '';
+ ui_require_javascript_file('manage_extensions');
+ try {
+ $columns = [
+ 'name',
+ 'short_name',
+ 'section',
+ 'description',
+ 'version',
+ [
+ 'text' => 'actions',
+ 'class' => 'flex flex-items-center',
+ ],
+ ];
+
+ $columnNames = [
+ __('Name'),
+ __('Short name'),
+ __('Section'),
+ __('Description'),
+ __('Version'),
+ __('Actions'),
+ ];
+
+ // Load datatables user interface.
+ ui_print_datatable(
+ [
+ 'id' => 'list_extensions',
+ 'class' => 'info_table',
+ 'style' => 'width: 99%',
+ 'dom_elements' => 'plfti',
+ 'filter_main_class' => 'box-flat white_table_graph fixed_filter_bar',
+ 'columns' => $columns,
+ 'column_names' => $columnNames,
+ 'ajax_url' => $this->ajaxController,
+ 'ajax_data' => ['method' => 'getExtensionsInstalled'],
+ 'no_sortable_columns' => [-1],
+ 'order' => [
+ 'field' => 'name',
+ 'direction' => 'asc',
+ ],
+ 'search_button_class' => 'sub filter float-right',
+ ]
+ );
+ } catch (Exception $e) {
+ echo $e->getMessage();
+ }
+
+ }
+
+
+ /**
+ * Upload extension to server.
+ *
+ * @param array $disco File disco tu upload.
+ *
+ * @return boolean $result Of operation, true if is ok.
+ */
+ private function uploadExtension($disco)
+ {
+ global $config;
+ if (substr($disco['name'], -6) !== '.disco') {
+ return false;
+ }
+
+ $nameFile = str_replace('.disco', '.zip', $disco['name']);
+ $nameTempDir = $config['attachment_store'].'/downloads/';
+ if (file_exists($nameTempDir) === false) {
+ mkdir($nameTempDir);
+ }
+
+ $tmpPath = Files::tempdirnam(
+ $nameTempDir,
+ 'extensions_uploaded_'
+ );
+ $result = move_uploaded_file($disco['tmp_name'], $tmpPath.'/'.$nameFile);
+ if ($result === true) {
+ $unzip = $this->unZip($tmpPath.'/'.$nameFile, $tmpPath);
+ if ($unzip === true) {
+ unlink($tmpPath.'/'.$nameFile);
+ db_process_sql_begin();
+ $this->iniFile = parse_ini_file($tmpPath.'/discovery_definition.ini', true, INI_SCANNER_TYPED);
+ if ($this->iniFile === false) {
+ db_process_sql_rollback();
+ Files::rmrf($tmpPath);
+ return __('Failed to upload extension: Error while parsing dicovery_definition.ini');
+ }
+
+ $error = ExtensionsDiscovery::validateIni($this->iniFile);
+ if ($error !== false) {
+ db_process_sql_rollback();
+ Files::rmrf($tmpPath);
+ return $error;
+ }
+
+ $id = $this->installExtension();
+ if ($id === false) {
+ db_process_sql_rollback();
+ Files::rmrf($tmpPath);
+ return false;
+ }
+
+ $result = $this->autoLoadConfigExec($id);
+ if ($result === false) {
+ db_process_sql_rollback();
+ Files::rmrf($tmpPath);
+ return false;
+ }
+
+ $result = $this->autoUpdateDefaultMacros($id);
+ if ($result === false) {
+ db_process_sql_rollback();
+ Files::rmrf($tmpPath);
+ return false;
+ }
+
+ $nameFolder = $this->iniFile['discovery_extension_definition']['short_name'];
+ $installationFolder = $config['homedir'].'/'.$this->path.'/'.$nameFolder;
+ if (file_exists($installationFolder) === false) {
+ mkdir($installationFolder, 0777, true);
+ } else {
+ Files::rmrf($installationFolder, true);
+ }
+
+ $result = Files::move($tmpPath, $installationFolder, true);
+ if ($result === false) {
+ db_process_sql_rollback();
+ Files::rmrf($tmpPath);
+ return false;
+ }
+
+ $this->setPermissionfiles($installationFolder, $this->iniFile['discovery_extension_definition']['execution_file']);
+ $this->setPermissionfiles(
+ $installationFolder,
+ [
+ $this->iniFile['discovery_extension_definition']['passencrypt_script'],
+ $this->iniFile['discovery_extension_definition']['passdecrypt_script'],
+ ]
+ );
+
+ $result = $this->copyExtensionToServer($installationFolder, $nameFolder);
+ if ($result === false) {
+ db_process_sql_rollback();
+ Files::rmrf($tmpPath);
+ return false;
+ }
+
+ Files::rmrf($tmpPath);
+ db_process_sql_commit();
+ return true;
+ }
+ } else {
+ Files::rmrf($tmpPath);
+ return false;
+ }
+ }
+
+
+ /**
+ * Copy the extension folder into remote path server.
+ *
+ * @param string $path Path extension folder.
+ * @param string $nameFolder Name of extension folder.
+ *
+ * @return boolean Result of operation.
+ */
+ public function copyExtensionToServer($path, $nameFolder)
+ {
+ global $config;
+ $filesToExclude = [
+ 'discovery_definition.ini',
+ 'logo.png',
+ ];
+ $serverPath = $config['remote_config'].'/discovery/'.$nameFolder;
+ if (file_exists($serverPath) === false) {
+ mkdir($serverPath, 0777, true);
+ } else {
+ Files::rmrf($serverPath, true);
+ }
+
+ $result = $this->copyFolder($path, $serverPath, $filesToExclude);
+ $this->setPermissionfiles($serverPath, $this->iniFile['discovery_extension_definition']['execution_file']);
+
+ return $result;
+ }
+
+
+ /**
+ * Copy from $source path to $destination
+ *
+ * @param string $source Initial folder path.
+ * @param string $destination Destination folder path.
+ * @param array $exclude Files to exlcude in copy.
+ *
+ * @return boolean Result of operation.
+ */
+ public function copyFolder($source, $destination, $exclude=[])
+ {
+ if (file_exists($destination) === false) {
+ mkdir($destination, 0777, true);
+ }
+
+ $files = scandir($source);
+ foreach ($files as $file) {
+ if ($file !== '.' && $file !== '..') {
+ if (is_dir($source.'/'.$file)) {
+ $result = $this->copyFolder($source.'/'.$file, $destination.'/'.$file);
+ if ($result === false) {
+ return false;
+ }
+ } else {
+ if (in_array($file, $exclude) === false) {
+ $result = copy($source.'/'.$file, $destination.'/'.$file);
+ if ($result === false) {
+ return false;
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Delete extension from database and delete folder
+ *
+ * @param integer $shortName Short name app for delete.
+ *
+ * @return boolean Result of operation.
+ */
+ private function uninstallExtension($shortName)
+ {
+ global $config;
+
+ $result = db_process_sql_delete(
+ 'tdiscovery_apps',
+ ['short_name' => $shortName]
+ );
+
+ if ($result !== false) {
+ Files::rmrf($config['homedir'].'/'.$this->path.'/'.$shortName);
+ Files::rmrf($config['remote_config'].'/discovery/'.$shortName);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+
+ /**
+ * Load the basic information of the app into database.
+ *
+ * @return boolean Result of query.
+ */
+ private function installExtension()
+ {
+ $exist = db_get_row_filter(
+ 'tdiscovery_apps',
+ [
+ 'short_name' => $this->iniFile['discovery_extension_definition']['short_name'],
+ ]
+ );
+ $version = $this->iniFile['discovery_extension_definition']['version'];
+ if ($version === null) {
+ $version = '';
+ }
+
+ $description = $this->iniFile['discovery_extension_definition']['description'];
+ if ($description === null) {
+ $description = '';
+ }
+
+ if ($exist === false) {
+ return db_process_sql_insert(
+ 'tdiscovery_apps',
+ [
+ 'short_name' => $this->iniFile['discovery_extension_definition']['short_name'],
+ 'name' => io_safe_input($this->iniFile['discovery_extension_definition']['name']),
+ 'description' => io_safe_input($description),
+ 'section' => $this->iniFile['discovery_extension_definition']['section'],
+ 'version' => $version,
+ ]
+ );
+ } else {
+ $result = db_process_sql_update(
+ 'tdiscovery_apps',
+ [
+ 'name' => io_safe_input($this->iniFile['discovery_extension_definition']['name']),
+ 'description' => io_safe_input($description),
+ 'section' => $this->iniFile['discovery_extension_definition']['section'],
+ 'version' => $version,
+ ],
+ [
+ 'short_name' => $this->iniFile['discovery_extension_definition']['short_name'],
+ ]
+ );
+
+ if ($result !== false) {
+ return $exist['id_app'];
+ }
+ }
+ }
+
+
+ /**
+ * Return all extension installed by ajax.
+ *
+ * @return void
+ */
+ public function getExtensionsInstalled()
+ {
+ global $config;
+
+ $data = [];
+ $start = get_parameter('start', 0);
+ $length = get_parameter('length', $config['block_size']);
+ $orderDatatable = get_datatable_order(true);
+ $pagination = '';
+ $order = '';
+
+ try {
+ ob_start();
+
+ if (isset($orderDatatable)) {
+ $order = sprintf(
+ ' ORDER BY %s %s',
+ $orderDatatable['field'],
+ $orderDatatable['direction']
+ );
+ }
+
+ if (isset($length) && $length > 0
+ && isset($start) && $start >= 0
+ ) {
+ $pagination = sprintf(
+ ' LIMIT %d OFFSET %d ',
+ $length,
+ $start
+ );
+ }
+
+ $sql = sprintf(
+ 'SELECT short_name, name, section, description, version
+ FROM tdiscovery_apps
+ %s %s',
+ $order,
+ $pagination
+ );
+
+ $data = db_get_all_rows_sql($sql);
+
+ $sqlCount = sprintf(
+ 'SELECT short_name, name, section, description, version
+ FROM tdiscovery_apps
+ %s',
+ $order,
+ );
+
+ $count = db_get_num_rows($sqlCount);
+
+ foreach ($data as $key => $row) {
+ $logo = $this->path.'/'.$row['short_name'].'/logo.png';
+ if (file_exists($logo) === false) {
+ $logo = $this->defaultLogo;
+ }
+
+ $logo = html_print_image($logo, true, ['style' => 'max-width: 30px; margin-right: 15px;']);
+ $data[$key]['name'] = $logo.io_safe_output($row['name']);
+ $data[$key]['short_name'] = $row['short_name'];
+ $data[$key]['description'] = io_safe_output($row['description']);
+ $data[$key]['version'] = $row['version'];
+ $data[$key]['actions'] = '';
+ if ($this->checkFolderConsole($row['short_name']) === true) {
+ $data[$key]['actions'] .= '';
+ } else {
+ $data[$key]['actions'] .= html_print_image(
+ 'images/error_red.png',
+ true,
+ [
+ 'title' => __('The extension directory or .ini does not exist in console.'),
+ 'alt' => __('The extension directory or .ini does not exist in console.'),
+ 'class' => 'main_menu_icon invert_filter',
+ ],
+ );
+ }
+ }
+
+ if (empty($data) === true) {
+ $total = 0;
+ $data = [];
+ } else {
+ $total = $count;
+ }
+
+ echo json_encode(
+ [
+ 'data' => $data,
+ 'recordsTotal' => $total,
+ 'recordsFiltered' => $total,
+ ]
+ );
+ // Capture output.
+ $response = ob_get_clean();
+ } catch (Exception $e) {
+ echo json_encode(['error' => $e->getMessage()]);
+ exit;
+ }
+
+ json_decode($response);
+ if (json_last_error() === JSON_ERROR_NONE) {
+ echo $response;
+ } else {
+ echo json_encode(
+ [
+ 'success' => false,
+ 'error' => $response,
+ ]
+ );
+ }
+
+ exit;
+ }
+
+
+ /**
+ * Insert new the default values for extension.
+ *
+ * @param integer $id Id of extension.
+ *
+ * @return boolean Result of query.
+ */
+ private function autoUpdateDefaultMacros($id)
+ {
+ $defaultValues = $this->iniFile['discovery_extension_definition']['default_value'];
+
+ foreach ($defaultValues as $macro => $value) {
+ $sql = 'INSERT IGNORE INTO `tdiscovery_apps_tasks_macros`
+ (`id_task`, `macro`, `type`, `value`, `temp_conf`)
+ SELECT `id_rt`, "'.$macro.'", "custom", "'.(string) io_safe_input($value).'", "0"
+ FROM `trecon_task`
+ WHERE `id_app` = "'.$id.'";';
+ $result = db_process_sql($sql);
+ if ($result === false) {
+ return false;
+ }
+ }
+
+ $tempFiles = $this->iniFile['tempfile_confs']['file'];
+ foreach ($tempFiles as $macro => $value) {
+ $sql = 'UPDATE `tdiscovery_apps_tasks_macros`
+ SET `value` = "'.(string) io_safe_input($value).'" WHERE `id_task`
+ IN (SELECT `id_rt` FROM `trecon_task` WHERE `id_app` = "'.$id.'") AND `macro` = "'.$macro.'"';
+ $result = db_process_sql($sql);
+ if ($result === false) {
+ return false;
+ }
+
+ $sql = 'INSERT IGNORE INTO `tdiscovery_apps_tasks_macros`
+ (`id_task`, `macro`, `type`, `value`, `temp_conf`)
+ SELECT `id_rt`, "'.$macro.'", "custom", "'.(string) io_safe_input($value).'", "1"
+ FROM `trecon_task`
+ WHERE `id_app` = "'.$id.'";';
+ $result = db_process_sql($sql);
+ if ($result === false) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Load the exec files in database
+ *
+ * @param integer $id Id of extension.
+ *
+ * @return boolean Result of query.
+ */
+ private function autoLoadConfigExec($id)
+ {
+ $executionFiles = $this->iniFile['discovery_extension_definition']['execution_file'];
+
+ foreach ($executionFiles as $key => $value) {
+ $exist = db_get_row_filter(
+ 'tdiscovery_apps_scripts',
+ [
+ 'id_app' => $id,
+ 'macro' => $key,
+ ]
+ );
+ if ($exist === false) {
+ $result = db_process_sql_insert(
+ 'tdiscovery_apps_scripts',
+ [
+ 'id_app' => $id,
+ 'macro' => $key,
+ 'value' => io_safe_input($value),
+ ]
+ );
+ if ($result === false) {
+ return false;
+ }
+ } else {
+ $result = db_process_sql_update(
+ 'tdiscovery_apps_scripts',
+ ['value' => io_safe_input($value)],
+ [
+ 'id_app' => $id,
+ 'macro' => $key,
+ ]
+ );
+ if ($result === false) {
+ return false;
+ }
+ }
+ }
+
+ $execCommands = $this->iniFile['discovery_extension_definition']['exec'];
+ $result = db_process_sql_delete(
+ 'tdiscovery_apps_executions',
+ ['id_app' => $id]
+ );
+ if ($result === false) {
+ return false;
+ }
+
+ foreach ($execCommands as $key => $value) {
+ $result = db_process_sql_insert(
+ 'tdiscovery_apps_executions',
+ [
+ 'id_app' => $id,
+ 'execution' => io_safe_input($value),
+ ]
+ );
+ if ($result === false) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Check if exist folder extension in console.
+ *
+ * @param string $shortName Name of folder.
+ *
+ * @return boolean Return true if exist folder
+ */
+ private function checkFolderConsole($shortName)
+ {
+ global $config;
+
+ $folderPath = $config['homedir'].'/'.$this->path.'/'.$shortName;
+ $iniPath = $config['homedir'].'/'.$this->path.'/'.$shortName.'/discovery_definition.ini';
+ if (file_exists($folderPath) === false || file_exists($iniPath) === false) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+
+ /**
+ * Validate the ini name by ajax.
+ *
+ * @return void
+ */
+ public function validateIniName()
+ {
+ global $config;
+ $uploadDisco = get_parameter('upload_disco', '');
+ if (empty($uploadDisco) === false) {
+ if ($_FILES['file']['error'] == 0) {
+ $disco = $_FILES['file'];
+ } else {
+ echo json_encode(['success' => false, 'message' => 'Failed to upload extension']);
+ return;
+ }
+ }
+
+ if (substr($disco['name'], -6) !== '.disco') {
+ echo json_encode(['success' => false, 'message' => 'Failed to upload extension']);
+ return;
+ }
+
+ $nameFile = str_replace('.disco', '.zip', $disco['name']);
+ $nameTempDir = $config['attachment_store'].'/downloads/';
+ if (file_exists($nameTempDir) === false) {
+ mkdir($nameTempDir);
+ }
+
+ $tmpPath = Files::tempdirnam(
+ $nameTempDir,
+ 'extensions_uploaded_'
+ );
+ $result = move_uploaded_file($disco['tmp_name'], $tmpPath.'/'.$nameFile);
+ if ($result === true) {
+ $unzip = $this->unZip($tmpPath.'/'.$nameFile, $tmpPath, 'discovery_definition.ini');
+ if ($unzip === true) {
+ unlink($tmpPath.'/'.$nameFile);
+ $this->iniFile = parse_ini_file($tmpPath.'/discovery_definition.ini', true, INI_SCANNER_TYPED);
+ if ($this->iniFile === false) {
+ Files::rmrf($tmpPath);
+ echo json_encode(['success' => false, 'message' => __('Failed to upload extension: Error while parsing dicovery_definition.ini')]);
+ return;
+ }
+
+ $message = false;
+ $shortName = $this->iniFile['discovery_extension_definition']['short_name'];
+ if (strpos($shortName, 'pandorafms.') === 0) {
+ $message = __('The \'short_name\' starting with \'pandorafms.\' is reserved for Pandora FMS applications. If this is not an official Pandora FMS application, consider changing the \'short_name\'. Do you want to continue?');
+ }
+
+ $exist = db_get_row_filter(
+ 'tdiscovery_apps',
+ ['short_name' => $shortName]
+ );
+
+ if ($exist !== false) {
+ $message = __('There is another application with the same \'short_name\': \'%s\'. Do you want to overwrite the application and all of its contents?', $shortName);
+ }
+
+ if ($message !== false) {
+ echo json_encode(
+ [
+ 'success' => true,
+ 'warning' => true,
+ 'message' => $message,
+ ]
+ );
+ } else {
+ echo json_encode(['success' => true]);
+ }
+
+ Files::rmrf($tmpPath);
+ return;
+ }
+ } else {
+ Files::rmrf($tmpPath);
+ echo json_encode(['success' => false, 'message' => __('Failed to upload extension')]);
+ return;
+ }
+ }
+
+
+ /**
+ * Return all extensions from section.
+ *
+ * @param string $section Section to filter.
+ *
+ * @return array List of sections.
+ */
+ static public function getExtensionBySection($section)
+ {
+ return db_get_all_rows_filter(
+ 'tdiscovery_apps',
+ ['section' => $section]
+ );
+ }
+
+
+ /**
+ * Set execution permission in folder items and subfolders.
+ *
+ * @param string $path Array of files to apply permissions.
+ * @param array $filter Array of files for apply permission only.
+ *
+ * @return void
+ */
+ private function setPermissionfiles($path, $filter=false)
+ {
+ global $config;
+
+ if ($filter !== false && is_array($filter) === true) {
+ foreach ($filter as $key => $file) {
+ if (substr($file, 0, 1) !== '/') {
+ $file = $path.'/'.$file;
+ }
+
+ chmod($file, 0777);
+ }
+ } else {
+ chmod($path, 0777);
+
+ if (is_dir($path)) {
+ $items = scandir($path);
+ foreach ($items as $item) {
+ if ($item != '.' && $item != '..') {
+ $itemPath = $path.'/'.$item;
+ $this->setPermissionfiles($itemPath);
+ }
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Unzip folder or only file.
+ *
+ * @param string $zipFile File to unzip.
+ * @param string $target_path Target path into unzip.
+ * @param string $file If only need unzip one file.
+ *
+ * @return boolean $result True if the file has been successfully decompressed.
+ */
+ public function unZip($zipFile, $target_path, $file=null)
+ {
+ $zip = new \ZipArchive;
+
+ if ($zip->open($zipFile) === true) {
+ $zip->extractTo($target_path, $file);
+ $zip->close();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+
+}
diff --git a/pandora_console/images/wizard/Configurar_app@svg.svg b/pandora_console/images/wizard/Configurar_app@svg.svg
new file mode 100644
index 0000000000..59507e2cf4
--- /dev/null
+++ b/pandora_console/images/wizard/Configurar_app@svg.svg
@@ -0,0 +1,22 @@
+
+
\ No newline at end of file
diff --git a/pandora_console/images/wizard/Custom_apps@svg.svg b/pandora_console/images/wizard/Custom_apps@svg.svg
new file mode 100644
index 0000000000..23e251912d
--- /dev/null
+++ b/pandora_console/images/wizard/Custom_apps@svg.svg
@@ -0,0 +1,21 @@
+
+
\ No newline at end of file
diff --git a/pandora_console/images/wizard/app_generico.svg b/pandora_console/images/wizard/app_generico.svg
new file mode 100644
index 0000000000..99d3e5cf42
--- /dev/null
+++ b/pandora_console/images/wizard/app_generico.svg
@@ -0,0 +1,18 @@
+
+
\ No newline at end of file
diff --git a/pandora_console/images/wizard/applications.png b/pandora_console/images/wizard/applications.png
new file mode 100644
index 0000000000..01c10178bd
Binary files /dev/null and b/pandora_console/images/wizard/applications.png differ
diff --git a/pandora_console/images/wizard/cloud.png b/pandora_console/images/wizard/cloud.png
new file mode 100644
index 0000000000..a016599e2f
Binary files /dev/null and b/pandora_console/images/wizard/cloud.png differ
diff --git a/pandora_console/images/wizard/consoletasks.png b/pandora_console/images/wizard/consoletasks.png
new file mode 100644
index 0000000000..0087897495
Binary files /dev/null and b/pandora_console/images/wizard/consoletasks.png differ
diff --git a/pandora_console/include/ajax/manage_extensions.ajax.php b/pandora_console/include/ajax/manage_extensions.ajax.php
new file mode 100644
index 0000000000..b116277a23
--- /dev/null
+++ b/pandora_console/include/ajax/manage_extensions.ajax.php
@@ -0,0 +1,60 @@
+ajaxMethod($method) === true) {
+ $actions->{$method}();
+ } else {
+ $actions->errorAjax('Unavailable method.');
+ }
+} else {
+ $actions->errorAjax('Method not found. ['.$method.']');
+}
+
+
+// Stop any execution.
+exit;
diff --git a/pandora_console/include/class/ExtensionsDiscovery.class.php b/pandora_console/include/class/ExtensionsDiscovery.class.php
new file mode 100644
index 0000000000..da26fb1780
--- /dev/null
+++ b/pandora_console/include/class/ExtensionsDiscovery.class.php
@@ -0,0 +1,2555 @@
+section = $_section;
+ $this->mode = $_mode;
+ $this->url = 'index.php?sec=gservers&sec2=godmode/servers/discovery&wiz='.$_section;
+ $this->loadConfig();
+ }
+
+
+ /**
+ * Load config from extension.
+ *
+ * @return void
+ */
+ private function loadConfig()
+ {
+ $row = db_get_row('tdiscovery_apps', 'short_name', $this->mode);
+ $this->id = $row['id_app'];
+ $this->name = $row['name'];
+ $this->description = $row['description'];
+ }
+
+
+ /**
+ * Return array extensions filtered by section
+ *
+ * @return array Extensions for
+ */
+ public function loadExtensions()
+ {
+ global $config;
+ // Check access.
+ check_login();
+ $extensions = [];
+ $rows = $this->getExtensionsApps();
+ foreach ($rows as $key => $extension) {
+ $logo = $this->path.'/'.$extension['short_name'].'/'.$this->icon;
+ if (file_exists($config['homedir'].$logo) === false) {
+ $logo = $this->defaultLogo;
+ }
+
+ $extensions[] = [
+ 'icon' => $logo,
+ 'label' => $extension['name'],
+ 'url' => ui_get_full_url(
+ 'index.php?sec=gservers&sec2=godmode/servers/discovery&wiz='.$this->section.'&mode='.$extension['short_name']
+ ),
+ ];
+ }
+
+ return $extensions;
+ }
+
+
+ /**
+ * Return all extensions from apps section
+ *
+ * @return array extensions.
+ */
+ public function getExtensionsApps()
+ {
+ return db_get_all_rows_filter('tdiscovery_apps', ['section' => $this->section]);
+
+ }
+
+
+ /**
+ * Load the extension information from discovery_definition.ini.
+ *
+ * @return array Information ini file.
+ */
+ public function loadIni()
+ {
+ global $config;
+ $iniFile = parse_ini_file($config['homedir'].$this->path.'/'.$this->mode.'/discovery_definition.ini', true, INI_SCANNER_TYPED);
+
+ return $iniFile;
+ }
+
+
+ /**
+ * Return next page from config_steps.
+ *
+ * @return integer Return the number of next page.
+ */
+ public function nextPage()
+ {
+ $pages = array_keys($this->iniFile['config_steps']['name']);
+ if ($this->currentPage === 0 || empty($this->currentPage) === true) {
+ return $pages[0];
+ }
+
+ foreach ($pages as $k => $page) {
+ if ($page === $this->currentPage) {
+ if (end($pages) === $this->currentPage) {
+ return $this->currentPage;
+ } else {
+ return $pages[($k + 1)];
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Draw the extension forms.
+ *
+ * @return boolean Return boolean if exist error.
+ */
+ public function run()
+ {
+ ui_require_javascript_file('extensions_discovery');
+ $_iniFile = $this->loadIni();
+ if ($_iniFile === false) {
+ include 'general/noaccess.php';
+ return false;
+ }
+
+ $this->iniFile = $_iniFile;
+ if (empty($this->iniFile['config_steps']) === false) {
+ $this->lastPage = end(array_keys($this->iniFile['config_steps']['name']));
+ } else {
+ $this->lastPage = 0;
+ }
+
+ $this->currentPage = (int) get_parameter('page', '0');
+ $this->idTask = get_parameter('id_task', '');
+ $action = get_parameter('action', '');
+ $isTheEnd = get_parameter('complete_button', '');
+
+ // Control parameters and errors.
+ $error = false;
+
+ if ($action === 'task_definition_form') {
+ $error = $this->processTaskDefinition();
+ }
+
+ if ($action === 'process_macro') {
+ $error = $this->processCustomMacro();
+ }
+
+ $task = $this->getTask();
+
+ if ($task === false && $this->currentPage > 0) {
+ $error = __('Task not defined');
+ }
+
+ // Build breadcrum.
+ $breadcrum = [
+ [
+ 'link' => 'index.php?sec=gservers&sec2=godmode/servers/discovery',
+ 'label' => 'Discovery',
+ ],
+ ];
+
+ switch ($this->section) {
+ case 'app':
+ $breadcrum[] = [
+ 'link' => $this->url,
+ 'label' => __('Application'),
+ ];
+ break;
+
+ case 'cloud':
+ $breadcrum[] = [
+ 'link' => $this->url,
+ 'label' => __('Cloud'),
+ ];
+ break;
+
+ case 'custom':
+ $breadcrum[] = [
+ 'link' => $this->url,
+ 'label' => __('Custom'),
+ ];
+ break;
+
+ default:
+ $breadcrum[] = [
+ 'link' => $this->url,
+ 'label' => __('Custom'),
+ ];
+ break;
+ }
+
+ $parameters = '';
+ if (empty($this->idTask) === false) {
+ $parameters .= '&id_task='.$this->idTask;
+ }
+
+ $breadcrum[] = [
+ 'link' => $this->url.'&mode='.$this->mode.$parameters,
+ 'label' => 'Task definition',
+ 'selected' => ((0 === (int) $this->currentPage) ? 1 : 0),
+ ];
+
+ foreach ($this->iniFile['config_steps']['name'] as $key => $step) {
+ $parameters = '&mode='.$this->mode.'&page='.$key;
+ if (empty($this->idTask) === false) {
+ $parameters .= '&id_task='.$this->idTask;
+ }
+
+ $breadcrum[] = [
+ 'link' => $this->url.$parameters,
+ 'label' => $step,
+ 'selected' => (($key === (int) $this->currentPage) ? 1 : 0),
+ ];
+ }
+
+ // Avoid to print header out of wizard.
+ $this->prepareBreadcrum($breadcrum);
+
+ // Header.
+ ui_print_page_header(
+ $this->iniFile['discovery_extension_definition']['name'],
+ '',
+ false,
+ '',
+ true,
+ '',
+ false,
+ '',
+ GENERIC_SIZE_TEXT,
+ '',
+ $this->printHeader(true)
+ );
+
+ if ($error !== false) {
+ ui_print_error_message(
+ $error
+ );
+ return;
+ } else if ($action !== '') {
+ ui_print_success_message(__('Operation realized'));
+
+ if (empty($isTheEnd) === false) {
+ header('Location:'.$config['homeurl'].'index.php?sec=discovery&sec2=godmode/servers/discovery&wiz=tasklist');
+ }
+ }
+
+ $_url = ui_get_full_url(
+ sprintf(
+ $this->url.'&mode=%s&page=%s%s',
+ $this->mode,
+ $this->nextPage(),
+ (empty($this->idTask) === false) ? '&id_task='.$this->idTask : '',
+ )
+ );
+
+ $table = new StdClass();
+ $table->id = 'form_editor';
+ $table->width = '100%';
+ $table->class = 'databox filter-table-adv max_floating_element_size';
+
+ $table->style = [];
+ $table->style[0] = 'width: 50%';
+ $table->style[1] = 'width: 50%';
+ $table->data = [];
+ if ($this->currentPage === 0) {
+ // If page is 0 then create form for task definition.
+ $table->data = $this->viewTaskDefinition();
+ } else {
+ // If page is bigger than 0 then render form .ini.
+ $table->data = $this->viewMacroForm();
+ }
+
+ echo '';
+
+ }
+
+
+ /**
+ * Draw a select with the pandora data
+ *
+ * @param string $selectData Type of select.
+ * @param string $name Name of select.
+ * @param string $defaultValue Default value.
+ * @param boolean $multiple Define if the select is multiple.
+ * @param boolean $required Define if field is required.
+ *
+ * @return string Return the html select.
+ */
+ private function drawSelectPandora($selectData, $name, $defaultValue, $multiple=false, $required=false)
+ {
+ if ($multiple === true && $selectData !== 'interval') {
+ $name .= '[]';
+ $defaultValue = json_decode($defaultValue);
+ } else {
+ $defaultValue = io_safe_input($defaultValue);
+ }
+
+ switch ($selectData) {
+ case 'agent_groups':
+ $input = html_print_select_groups(
+ false,
+ 'AR',
+ true,
+ $name,
+ $defaultValue,
+ '',
+ '',
+ '',
+ true,
+ $multiple,
+ false,
+ '',
+ false,
+ false,
+ false,
+ false,
+ 'id_grupo',
+ true,
+ false,
+ false,
+ false,
+ false,
+ $required
+ );
+ break;
+
+ case 'agents':
+ $input = html_print_select_from_sql(
+ 'SELECT nombre, alias as n FROM tagente',
+ $name,
+ $defaultValue,
+ '',
+ ($multiple === false) ? __('None selected') : '',
+ '',
+ true,
+ $multiple,
+ true,
+ false,
+ 'width: 100%;',
+ false,
+ GENERIC_SIZE_TEXT,
+ '',
+ $required
+ );
+ break;
+
+ case 'module_groups':
+ $input = html_print_select_from_sql(
+ 'SELECT id_mg, name
+ FROM tmodule_group ORDER BY name',
+ $name,
+ $defaultValue,
+ '',
+ ($multiple === false) ? __('None selected') : '',
+ '',
+ true,
+ $multiple,
+ true,
+ false,
+ 'width: 100%',
+ false,
+ GENERIC_SIZE_TEXT,
+ '',
+ $required
+ );
+ break;
+
+ case 'modules':
+ $input = html_print_select_from_sql(
+ 'select nombre, nombre as n from tagente_modulo',
+ $name,
+ $defaultValue,
+ '',
+ ($multiple === false) ? __('None selected') : '',
+ '',
+ true,
+ $multiple,
+ true,
+ false,
+ 'width: 100%',
+ false,
+ GENERIC_SIZE_TEXT,
+ '',
+ $required
+ );
+ break;
+
+ case 'module_types':
+ $input = html_print_select_from_sql(
+ 'select nombre, descripcion from ttipo_modulo',
+ $name,
+ $defaultValue,
+ '',
+ ($multiple === false) ? __('None selected') : '',
+ '',
+ true,
+ $multiple,
+ true,
+ false,
+ 'width: 100%',
+ false,
+ GENERIC_SIZE_TEXT,
+ '',
+ $required
+ );
+ break;
+
+ case 'status':
+ $module_status_arr = [];
+ // Default.
+ $module_status_arr[AGENT_MODULE_STATUS_NORMAL] = __('Normal');
+ $module_status_arr[AGENT_MODULE_STATUS_WARNING] = __('Warning');
+ $module_status_arr[AGENT_MODULE_STATUS_CRITICAL_BAD] = __('Critical');
+ $module_status_arr[AGENT_MODULE_STATUS_UNKNOWN] = __('Unknown');
+ $module_status_arr[AGENT_MODULE_STATUS_NOT_INIT] = __('Not init');
+ $input = html_print_select(
+ $module_status_arr,
+ $name,
+ $defaultValue,
+ '',
+ ($multiple === false) ? __('None selected') : '',
+ '',
+ true,
+ $multiple,
+ true,
+ '',
+ false,
+ 'width:100%',
+ false,
+ false,
+ false,
+ '',
+ false,
+ false,
+ $required,
+ false,
+ true,
+ true
+ );
+ break;
+
+ case 'alert_templates':
+ $input = html_print_select_from_sql(
+ 'select id, name from talert_templates',
+ $name,
+ $defaultValue,
+ '',
+ ($multiple === false) ? __('None selected') : '',
+ '',
+ true,
+ $multiple,
+ true,
+ false,
+ 'width: 100%',
+ false,
+ GENERIC_SIZE_TEXT,
+ '',
+ $required
+ );
+ break;
+
+ case 'alert_actions':
+ $input = html_print_select_from_sql(
+ 'select id, name from talert_actions',
+ $name,
+ $defaultValue,
+ '',
+ ($multiple === false) ? __('None selected') : '',
+ '',
+ true,
+ $multiple,
+ true,
+ false,
+ 'width: 100%',
+ false,
+ GENERIC_SIZE_TEXT,
+ '',
+ $required
+ );
+ break;
+
+ case 'interval':
+ $input = html_print_extended_select_for_time(
+ $name,
+ (string) $defaultValue,
+ '',
+ '',
+ '0',
+ false,
+ true
+ );
+ break;
+
+ case 'tags':
+ $input = html_print_select_from_sql(
+ 'select id_tag, name from ttag',
+ $name,
+ $defaultValue,
+ '',
+ ($multiple === false) ? __('None selected') : '',
+ '',
+ true,
+ $multiple,
+ true,
+ false,
+ 'width: 100%',
+ false,
+ GENERIC_SIZE_TEXT,
+ '',
+ $required
+ );
+ break;
+
+ case 'credentials.custom':
+ $input = html_print_select_from_sql(
+ 'select identifier, identifier as i from tcredential_store WHERE product = "custom"',
+ $name,
+ $defaultValue,
+ '',
+ ($multiple === false) ? __('None selected') : '',
+ '',
+ true,
+ $multiple,
+ true,
+ false,
+ 'width: 100%',
+ false,
+ GENERIC_SIZE_TEXT,
+ '',
+ $required
+ );
+ break;
+
+ case 'credentials.aws':
+ $input = html_print_select_from_sql(
+ 'select identifier, identifier as i from tcredential_store WHERE product = "AWS"',
+ $name,
+ $defaultValue,
+ '',
+ ($multiple === false) ? __('None selected') : '',
+ '',
+ true,
+ $multiple,
+ true,
+ false,
+ 'width: 100%',
+ false,
+ GENERIC_SIZE_TEXT,
+ '',
+ $required
+ );
+ break;
+
+ case 'credentials.azure':
+ $input = html_print_select_from_sql(
+ 'select identifier, identifier as i from tcredential_store WHERE product = "AZURE"',
+ $name,
+ $defaultValue,
+ '',
+ ($multiple === false) ? __('None selected') : '',
+ '',
+ true,
+ $multiple,
+ true,
+ false,
+ 'width: 100%',
+ false,
+ GENERIC_SIZE_TEXT,
+ '',
+ $required
+ );
+ break;
+
+ case 'credentials.sap':
+ $input = html_print_select_from_sql(
+ 'select identifier, identifier as i from tcredential_store WHERE product = "SAP"',
+ $name,
+ $defaultValue,
+ '',
+ ($multiple === false) ? __('None selected') : '',
+ '',
+ true,
+ $multiple,
+ true,
+ false,
+ 'width: 100%',
+ false,
+ GENERIC_SIZE_TEXT,
+ '',
+ $required
+ );
+ break;
+
+ case 'credentials.snmp':
+ $input = html_print_select_from_sql(
+ 'select identifier, identifier as i from tcredential_store WHERE product = "SNMP"',
+ $name,
+ $defaultValue,
+ '',
+ ($multiple === false) ? __('None selected') : '',
+ '',
+ true,
+ $multiple,
+ true,
+ false,
+ 'width: 100%',
+ false,
+ GENERIC_SIZE_TEXT,
+ '',
+ $required
+ );
+ break;
+
+ case 'credentials.gcp':
+ $input = html_print_select_from_sql(
+ 'select identifier, identifier as i from tcredential_store WHERE product = "GOOGLE"',
+ $name,
+ $defaultValue,
+ '',
+ ($multiple === false) ? __('None selected') : '',
+ '',
+ true,
+ $multiple,
+ true,
+ false,
+ 'width: 100%',
+ false,
+ GENERIC_SIZE_TEXT,
+ '',
+ $required
+ );
+ break;
+
+ case 'credentials.wmi':
+ $input = html_print_select_from_sql(
+ 'select identifier, identifier as i from tcredential_store WHERE product = "WMI"',
+ $name,
+ $defaultValue,
+ '',
+ ($multiple === false) ? __('None selected') : '',
+ '',
+ true,
+ $multiple,
+ true,
+ false,
+ 'width: 100%',
+ false,
+ GENERIC_SIZE_TEXT,
+ '',
+ $required
+ );
+ break;
+
+ case 'os':
+ $input = html_print_select_from_sql(
+ 'SELECT id_os, name FROM tconfig_os ORDER BY name',
+ $name,
+ $defaultValue,
+ '',
+ ($multiple === false) ? __('None selected') : '',
+ '',
+ true,
+ $multiple,
+ true,
+ false,
+ 'width: 100%',
+ false,
+ GENERIC_SIZE_TEXT,
+ '',
+ $required
+ );
+ break;
+
+ default:
+ $input = html_print_select(
+ [],
+ $name,
+ $defaultValue,
+ '',
+ '',
+ 0,
+ true
+ );
+ break;
+ }
+
+ return $input;
+ }
+
+
+ /**
+ * Draw input from parameters of .ini.
+ *
+ * @param array $parameters Configuration of input.
+ * @param boolean $implicit Indicates if all the configuration is indicated in the array.
+ *
+ * @return string Html from input.
+ */
+ public function drawInput($parameters, $implicit=false)
+ {
+ $input = '';
+ $defaultValue = $this->macrosValues[$parameters['macro']];
+ switch ($parameters['type']) {
+ case 'string':
+ $input = html_print_input_text(
+ $parameters['macro'],
+ $defaultValue,
+ '',
+ 50,
+ 255,
+ true,
+ false,
+ ($parameters['mandatory_field'] === false) ? false : true
+ );
+ break;
+
+ case 'number':
+ $config = [
+ 'type' => 'number',
+ 'name' => $parameters['macro'],
+ 'value' => $defaultValue,
+ 'return' => true,
+ ];
+ if ($parameters['mandatory_field'] !== false) {
+ $config['required'] = true;
+ }
+
+ $input = html_print_input($config);
+ break;
+
+ case 'password':
+ $isEncrypted = (bool) $this->macrosValues[$parameters['encrypt_on_true']];
+ if ($isEncrypted === true) {
+ $defaultValueEncrypted = $this->encryptPassword($defaultValue, true);
+ if (empty($defaultValueEncrypted) === false) {
+ $defaultValue = $defaultValueEncrypted;
+ }
+ }
+
+ $input = html_print_input_password(
+ $parameters['macro'],
+ $defaultValue,
+ '',
+ 50,
+ 255,
+ true,
+ false,
+ ($parameters['mandatory_field'] === false) ? false : true,
+ '',
+ 'on'
+ );
+ if (empty($parameters['encrypt_on_true']) === false) {
+ $input .= html_print_input_hidden(
+ $parameters['macro'].'_encrypt',
+ $parameters['encrypt_on_true'],
+ true
+ );
+ }
+ break;
+
+ case 'checkbox':
+ $input = html_print_checkbox_switch(
+ $parameters['macro'],
+ 1,
+ (bool) $defaultValue,
+ true
+ );
+ break;
+
+ case 'textarea':
+ $input = html_print_textarea(
+ $parameters['macro'],
+ 5,
+ 20,
+ $defaultValue,
+ ($parameters['mandatory_field'] === false) ? '' : 'required="required"',
+ true
+ );
+ break;
+
+ case 'select':
+ if (in_array($parameters['select_data'], $this->pandoraSelectData) === true) {
+ $input = $this->drawSelectPandora(
+ $parameters['select_data'],
+ $parameters['macro'],
+ $defaultValue,
+ false,
+ ($parameters['mandatory_field'] === false) ? false : true,
+ );
+ $parameters['type'] = $parameters['select_data'];
+ } else {
+ if ($implicit === false) {
+ $options = $this->iniFile[$parameters['select_data']]['option'];
+ } else {
+ $options = $parameters['select_data'];
+ }
+
+ $input = html_print_select(
+ $options,
+ $parameters['macro'],
+ $defaultValue,
+ '',
+ __('None selected'),
+ '',
+ true,
+ false,
+ true,
+ '',
+ false,
+ 'width: 100%;',
+ false,
+ false,
+ false,
+ '',
+ false,
+ false,
+ ($parameters['mandatory_field'] === false) ? false : true,
+ );
+ }
+ break;
+
+ case 'multiselect':
+ if (in_array($parameters['select_data'], $this->pandoraSelectData) === true) {
+ $input = $this->drawSelectPandora(
+ $parameters['select_data'],
+ $parameters['macro'],
+ $defaultValue,
+ true,
+ );
+ $parameters['type'] = $parameters['select_data'];
+ } else {
+ if ($implicit === false) {
+ $options = $this->iniFile[$parameters['select_data']]['option'];
+ } else {
+ $options = $parameters['select_data'];
+ }
+
+ $input = html_print_select(
+ $options,
+ $parameters['macro'].'[]',
+ json_decode($defaultValue, true),
+ '',
+ '',
+ 0,
+ true,
+ true,
+ true,
+ '',
+ false,
+ 'width: 100%',
+ false,
+ false,
+ false,
+ '',
+ false,
+ false,
+ false,
+ false,
+ true,
+ true
+ );
+ }
+ break;
+
+ case 'tree':
+ // Show bucket tree explorer.
+ ui_require_javascript_file('pandora_snmp_browser');
+ if ($implicit === false) {
+ $treeData = $this->iniFile[$parameters['tree_data']];
+ $treeInfo = $this->getTreeStructure($parameters, $treeData);
+ } else {
+ $treeData = $parameters['tree_data'];
+ $treeInfo = $this->getTreeStructureByScript($parameters, $treeData);
+ }
+
+ $input = ui_print_tree(
+ $treeInfo,
+ // Id.
+ 0,
+ // Depth.
+ 0,
+ // Last.
+ 0,
+ // Last_array.
+ [],
+ // Sufix.
+ true,
+ // Return.
+ true,
+ // Descriptive ids.
+ false
+ );
+ break;
+
+ default:
+ $input = html_print_input_text(
+ $parameters['macro'],
+ $defaultValue,
+ '',
+ 50,
+ 255,
+ true,
+ false,
+ ($parameters['mandatory_field'] === false) ? false : true
+ );
+ break;
+ }
+
+ $input .= html_print_input_hidden(
+ $parameters['macro'].'type',
+ $parameters['type'],
+ true
+ );
+ $class = '';
+ if ($parameters['show_on_true'] !== null) {
+ $class = $parameters['macro'].'_hide';
+ $input .= $this->showOnTrue($parameters['show_on_true'], $class);
+ }
+
+ $name = $parameters['name'];
+ if (empty($parameters['tip']) === false) {
+ $name .= ui_print_help_tip($parameters['tip'], true);
+ }
+
+ return html_print_label_input_block(
+ $name,
+ $input,
+ ['div_class' => $class]
+ );
+ }
+
+
+ /**
+ * Return the task app from database.
+ *
+ * @return array $task Task of database.
+ */
+ private function getTask()
+ {
+ return db_get_row_filter(
+ 'trecon_task',
+ [
+ 'id_app' => $this->id,
+ 'id_rt' => $this->idTask,
+ 'type' => 15,
+ ],
+ );
+ }
+
+
+ /**
+ * Returns the value of the macro.
+ *
+ * @param string $macro Name of macro for filter.
+ *
+ * @return mixed Value of the macro.
+ */
+ private function getValueMacro($macro)
+ {
+ return db_get_value_filter(
+ 'value',
+ 'tdiscovery_apps_tasks_macros',
+ [
+ 'id_task' => $this->idTask,
+ 'macro' => $macro,
+ ]
+ );
+ }
+
+
+ /**
+ * Return form for macro form.
+ *
+ * @return array $form Form macro.
+ */
+ private function viewMacroForm()
+ {
+ $data = [];
+
+ $macros = db_get_all_rows_filter(
+ 'tdiscovery_apps_tasks_macros',
+ ['id_task' => $this->idTask],
+ ['*']
+ );
+ if ($macros !== false) {
+ foreach ($macros as $key => $macro) {
+ $this->macrosValues[$macro['macro']] = io_safe_output($macro['value']);
+ }
+ }
+
+ // Process ini or script.
+ $customFields = $this->iniFile['config_steps']['custom_fields'][$this->currentPage];
+ $customFieldsByScript = $this->getStructureFormByScript($this->iniFile['config_steps']['script_data_fields'][$this->currentPage]);
+
+ if ($customFields === null && $customFieldsByScript === null) {
+ $data[0][0] = html_print_image(
+ 'images/no_data_toshow.png',
+ true,
+ ['class' => 'w200px']
+ );
+ $data[1][0] = html_print_input_hidden(
+ 'action',
+ 'process_macro',
+ true
+ );
+ return $data;
+ }
+
+ $columns = 2;
+ if ($this->iniFile['config_steps']['fields_columns'][$this->currentPage] !== null
+ && $this->iniFile['config_steps']['fields_columns'][$this->currentPage] === 1
+ ) {
+ $columns = 1;
+ }
+
+ $row = 0;
+ $col = 0;
+ foreach ($customFieldsByScript as $key => $value) {
+ $this->nameFields[] = $value['macro'];
+ $data[$row][$col] = $this->drawInput($value, true);
+ $col++;
+ if ($col == $columns) {
+ $row++;
+ $col = 0;
+ }
+ }
+
+ foreach ($this->iniFile[$customFields]['macro'] as $key => $id) {
+ $parameters = [
+ 'macro' => $id,
+ 'name' => $this->iniFile[$customFields]['name'][$key],
+ 'tip' => $this->iniFile[$customFields]['tip'][$key],
+ 'type' => $this->iniFile[$customFields]['type'][$key],
+ 'placeholder' => $this->iniFile[$customFields]['placeholder'][$key],
+ 'mandatory_field' => $this->iniFile[$customFields]['mandatory_field'][$key],
+ 'show_on_true' => $this->iniFile[$customFields]['show_on_true'][$key],
+ 'encrypt_on_true' => $this->iniFile[$customFields]['encrypt_on_true'][$key],
+ 'select_data' => $this->iniFile[$customFields]['select_data'][$key],
+ 'tree_data' => $this->iniFile[$customFields]['tree_data'][$key],
+ ];
+ $this->nameFields[] = $id;
+ $data[$row][$col] = $this->drawInput($parameters);
+ $col++;
+ if ($col == $columns) {
+ $row++;
+ $col = 0;
+ }
+ }
+
+ $data[($row + 1)][1] = html_print_input_hidden(
+ 'action',
+ 'process_macro',
+ true
+ );
+ $data[($row + 1)][1] .= html_print_input_hidden(
+ 'name_fields',
+ implode(',', $this->nameFields),
+ true
+ );
+
+ return $data;
+ }
+
+
+ /**
+ * Return form for task definition.
+ *
+ * @return array $form Form for task definition.
+ */
+ private function viewTaskDefinition()
+ {
+ $task = $this->getTask();
+
+ $data = [];
+ $data[0][0] = html_print_label_input_block(
+ __('Task name'),
+ html_print_input_text(
+ 'task_name',
+ $task['name'],
+ '',
+ 50,
+ 255,
+ true,
+ false,
+ true
+ )
+ );
+
+ $data[1][0] = html_print_label_input_block(
+ __('Description'),
+ html_print_textarea(
+ 'description',
+ 5,
+ 20,
+ $task['description'],
+ '',
+ true
+ )
+ );
+
+ $data[2][0] = html_print_label_input_block(
+ __('Discovery server'),
+ html_print_select_from_sql(
+ sprintf(
+ 'SELECT id_server, name
+ FROM tserver
+ WHERE server_type = %d
+ ORDER BY name',
+ SERVER_TYPE_DISCOVERY
+ ),
+ 'discovery_server',
+ $task['id_recon_server'],
+ '',
+ '',
+ '0',
+ true,
+ false,
+ true,
+ false,
+ false,
+ false,
+ GENERIC_SIZE_TEXT,
+ '',
+ false
+ )
+ );
+
+ $data[3][0] = html_print_label_input_block(
+ __('Group'),
+ html_print_select_groups(
+ false,
+ 'AR',
+ false,
+ 'group',
+ $task['id_group'],
+ '',
+ '',
+ 0,
+ true,
+ false,
+ false,
+ '',
+ false,
+ false,
+ false,
+ false,
+ 'id_grupo',
+ false,
+ false,
+ false,
+ false,
+ false,
+ true
+ )
+ );
+
+ $inputs_interval = html_print_select(
+ [
+ 'defined' => 'Defined',
+ 'manual' => 'Manual',
+ ],
+ 'mode_interval',
+ ($task['interval_sweep'] === '0') ? 'manual' : 'defined',
+ 'changeModeInterval(this)',
+ '',
+ '0',
+ true,
+ false,
+ true,
+ '',
+ false
+ ).html_print_extended_select_for_time(
+ 'interval',
+ (empty($task['interval_sweep']) === true) ? '300' : $task['interval_sweep'],
+ '',
+ '',
+ '0',
+ false,
+ true,
+ false,
+ true,
+ );
+ $js_variables = '';
+ $data[4][0] = html_print_label_input_block(
+ __('Interval'),
+ html_print_div(
+ [
+ 'style' => 'display: flex;max-width: 345px; justify-content: space-between;',
+ 'content' => $inputs_interval.$js_variables,
+ ],
+ true
+ )
+ );
+
+ $data[5][0] = html_print_label_input_block(
+ __('Timeout').ui_print_help_tip('This timeout will be applied for each task execution', true),
+ html_print_extended_select_for_time(
+ 'tiemout',
+ (empty($task['executions_timeout']) === true) ? '60' : $task['executions_timeout'],
+ '',
+ '',
+ '0',
+ false,
+ true
+ ),
+ );
+
+ $data[6][0] = html_print_input_hidden(
+ 'action',
+ 'task_definition_form',
+ true
+ );
+
+ $data[7][0] = html_print_input_hidden(
+ 'id_task',
+ $task['id_rt'],
+ true
+ );
+
+ return $data;
+ }
+
+
+ /**
+ * Sabe data from task definition form.
+ *
+ * @return string $error Error string if exist.
+ */
+ private function processTaskDefinition()
+ {
+ $taskName = get_parameter('task_name', '');
+ $description = get_parameter('description', '');
+ $discoveryServer = get_parameter('discovery_server', '');
+ $group = get_parameter('group', 0);
+ $mode_interval = get_parameter('mode_interval', 'defined');
+ $interval = get_parameter('interval', '');
+ $tiemout = get_parameter('tiemout', 60);
+ $completeTask = get_parameter('complete_button', '');
+
+ if ($mode_interval === 'manual') {
+ $interval = '0';
+ }
+
+ $error = false;
+
+ if ($taskName === ''
+ || $discoveryServer === ''
+ || $group === ''
+ || $interval === ''
+ ) {
+ $error = __('Fields empties');
+ return $error;
+ }
+
+ if ($this->idTask === '') {
+ db_process_sql_begin();
+ try {
+ $_idTask = db_process_sql_insert(
+ 'trecon_task',
+ [
+ 'id_app' => $this->id,
+ 'name' => $taskName,
+ 'description' => $description,
+ 'id_group' => $group,
+ 'interval_sweep' => $interval,
+ 'id_recon_server' => $discoveryServer,
+ 'type' => 15,
+ 'setup_complete' => (empty($completeTask) === false) ? 1 : 0,
+ 'executions_timeout' => $tiemout,
+ ]
+ );
+
+ if ($_idTask === false) {
+ $error = __('Error creating the discovery task');
+ } else {
+ $this->idTask = $_idTask;
+ $this->autoLoadConfigMacro();
+ }
+ } catch (Exception $e) {
+ $error = __('Error creating the discovery task');
+ }
+
+ if ($error === false) {
+ db_process_sql_commit();
+ } else {
+ db_process_sql_rollback();
+ }
+ } else {
+ $result = db_process_sql_update(
+ 'trecon_task',
+ [
+ 'id_app' => $this->id,
+ 'name' => $taskName,
+ 'description' => $description,
+ 'id_group' => $group,
+ 'interval_sweep' => $interval,
+ 'id_recon_server' => $discoveryServer,
+ 'type' => 15,
+ 'setup_complete' => (empty($completeTask) === false) ? 1 : 0,
+ 'executions_timeout' => $tiemout,
+ ],
+ ['id_rt' => $this->idTask]
+ );
+
+ if ($result === false) {
+ $error = __('Error updating the discovery task');
+ }
+ }
+
+ return $error;
+ }
+
+
+ /**
+ * Process the values of input from macro defined in .ini
+ *
+ * @return string $error Error string if exist.
+ */
+ private function processCustomMacro()
+ {
+ $error = false;
+
+ $keyParameters = explode(',', get_parameter('name_fields', ''));
+
+ foreach ($keyParameters as $v => $key) {
+ $type = get_parameter($key.'type', '');
+ switch ($type) {
+ case 'checkbox':
+ $value = get_parameter_switch($key, 0);
+ break;
+
+ case 'multiselect':
+ $value = io_safe_input(json_encode(get_parameter($key, '')));
+ break;
+
+ case 'password':
+ $value = get_parameter($key, '');
+ $encryptKey = get_parameter($key.'_encrypt', '');
+ if ($encryptKey !== '') {
+ $encrypt = (bool) get_parameter_switch($encryptKey, 0);
+ if ($encrypt === true) {
+ $valueEncrypt = $this->encryptPassword($value);
+ if (empty($valueEncrypt) === false) {
+ $value = $valueEncrypt;
+ }
+ }
+ }
+ break;
+
+ default:
+ $value = get_parameter($key, '');
+ break;
+ }
+
+ if (is_array($value) === true) {
+ $value = io_safe_input(json_encode($value));
+ }
+
+ $exist = db_get_row_filter(
+ 'tdiscovery_apps_tasks_macros',
+ [
+ 'id_task' => $this->idTask,
+ 'macro' => $key,
+ ]
+ );
+
+ if (in_array($type, $this->pandoraSelectData) === false) {
+ $type = 'custom';
+ }
+
+ if ($exist === false) {
+ $result = db_process_sql_insert(
+ 'tdiscovery_apps_tasks_macros',
+ [
+ 'id_task' => $this->idTask,
+ 'macro' => $key,
+ 'value' => $value,
+ 'type' => $type,
+ ]
+ );
+ if ($result === false) {
+ $error = __('Field %s not insert', $key);
+ }
+ } else {
+ $result = db_process_sql_update(
+ 'tdiscovery_apps_tasks_macros',
+ [
+ 'value' => $value,
+ 'type' => $type,
+ ],
+ [
+ 'id_task' => $this->idTask,
+ 'macro' => $key,
+ ]
+ );
+ if ($result === false) {
+ $error = __('Field %s not updated', $key);
+ }
+ }
+ }
+
+ $completeTask = get_parameter('complete_button', '');
+ if (empty($completeTask) === false) {
+ $result = db_process_sql_update(
+ 'trecon_task',
+ ['setup_complete' => 1],
+ ['id_rt' => $this->idTask]
+ );
+ if ($result === false) {
+ $error = __('Task not updated');
+ }
+ }
+
+ return $error;
+ }
+
+
+ /**
+ * Check if name of input macro is correct.
+ *
+ * @param string $name Name of input.
+ *
+ * @return boolean value true if name is correct.
+ */
+ private function isCorrectNameInput($name)
+ {
+ if (substr($name, 0, 1) === '_' && substr($name, -1) === '_') {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+
+ /**
+ * Return logic for component show on true.
+ *
+ * @param string $checkbox Name the checkbox for hide input.
+ * @param string $elementToHide Name the element to hide HIS PARENT.
+ *
+ * @return string String Name the element
+ */
+ private function showOnTrue($checkbox, $elementToHide)
+ {
+ return '';
+ }
+
+
+ /**
+ * Load the macros task in database
+ *
+ * @throws Exception Excepcion to control possible error for default value.
+ *
+ * @return void
+ */
+ private function autoLoadConfigMacro()
+ {
+ $defaultValues = $this->iniFile['discovery_extension_definition']['default_value'];
+
+ foreach ($defaultValues as $key => $value) {
+ if ($value === false) {
+ $value = 0;
+ }
+
+ $result = db_process_sql_insert(
+ 'tdiscovery_apps_tasks_macros',
+ [
+ 'id_task' => $this->idTask,
+ 'macro' => $key,
+ 'value' => (string) io_safe_input($value),
+ 'type' => 'custom',
+ ]
+ );
+ if ($result === false) {
+ throw new Exception('Error creating task');
+ }
+ }
+
+ $tempFiles = $this->iniFile['tempfile_confs']['file'];
+
+ foreach ($tempFiles as $key => $value) {
+ $result = db_process_sql_insert(
+ 'tdiscovery_apps_tasks_macros',
+ [
+ 'id_task' => $this->idTask,
+ 'macro' => $key,
+ 'value' => (string) io_safe_input($value),
+ 'type' => 'custom',
+ 'temp_conf' => 1,
+ ]
+ );
+ if ($result === false) {
+ throw new Exception('Error creating task');
+ }
+ }
+ }
+
+
+ /**
+ * Return array structure for draw tree when array is by .ini.
+ *
+ * @param array $parent Parent from the tree.
+ * @param array $firstChildren First children from parent.
+ *
+ * @return array $treeInfo Return the array with format for render treee
+ */
+ private function getTreeStructure($parent, $firstChildren)
+ {
+ $treeInfo = [];
+ foreach ($firstChildren['name'] as $key => $value) {
+ $checked = false;
+ $name = (empty($firstChildren['macro'][$key]) === false) ? $firstChildren['macro'][$key].'[]' : $parent['id'].'[]';
+ $nameField = (empty($firstChildren['macro'][$key]) === false) ? $firstChildren['macro'][$key] : $parent['id'];
+ if (in_array($nameField, $this->nameFields) === false) {
+ $this->nameFields[] = $nameField;
+ }
+
+ $checkedValues = json_decode(io_safe_output($this->macrosValues[$nameField]), true);
+ if (empty($checkedValues) === false) {
+ if (in_array($firstChildren['value'][$key], $checkedValues)) {
+ $checked = true;
+ }
+ }
+
+ $treeInfo['__LEAVES__'][$key] = [
+ 'label' => $value,
+ 'selectable' => (bool) $firstChildren['selectable'][$key],
+ 'name' => $name,
+ 'value' => $firstChildren['value'][$key],
+ 'checked' => $checked,
+ ];
+
+ if (empty($firstChildren['children'][$key]) === false) {
+ $children = $this->iniFile[$firstChildren['children'][$key]];
+ $treeInfo['__LEAVES__'][$key]['sublevel'] = $this->getTreeStructure($parent, $children);
+ }
+ }
+
+ return $treeInfo;
+ }
+
+
+ /**
+ * Return array structure for draw tree when array is by script.
+ *
+ * @param array $parent Parent from the tree.
+ * @param array $firstChildren First children from parent.
+ *
+ * @return array $treeInfo Return the array with format for render treee
+ */
+ private function getTreeStructureByScript($parent, $firstChildren)
+ {
+ $treeInfo = [];
+ foreach ($firstChildren as $key => $value) {
+ $checked = false;
+ $name = (empty($value['macro']) === false) ? $value['macro'].'[]' : $parent['macro'].'[]';
+ $nameField = (empty($value['macro']) === false) ? $value['macro'] : $parent['macro'];
+ if (in_array($nameField, $this->nameFields) === false) {
+ $this->nameFields[] = $nameField;
+ }
+
+ $checkedValues = json_decode(io_safe_output($this->macrosValues[$nameField]), true);
+ if (empty($checkedValues) === false) {
+ if (in_array($value['value'], $checkedValues, true) === true) {
+ $checked = true;
+ }
+ }
+
+ $treeInfo['__LEAVES__'][$key] = [
+ 'label' => $value['name'],
+ 'selectable' => (bool) $value['selectable'],
+ 'name' => $name,
+ 'value' => $value['value'],
+ 'checked' => $checked,
+ ];
+
+ if (empty($value['children']) === false) {
+ $children = $value['children'];
+ $treeInfo['__LEAVES__'][$key]['sublevel'] = $this->getTreeStructureByScript($parent, $children);
+ }
+ }
+
+ return $treeInfo;
+ }
+
+
+ /**
+ * Return a json with the form structure for draw.
+ *
+ * @param mixed $command String.
+ *
+ * @return array Result of command.
+ */
+ private function getStructureFormByScript($command)
+ {
+ global $config;
+ $executionFiles = $this->iniFile['discovery_extension_definition']['execution_file'];
+ foreach ($executionFiles as $key => $file) {
+ $file = $config['homedir'].$this->path.'/'.$this->mode.'/'.$file;
+ $command = str_replace($key, $file, $command);
+ }
+
+ $values = $this->replaceValues($command);
+ $command = $values['command'];
+ $toDelete = $values['delete'];
+ if (empty($command) === false) {
+ $result = $this->executeCommand($command);
+ }
+
+ if (count($toDelete) > 0) {
+ foreach ($toDelete as $key => $folder) {
+ Files::rmrf($folder);
+ }
+ }
+
+ return json_decode($result, true);
+ }
+
+
+ /**
+ * Replace values in command
+ *
+ * @param string $command String command for replace macros.
+ *
+ * @return array $values Command and files to delete.
+ */
+ private function replaceValues($command)
+ {
+ preg_match_all('/\b_[a-zA-Z0-9]*_\b/', $command, $matches);
+ $foldersToDelete = [];
+ foreach ($matches[0] as $key => $macro) {
+ $row = db_get_row_filter(
+ 'tdiscovery_apps_tasks_macros',
+ [
+ 'macro' => $macro,
+ 'id_task' => $this->idTask,
+ ]
+ );
+ if ($row !== false) {
+ if (in_array($row['type'], $this->pandoraSelectData) === true) {
+ $value = $this->getValuePandoraSelect($row['type'], $row['value']);
+ $command = str_replace($macro, $value, $command);
+ } else if ((int) $row['temp_conf'] === 1) {
+ $nameFile = $row['id_task'].'_'.$row['id_task'].'_'.uniqid();
+ $value = $this->getValueTempFile($nameFile, $row['value']);
+ $command = str_replace($macro, $value, $command);
+ $foldersToDelete[] = str_replace($nameFile, '', $value);
+ } else {
+ $command = str_replace($macro, io_safe_output($row['value']), $command);
+ }
+ }
+ }
+
+ return [
+ 'command' => $command,
+ 'delete' => $foldersToDelete,
+ ];
+ }
+
+
+ /**
+ * Create a temp file for tempfile_confs macros.
+ *
+ * @param string $nameFile Name file only.
+ * @param string $content Content to save to file.
+ *
+ * @return string $pathNameFile Name file and with path for replace.
+ */
+ private function getValueTempFile($nameFile, $content)
+ {
+ global $config;
+ $content = io_safe_output($content);
+ $content = $this->replaceValues($content)['command'];
+ $nameTempDir = $config['attachment_store'].'/temp_files/';
+ if (file_exists($nameTempDir) === false) {
+ mkdir($nameTempDir);
+ }
+
+ $tmpPath = Files::tempdirnam(
+ $nameTempDir,
+ 'temp_files_'
+ );
+ $pathNameFile = $tmpPath.'/'.$nameFile;
+ file_put_contents($pathNameFile, $content);
+
+ return $pathNameFile;
+ }
+
+
+ /**
+ * Return the correct value for pandora select
+ *
+ * @param string $type Type of input.
+ * @param string $id Value of the row macro.
+ *
+ * @return string $id New id with the values replaced
+ */
+ private function getValuePandoraSelect($type, $id)
+ {
+ $id = io_safe_output($id);
+ $idsArray = json_decode($id);
+ if (is_array($idsArray) === false) {
+ $idsArray = [$id];
+ }
+
+ foreach ($idsArray as $key => $v) {
+ $value = false;
+ switch ($type) {
+ case 'agent_groups':
+ $value = groups_get_name($v);
+ break;
+
+ case 'module_groups':
+ $value = modules_get_modulegroup_name($v);
+ break;
+
+ case 'tags':
+ $value = tags_get_name($v);
+ break;
+
+ case 'alert_templates':
+ $value = alerts_get_alert_template_name($v);
+ break;
+
+ case 'alert_actions':
+ $value = alerts_get_alert_action_name($v);
+ break;
+
+ case 'credentials.custom':
+ $credentials = CredentialStore::getKey($v);
+ $value = base64_encode(
+ json_encode(
+ [
+ 'user' => $credentials['username'],
+ 'password' => $credentials['password'],
+ ]
+ )
+ );
+ break;
+
+ case 'credentials.aws':
+ $credentials = CredentialStore::getKey($v);
+ $value = base64_encode(
+ json_encode(
+ [
+ 'access_key_id' => $credentials['username'],
+ 'secret_access_key' => $credentials['password'],
+ ]
+ )
+ );
+ break;
+
+ case 'credentials.azure':
+ $credentials = CredentialStore::getKey($v);
+ $value = base64_encode(
+ json_encode(
+ [
+ 'client_id' => $credentials['username'],
+ 'application_secret' => $credentials['password'],
+ 'tenant_domain' => $credentials['extra_1'],
+ 'subscription_id' => $credentials['extra_2'],
+ ]
+ )
+ );
+ break;
+
+ case 'credentials.gcp':
+ $credentials = CredentialStore::getKey($v);
+ $value = base64_encode($credentials['extra_1']);
+ break;
+
+ case 'credentials.sap':
+ $credentials = CredentialStore::getKey($v);
+ $value = base64_encode(
+ json_encode(
+ [
+ 'user' => $credentials['username'],
+ 'password' => $credentials['password'],
+ ]
+ )
+ );
+ break;
+
+ case 'credentials.snmp':
+ $credentials = CredentialStore::getKey($v);
+ $value = base64_encode($credentials['extra_1']);
+ break;
+
+ case 'credentials.wmi':
+ $credentials = CredentialStore::getKey($v);
+ $value = base64_encode(
+ json_encode(
+ [
+ 'user' => $credentials['username'],
+ 'password' => $credentials['password'],
+ 'namespace' => $credentials['extra_1'],
+ ]
+ )
+ );
+ break;
+
+ case 'os':
+ $value = get_os_name($v);
+ break;
+
+ default:
+ continue;
+ }
+
+ if ($value !== false) {
+ $id = str_replace($v, io_safe_output($value), $id);
+ }
+ }
+
+ return $id;
+ }
+
+
+ /**
+ * Encrypt and decode password with the user script.
+ *
+ * @param string $password Password to encrypt.
+ * @param boolean $decode True for decode password.
+ *
+ * @return string Password encrypted
+ */
+ private function encryptPassword($password, $decode=false)
+ {
+ global $config;
+ if ($decode === false) {
+ $command = $this->iniFile['discovery_extension_definition']['passencrypt_exec'];
+ $nameFile = $this->iniFile['discovery_extension_definition']['passencrypt_script'];
+ $file = $config['homedir'].$this->path.'/'.$this->mode.'/'.$nameFile;
+ $command = str_replace('_passencrypt_script_', $file, $command);
+ } else {
+ $command = $this->iniFile['discovery_extension_definition']['passdecrypt_exec'];
+ $nameFile = $this->iniFile['discovery_extension_definition']['passdecrypt_script'];
+ $file = $config['homedir'].$this->path.'/'.$this->mode.'/'.$nameFile;
+ $command = str_replace('_passdecrypt_script_', $file, $command);
+ }
+
+ $command = str_replace('_password_', $password, $command);
+
+ if (empty($command) === false) {
+ return $this->executeCommand($command);
+ } else {
+ return false;
+ }
+ }
+
+
+ /**
+ * Valid the .ini
+ *
+ * @param array $iniForValidate IniFile to validate.
+ *
+ * @return mixed Return false if is ok and string for error.
+ */
+ public static function validateIni($iniForValidate)
+ {
+ $discoveryExtension = $iniForValidate['discovery_extension_definition'];
+
+ if (!$discoveryExtension) {
+ return __('The file does not contain the block \'discovery_extension_definition\'');
+ }
+
+ if (!array_key_exists('short_name', $discoveryExtension)) {
+ return __('The \'discovery_extension_definition\' block must contain a \'short_name\' parameter');
+ }
+
+ $defaultValues = $discoveryExtension['default_value'];
+ foreach ($defaultValues as $key => $value) {
+ if (!preg_match('/^_[a-zA-Z0-9]+_$/', $key)) {
+ return __(
+ 'The \'discovery_extension_definition\' block \'default_value\' parameter has a key with invalid format. Use only letters (A-Z and a-z) and numbers (0-9) between opening and ending underscores (_): \'%s\'',
+ $key
+ );
+ }
+ }
+
+ $shortName = $discoveryExtension['short_name'];
+
+ if (!preg_match('/^[A-Za-z0-9._-]+$/', $shortName)) {
+ return __('The \'discovery_extension_definition\' block \'short_name\' parameter contains illegal characters. Use only letters (A-Z and a-z), numbers (0-9), points (.), hyphens (-) and underscores (_)');
+ }
+
+ if (!array_key_exists('section', $discoveryExtension) || !array_key_exists('name', $discoveryExtension)) {
+ return __('The \'discovery_extension_definition\' block must contain a \'section\' and a \'name\' parameters');
+ }
+
+ $section = $discoveryExtension['section'];
+ $name = $discoveryExtension['name'];
+
+ if (!in_array($section, ['app', 'cloud', 'custom'])) {
+ return __('The \'discovery_extension_definition\' block \'section\' parameter must be \'app\', \'cloud\' or \'custom\'');
+ }
+
+ if (empty($name)) {
+ return __('The \'discovery_extension_definition\' block \'name\' parameter can not be empty');
+ }
+
+ if (!array_key_exists('exec', $discoveryExtension)) {
+ return __('The \'discovery_extension_definition\' block must contain an \'exec\' parameter');
+ }
+
+ $execs = $discoveryExtension['exec'];
+
+ foreach ($execs as $exec) {
+ if (empty($exec)) {
+ return __('All the \'discovery_extension_definition\' block \'exec\' parameter definitions can not be empty');
+ }
+ }
+
+ $checkEmptyFields = [
+ 'passencrypt_script',
+ 'passencrypt_exec',
+ 'passdecrypt_script',
+ 'passdecrypt_exec',
+ ];
+
+ foreach ($checkEmptyFields as $key) {
+ if ($discoveryExtension[$key] !== null && empty($discoveryExtension[$key]) === true) {
+ return __('The \'discovery_extension_definition\' block \'%s\' parameter can not be empty', $key);
+ }
+ }
+
+ foreach ($discoveryExtension['execution_file'] as $key => $value) {
+ if (!preg_match('/^_[a-zA-Z0-9]+_$/', $key)) {
+ return __('The \'discovery_extension_definition\' block \'execution_file\' parameter has a key with invalid format. Use only letters (A-Z and a-z) and numbers (0-9) between opening and ending underscores (_): \'%s\'', $key);
+ }
+
+ if (empty($value) === true) {
+ return __('All the \'discovery_extension_definition\' block \'execution_file\' parameter definitions can not be empty: \'%s\'', $key);
+ }
+ }
+
+ if ($iniForValidate['config_steps'] !== null && empty($iniForValidate['config_steps']) === true) {
+ return __('The \'config_steps\' block must contain a \'name\' parameter that can not be empty.');
+ }
+
+ foreach ($iniForValidate['config_steps'] as $key => $value) {
+ foreach ($value as $innerKey => $inner_value) {
+ if (isset($inner_steps[$innerKey])) {
+ $inner_steps[$innerKey][$key] = $inner_value;
+ } else {
+ $inner_steps[$innerKey] = [$key => $inner_value];
+ }
+ }
+ }
+
+ $customFields = [];
+ foreach ($inner_steps as $key => $step) {
+ if (is_numeric($key) === false || $key === 0) {
+ return __('All the \'config_steps\' block parameters must use numbers greater than 0 as keys: \'%s\'.', $key);
+ }
+
+ if (empty($step['name']) === true) {
+ return __('The \'config_steps\' block must contain a \'name\' parameter for all the configuration steps: \'%s\'', $key);
+ }
+
+ if (empty($step['custom_fields']) === true
+ && empty($step['script_data_fields']) === true
+ ) {
+ return __('The \'config_steps\' block must contain a \'custom_fields\' or \'script_data_fields\' parameter that can not be empty');
+ } else if (empty($step['custom_fields']) === false) {
+ if (empty($iniForValidate[$step['custom_fields']]) === true) {
+ return __('The \'config_steps\' block \'custom_fields\' parameter has a key value reference that does not exist: \'%s\'', $step['custom_fields']);
+ } else {
+ $customFields[] = $step['custom_fields'];
+ }
+ }
+
+ $customFields[] = $step['name'];
+ }
+
+ $requiredKeys = [
+ 'macro',
+ 'name',
+ 'type',
+ ];
+
+ $validTypes = [
+ 'string',
+ 'number',
+ 'password',
+ 'textarea',
+ 'checkbox',
+ 'select',
+ 'multiselect',
+ 'tree',
+ ];
+
+ $validSelectData = [
+ 'agent_groups',
+ 'agents',
+ 'module_groups',
+ 'modules',
+ 'module_types',
+ 'status',
+ 'alert_templates',
+ 'alert_actions',
+ 'interval',
+ 'tags',
+ 'credentials.custom',
+ 'credentials.aws',
+ 'credentials.azure',
+ 'credentials.sap',
+ 'credentials.snmp',
+ 'credentials.gcp',
+ 'credentials.wmi',
+ 'os',
+ ];
+
+ $selectDataNames = [];
+ $treeDataNames = [];
+
+ foreach ($customFields as $key => $customField) {
+ $innerFields = [];
+ foreach ($iniForValidate[$customField] as $key => $value) {
+ foreach ($value as $innerKey => $innerValue) {
+ if (isset($innerFields[$innerKey])) {
+ $innerFields[$innerKey][$key] = $innerValue;
+ } else {
+ $innerFields[$innerKey] = [$key => $innerValue];
+ }
+ }
+ }
+
+ foreach ($innerFields as $key => $field) {
+ if (is_numeric($key) === false || $key === 0) {
+ return __('All the \'%s\' block parameters must use numbers greater than 0 as keys: \'%s\'.', $customField, $key);
+ }
+
+ foreach ($requiredKeys as $k => $value) {
+ if (empty($field[$value]) === true) {
+ return __('The \'%s\' block \'%s\' parameter definitions can not be empty: \'%s\'.', $customField, $value, $key);
+ }
+ }
+
+ if (!preg_match('/^_[a-zA-Z0-9]+_$/', $field['macro'])) {
+ return __('The \'%s\' block \'macro\' parameter has a definition with invalid format. Use only letters (A-Z and a-z) and numbers (0-9) between opening and ending underscores (_): \'%s\'', $customField, $field['macro']);
+ }
+
+ if (in_array($field['type'], $validTypes) === false) {
+ return __('The \'%s\' block \'type\' parameter has a definition with invalid value. Must be \'string\', \'number\', \'password\', \'textarea\', \'checkbox\', \'select\', \'multiselect\' or \'tree\': \'%s\'', $customField, $field['type']);
+ }
+
+ if ($field['type'] === 'select' || $field['type'] === 'multiselect') {
+ if (empty($field['select_data']) === true) {
+ return __('All the \'%s\' block \'select_data\' parameter definitions can not be empty: \'%s\'.', $customField, $key);
+ } else if ($iniForValidate[$field['select_data']] === null && in_array($field['select_data'], $validSelectData) === false) {
+ return __(
+ 'The \'%s\' block \'select_data\' parameter has a definition with invalid select type. Must be \'agent_groups\', \'agents\', \'module_groups\', \'modules\', \'module_types\', \'tags\', \'status\', \'alert_templates\', \'alert_actions\', \'interval\', \'credentials.custom\', \'credentials.aws\', \'credentials.azure\', \'credentials.gcp\', \'credentials.sap\', \'credentials.snmp\', \'os\' or an existint reference: \'%s\'',
+ $customField,
+ $field['select_data']
+ );
+ } else if ($iniForValidate[$field['select_data']] !== null) {
+ $selectDataNames[] = $field['select_data'];
+ }
+ }
+
+ if ($field['type'] === 'tree') {
+ if (empty($field['tree_data']) === true) {
+ return __('All the \'%s\' block \'tree_data\' parameter definitions can not be empty: \'%s\'', $field['macro'], $key);
+ } else if ($iniForValidate[$field['tree_data']] === null) {
+ return __('The \'%s\' block \'tree_data\' parameter has a key value reference that does not exist: \'%s\'', $customField, $field['tree_data']);
+ } else {
+ $treeDataNames[] = $field['tree_data'];
+ }
+ }
+
+ if (empty($field['mandatory_field']) === false) {
+ $validValues = [
+ 'true',
+ 'false',
+ '1',
+ '0',
+ 'yes',
+ 'no',
+ ];
+
+ if (in_array($field['mandatory_field'], $validValues) === false) {
+ return __(
+ 'The \'%s\' block \'mandatory_field\' parameter has a definition with invalid value. Must be \'true\' or \'false\', \'1\' or \'0\', \'yes\' or \'no\': \'%s\'',
+ $customField,
+ $field['mandatory_field']
+ );
+ }
+ }
+
+ if ($field['tip'] !== null && empty($field['tip']) === true) {
+ return __('All the \'%s\' block \'tip\' parameter definitions can not be empty: \'%s\'.', $customField, $key);
+ }
+
+ if ($field['placeholder'] !== null && empty($field['placeholder']) === true) {
+ return __('All the \'%s\' block \'placeholder\' parameter definitions can not be empty: \'%s\'.', $customField, $key);
+ }
+
+ if (empty($field['show_on_true']) === false) {
+ if (!preg_match('/^_[a-zA-Z0-9]+_$/', $field['show_on_true'])) {
+ return __(
+ 'The \'%s\' block \'show_on_true\' parameter has a definition with invalid format. Use only letters (A-Z and a-z) and numbers (0-9) between opening and ending underscores (_): \'%s\'',
+ $customField,
+ $field['show_on_true']
+ );
+ }
+ }
+
+ if (empty($field['encrypt_on_true']) === false) {
+ if (!preg_match('/^_[a-zA-Z0-9]+_$/', $field['encrypt_on_true'])) {
+ return __(
+ 'The \'%s\' block \'encrypt_on_true\' parameter has a definition with invalid format. Use only letters (A-Z and a-z) and numbers (0-9) between opening and ending underscores (_): \'%s\'',
+ $customField,
+ $field['encrypt_on_true']
+ );
+ }
+ }
+ }
+ }
+
+ foreach ($treeDataNames as $key => $name) {
+ $error = self::validateTreeRecursive($name, $iniForValidate);
+ if ($error !== false) {
+ return $error;
+ }
+ }
+
+ foreach ($selectDataNames as $key => $name) {
+ if (empty($iniForValidate[$name]['option']) === true) {
+ return __('The \'%s\' block must contain an \'option\' parameter', $name);
+ }
+
+ foreach ($iniForValidate[$name]['option'] as $key => $option) {
+ if (empty($option) === true) {
+ return __('All the \'%s\' block \'option\' parameter definitions can not be empty: \'%s\'.', $name, $key);
+ }
+ }
+ }
+
+ if ($iniForValidate['tempfile_confs'] !== null && empty($iniForValidate['tempfile_confs']['file']) === true) {
+ return __('The \'tempfile_confs\' block must contain a \'file\' parameter.');
+ }
+
+ foreach ($iniForValidate['tempfile_confs']['file'] as $key => $tempfile) {
+ if (!preg_match('/^_[a-zA-Z0-9]+_$/', $key)) {
+ return __(
+ 'The \'tempfile_confs\' block \'file\' parameter has a key with invalid format. Use only letters (A-Z and a-z) and numbers (0-9) between opening and ending underscores (_): \'%s\'',
+ $key
+ );
+ }
+
+ if (empty($tempfile) === true) {
+ return __('All the \'tempfile_confs\' block \'file\' parameter definitions can not be empty: \'%s\'.', $key);
+ }
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Validate a tree recursively
+ *
+ * @param string $dataTree Name of parent data_tree.
+ * @param array $iniFile Inifile for search children.
+ * @param array $parents Array of parents for recursive action, DO NOT SET.
+ *
+ * @return boolean True if tree is correct.
+ */
+ public static function validateTreeRecursive($dataTree, $iniFile, $parents=[])
+ {
+ $innerData = [];
+ $parents[] = $dataTree;
+ foreach ($iniFile[$dataTree] as $key => $value) {
+ foreach ($value as $innerKey => $innerValue) {
+ if (isset($innerData[$innerKey])) {
+ $innerData[$innerKey][$key] = $innerValue;
+ } else {
+ $innerData[$innerKey] = [$key => $innerValue];
+ }
+ }
+ }
+
+ if (count($innerData) === 0) {
+ return __('The \'%s\' block must contain a \'name\' parameter that can not be empty.', $dataTree);
+ }
+
+ foreach ($innerData as $key => $prop) {
+ if (is_numeric($key) === false || $key === 0) {
+ return __('All the \'%s\' block parameters must use numbers greater than 0 as keys: \'%s\'.', $dataTree, $key);
+ }
+
+ if (empty($prop['name']) === true) {
+ return __('The \'%s\' block must contain a \'name\' parameter for all the tree elements: \'%s\'.', $dataTree, $key);
+ }
+
+ if ($prop['selectable'] !== null && $prop['selectable'] === '') {
+ return __('All the \'%s\' block \'selectable\' parameter definitions can not be empty: \'%s\'.', $dataTree, $key);
+ } else {
+ $validValues = [
+ 'true',
+ 'false',
+ '1',
+ '0',
+ 'yes',
+ 'no',
+ ];
+
+ if (in_array($prop['selectable'], $validValues) === false) {
+ return __(
+ 'The \'%s\' block \'selectable\' parameter has a definition with invalid value. Must be \'true\' or \'false\', \'1\' or \'0\', \'yes\' or \'no\': \'%s\'',
+ $dataTree,
+ $prop['selectable']
+ );
+ }
+ }
+
+ if ($prop['macro'] !== null && !preg_match('/^_[a-zA-Z0-9]+_$/', $prop['macro'])) {
+ return __(
+ 'The \'%s\' block \'macro\' parameter has a definition with invalid format. Use only letters (A-Z and a-z) and numbers (0-9) between opening and ending underscores (_): \'%s\'',
+ $dataTree,
+ $prop['macro']
+ );
+ }
+
+ if ($prop['children'] !== null && empty($iniFile[$prop['children']]) === true) {
+ return __('The \'%s\' block \'children\' parameter has a key value reference that does not exist: \'%s\'', $dataTree, $prop['children']);
+ } else if (in_array($prop['children'], $parents) === true) {
+ return __('The \'%s\' block \'children\' parameter has a key value reference to a parent tree element: \'%s\'', $dataTree, $prop['children']);
+ } else if (empty($iniFile[$prop['children']]) === false) {
+ $result = self::validateTreeRecursive($prop['children'], $iniFile, $parents);
+ if ($result !== false) {
+ return $result;
+ }
+ }
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Excute command with the timeout of the task.
+ *
+ * @param string $command Command to execute.
+ *
+ * @return string Output of command
+ */
+ private function executeCommand($command)
+ {
+ $task = $this->getTask();
+ $timeout = $task['executions_timeout'];
+
+ $descriptors = [
+ 0 => [
+ 'pipe',
+ 'r',
+ ],
+ 1 => [
+ 'pipe',
+ 'w',
+ ],
+ 2 => [
+ 'pipe',
+ 'w',
+ ],
+ ];
+
+ $process = proc_open($command, $descriptors, $pipes);
+
+ if (!is_resource($process)) {
+ return false;
+ }
+
+ stream_set_blocking($pipes[1], 0);
+
+ stream_set_blocking($pipes[2], 0);
+
+ if (!$timeout) {
+ $timeout = 5;
+ }
+
+ $real_timeout = ($timeout * 1000000);
+
+ $buffer = '';
+
+ while ($real_timeout > 0) {
+ $start = microtime(true);
+
+ $read = [$pipes[1]];
+ $other = [];
+ stream_select($read, $other, $other, 0, $real_timeout);
+
+ $status = proc_get_status($process);
+
+ $buffer .= stream_get_contents($pipes[1]);
+
+ if ($status['running'] === false) {
+ break;
+ }
+
+ $real_timeout -= ((microtime(true) - $start) * 1000000);
+ }
+
+ if ($real_timeout <= 0) {
+ proc_terminate($process, 9);
+
+ fclose($pipes[0]);
+ fclose($pipes[1]);
+ fclose($pipes[2]);
+
+ proc_close($process);
+
+ return false;
+ }
+
+ $errors = stream_get_contents($pipes[2]);
+
+ if (empty($errors) === false && empty($buffer)) {
+ proc_terminate($process, 9);
+
+ fclose($pipes[0]);
+ fclose($pipes[1]);
+ fclose($pipes[2]);
+
+ proc_close($process);
+
+ return false;
+ }
+
+ proc_terminate($process, 9);
+
+ fclose($pipes[0]);
+ fclose($pipes[1]);
+ fclose($pipes[2]);
+
+ proc_close($process);
+
+ return $buffer;
+ }
+
+
+}
diff --git a/pandora_console/include/constants.php b/pandora_console/include/constants.php
index 64260179ce..e8d4eb3a4f 100644
--- a/pandora_console/include/constants.php
+++ b/pandora_console/include/constants.php
@@ -648,6 +648,7 @@ define('DISCOVERY_APP_DB2', 11);
define('DISCOVERY_APP_MICROSOFT_SQL_SERVER', 12);
define('DISCOVERY_CLOUD_GCP_COMPUTE_ENGINE', 13);
define('DISCOVERY_CLOUD_AWS_S3', 14);
+define('DISCOVERY_EXTENSION', 15);
// Force task build tmp results.
define('DISCOVERY_REVIEW', 0);
diff --git a/pandora_console/include/functions_ui.php b/pandora_console/include/functions_ui.php
index c5b83e48ae..fd907c55fc 100755
--- a/pandora_console/include/functions_ui.php
+++ b/pandora_console/include/functions_ui.php
@@ -7975,6 +7975,133 @@ function ui_print_fav_menu($id_element, $url, $label, $section)
}
+function ui_print_tree(
+ $tree,
+ $id=0,
+ $depth=0,
+ $last=0,
+ $last_array=[],
+ $sufix=false,
+ $descriptive_ids=false,
+ $previous_id=''
+) {
+ static $url = false;
+ $output = '';
+
+ // Get the base URL for images.
+ if ($url === false) {
+ $url = ui_get_full_url('operation/tree', false, false, false);
+ }
+
+ // Leaf.
+ if (empty($tree['__LEAVES__'])) {
+ return '';
+ }
+
+ $count = 0;
+ $total = (count(array_keys($tree['__LEAVES__'])) - 1);
+ $last_array[$depth] = $last;
+ $class = 'item_'.$depth;
+
+ if ($depth > 0) {
+ $output .= '';
+ } else {
+ $output .= '';
+ }
+
+ foreach ($tree['__LEAVES__'] as $level => $sub_level) {
+ // Id used to expand leafs.
+ $sub_id = time().rand(0, getrandmax());
+ // Display the branch.
+ $output .= '- ';
+
+ // Indent sub branches.
+ for ($i = 1; $i <= $depth; $i++) {
+ if ($last_array[$i] == 1) {
+ $output .= '
';
+ } else {
+ $output .= '
';
+ }
+ }
+
+ // Branch.
+ if (! empty($sub_level['sublevel']['__LEAVES__'])) {
+ $output .= "";
+ if ($depth == 0 && $count == 0) {
+ if ($count == $total) {
+ $output .= '
';
+ } else {
+ $output .= '
';
+ }
+ } else if ($count == $total) {
+ $output .= '
';
+ } else {
+ $output .= '
';
+ }
+
+ $output .= '';
+ }
+
+ // Leave.
+ else {
+ if ($depth == 0 && $count == 0) {
+ if ($count == $total) {
+ $output .= '
';
+ } else {
+ $output .= '
';
+ }
+ } else if ($count == $total) {
+ $output .= '
';
+ } else {
+ $output .= '
';
+ }
+ }
+
+ $checkbox_name_sufix = ($sufix === true) ? '_'.$level : '';
+ if ($descriptive_ids === true) {
+ $checkbox_name = 'create_'.$sub_id.$previous_id.$checkbox_name_sufix;
+ } else {
+ $checkbox_name = 'create_'.$sub_id.$checkbox_name_sufix;
+ }
+
+ $previous_id = $checkbox_name_sufix;
+ if ($sub_level['selectable'] === true) {
+ $output .= html_print_checkbox(
+ $sub_level['name'],
+ $sub_level['value'],
+ $sub_level['checked'],
+ true,
+ false,
+ '',
+ true
+ );
+ }
+
+ $output .= ' '.$sub_level['label'].'';
+
+ $output .= ' ';
+
+ // Recursively print sub levels.
+ $output .= ui_print_tree(
+ $sub_level['sublevel'],
+ $sub_id,
+ ($depth + 1),
+ (($count == $total) ? 1 : 0),
+ $last_array,
+ $sufix,
+ $descriptive_ids,
+ $previous_id
+ );
+
+ $count++;
+ }
+
+ $output .= '
';
+
+ return $output;
+}
+
+
function ui_update_name_fav_element($id_element, $section, $label)
{
$label = io_safe_output($label);
diff --git a/pandora_console/include/javascript/extensions_discovery.js b/pandora_console/include/javascript/extensions_discovery.js
new file mode 100644
index 0000000000..a2cb6c85d0
--- /dev/null
+++ b/pandora_console/include/javascript/extensions_discovery.js
@@ -0,0 +1,25 @@
+/* global $, interval */
+$(document).ready(() => {
+ if (interval === "0") {
+ setTimeout(() => {
+ $("#mode_interval")
+ .parent()
+ .find("[id^='interval']")
+ .hide();
+ }, 100);
+ }
+});
+
+function changeModeInterval(e) {
+ if ($(e).val() === "manual") {
+ $(e)
+ .parent()
+ .find("[id^='interval']")
+ .hide();
+ } else {
+ var interval = $(e)
+ .parent()
+ .find("div[id^='interval']")[0];
+ $(interval).show();
+ }
+}
diff --git a/pandora_console/include/javascript/manage_extensions.js b/pandora_console/include/javascript/manage_extensions.js
new file mode 100644
index 0000000000..e5c98c6a58
--- /dev/null
+++ b/pandora_console/include/javascript/manage_extensions.js
@@ -0,0 +1,72 @@
+/* globals $, page, url, textsToTranslate, confirmDialog*/
+$(document).ready(function() {
+ function loading(status) {
+ if (status) {
+ $(".spinner-fixed").show();
+ $("#button-upload_button").attr("disabled", "true");
+ } else {
+ $(".spinner-fixed").hide();
+ $("#button-upload_button").removeAttr("disabled");
+ }
+ }
+
+ $("#uploadExtension").submit(function(e) {
+ e.preventDefault();
+ var formData = new FormData(this);
+ formData.append("page", page);
+ formData.append("method", "validateIniName");
+ loading(true);
+ $.ajax({
+ method: "POST",
+ url: url,
+ data: formData,
+ processData: false,
+ contentType: false,
+ success: function(data) {
+ loading(false);
+ data = JSON.parse(data);
+ if (data.success) {
+ if (data.warning) {
+ confirmDialog({
+ title: textsToTranslate["Warning"],
+ message: data.message,
+ strOKButton: textsToTranslate["Confirm"],
+ strCancelButton: textsToTranslate["Cancel"],
+ onAccept: function() {
+ loading(true);
+ $("#uploadExtension")[0].submit();
+ },
+ onDeny: function() {
+ return false;
+ }
+ });
+ } else {
+ $("#uploadExtension")[0].submit();
+ }
+ } else {
+ confirmDialog({
+ title: textsToTranslate["Error"],
+ message: data.message,
+ ok: textsToTranslate["Ok"],
+ hideCancelButton: true,
+ onAccept: function() {
+ return false;
+ }
+ });
+ }
+ },
+ error: function() {
+ loading(false);
+ confirmDialog({
+ title: textsToTranslate["Error"],
+ message: textsToTranslate["Failed to upload extension"],
+ ok: textsToTranslate["Ok"],
+ hideCancelButton: true,
+ onAccept: function() {
+ return false;
+ }
+ });
+ }
+ });
+ });
+});
diff --git a/pandora_console/include/styles/discovery.css b/pandora_console/include/styles/discovery.css
index 4bb7b200c6..2e6967f19a 100644
--- a/pandora_console/include/styles/discovery.css
+++ b/pandora_console/include/styles/discovery.css
@@ -4,14 +4,13 @@
ul.bigbuttonlist {
min-height: 200px;
+ display: flex;
+ flex-wrap: wrap;
}
li.discovery {
- display: inline-block;
- float: left;
width: 250px;
margin: 15px;
- padding-bottom: 50px;
}
li.discovery > a {
@@ -37,8 +36,7 @@ div.data_container {
width: 100%;
height: 100%;
text-align: center;
- padding-top: 30px;
- padding-bottom: 30px;
+ padding: 6px;
}
div.data_container:hover {
diff --git a/pandora_console/include/styles/pandora.css b/pandora_console/include/styles/pandora.css
index 5baee4c848..018cd7a0a6 100644
--- a/pandora_console/include/styles/pandora.css
+++ b/pandora_console/include/styles/pandora.css
@@ -9018,8 +9018,7 @@ div.graph div.legend table {
}
.app_mssg {
- position: absolute;
- bottom: 1em;
+ margin: 1em;
clear: both;
color: #888;
}
diff --git a/pandora_console/include/styles/tables.css b/pandora_console/include/styles/tables.css
index bc6c504529..6974aed9e8 100644
--- a/pandora_console/include/styles/tables.css
+++ b/pandora_console/include/styles/tables.css
@@ -237,7 +237,8 @@
.table_action_buttons > img,
.table_action_buttons > button,
.table_action_buttons > form,
-.table_action_buttons > div {
+.table_action_buttons > div,
+.table_action_buttons .action_button_hidden {
visibility: hidden;
}
.info_table > tbody > tr:hover {
@@ -250,7 +251,8 @@
.info_table > tbody > tr:hover .table_action_buttons > img,
.info_table > tbody > tr:hover .table_action_buttons > button,
.info_table > tbody > tr:hover .table_action_buttons > form,
-.info_table > tbody > tr:hover .table_action_buttons > div {
+.info_table > tbody > tr:hover .table_action_buttons > div,
+.info_table > tbody > tr:hover .table_action_buttons .action_button_hidden {
visibility: visible;
}
diff --git a/pandora_console/pandoradb.sql b/pandora_console/pandoradb.sql
index 87f6283152..2d7a6c6b34 100644
--- a/pandora_console/pandoradb.sql
+++ b/pandora_console/pandoradb.sql
@@ -831,6 +831,17 @@ CREATE TABLE IF NOT EXISTS `tmodule_group` (
PRIMARY KEY (`id_mg`)
) ENGINE=InnoDB DEFAULT CHARSET=UTF8MB4;
+CREATE TABLE IF NOT EXISTS `tdiscovery_apps` (
+ `id_app` int(10) auto_increment,
+ `short_name` varchar(250) NOT NULL DEFAULT '',
+ `name` varchar(250) NOT NULL DEFAULT '',
+ `section` varchar(250) NOT NULL DEFAULT 'custom',
+ `description` varchar(250) NOT NULL DEFAULT '',
+ `version` varchar(250) NOT NULL DEFAULT '',
+ PRIMARY KEY (`id_app`),
+ UNIQUE (`short_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8MB4;
+
-- This table was moved cause the `tmodule_relationship` will add
-- a foreign key for the trecon_task(id_rt)
-- ----------------------------------------------------------------------
@@ -881,8 +892,12 @@ CREATE TABLE IF NOT EXISTS `trecon_task` (
`type` INT NOT NULL DEFAULT 0,
`subnet_csv` TINYINT UNSIGNED DEFAULT 0,
`snmp_skip_non_enabled_ifs` TINYINT UNSIGNED DEFAULT 1,
+ `id_app` int(10),
+ `setup_complete` tinyint unsigned NOT NULL DEFAULT 0,
+ `executions_timeout` int unsigned NOT NULL DEFAULT 60,
PRIMARY KEY (`id_rt`),
- KEY `recon_task_daemon` (`id_recon_server`)
+ KEY `recon_task_daemon` (`id_recon_server`),
+ FOREIGN KEY (`id_app`) REFERENCES tdiscovery_apps(`id_app`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=UTF8MB4;
-- ----------------------------------------------------------------------
@@ -4330,6 +4345,44 @@ CREATE TABLE IF NOT EXISTS `tsesion_filter_log_viewer` (
PRIMARY KEY (`id_filter`)
) ENGINE=InnoDB DEFAULT CHARSET=UTF8MB4;
+-- ---------------------------------------------------------------------
+-- Table `tdiscovery_apps_scripts`
+-- ---------------------------------------------------------------------
+
+CREATE TABLE IF NOT EXISTS `tdiscovery_apps_scripts` (
+ `id_app` int(10),
+ `macro` varchar(250) NOT NULL DEFAULT '',
+ `value` text NOT NULL DEFAULT '',
+ PRIMARY KEY (`id_app`, `macro`),
+ FOREIGN KEY (`id_app`) REFERENCES tdiscovery_apps(`id_app`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8MB4;
+
+-- ---------------------------------------------------------------------
+-- Table `tdiscovery_apps_executions`
+-- ---------------------------------------------------------------------
+
+CREATE TABLE IF NOT EXISTS `tdiscovery_apps_executions` (
+ `id` int(10) unsigned NOT NULL auto_increment,
+ `id_app` int(10),
+ `execution` text NOT NULL DEFAULT '',
+ PRIMARY KEY (`id`, `id_app`),
+ FOREIGN KEY (`id_app`) REFERENCES tdiscovery_apps(`id_app`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8MB4;
+
+-- ---------------------------------------------------------------------
+-- Table `tdiscovery_apps_tasks_macros`
+-- ---------------------------------------------------------------------
+
+CREATE TABLE IF NOT EXISTS `tdiscovery_apps_tasks_macros` (
+ `id_task` int(10) unsigned NOT NULL,
+ `macro` varchar(250) NOT NULL DEFAULT '',
+ `type` varchar(250) NOT NULL DEFAULT 'custom',
+ `value` text NOT NULL DEFAULT '',
+ `temp_conf` tinyint unsigned NOT NULL DEFAULT 0,
+ PRIMARY KEY (`id_task`, `macro`),
+ FOREIGN KEY (`id_task`) REFERENCES trecon_task(`id_rt`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=UTF8MB4;
+
-- ---------------------------------------------------------------------
-- Table `tnetwork_explorer_filter`
-- ---------------------------------------------------------------------
@@ -4352,4 +4405,4 @@ CREATE TABLE IF NOT EXISTS `tnetwork_explorer_filter` (
`action` VARCHAR(45) NULL,
`advanced_filter` TEXT NULL,
PRIMARY KEY (`id`)
- ) ENGINE=InnoDB DEFAULT CHARSET=UTF8MB4;
\ No newline at end of file
+ ) ENGINE=InnoDB DEFAULT CHARSET=UTF8MB4;
diff --git a/pandora_server/lib/PandoraFMS/DB.pm b/pandora_server/lib/PandoraFMS/DB.pm
index 7ed8d9e2f8..a5a6d98b80 100644
--- a/pandora_server/lib/PandoraFMS/DB.pm
+++ b/pandora_server/lib/PandoraFMS/DB.pm
@@ -62,6 +62,7 @@ our @EXPORT = qw(
set_update_agent
set_update_agentmodule
get_action_id
+ get_action_name
get_addr_id
get_agent_addr_id
get_agent_id
@@ -102,6 +103,8 @@ our @EXPORT = qw(
get_server_id
get_tag_id
get_tag_name
+ get_template_id
+ get_template_name
get_group_name
get_template_id
get_template_module_id
@@ -257,15 +260,28 @@ sub get_console_api_url ($$) {
}
########################################################################
-## Return action ID given the action name.
+## Return the ID of an alert action given its name.
########################################################################
sub get_action_id ($$) {
my ($dbh, $action_name) = @_;
- my $rc = get_db_value ($dbh, "SELECT id FROM talert_actions WHERE name = ?", $action_name);
+ my $rc = get_db_value ($dbh, "SELECT id FROM talert_actions
+ WHERE name = ?", safe_input($action_name));
return defined ($rc) ? $rc : -1;
}
+########################################################################
+## Return the name of an alert action given its ID.
+########################################################################
+sub get_action_name ($$) {
+ my ($dbh, $action_id) = @_;
+
+ my $rc = get_db_value ($dbh, "SELECT name FROM talert_actions
+ WHERE id = ?", safe_input($action_id));
+ return defined ($rc) ? $rc : -1;
+}
+
+
########################################################################
## Return command ID given the command name.
########################################################################
@@ -304,6 +320,29 @@ sub get_agent_ids_from_alias ($$) {
return @rc;
}
+########################################################################
+## Return the ID of an alert template given its name.
+########################################################################
+sub get_template_id ($$) {
+ my ($dbh, $template_name) = @_;
+
+ my $rc = get_db_value ($dbh, "SELECT id FROM talert_templates
+ WHERE name = ?", safe_input($template_name));
+ return defined ($rc) ? $rc : -1;
+}
+
+########################################################################
+## Return the name of an alert template given its ID.
+########################################################################
+sub get_template_name ($$) {
+ my ($dbh, $template_id) = @_;
+
+ my $rc = get_db_value ($dbh, "SELECT name FROM talert_templates
+ WHERE id = ?", safe_input($template_id));
+ return defined ($rc) ? $rc : -1;
+}
+
+
########################################################################
## Return server ID given the name of server.
########################################################################
@@ -698,24 +737,6 @@ sub get_agent_module_id ($$$) {
return defined ($rc) ? $rc : -1;
}
-##########################################################################
-## Return template id given the template name.
-##########################################################################
-sub get_template_id ($$) {
- my ($dbh, $template_name) = @_;
-
- my $field;
- if ($RDBMS eq 'oracle') {
- $field = "to_char(name)";
- }
- else {
- $field = "name";
- }
-
- my $rc = get_db_value ($dbh, "SELECT id FROM talert_templates WHERE $field = ?", safe_input($template_name));
- return defined ($rc) ? $rc : -1;
-}
-
##########################################################################
## Return the module template id given the module id and the template id.
##########################################################################
diff --git a/pandora_server/lib/PandoraFMS/DiscoveryServer.pm b/pandora_server/lib/PandoraFMS/DiscoveryServer.pm
index bce36263d3..d331c7986d 100644
--- a/pandora_server/lib/PandoraFMS/DiscoveryServer.pm
+++ b/pandora_server/lib/PandoraFMS/DiscoveryServer.pm
@@ -68,6 +68,7 @@ use constant {
DISCOVERY_REVIEW => 0,
DISCOVERY_STANDARD => 1,
DISCOVERY_RESULTS => 2,
+ DISCOVERY_APP => 15,
};
################################################################################
@@ -147,17 +148,23 @@ sub data_producer ($) {
WHERE id_recon_server = ?
AND disabled = 0
AND ((utimestamp = 0 AND interval_sweep != 0 OR status = 1)
- OR (status = -1 AND interval_sweep > 0 AND (utimestamp + interval_sweep) < UNIX_TIMESTAMP()))', $server_id);
+ OR (status < 0 AND interval_sweep > 0 AND (utimestamp + interval_sweep) < UNIX_TIMESTAMP()))', $server_id);
} else {
@rows = get_db_rows ($dbh, 'SELECT * FROM trecon_task
WHERE (id_recon_server = ? OR id_recon_server NOT IN (SELECT id_server FROM tserver WHERE status = 1 AND server_type = ?))
AND disabled = 0
AND ((utimestamp = 0 AND interval_sweep != 0 OR status = 1)
- OR (status = -1 AND interval_sweep > 0 AND (utimestamp + interval_sweep) < UNIX_TIMESTAMP()))', $server_id, DISCOVERYSERVER);
+ OR (status < 0 AND interval_sweep > 0 AND (utimestamp + interval_sweep) < UNIX_TIMESTAMP()))', $server_id, DISCOVERYSERVER);
}
foreach my $row (@rows) {
+ # Discovery apps must be fully set up.
+ if ($row->{'type'} == DISCOVERY_APP && $row->{'setup_complete'} != 1) {
+ logger($pa_config, 'Setup for recon app task ' . $row->{'id_app'} . ' not complete.', 10);
+ next;
+ }
+
# Update task status
update_recon_task ($dbh, $row->{'id_rt'}, 1);
@@ -185,7 +192,13 @@ sub data_consumer ($$) {
if (defined ($task->{'id_recon_script'}) && ($task->{'id_recon_script'} != 0)) {
exec_recon_script ($pa_config, $dbh, $task);
return;
- } else {
+ }
+ # Is it a discovery app?
+ elsif ($task->{'type'} == DISCOVERY_APP) {
+ exec_recon_app ($pa_config, $dbh, $task);
+ return;
+ }
+ else {
logger($pa_config, 'Starting recon task for net ' . $task->{'subnet'} . '.', 10);
}
@@ -407,7 +420,373 @@ sub exec_recon_script ($$$) {
}
################################################################################
-# Guess the OS using nmap.
+# Executes recon apps.
+################################################################################
+sub exec_recon_app ($$$) {
+ my ($pa_config, $dbh, $task) = @_;
+
+ # Get execution, macro and script data.
+ my @executions = get_db_rows($dbh, 'SELECT * FROM tdiscovery_apps_executions WHERE id_app = ?', $task->{'id_app'});
+ my @scripts = get_db_rows($dbh, 'SELECT * FROM tdiscovery_apps_scripts WHERE id_app = ?', $task->{'id_app'});
+
+ logger($pa_config, 'Executing recon app ID ' . $task->{'id_app'}, 10);
+
+ # Configure macros.
+ my %macros = (
+ "__taskMD5__" => md5($task->{'id_rt'}),
+ "__taskInterval__" => $task->{'interval_sweep'},
+ "__taskGroup__" => get_group_name($dbh, $task->{'id_group'}),
+ "__taskGroupID__" => $task->{'id_group'},
+ "__temp__" => $pa_config->{'temporal'},
+ "__incomingDir__" => $pa_config->{'incomingdir'},
+ "__consoleAPIURL__" => $pa_config->{'console_api_url'},
+ "__consoleAPIPass__" => $pa_config->{'console_api_pass'},
+ "__consoleUser__" => $pa_config->{'console_user'},
+ "__consolePass__" => $pa_config->{'console_pass'},
+ get_recon_app_macros($pa_config, $dbh, $task),
+ get_recon_script_macros($pa_config, $dbh, $task)
+ );
+
+ # Dump macros to disk.
+ dump_recon_app_macros($pa_config, $dbh, $task, \%macros);
+
+ # Run executions.
+ my $status = -1;
+ my @summary;
+ for (my $i = 0; $i < scalar(@executions); $i++) {
+ my $execution = $executions[$i];
+
+ # NOTE: Add the redirection before calling subst_alert_macros to prevent it from escaping quotes.
+ my $cmd = $pa_config->{'plugin_exec'} . ' ' . $task->{'executions_timeout'} . ' ' .
+ subst_alert_macros(safe_output($execution->{'execution'}) . ' 2>&1', \%macros);
+ logger($pa_config, 'Executing command for recon app ID ' . $task->{'id_app'} . ': ' . $cmd, 10);
+ my $output_json = `$cmd`;
+
+ # Something went wrong.
+ my $rc = $? >> 8;
+ if ($rc != 0) {
+ $status = -2;
+ }
+
+ # Timeout specific mesage.
+ if ($rc == 124) {
+ push(@summary, "The execution timed out.");
+ next;
+ }
+
+ # No output message.
+ if (!defined($output_json)) {
+ push(@summary, "The execution returned no output.");
+ next;
+ }
+
+ # Parse the output.
+ my $output = eval {
+ local $SIG{'__DIE__'};
+ decode_json($output_json);
+ };
+
+ # Non-JSON output.
+ if (!defined($output)) {
+ push(@summary, $output_json);
+ next;
+ }
+
+ # Process monitoring data.
+ if (defined($output->{'monitoring_data'})) {
+ my $recon = new PandoraFMS::Recon::Base(
+ dbh => $dbh,
+ group_id => $task->{'id_group'},
+ id_os => $task->{'id_os'},
+ pa_config => $pa_config,
+ snmp_enabled => 0,
+ task_id => $task->{'id_rt'},
+ task_data => $task,
+ );
+ $recon->create_agents($output->{'monitoring_data'});
+ delete($output->{'monitoring_data'});
+ }
+
+ # Store output data.
+ push(@summary, $output);
+
+ # Update the progress.
+ update_recon_task($dbh, $task->{'id_rt'}, int((100 * ($i + 1)) / scalar(@executions)));
+ }
+
+ # Parse the output.
+ my $summary_json = eval {
+ local $SIG{'__DIE__'};
+ encode_json(\@summary);
+ };
+ if (!defined($summary_json)) {
+ logger($pa_config, 'Invalid summary for recon app ID ' . $task->{'id_app'}, 10);
+ } else {
+ db_do($dbh, "UPDATE trecon_task SET summary=? WHERE id_rt=?", $summary_json, $task->{'id_rt'});
+ }
+
+ update_recon_task($dbh, $task->{'id_rt'}, $status);
+
+ return;
+}
+
+################################################################################
+# Processe app macros and return them ready to be used by subst_alert_macros.
+################################################################################
+sub get_recon_app_macros ($$$) {
+ my ($pa_config, $dbh, $task) = @_;
+ my %macros;
+
+ # Get a list of macros for the given task.
+ my @macro_array = get_db_rows($dbh, 'SELECT * FROM tdiscovery_apps_tasks_macros WHERE id_task = ?', $task->{'id_rt'});
+ foreach my $macro_item (@macro_array) {
+ my $macro_id = safe_output($macro_item->{'id_task'});
+ my $macro_name = safe_output($macro_item->{'macro'});
+ my $macro_type = $macro_item->{'type'};
+ my $macro_value = safe_output($macro_item->{'value'});
+ my $computed_value = '';
+
+ # The value can be a JSON array of values.
+ my $value_array = eval {
+ local $SIG{'__DIE__'};
+ decode_json($macro_value);
+ };
+ if (defined($value_array) && ref($value_array) eq 'ARRAY') {
+ # Multi value macro.
+ my @tmp;
+ foreach my $value_item (@{$value_array}) {
+ push(@tmp, get_recon_macro_value($pa_config, $dbh, $macro_type, $value_item));
+ }
+ $computed_value = p_encode_json($pa_config, \@tmp);
+ if (!defined($computed_value)) {
+ logger($pa_config, "Error encoding macro $macro_name for task ID " . $task->{'id_rt'}, 10);
+ next;
+ }
+ } else {
+ # Single value macro.
+ $computed_value = get_recon_macro_value($pa_config, $dbh, $macro_type, $macro_value);
+ }
+
+ # Store the computed value.
+ $macros{$macro_name} = $computed_value;
+ }
+
+ return %macros;
+}
+
+################################################################################
+# Dump macros that must be saved to disk.
+# The macros dictionary is modified in-place.
+################################################################################
+sub dump_recon_app_macros ($$$$) {
+ my ($pa_config, $dbh, $task, $macros) = @_;
+
+ # Get a list of macros that must be dumped to disk.
+ my @macro_array = get_db_rows($dbh, 'SELECT * FROM tdiscovery_apps_tasks_macros WHERE id_task = ? AND temp_conf = 1', $task->{'id_rt'});
+ foreach my $macro_item (@macro_array) {
+
+ # Make sure the macro has already been parsed.
+ my $macro_name = safe_output($macro_item->{'macro'});
+ next unless defined($macros->{$macro_name});
+ my $macro_value = $macros->{$macro_name};
+ my $macro_id = safe_output($macro_item->{'id_task'});
+
+ # Save the computed value to a temporary file.
+ my $temp_dir = $pa_config->{'incomingdir'} . '/discovery/tmp';
+ mkdir($temp_dir) if (! -d $temp_dir);
+ my $fname = $temp_dir . '/' . md5($task->{'id_rt'} . '_' . $macro_name) . '.macro';
+ eval {
+ open(my $fh, '>', $fname) or die($!);
+ print $fh subst_alert_macros($macro_value, $macros);
+ close($fh);
+ };
+ if ($@) {
+ logger($pa_config, "Error writing macro $macro_name for task ID " . $task->{'id_rt'} . " to disk: $@", 10);
+ next;
+ }
+
+ # Set the macro value to the temporary file name.
+ $macros->{$macro_name} = $fname;
+ }
+}
+
+################################################################################
+# Processe recon script macros and return them ready to be used by
+# subst_alert_macros.
+################################################################################
+sub get_recon_script_macros ($$$) {
+ my ($pa_config, $dbh, $task) = @_;
+ my %macros;
+
+ # Get a list of script macros.
+ my @macro_array = get_db_rows($dbh, 'SELECT * FROM tdiscovery_apps_scripts WHERE id_app = ?', $task->{'id_app'});
+ foreach my $macro_item (@macro_array) {
+ my $macro_name = safe_output($macro_item->{'macro'});
+ my $macro_value = safe_output($macro_item->{'value'});
+
+ # Compose the full path to the script: /discovery//