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; } } }