prepare('REPLACE INTO package (hash, name, author, homepage, email, description, class, tags, installation, version, copyright) VALUES (:hash, :name, :author, :homepage, :email, :description, :class, :tags, :installation, :version, :copyright)'); $query->bindValue(':hash', $hash, SQLITE3_TEXT); $query->bindValue(':name', $info['name'], SQLITE3_TEXT); $query->bindValue(':author', $info['author'], SQLITE3_TEXT); $query->bindValue(':homepage', $info['homepage'], SQLITE3_TEXT); $query->bindValue(':email', $info['email'], SQLITE3_TEXT); $query->bindValue(':description', $info['description'], SQLITE3_TEXT); $query->bindValue(':class', $info['class'], SQLITE3_TEXT); $query->bindValue(':tags', $info['tags'], SQLITE3_TEXT); $query->bindValue(':installation', $info['installation'], SQLITE3_TEXT); $query->bindValue(':version', $info['version'], SQLITE3_TEXT); $query->bindValue(':copyright', $info['copyright'], SQLITE3_TEXT); $result = $query->execute(); if ($result !== false) { return true; } else { return false; } } else { set_config_option('package_export_' . $hash, json_encode($info)); return true; } } function check_template_dependencies(string $export_type, int $template_id) : void { // FIX ME: This function is not used // $error_message .= ($error_message != '' ? '
':'') . __('Script or Resource File \'%s\' does not exist. Please repackage after locating and installing this file', $file['file']); } function check_get_author_info() : mixed { if (file_exists(CACTI_PATH_PKI . '/package.info')) { $info = parse_ini_file(CACTI_PATH_PKI . '/package.info', true); $info = $info['info']; return $info; } else { ?> exec($db_struct); } return $cnn; } else { raise_message('package_nowrite', __('The Web Server must have write access to the \'%s\' directory', CACTI_PATH_PKI), MESSAGE_LEVEL_ERROR); } return false; } function get_packager_metadata(string $hash) : mixed { $cnn = open_packager_metadata_table(); if (is_object($cnn)) { $query = $cnn->prepare('SELECT * FROM package WHERE hash = :hash'); $query->bindValue(':hash', $hash, SQLITE3_TEXT); $result = $query->execute(); if ($result !== false) { return $result->fetchArray(); } else { return false; } } else { raise_message('package_connection', __('Unable to initialize SQLite3'), MESSAGE_LEVEL_ERROR); } return false; } function get_package_contents(string $export_type, int $export_item_id, bool $include_deps = true) : string { global $export_errors; $types = [ 'host_template', 'graph_template', 'data_query' ]; $graph_templates = []; $queries = []; $query_graph_templates = []; switch($export_type) { case 'host_template': $graph_templates = db_fetch_assoc_prepared('SELECT gt.id, gt.name FROM host_template_graph AS htg INNER JOIN graph_templates AS gt ON gt.id = htg.graph_template_id WHERE host_template_id = ?', [$export_item_id]); $queries = db_fetch_assoc_prepared('SELECT sq.* FROM host_template_snmp_query AS htsq INNER JOIN snmp_query AS sq ON sq.id = htsq.snmp_query_id WHERE host_template_id = ?', [$export_item_id]); break; case 'data_query': $queries = db_fetch_assoc_prepared('SELECT sq.* FROM host_template_snmp_query AS htsq INNER JOIN snmp_query AS sq ON sq.id = htsq.snmp_query_id WHERE host_template_id = ?', [$export_item_id]); $query_graph_templates = db_fetch_assoc_prepared('SELECT sqg.graph_template_id, sqg.name FROM host_template_snmp_query AS htsq INNER JOIN snmp_query_graph AS sqg ON htsq.snmp_query_id = sqg.snmp_query_id WHERE host_template_id = ?', [$export_item_id]); break; case 'graph_template': $graph_templates = db_fetch_assoc_prepared('SELECT * FROM graph_templates AS gt WHERE id = ?', [$export_item_id]); if (cacti_sizeof($graph_templates)) { $in = ''; foreach ($graph_templates as $gt) { $in .= ($in != '' ? ',' : '') . $gt['id']; } $queries = db_fetch_assoc("SELECT sq.* FROM graph_templates AS gt INNER JOIN snmp_query_graph AS sqg ON gt.id = sqg.graph_template_id INNER JOIN snmp_query AS sq ON sq.id = sqg.snmp_query_id WHERE gt.id IN ($in)"); } break; } // Determine what files are included $export_errors = 0; $xml_data = get_item_xml($export_type, $export_item_id, $include_deps); $files = []; if ($export_errors == 0) { // @phpstan-ignore-line - This values is set as a global in get_item_xml $files = find_dependent_files($xml_data, true); // search xml files for scripts if (cacti_sizeof($files)) { foreach ($files as $file) { if (str_contains($file['file'], '.xml')) { $files = array_merge($files, find_dependent_files((string) file_get_contents($file['file']), true)); } } } } else { return __('Cacti Template has Errors. Unable to parse entire template.'); } /** * When exporting Graph Templates, you have to check for data queries * and process their XML files for additional scripts */ if ($export_type == 'graph_template' && cacti_sizeof($queries)) { foreach ($queries as $dq) { $xml_data = get_item_xml('data_query', $dq['id'], $include_deps); $nfiles = find_dependent_files($xml_data, true); // search xml files for scripts if (cacti_sizeof($nfiles)) { foreach ($nfiles as $file) { if (str_contains($file['file'], '.xml')) { $files = array_merge($files, find_dependent_files((string) file_get_contents($file['file']), true)); } } } } } $output = '
'; if (cacti_sizeof($graph_templates)) { $output .= '
'; $output .= '
' . __('Graph Templates') . '
'; foreach ($graph_templates as $t) { $output .= '
' . htmle($t['name']) . '
'; } $output .= '
'; } if (cacti_sizeof($queries)) { $output .= '
'; $output .= '
' . __('Data Queries') . '
'; foreach ($queries as $q) { $output .= '
' . htmle($q['name']) . '
'; } $output .= '
'; } if (cacti_sizeof($query_graph_templates)) { $output .= '
'; $output .= '
' . __('Data Query Graph Templates') . '
'; foreach ($graph_templates as $t) { $output .= '
' . htmle($t['name']) . '
'; } $output .= '
'; } if (cacti_sizeof($queries)) { $output .= '
'; $output .= '
' . __('Resource Files') . '
'; foreach ($queries as $q) { $file = str_replace('', CACTI_PATH_BASE, $q['xml_path']); $exists = file_exists($file); $output .= '
' . htmle(basename($file)) . ($exists ? '' : '') . '
'; } $output .= '
'; } if (cacti_sizeof($files)) { $output .= '
'; $output .= '
' . __('Script Files') . '
'; $found = []; foreach ($files as $file) { if (array_search($file, $found, true) === false) { if (!str_contains($file['file'], '/resource/')) { $exists = file_exists($file['file']); $output .= '
' . htmle(basename($file['file'])) . ($exists ? '' : '') . '
'; } $found[] = $file; } } $output .= '
'; } $output .= '
'; return $output; } function get_package_private_key() : mixed { if (file_exists(CACTI_PATH_PKI . '/package.key')) { return 'file://' . CACTI_PATH_PKI . '/package.key'; } else { print 'FATAL: You must run genkey.php to generate your key first' . PHP_EOL; return false; } } function get_package_public_key() : mixed { if (file_exists(CACTI_PATH_PKI . '/package.pem')) { $key = openssl_pkey_get_public('file://' . CACTI_PATH_PKI . '/package.pem'); if ($key === false) { cacti_log('FATAL: Unable to extract Public Key from Pem File.'); return false; } else { $keyData = openssl_pkey_get_details($key); return $keyData['key']; } } else { print 'FATAL: You must run genkey.php to generate your key first' . PHP_EOL; return false; } } function find_dependent_files(string $xml_data, bool $raise_message = false) : array { $files = []; $data = explode("\n", $xml_data); foreach ($data as $line) { if (str_contains($line, '')) { $line = str_replace('', '', $line); $line = str_replace('', '', $line); $files = process_paths($line, $files, $raise_message); } elseif (str_contains($line, '')) { $line = str_replace('', '', $line); $line = str_replace('', '', $line); $files = process_paths($line, $files, $raise_message); } elseif (str_contains($line, '')) { $line = str_replace('', '', $line); $line = str_replace('', '', $line); $line = base64_decode($line, true); $line = xml_character_decode((string) $line); $line = str_replace('><', '> <', $line); $line = str_replace('>""<', '>" "<', $line); $line = str_replace('>\'\'<', '>\' \'<', $line); $files = process_paths($line, $files, $raise_message); } } return $files; } function process_paths(string $line, array $files, bool $raise_message) : array { $paths = find_paths(trim($line)); if (cacti_sizeof($paths['paths'])) { $files = array_merge($files, $paths['paths']); } if (cacti_sizeof($paths['missing_paths'])) { if ($raise_message) { foreach ($paths['missing_paths'] as $p) { raise_message('missing_' . $p['file'], __esc('A Critical Template file \'%s\' is missing. Please locate this file before packaging', $p['file']), MESSAGE_LEVEL_ERROR); } } } return $files; } /** * types include * xml => location found in template xml * script => location found in xml * resource_xml => location found in resource xml file * * @param string $input * @param string $type * * @return array */ function find_paths(string $input, string $type = 'cacti_xml') : array { $excluded_paths = [ '/bin/', '/usr/bin/', '/usr/local/bin/' ]; $excluded_basenames = [ 'bash', 'snmpwalk', 'snmpget', 'snmpbulkwalk', 'csh', 'tcsh', 'ksh', 'sh', 'python', 'perl', 'php', 'grep', 'awk', 'wc' ]; $paths = []; $mpaths = []; $real_base = realpath(CACTI_PATH_BASE); if ($real_base === false) { cacti_log('WARNING: Unable to resolve CACTI_PATH_BASE in find_paths()', false, 'IMPORT'); return []; } $input = htmlspecialchars_decode($input); $parts = preg_split('/\s+/', $input); foreach (($parts ?: []) as $part) { $opath = htmlspecialchars($part); $part = str_replace('', CACTI_PATH_BASE, $part); $part = str_replace('|path_cacti|', CACTI_PATH_BASE, $part); $part = str_replace('|path_php_binary|', '', $part); if (trim($part) == '') { continue; } $valid = true; if (file_exists($part)) { $real_part = realpath($part); $base_prefix = $real_base . DIRECTORY_SEPARATOR; $path_prefix = ($real_part === false ? '' : $real_part . DIRECTORY_SEPARATOR); if ($real_part === false || strpos($path_prefix, $base_prefix) !== 0) { $mpaths[] = ['opath' => $opath, 'file' => $part]; cacti_log("WARNING: Skipping package path outside CACTI_PATH_BASE: $part", false, 'IMPORT'); continue; } foreach ($excluded_paths as $path) { if (str_contains($part, $path)) { $valid = false; break; } } if ($valid && in_array(basename($part), $excluded_basenames, true)) { $valid = false; } if ($valid) { $paths[] = ['opath' => $opath, 'file' => $real_part]; } } elseif (str_contains($part, '/') || str_contains($part, '\\')) { $mpaths[] = ['opath' => $opath, 'file' => $part]; } } return ['paths' => $paths, 'missing_paths' => $mpaths]; } function package_template(string &$template, array &$info, array &$files, string &$debug) : bool { global $export_errors, $package_file; $binary_signature = ''; $debug = ''; $private_key = get_package_private_key(); $public_key = get_package_public_key(); $my_base = CACTI_PATH_BASE . '/'; // set new timeout and memory settings ini_set('max_execution_time', '0'); ini_set('memory_limit', '-1'); ini_set('zlib.output_compression', '0'); // establish a temp directory if (CACTI_SERVER_OS == 'unix') { $tmpdir = '/tmp/'; } else { $tmpdir = getenv('TEMP'); } // write the template to disk $archive_name = basename((string) clean_up_name($info['name'])); $xmlfile = $tmpdir . '/' . $archive_name . '.xml'; file_put_contents($xmlfile, $template); // create the package xml file $xml = "\n"; $xml .= " \n"; $xml .= ' ' . $info['name'] . "\n"; if (isset($info['author'])) { $xml .= ' ' . $info['author'] . "\n"; $debug .= ' Author : ' . $info['author'] . "\n"; } if (isset($info['homepage'])) { $xml .= ' ' . $info['homepage'] . "\n"; $debug .= ' Homepage : ' . $info['homepage'] . "\n"; } if (isset($info['email'])) { $xml .= ' ' . $info['email'] . "\n"; $debug .= ' Email : ' . $info['email'] . "\n"; } if (isset($info['description'])) { $xml .= ' ' . $info['description'] . "\n"; $debug .= ' Description: ' . $info['description'] . "\n"; } if (isset($info['class'])) { $xml .= ' ' . $info['class'] . "\n"; $debug .= ' Class: ' . $info['class'] . "\n"; } if (isset($info['tags'])) { $xml .= ' ' . $info['tags'] . "\n"; $debug .= ' Tags: ' . $info['tags'] . "\n"; } if (isset($info['installation'])) { $xml .= ' ' . $info['installation'] . "\n"; $debug .= ' Instructions: ' . $info['installation'] . "\n"; } if (isset($info['version'])) { $xml .= ' ' . $info['version'] . "\n"; $debug .= ' Version : ' . $info['version'] . "\n"; } if (isset($info['copyright'])) { $xml .= ' ' . $info['copyright'] . "\n"; $debug .= ' Copyright : ' . $info['copyright'] . "\n"; } $xml .= " \n"; $debug .= "Packaging Dependent files....\n"; $debug .= ' Files Specified: ' . count($files) . "\n"; // calculate directories $directories = []; if (cacti_sizeof($files)) { foreach ($files as $file) { $directories[dirname($file['file'])] = dirname($file['file']); } } $debug .= ' Directories extracted: ' . count($directories) . "\n"; $xml .= " \n"; if (cacti_sizeof($directories)) { foreach ($directories as $dir) { $debug .= " Adding Directory: $dir\n"; $xml .= ' ' . str_replace($my_base, '', $dir) . "\n"; } } $xml .= " \n"; $files['template'] = ['file' => $xmlfile, 'type' => 'template']; $xml .= " \n"; $dupfiles = []; foreach ($files as $file) { $name = $file['file']; // Prevent doing a file twice if (isset($dupfiles[$name])) { continue; } $dupfiles[$name] = true; if (isset($file['opath'])) { $opath = $file['opath']; } else { $opath = ''; } if (isset($file['type'])) { $type = $file['type']; } else { $type = ''; } $debug .= " Adding File: $name\n"; $binary_signature = ''; $xml .= " \n"; if ($type != '') { $xml .= ' ' . basename($name) . "\n"; } else { $xml .= ' ' . str_replace($my_base, '', $name) . "\n"; } if ($opath != '') { $xml .= " $opath\n"; } if ($type != '') { $xml .= " template\n"; } if (file_exists($name)) { $data = file_get_contents($name); } else { $data = 'Not Found'; } openssl_sign((string) $data, $binary_signature, $private_key, OPENSSL_ALGO_SHA256); if ($data) { $data = base64_encode($data); } $xml .= " $data\n"; $xml .= ' ' . base64_encode($binary_signature) . "\n"; $xml .= " \n"; } $xml .= " \n"; $xml .= ' ' . $info['author'] . "\n"; $xml .= ' ' . base64_encode($public_key) . "\n"; // get rid of the temp file unlink($files['template']['file']); $debug .= "NOTE: Signing Plugin using SHA256\n"; $binary_signature = ''; openssl_sign($xml . " \n", $binary_signature, $private_key, OPENSSL_ALGO_SHA256); $ok = openssl_verify($xml . " \n", $binary_signature, $public_key, OPENSSL_ALGO_SHA256); $debug .= "NOTE: Base 64 Encoding Files and SHA256 Signing each file\n"; if ($ok == 1) { $basesig = base64_encode($binary_signature); $debug .= "NOTE: Signing Complete\n"; } elseif ($ok == 0) { $basesig = ''; $debug .= "ERROR: Could not sign\n"; return false; } else { $basesig = ''; $debug .= "ERROR: Could not sign\n"; return false; } $xml .= ' ' . $basesig . "\n"; $debug .= 'NOTE: Creating compressed template xml "' . $archive_name . ".xml.gz\"\n"; $f = fopen("compress.zlib://$tmpdir/" . $archive_name . '.xml.gz','wb'); if (!is_resource($f)) { $debug .= "ERROR: Could not open package file for writing\n"; return false; } fwrite($f, $xml, strlen($xml)); fclose($f); $package_file = $tmpdir . '/' . $archive_name . '.xml.gz'; return true; } function get_item_name(string $export_type, int $export_id) : string { $name = 'Unknown'; $name = match ($export_type) { 'host_template' => db_fetch_cell_prepared('SELECT name FROM host_template WHERE id = ?', [$export_id]), 'graph_template' => db_fetch_cell_prepared('SELECT name FROM graph_templates WHERE id = ?', [$export_id]), 'data_query' => db_fetch_cell_prepared('SELECT name FROM snmp_query WHERE id = ?', [$export_id]), default => $name, }; return $name; }