From 6c6a4938ae3b4ae78850f624cf98b46289d0fba3 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Fri, 29 May 2026 12:51:11 -0700 Subject: [PATCH 1/5] feat(support): add technical support information page Adds support.php, an authenticated diagnostics page (rrdtool/snmp versions, recent database queries, running background processes), plus a filter_sanitize() wrapper on the table filter for the page's sanitize-only path. Signed-off-by: Thomas Vincent --- lib/html_filter.php | 6 + support.php | 1750 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1756 insertions(+) create mode 100644 support.php diff --git a/lib/html_filter.php b/lib/html_filter.php index 6082a7b0e0..24c72b5ad6 100644 --- a/lib/html_filter.php +++ b/lib/html_filter.php @@ -146,6 +146,12 @@ public function filter_render() { return true; } + public function filter_sanitize() { + $this->sanitize_filter_variables(); + + return true; + } + private function create_filter() { if (!cacti_sizeof($this->filter_array)) { $this->filter_array = $this->default_filter; diff --git a/support.php b/support.php new file mode 100644 index 0000000000..ed5b43c559 --- /dev/null +++ b/support.php @@ -0,0 +1,1750 @@ + session_id(), 'time' => time()])); + } else { + raise_message('lockout', __('Cacti maintenance lockout has been cleared by \'%s\'. Press the button again after Cacti maintenance is over.', get_username($admin)), MESSAGE_LEVEL_INFO); + cacti_log('WARNING: Cacti maintenance lockout has been cleared by the primary administrator!'); + set_config_option('cacti_lockout_status', ''); + } + } + + header('Location: support.php?tab=summary'); + + exit; +} + +function support_view_tech() : void { + global $database_hostname, $poller_options, $input_types, $local_db_cnn_id; + + // ================= input validation ================= + gfrv('tab', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^([a-z_A-Z]+)$/']]); + // ==================================================== + + // present a tabbed interface + $tabs = [ + 'summary' => [ + 'display' => __('Summary'), + 'header' => true + ], + 'database' => [ + 'display' => __('Database Tables'), + 'header' => true + ], + 'dbsettings' => [ + 'display' => __('Database Settings'), + 'header' => true + ], + 'dbstatus' => [ + 'display' => __('Database Status'), + 'header' => true + ], + 'dbperms' => [ + 'display' => __('Database Permissions'), + 'header' => true + ], + 'processes' => [ + 'display' => __('Database Queries'), + 'header' => false + ], + 'background' => [ + 'display' => __('Background Processes'), + 'header' => false + ], + 'poller' => [ + 'display' => __('Poller Stats'), + 'header' => true + ], + 'phpinfo' => [ + 'display' => __('PHP Info'), + 'header' => true + ], + 'changelog' => [ + 'display' => __('ChangeLog'), + 'header' => true + ], + ]; + + // set the default tab + load_current_session_value('tab', 'sess_ts_tabs', 'summary'); + $current_tab = gnrv('tab'); + + // the processes and background will set their own timeouts + $page = 'support.php?tab=' . $current_tab; + + if ($current_tab != 'processes' && $current_tab != 'background') { + $refresh = [ + 'seconds' => 999999, + 'page' => $page, + 'logout' => 'false' + ]; + + set_page_refresh($refresh); + } + + $header_label = __esc('Technical Support [ %s ]', $tabs[$current_tab]['display']); + + top_header(); + + if (cacti_sizeof($tabs)) { + // draw the tabs + print "
'; + } + + // Display tech information + if (!isset($tabs[$current_tab]['header']) || $tabs[$current_tab]['header'] === true) { + html_start_box($header_label, '100%', false, 3, 'center', ''); + } + + switch (grv('tab')) { + case 'summary': + show_tech_summary(); + + break; + case 'dbstatus': + show_database_status(); + + break; + case 'dbperms': + show_database_permissions(); + + break; + case 'dbsettings': + show_database_settings(); + + break; + case 'changelog': + show_cacti_changelog(); + + break; + case 'database': + show_database_tables(); + + break; + case 'poller': + show_cacti_poller(); + + break; + case 'phpinfo': + show_php_modules(); + + break; + case 'processes': + show_database_processes(); + + break; + case 'background': + show_cacti_processes(); + + break; + } + + if (!isset($tabs[$current_tab]['header']) || $tabs[$current_tab]['header'] === true) { + html_end_box(); + } + + ?> + + __('Any')]; + $none = ['0' => __('None')]; + + $refresh = [ + 1 => __esc('%d Seconds', 1), + 3 => __esc('%d Seconds', 3), + 5 => __esc('%d Seconds', 5), + 10 => __esc('%d Seconds', 10), + 15 => __esc('%d Seconds', 15), + 20 => __esc('%d Seconds', 20) + ]; + + $pollers = [ + '0' => __('No'), + '1' => __('Yes') + ]; + + $chars = [ + 150 => __esc('%d Chars', 150), + 180 => __esc('%d Chars', 180), + 300 => __esc('%d Chars', 300), + 500 => __esc('%d Chars', 500), + 1000 => __esc('%d Chars', 1000), + ]; + + return [ + 'rows' => [ + [ + 'filter' => [ + 'method' => 'textbox', + 'friendly_name' => __('Search'), + 'filter' => FILTER_DEFAULT, + 'placeholder' => __('Enter a search term'), + 'size' => '30', + 'default' => '', + 'pageset' => true, + 'max_length' => '120', + 'value' => '' + ], + 'refresh' => [ + 'method' => 'drop_array', + 'friendly_name' => __('Refresh'), + 'filter' => FILTER_VALIDATE_INT, + 'default' => '5', + 'pageset' => true, + 'array' => $refresh, + 'value' => '5' + ], + 'poller' => [ + 'method' => 'drop_array', + 'friendly_name' => __('Include Poller'), + 'filter' => FILTER_VALIDATE_INT, + 'default' => '0', + 'pageset' => true, + 'array' => $pollers, + 'value' => '0' + ], + 'length' => [ + 'method' => 'drop_array', + 'friendly_name' => __('Template'), + 'filter' => FILTER_VALIDATE_INT, + 'default' => '180', + 'pageset' => true, + 'array' => $chars, + 'value' => '180' + ], + 'rows' => [ + 'method' => 'drop_array', + 'friendly_name' => __('Queries'), + 'filter' => FILTER_VALIDATE_INT, + 'default' => '-1', + 'pageset' => true, + 'array' => $item_rows, + 'value' => '-1' + ] + ] + ], + 'buttons' => [ + 'go' => [ + 'method' => 'submit', + 'display' => __('Go'), + 'title' => __('Apply Filter to Table'), + ], + 'clear' => [ + 'method' => 'button', + 'display' => __('Clear'), + 'title' => __('Reset Filter to Default Values'), + ] + ], + 'sort' => [ + 'sort_column' => 'runtime', + 'sort_direction' => 'DESC' + ], + 'javascript' => [ + 'ready' => "$('#refresh').click(function() { clearTimeout(myRefresh); });" + ] + ]; +} + +function draw_database_process_filter(bool $render = false) : void { + $filters = create_database_process_filter(); + + $header = __('Technical Support [ Database Queries ]'); + + // create the page filter + $pageFilter = new CactiTableFilter($header, 'support.php?tab=processes', 'form_db_stats', 'sess_ts_proc'); + + $pageFilter->set_filter_array($filters); + + if ($render) { + $pageFilter->filter_render(); + } else { + $pageFilter->filter_sanitize(); + } +} + +function show_database_processes() : void { + global $item_rows; + + draw_database_process_filter(true); + + if (grv('rows') == '-1') { + $rows = read_config_option('num_rows_table'); + } else { + $rows = grv('rows'); + } + + $sql_where = 'WHERE info NOT LIKE "%FROM processlist%" AND info != "NULL"'; + $sql_params = []; + + // form the 'where' clause for our main sql query + if (grv('filter') != '') { + $sql_where .= ($sql_where != '' ? ' AND ' : 'WHERE ') . + '(command LIKE ? OR info LIKE ?)'; + + $sql_params[] = '%' . grv('filter') . '%'; + $sql_params[] = '%' . grv('filter') . '%'; + } + + if (grv('poller') == '0') { + $sql_where .= ($sql_where != '' ? ' AND ' : 'WHERE ') . 'info NOT LIKE "%poller_output%" AND ' . + 'info NOT LIKE "%poller_item%" AND info NOT LIKE "%SQL_NO_CACHE%"'; + } + + $total_rows = db_fetch_cell_prepared("SELECT COUNT(*) + FROM information_schema.processlist + $sql_where", + $sql_params); + + $sql_order = get_order_string(); + $sql_limit = ' LIMIT ' . ($rows * (grv('page') - 1)) . ',' . $rows; + $info_len = grv('length'); + + $version = db_get_global_variable('innodb_version'); + + if (db_column_exists('information_schema`.`processlist', 'query_id')) { + $query_id = 'query_id'; + $time_ms = 'ROUND(time_ms/1000,2) AS runtime'; + } else { + $query_id = "'N/A' AS query_id"; + $time_ms = '`time` AS runtime'; + } + + $processes = db_fetch_assoc_prepared("SELECT id, $query_id, user, state, $time_ms, LENGTH(info) AS query_len, + SUBSTRING(REPLACE(REPLACE(REPLACE(info, '\n', ' '), ',', ', '), '\t', ' '), 1, $info_len) AS info + FROM information_schema.processlist + $sql_where + $sql_order + $sql_limit", + $sql_params); + + $display_text = [ + 'id' => [ + 'display' => __('Process ID'), + 'align' => 'left', + 'sort' => 'ASC', + 'tip' => __('The Connection ID of the running process.') + ], + 'query_id' => [ + 'display' => __('Query ID'), + 'align' => 'left', + 'sort' => 'ASC', + 'tip' => __('The Query ID of the currently running Query.') + ], + 'user' => [ + 'display' => __('User'), + 'align' => 'left', + 'sort' => 'ASC', + 'tip' => __('The MariaDB/MySQL user currently running the Query.') + ], + 'runtime' => [ + 'display' => __('Run Time'), + 'align' => 'right', + 'tip' => __('The Runtime of the current query in seconds.') + ], + 'query_len' => [ + 'display' => __('Query Length'), + 'align' => 'right', + 'tip' => __('The total string length of the Query.') + ], + 'state' => [ + 'display' => __('Query State'), + 'align' => 'center', + 'sort' => 'ASC', + 'tip' => __('The MariaDB/MySQL process state.') + ], + 'info' => [ + 'display' => __('Query Details'), + 'align' => 'left', + 'tip' => __('The Query Details for the current query upto a maximum string length.') + ] + ]; + + $nav = html_nav_bar('support.php?tab=processes', MAX_DISPLAY_PAGES, grv('page'), $rows, $total_rows, 7, __('Queries'), 'page', 'main'); + + print $nav; + + html_start_box('', '100%', false, 3, 'center', ''); + + html_header_sort($display_text, grv('sort_column'), grv('sort_direction'), 1, 'support.php?tab=processes', 'main'); + + if (cacti_sizeof($processes)) { + foreach ($processes as $p) { + form_alternate_row('line' . $p['id'], false); + + form_selectable_cell($p['id'], $p['id']); + form_selectable_cell($p['query_id'], $p['id']); + form_selectable_ecell($p['user'], $p['id']); + form_selectable_cell(number_format_i18n($p['runtime'], 2), $p['id'], '', 'right'); + form_selectable_cell(number_format_i18n($p['query_len']), $p['id'], '', 'right'); + form_selectable_ecell($p['state'], $p['id'], '', 'center'); + form_selectable_ecell($p['info'], $p['id'], '', 'white-space:pre-wrap'); + + form_end_row(); + } + } else { + print "" . __('No Database Queries Found') . ''; + } + + html_end_box(false); + + if (cacti_sizeof($processes)) { + print $nav; + } +} + +function create_cacti_process_filter(array $tables) : array { + global $item_rows; + + $all = ['all' => __('All')]; + + $refresh = [ + 1 => __esc('%d Seconds', 1), + 3 => __esc('%d Seconds', 3), + 5 => __esc('%d Seconds', 5), + 10 => __esc('%d Seconds', 10), + 15 => __esc('%d Seconds', 15), + 20 => __esc('%d Seconds', 20) + ]; + + $tables = $all + $tables; + + return [ + 'rows' => [ + [ + 'filter' => [ + 'method' => 'textbox', + 'friendly_name' => __('Search'), + 'filter' => FILTER_DEFAULT, + 'placeholder' => __('Enter a search term'), + 'size' => '30', + 'default' => '', + 'pageset' => true, + 'max_length' => '120', + 'value' => '' + ], + 'tasks' => [ + 'method' => 'drop_array', + 'friendly_name' => __('Task Type'), + 'filter' => FILTER_CALLBACK, + 'filter_options' => ['options' => 'sanitize_search_string'], + 'default' => 'all', + 'pageset' => true, + 'array' => $tables, + 'value' => 'all' + ], + 'refresh' => [ + 'method' => 'drop_array', + 'friendly_name' => __('Refresh'), + 'filter' => FILTER_VALIDATE_INT, + 'default' => '5', + 'pageset' => true, + 'array' => $refresh, + 'value' => '5' + ], + 'rows' => [ + 'method' => 'drop_array', + 'friendly_name' => __('Processes'), + 'filter' => FILTER_VALIDATE_INT, + 'default' => '-1', + 'pageset' => true, + 'array' => $item_rows, + 'value' => '-1' + ] + ] + ], + 'buttons' => [ + 'go' => [ + 'method' => 'submit', + 'display' => __('Go'), + 'title' => __('Apply Filter to Table'), + ], + 'clear' => [ + 'method' => 'button', + 'display' => __('Clear'), + 'title' => __('Reset Filter to Default Values'), + ] + ], + 'sort' => [ + 'sort_column' => 'runtime', + 'sort_direction' => 'DESC' + ], + 'javascript' => [ + 'ready' => "$('#refresh').click(function() { clearTimeout(myRefresh); });" + ] + ]; +} + +function draw_cacti_process_filter(bool $render = false, array $tables = []) : void { + $filters = create_cacti_process_filter($tables); + + $header = __('Technical Support [ Background Processes ]'); + + // create the page filter + $pageFilter = new CactiTableFilter($header, 'support.php?tab=background', 'form_cacti_procs', 'sess_ts_bg'); + + $pageFilter->set_filter_array($filters); + + if ($render) { + $pageFilter->filter_render(); + } else { + $pageFilter->filter_sanitize(); + } +} + +function show_cacti_processes() : void { + global $item_rows; + + // the full set of process tables known to Cacti + $tables = [ + 'poller_time' => __('Cacti Poller'), // Core Cacti poller table + 'processes' => __('Cacti Process'), // Cacti process table + 'grid_processes' => __('RTM Process'), // RTM process table + 'automation_processes' => __('Automation Process'), // Automation process table + 'plugin_hmib_processes' => __('HMIB Process'), // HMIB process table + 'plugin_microtik_processes' => __('MikroTik Process'), // Mikrotik process table + 'plugin_webseer_processes' => __('WebSeer Process'), // WebSeer process table + 'plugin_servcheck_processes' => __('Service Check Process'), // Service Check process table + 'mac_track_processes' => __('MacTrack Process'), // WebSeer process table + ]; + + // reduce the set of tables based if they exist + foreach ($tables as $table => $name) { + if (!db_table_exists($table)) { + unset($tables[$table]); + } + } + + draw_cacti_process_filter(true, $tables); + + if (grv('rows') == '-1') { + $rows = read_config_option('num_rows_table'); + } else { + $rows = grv('rows'); + } + + $poller_interval = read_config_option('poller_interval'); + + $sql_where = ''; + $sql_params = []; + + // form the 'where' clause for our main sql query + if (grv('filter') != '') { + $sql_where = ($sql_where != '' ? ' AND ' : 'WHERE ') . + '(taskname LIKE ? OR tasktype LIKE ?)'; + + $sql_params[] = '%' . grv('filter') . '%'; + $sql_params[] = '%' . grv('filter') . '%'; + } + + if (grv('tasks') != 'all') { + $tables = [ + grv('tasks') => $tables[grv('tasks')] + ]; + } + + $total_rows_sql = 'SELECT COUNT(*) FROM ('; + $sql_inner = ''; + + foreach ($tables as $table => $name) { + switch($table) { + case 'poller_time': + $sql_inner .= ($sql_inner != '' ? ' UNION ' : '') . + "SELECT pid, '" . __('Cacti Poller') . "' AS tasktype, + CONCAT('PollerID:', poller_id) AS taskname, + id AS taskid, '$poller_interval' AS timeout, + start_time AS started, + start_time AS last_update, + UNIX_TIMESTAMP() - UNIX_TIMESTAMP(start_time) AS runtime + FROM poller_time WHERE end_time = '0000-00-00'"; + + break; + case 'processes': + $sql_inner .= ($sql_inner != '' ? ' UNION ' : '') . + "SELECT pid, CONCAT('$name (', tasktype, ')') AS tasktype, + taskname, taskid, timeout, + started, last_update, + UNIX_TIMESTAMP() - UNIX_TIMESTAMP(started) AS runtime + FROM processes"; + + break; + case 'grid_processes': + $sql_inner .= ($sql_inner != '' ? ' UNION ' : '') . + "SELECT pid, '$name' AS tasktype, + taskname, taskid, 'N/A' AS timeout, + '-' AS started, heartbeat AS last_update, + UNIX_TIMESTAMP() - UNIX_TIMESTAMP(last_updated) AS runtime + FROM grid_processes"; + + break; + case 'automation_processes': + $sql_inner .= ($sql_inner != '' ? ' UNION ' : '') . + "SELECT pid, '$name' AS tasktype, + CONCAT('" . __('Poller:') . "', an.poller_id) AS taskname, + network_id AS taskid, 'N/A' AS timeout, an.last_started AS started, ap.heartbeat AS last_update, + UNIX_TIMESTAMP() - UNIX_TIMESTAMP(an.last_started) AS runtime + FROM automation_processes AS ap INNER JOIN automation_networks AS an ON an.id = ap.network_id"; + + break; + case 'mac_track_processes': + $sql_inner .= ($sql_inner != '' ? ' UNION ' : '') . + "SELECT process_id AS pid, '$name' AS tasktype, + CONCAT('" . __('Device:') . "', device_id) AS taskname, device_id AS taskid, 'N/A' AS timeout, + start_date AS started, 'N/A' AS last_updated, + UNIX_TIMESTAMP() - UNIX_TIMESTAMP(start_date) AS runtime + FROM mac_track_processes"; + + break; + case 'plugin_hmib_processes': + $sql_inner .= ($sql_inner != '' ? ' UNION ' : '') . + "SELECT pid, '$name' AS tasktype, + '" . __('Collector') . "' AS taskname, taskid, 'N/A' AS timeout, + started, 'N/A' AS last_update, + UNIX_TIMESTAMP() - UNIX_TIMESTAMP(started) AS runtime + FROM plugin_hmib_processes"; + + break; + case 'plugin_microtik_processes': + $sql_inner .= ($sql_inner != '' ? ' UNION ' : '') . + "SELECT pid, '$name' AS tasktype, + '" . __('Collector') . "' AS taskname, taskid, 'N/A' AS timeout, + started, 'N/A' AS last_update, + UNIX_TIMESTAMP() - UNIX_TIMESTAMP(started) AS runtime + FROM plugin_mikrotik_processes"; + + break; + case 'plugin_webseer_processes': + $sql_inner .= ($sql_inner != '' ? ' UNION ' : '') . + "SELECT pid, '$name' AS tasktype, + CONCAT('" . __('Poller:') . "', poller_id) AS taskname, url_id AS taskid, 'N/A' AS timeout, + time AS started, 'N/A' AS last_update, + UNIX_TIMESTAMP() - UNIX_TIMESTAMP(time) AS runtime + FROM plugin_webseer_processes"; + + break; + case 'plugin_servcheck_processes': + $sql_inner .= ($sql_inner != '' ? ' UNION ' : '') . + "SELECT pid, '$name' AS tasktype, + CONCAT('" . __('Poller:') . "', poller_id) AS taskname, test_id AS taskid, 'N/A' AS timeout, + time AS started, 'N/A' AS last_update, + UNIX_TIMESTAMP() - UNIX_TIMESTAMP(time) AS runtime + FROM plugin_servcheck_processes"; + + break; + } + } + + $total_rows = db_fetch_cell_prepared("SELECT COUNT(*) + FROM ($sql_inner) AS rs + $sql_where", + $sql_params); + + $sql_order = get_order_string(); + $sql_limit = ' LIMIT ' . ($rows * (grv('page') - 1)) . ',' . $rows; + + $processes = db_fetch_assoc_prepared("SELECT * + FROM ($sql_inner) AS rs + $sql_where + $sql_order + $sql_limit", + $sql_params); + + $display_text = [ + 'tasktype' => [ + 'display' => __('Task Type'), + 'align' => 'left', + 'sort' => 'ASC', + 'tip' => __('The Type of Task. Generally represents the plugin and task within the plugin.') + ], + 'taskname' => [ + 'display' => __('Task Name'), + 'align' => 'left', + 'sort' => 'ASC', + 'tip' => __('The name of the Task.') + ], + 'taskid' => [ + 'display' => __('Task ID'), + 'align' => 'right', + 'sort' => 'DESC', + 'tip' => __('The ID of the Task which is often times the LSF clusterid or the License Service ID, but can be other metrics as well.') + ], + 'runtime' => [ + 'display' => __('Run/Update Time'), + 'align' => 'right', + 'tip' => __('The Process runtime or times since last heartbeat.') + ], + 'pid' => [ + 'display' => __('Process ID'), + 'align' => 'right', + 'tip' => __('The Process ID for the task.') + ], + 'timeout' => [ + 'display' => __('Timeout'), + 'align' => 'right', + 'sort' => 'DESC', + 'tip' => __('The background process timeout in seconds when a Cacti process. Otherwise controlled by the individual plugins.') + ], + 'started' => [ + 'display' => __('Start Time'), + 'align' => 'right', + 'sort' => 'DESC', + 'tip' => __('Time the background process started for a Cacti process. The start time may or may not be supported for various plugins.') + ], + 'last_update' => [ + 'display' => __('Last Updated'), + 'align' => 'right', + 'sort' => 'DESC', + 'tip' => __('Time the process last registered its status to the status tables.') + ] + ]; + + $nav = html_nav_bar('support.php?tab=background', MAX_DISPLAY_PAGES, grv('page'), $rows, $total_rows, 8, __('Processes'), 'page', 'main'); + + print $nav; + + html_start_box('', '100%', false, 3, 'center', ''); + + html_header_sort($display_text, grv('sort_column'), grv('sort_direction'), 1, 'support.php?tab=background', 'main'); + + if (cacti_sizeof($processes)) { + foreach ($processes as $p) { + form_alternate_row('line' . $p['pid'], false); + + if ($p['timeout'] != 'N/A') { + $timeout_time = $p['timeout']; + $timeout_date = get_daysfromtime($timeout_time, true); + + if (strpos($timeout_date, 'y:') !== false) { + $timeout_date = __('> 1 Year'); + } + } else { + $timeout_date = $p['timeout']; + } + + form_selectable_cell($p['tasktype'], $p['pid']); + form_selectable_cell(filter_value(cacti_strtoupper($p['taskname']), ''), $p['pid']); + form_selectable_cell($p['taskid'], $p['pid'], '', 'right'); + form_selectable_cell($p['runtime'], $p['pid'], '', 'right'); + form_selectable_cell($p['pid'], $p['pid'], '', 'right'); + + // form_selectable_cell($p['timeout'], $p['pid'], '', 'right'); + + form_selectable_cell($timeout_date, $p['pid'], '', 'right'); + form_selectable_cell($p['started'], $p['pid'], '', 'right'); + form_selectable_cell($p['last_update'], $p['pid'], '', 'right'); + + form_end_row(); + } + } else { + print "" . __('No Cacti or Plugin Background Processes Found') . ''; + } + + html_end_box(false); + + if (cacti_sizeof($processes)) { + print $nav; + } +} + +function show_php_modules() : void { + $php_info = utilities_php_modules(); + + html_section_header(__('PHP Module Information'), 2); + + $php_info = str_replace( + ['width="600"', 'th colspan="2"', ','], + ['', 'th class="subHeaderColumn"', ', '], + $php_info + ); + + print "" . $php_info . ''; +} + +function show_cacti_poller() : void { + if (db_column_exists('sites', 'disabled')) { + $sql_where = 'AND IFNULL(s.disabled, "") != "on"'; + } else { + $sql_where = ''; + } + + $problematic = db_fetch_assoc("SELECT h.id, h.description, h.polling_time, h.avg_time + FROM host h + LEFT JOIN sites s + ON s.id = h.site_id + WHERE IFNULL(h.disabled, '') != 'on' + $sql_where + ORDER BY polling_time DESC + LIMIT 20"); + + html_section_header(__('Worst 20 Polling Time Devices'), 2); + + form_alternate_row(); + + print ""; + + if (cacti_sizeof($problematic)) { + print ""; + print ''; + print ""; + print " '; + print " '; + print " '; + print " '; + print ''; + print ''; + + foreach ($problematic as $host) { + form_alternate_row(); + print ''; + print ''; + print ''; + print ''; + form_end_row(); + } + + print '
" . __('Description') . '" . __('ID') . '" . __('Avg Polling Time') . '" . __('Actual Polling Time') . '
' . htmle($host['description']) . '' . $host['id'] . '' . number_format_i18n($host['avg_time'],3) . '' . number_format_i18n($host['polling_time'],3) . '
'; + } else { + print __('No host found'); + } + + print ''; + + form_end_row(); + + $problematic = db_fetch_assoc('SELECT h.id, h.description, h.failed_polls/h.total_polls AS ratio + FROM host h + LEFT JOIN sites s + ON h.site_id = s.id + WHERE IFNULL(h.disabled,"") != "on" + AND IFNULL(s.disabled,"") != "on" + ORDER BY ratio DESC + LIMIT 20'); + + html_section_header(__('Worst 20 failed/total polls ratio'), 2); + + form_alternate_row(); + print ""; + + if (cacti_sizeof($problematic)) { + print ""; + print ''; + print ""; + print " '; + print " '; + print " '; + print ''; + print ''; + + foreach ($problematic as $host) { + form_alternate_row(); + print ''; + print ''; + print ''; + form_end_row(); + } + + print '
" . __('Description') . '" . __('ID') . '" . __('Failed/Total polls') . '
' . htmle($host['description']) . '' . $host['id'] . '' . number_format_i18n($host['ratio'],3) . '
'; + } else { + print __('No host found'); + } + + print ''; + + form_end_row(); +} + +function show_database_tables() : void { + global $local_db_cnn_id; + + // Get table status + if (POLLER_ID == 1) { + $tables = db_fetch_assoc('SELECT * + FROM information_schema.tables + WHERE table_schema = SCHEMA()'); + } else { + $tables = db_fetch_assoc('SELECT * + FROM information_schema.tables + WHERE table_schema = SCHEMA()', false, $local_db_cnn_id); + } + + form_alternate_row(); + + print " "; + + if (cacti_sizeof($tables)) { + print ""; + print ''; + print ""; + print " '; + print " '; + print " '; + print " '; + print " '; + print " '; + print " '; + print " '; + print " '; + print ''; + print ''; + + foreach ($tables as $table) { + form_alternate_row(); + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + form_end_row(); + } + + print '
" . __('Name') . '" . __('Engine') . '" . __('Rows') . '" . __('Avg Row Length') . '" . __('Data Length') . '" . __('Index Length') . '" . __('Collation') . '" . __('Row Format') . '" . __('Comment') . '
' . $table['TABLE_NAME'] . '' . $table['ENGINE'] . '' . number_format_i18n($table['TABLE_ROWS'], -1) . '' . number_format_i18n($table['AVG_ROW_LENGTH'], -1) . '' . number_format_i18n($table['DATA_LENGTH'], -1) . '' . number_format_i18n($table['INDEX_LENGTH'], -1) . '' . $table['TABLE_COLLATION'] . '' . $table['ROW_FORMAT'] . '' . $table['TABLE_COMMENT'] . '
'; + } else { + print __('Unable to retrieve table status'); + } + + print ''; + + form_end_row(); +} + +function show_cacti_changelog() : void { + $changelog = file(CACTI_PATH_BASE . '/CHANGELOG'); + + foreach ($changelog as $s) { + if (trim($s) == '') { + continue; + } + + if (strlen(trim($s)) && stripos($s, 'CHANGELOG') === false) { + if (strpos($s, '-') === false || strpos($s, '-dev') !== false) { + html_section_header(__('Version %s', $s), 2); + } else { + form_alternate_row(); + print '' . $s . ''; + form_end_row(); + } + } + } +} + +function show_database_settings() : void { + $status = db_fetch_assoc('show global variables'); + + print ""; + print ''; + print ""; + print " '; + print " '; + print ''; + print ''; + + foreach ($status as $s) { + form_alternate_row(); + print ''; + + if (strlen($s['Value']) > 70) { + $s['Value'] = str_replace(',', ', ', $s['Value']); + } + + print ''; + form_end_row(); + } +} + +function show_database_permissions() : void { + $status = db_get_permissions(true); + + $display_text = [ + __('Permission Name'), __('Database'), __('Value'), + __('Permission Name'), __('Database'), __('Value') + ]; + + html_header($display_text); + + $r = 0; + $j = 1; + + foreach ($status as $database => $perms) { + $first = true; + $r = 0; + + foreach ($perms as $key => $value) { + if (($r % 2) == 0 || $first) { + form_alternate_row("line$j"); + } + + print ''; + print ''; + print ''; + + if (($r % 2) == 1) { + form_end_row(); + } + + $r++; + $first = false; + } + + if (($r % 2) == 1) { + print ''; + print ''; + print ''; + form_end_row(); + $j++; + } + } +} + +function show_database_status() : void { + $status = db_fetch_assoc('show global status'); + + print "
" . __('Variable Name') . '" . __('Value') . '
' . htmle($s['Variable_name']) . '' . (is_numeric($s['Value']) ? number_format_i18n($s['Value'], -1) : htmle($s['Value'])) . '' . $key . '' . $database . '' . ($value ? __('Yes') : __('No')) . '   
"; + print ''; + print ""; + print " '; + print " '; + print ''; + print ''; + + foreach ($status as $s) { + form_alternate_row(); + print ''; + print ''; + form_end_row(); + } +} + +function show_tech_summary() : void { + global $database_hostname, $poller_options, $input_types, $local_db_cnn_id; + + // Get poller stats + $poller_item = db_fetch_assoc('SELECT action, count(action) AS total + FROM poller_item + GROUP BY action'); + + // Get system stats + $host_count = db_fetch_cell('SELECT COUNT(*) FROM host WHERE deleted = ""'); + $graph_count = db_fetch_cell('SELECT COUNT(*) FROM graph_local'); + $data_count = db_fetch_assoc('SELECT i.type_id, COUNT(i.type_id) AS total + FROM data_template_data AS d, data_input AS i + WHERE d.data_input_id = i.id + AND local_data_id > 0 + GROUP BY i.type_id'); + + // Get RRDtool version + $rrdtool_version = __('Unknown'); + $rrdtool_release = __('Unknown'); + $storage_location = read_config_option('storage_location'); + + $out_array = []; + + if ($storage_location == 0) { + if ((file_exists(read_config_option('path_rrdtool'))) && ((function_exists('is_executable')) && (is_executable(read_config_option('path_rrdtool'))))) { + cacti_exec(read_config_option('path_rrdtool'), array(), $out_array); + } + } else { + $rrdtool_pipe = rrd_init(); + $out_array[] = rrdtool_execute('info', false, RRDTOOL_OUTPUT_STDOUT, $rrdtool_pipe, 'WEBLOG'); + rrd_close($rrdtool_pipe); + } + + if (cacti_sizeof($out_array) > 0) { + if (preg_match('/^RRDtool ([0-9.]+)/', $out_array[0], $m)) { + preg_match('/^([0-9]+\.[0-9]+\.[0.9]+)/', $m[1], $m2); + $rrdtool_release = $m[1]; + $rrdtool_version = $rrdtool_release; + } + } + + // Get SNMP cli version + if ((file_exists(read_config_option('path_snmpget'))) && ((function_exists('is_executable')) && (is_executable(read_config_option('path_snmpget'))))) { + $out = array(); + cacti_exec(read_config_option('path_snmpget'), array('-V'), $out); + $snmp_version = implode("\n", $out); + } else { + $snmp_version = "" . __('NET-SNMP Not Installed or its paths are not set. Please install if you wish to monitor SNMP enabled devices.') . ''; + } + + // Check RRDtool issues + $rrdtool_errors = array(); + + if (cacti_version_compare($rrdtool_version, get_rrdtool_version(), '<')) { + $rrdtool_errors[] = "" . __('ERROR: Installed RRDtool version does not exceed configured version.
Please visit the %s and select the correct RRDtool Utility Version.', "" . __('Configuration Settings') . '') . '
'; + } + + $graph_gif_count = db_fetch_cell('SELECT COUNT(*) FROM graph_templates_graph WHERE image_format_id = 2'); + + if ($graph_gif_count > 0) { + $rrdtool_errors[] = "" . __('ERROR: RRDtool 1.2.x+ does not support the GIF images format, but %d" graph(s) and/or templates have GIF set as the image format.', $graph_gif_count) . ''; + } + + // Get spine version + $spine_version = 'Unknown'; + + if ((file_exists(read_config_option('path_spine'))) && ((function_exists('is_executable')) && (is_executable(read_config_option('path_spine'))))) { + $out_array = array(); + cacti_exec(read_config_option('path_spine'), array('--version'), $out_array); + + if (cacti_sizeof($out_array) > 0) { + $spine_version = $out_array[0]; + } + } + + html_section_header(__('General Information'), 2); + + $lockout = read_config_option('cacti_lockout_status', true); + $enabled = read_config_option('auth_maint_lockout_type', true); + + form_alternate_row(); + + print ''; + + if ($enabled >= 0) { + if ($lockout != '') { + $lockout = json_decode($lockout, true); + + $unlock_time = $lockout['time'] + (30 * 60); + $unlock_hms = date('H:i', $unlock_time); + + print ''; + } else { + print ''; + } + } else { + print ''; + } + form_end_row(); + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + api_plugin_hook_function('custom_version_info'); + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + if (!empty($rrdtool_errors)) { + form_alternate_row(); + print ''; + $br = ''; + print ''; + form_end_row(); + } + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + html_section_header(__('Poller Information'), 2); + + form_alternate_row(); + print ''; + print ''; + + if (file_exists(read_config_option('path_spine')) && $poller_options[read_config_option('poller_type')] == 'spine') { + $type = $spine_version; + + if (!strpos($spine_version, CACTI_VERSION)) { + $type .= ' (' . __('Different version of Cacti and Spine!') . ')'; + } + } else { + $type = $poller_options[read_config_option('poller_type')]; + } + form_end_row(); + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + $processes = db_fetch_cell('SELECT + GROUP_CONCAT( + CONCAT("' . __('Name: ') . '", name, ", ' . __('Procs: ') . '", processes) SEPARATOR "
" + ) AS poller + FROM poller + WHERE disabled=""'); + + $threads = db_fetch_cell('SELECT + GROUP_CONCAT( + CONCAT("' . __('Name: ') . '", name, ", ' . __('Threads: ') . '", threads) SEPARATOR "
" + ) AS poller + FROM poller + WHERE disabled=""'); + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + $script_servers = read_config_option('php_servers'); + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + if (POLLER_ID == 1) { + $max_connections = db_get_global_variable('max_connections'); + $max_local_connections = 0; + } elseif (CACTI_CONNECTION == 'online') { + $max_connections = db_get_global_variable('max_connections'); + $max_local_connections = db_get_global_variable('max_connections', $local_db_cnn_id); + } else { + $max_connections = 0; + $max_local_connections = db_get_global_variable('max_connections'); + } + + $total_dc_threads = db_fetch_cell("SELECT + SUM((processes * threads) + (processes * $script_servers)) AS threads + FROM poller + WHERE disabled = ''"); + + $recommend_mc = $total_dc_threads + 100; + + if ($recommend_mc > $max_connections) { + if (POLLER_ID == 1) { + $db_connections = '' . __('Main Server: Current: %s, Min Required: %s', $max_connections, $recommend_mc) . ''; + } elseif (CACTI_CONNECTION == 'online') { + $db_connections = '' . __('Main Server: Current: %s, Min Required: %s', $max_connections, $recommend_mc) . ''; + } else { + $db_connections = ''; + } + } else { + if (POLLER_ID == 1) { + $db_connections = '' . __('Main Server: Current: %s, Min Required: %s', $max_connections, $recommend_mc) . ''; + } elseif (CACTI_CONNECTION == 'online') { + $db_connections = '' . __('Main Server: Current: %s, Min Required: %s', $max_connections, $recommend_mc) . ''; + } else { + $db_connections = ''; + } + } + + if (POLLER_ID > 1) { + if ($recommend_mc > $max_local_connections) { + $db_connections .= '
' . __('Local Server: Current: %s, Min Required: %s', $max_local_connections, $recommend_mc) . ''; + } else { + $db_connections .= '
' . __('Local Server: Current: %s, Min Required: %s', $max_local_connections, $recommend_mc) . ''; + } + } + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + // Get System Memory + $memInfo = utilities_get_system_memory(); + + // print '
';print_r($memInfo);print '
'; + + $total_memory = 0; + + if (cacti_sizeof($memInfo)) { + html_section_header(__('System Memory'), 2); + + foreach ($memInfo as $name => $value) { + if (CACTI_SERVER_OS == 'win32') { + form_alternate_row(); + print ""; + print ''; + form_end_row(); + } else { + switch($name) { + case 'SwapTotal': + case 'SwapFree': + case 'Cached': + case 'MemTotal': + case 'MemFree': + case 'MemAvailable': + case 'Buffers': + case 'Active': + case 'Inactive': + // Convert to GBi + $value /= (1000 * 1000 * 1000); + + form_alternate_row(); + print ""; + print ''; + form_end_row(); + + if ($name == 'MemTotal') { + $total_memory = $value; + } + } + } + } + + form_end_row(); + } + + $mysql_info = get_mysql_info(POLLER_ID); + + $database = $mysql_info['database']; + $version = $mysql_info['version']; + $link_ver = $mysql_info['link_ver']; + $variables = $mysql_info['variables']; + $myhost = php_uname('n'); + $dbhost = $database_hostname; + + if ($dbhost == 'localhost' || $myhost == $dbhost) { + $local_db = true; + } else { + $local_db = false; + } + + // Get Maximum Memory in GB for MySQL/MariaDB + if (POLLER_ID == 1) { + if (($database == 'MySQL' && version_compare($version, '8.0', '<')) || $database == 'MariaDB') { + $systemMemory = db_fetch_cell('SELECT + (@@GLOBAL.key_buffer_size + + @@GLOBAL.query_cache_size + + @@GLOBAL.tmp_table_size + + @@GLOBAL.innodb_buffer_pool_size + + @@GLOBAL.innodb_log_buffer_size) / 1024 / 1024 / 1024'); + + $maxPossibleMyMemory = db_fetch_cell('SELECT ( + (@@GLOBAL.key_buffer_size + + @@GLOBAL.query_cache_size + + @@GLOBAL.tmp_table_size + + @@GLOBAL.innodb_buffer_pool_size + + @@GLOBAL.innodb_log_buffer_size + + @@GLOBAL.max_connections * ( + @@GLOBAL.sort_buffer_size + + @@GLOBAL.read_buffer_size + + @@GLOBAL.read_rnd_buffer_size + + @@GLOBAL.join_buffer_size + + @@GLOBAL.thread_stack + + @@GLOBAL.binlog_cache_size) + ) / 1024 / 1024 / 1024)'); + } else { + $systemMemory = db_fetch_cell('SELECT + (@@GLOBAL.key_buffer_size + + @@GLOBAL.tmp_table_size + + @@GLOBAL.innodb_buffer_pool_size + + @@GLOBAL.innodb_log_buffer_size) / 1024 / 1024 / 1024'); + + $maxPossibleMyMemory = db_fetch_cell('SELECT ( + (@@GLOBAL.key_buffer_size + + @@GLOBAL.tmp_table_size + + @@GLOBAL.innodb_buffer_pool_size + + @@GLOBAL.innodb_log_buffer_size + + @@GLOBAL.max_connections * ( + @@GLOBAL.sort_buffer_size + + @@GLOBAL.read_buffer_size + + @@GLOBAL.read_rnd_buffer_size + + @@GLOBAL.join_buffer_size + + @@GLOBAL.thread_stack + + @@GLOBAL.binlog_cache_size) + ) / 1024 / 1024 / 1024)'); + } + + $clientMemory = db_fetch_cell('SELECT @@GLOBAL.max_connections * ( + @@GLOBAL.sort_buffer_size + + @@GLOBAL.read_buffer_size + + @@GLOBAL.read_rnd_buffer_size + + @@GLOBAL.join_buffer_size + + @@GLOBAL.thread_stack + + @@GLOBAL.binlog_cache_size) / 1024 / 1024 / 1024'); + } else { + if (($database == 'MySQL' && version_compare($version, '8.0', '<')) || $database == 'MariaDB') { + $maxPossibleMyMemory = db_fetch_cell('SELECT ( + (@@GLOBAL.key_buffer_size + + @@GLOBAL.query_cache_size + + @@GLOBAL.tmp_table_size + + @@GLOBAL.innodb_buffer_pool_size + + @@GLOBAL.innodb_log_buffer_size + + @@GLOBAL.max_connections * ( + @@GLOBAL.sort_buffer_size + + @@GLOBAL.read_buffer_size + + @@GLOBAL.read_rnd_buffer_size + + @@GLOBAL.join_buffer_size + + @@GLOBAL.thread_stack + + @@GLOBAL.binlog_cache_size) + ) / 1024 / 1024 / 1024)', '', false, $local_db_cnn_id); + + $systemMemory = db_fetch_cell('SELECT + (@@GLOBAL.key_buffer_size + + @@GLOBAL.query_cache_size + + @@GLOBAL.tmp_table_size + + @@GLOBAL.innodb_buffer_pool_size + + @@GLOBAL.innodb_log_buffer_size) / 1024 / 1024 / 1024', '', false, $local_db_cnn_id); + } else { + $maxPossibleMyMemory = db_fetch_cell('SELECT ( + (@@GLOBAL.key_buffer_size + + @@GLOBAL.tmp_table_size + + @@GLOBAL.innodb_buffer_pool_size + + @@GLOBAL.innodb_log_buffer_size + + @@GLOBAL.max_connections * ( + @@GLOBAL.sort_buffer_size + + @@GLOBAL.read_buffer_size + + @@GLOBAL.read_rnd_buffer_size + + @@GLOBAL.join_buffer_size + + @@GLOBAL.thread_stack + + @@GLOBAL.binlog_cache_size) + ) / 1024 / 1024 / 1024)', '', false, $local_db_cnn_id); + + $systemMemory = db_fetch_cell('SELECT + (@@GLOBAL.key_buffer_size + + @@GLOBAL.tmp_table_size + + @@GLOBAL.innodb_buffer_pool_size + + @@GLOBAL.innodb_log_buffer_size) / 1024 / 1024 / 1024', '', false, $local_db_cnn_id); + } + + $clientMemory = db_fetch_cell('SELECT @@GLOBAL.max_connections * ( + @@GLOBAL.sort_buffer_size + + @@GLOBAL.read_buffer_size + + @@GLOBAL.read_rnd_buffer_size + + @@GLOBAL.join_buffer_size + + @@GLOBAL.thread_stack + + @@GLOBAL.binlog_cache_size) / 1024 / 1024 / 1024', '', false, $local_db_cnn_id); + } + + html_section_header(__('MySQL/MariaDB Memory Statistics (Source: MySQL Tuner)'), 2); + + if ($total_memory > 0 && $local_db) { + if ($maxPossibleMyMemory > ($total_memory * 0.8)) { + form_alternate_row(); + print ''; + print ''; + form_end_row(); + form_alternate_row(); + print ''; + print ''; + form_end_row(); + } else { + form_alternate_row(); + print ''; + print ''; + form_end_row(); + } + } else { + form_alternate_row(); + print ''; + print ''; + form_end_row(); + } + + if ($total_memory > 0 && $local_db) { + if ($systemMemory > ($total_memory * 0.8)) { + form_alternate_row(); + print ''; + print ''; + form_end_row(); + } else { + form_alternate_row(); + print ''; + print ''; + form_end_row(); + } + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + if ($clientMemory > ($total_memory * 0.8)) { + form_alternate_row(); + print ''; + print ''; + form_end_row(); + } else { + form_alternate_row(); + print ''; + print ''; + form_end_row(); + } + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + } else { + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + } + + html_section_header(__('PHP Information'), 2); + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + form_alternate_row(); + print ''; + print ''; + form_end_row(); + + form_alternate_row(); + print ''; + print ''; + + form_end_row(); + + utilities_get_mysql_recommendations(); +} From ce277d7c5b40e258e63f48cc950c1485688ad11e Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Fri, 29 May 2026 13:46:19 -0700 Subject: [PATCH 2/5] test(security): refresh sink inventory baseline for support.php Signed-off-by: Thomas Vincent --- tests/security/baselines/sink_inventory.baseline.tsv | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/security/baselines/sink_inventory.baseline.tsv b/tests/security/baselines/sink_inventory.baseline.tsv index c0a43ae6bd..b05de2a392 100644 --- a/tests/security/baselines/sink_inventory.baseline.tsv +++ b/tests/security/baselines/sink_inventory.baseline.tsv @@ -937,6 +937,7 @@ header_redirect ./settings.php:303 header('Location: settings.php?tab=' . get_ header_redirect ./sites.php:266 header('Location: sites.php?header=false&action=edit&id=' . (empty($site_id) ? get_nfilter_request_var('id') : $site_id)); header_redirect ./sites.php:347 header('Location: sites.php?header=false'); header_redirect ./sites.php:397 header('Location: sites.php?header=false'); +header_redirect ./support.php:64 header('Location: support.php?tab=summary'); header_redirect ./templates_export.php:72 header('Location: templates_export.php'); header_redirect ./templates_import.php:122 header('Location: templates_import.php'); header_redirect ./templates_import.php:71 header('Location: templates_import.php'); exit; From 9dd331d43ae0631a7683babc06714517034d9060 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Sun, 7 Jun 2026 02:37:59 -0700 Subject: [PATCH 3/5] test(security): refresh sink baseline for rebased rrd.php line shifts Rebase onto 1.2.x shifted two dynamic_include line numbers in lib/rrd.php; regenerate the tracked sink inventory so the guard passes. Signed-off-by: Thomas Vincent --- tests/security/baselines/sink_inventory.baseline.tsv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/security/baselines/sink_inventory.baseline.tsv b/tests/security/baselines/sink_inventory.baseline.tsv index b05de2a392..0f860ee54b 100644 --- a/tests/security/baselines/sink_inventory.baseline.tsv +++ b/tests/security/baselines/sink_inventory.baseline.tsv @@ -422,8 +422,8 @@ dynamic_include ./lib/rrd.php:1350 include_once($config['library_path'] . '/boo dynamic_include ./lib/rrd.php:1351 include_once($config['library_path'] . '/xml.php'); dynamic_include ./lib/rrd.php:1352 include($config['include_path'] . '/global_arrays.php'); dynamic_include ./lib/rrd.php:2636 include($rrdtheme); -dynamic_include ./lib/rrd.php:3241 include_once($config['library_path'] . '/time.php'); -dynamic_include ./lib/rrd.php:4012 include($themefile); +dynamic_include ./lib/rrd.php:3242 include_once($config['library_path'] . '/time.php'); +dynamic_include ./lib/rrd.php:4019 include($themefile); dynamic_include ./lib/rrd.php:598 include ($config['include_path'] . '/global_arrays.php'); dynamic_include ./lib/rrd.php:917 include($config['include_path'] . '/global_arrays.php'); dynamic_include ./lib/rrd.php:991 include_once($config['library_path'] . '/boost.php'); From 767d0bde22cdb5f000cdc230fd0a93057c27d51b Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Sun, 7 Jun 2026 22:55:19 -0700 Subject: [PATCH 4/5] chore(security): refresh architectural hotspots baseline --- .../architectural_hotspots.baseline.tsv | 66 ++++++++++--------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/tests/security/baselines/architectural_hotspots.baseline.tsv b/tests/security/baselines/architectural_hotspots.baseline.tsv index acbca14470..60511f62d4 100644 --- a/tests/security/baselines/architectural_hotspots.baseline.tsv +++ b/tests/security/baselines/architectural_hotspots.baseline.tsv @@ -28,13 +28,13 @@ cmd_exec ./lib/functions.php:2249 $output = shell_exec($php . ' -q ' . $scr cmd_exec ./lib/functions.php:2533 $output = shell_exec($script_path); cmd_exec ./lib/functions.php:3034 return exec('whoami'); cmd_exec ./lib/functions.php:3693 exec($command_line,$out,$err); -cmd_exec ./lib/functions.php:6951 $shell = shell_exec(cacti_escapeshellcmd(read_config_option('path_rrdtool')) . ' -v'); -cmd_exec ./lib/functions.php:6953 $shell = shell_exec(cacti_escapeshellcmd(read_config_option('path_rrdtool')) . ' -v 2>&1'); -cmd_exec ./lib/functions.php:7152 exec('id -nu', $o, $r); -cmd_exec ./lib/functions.php:7162 exec(sprintf('grep :%s: /etc/passwd | cut -d: -f1', (int) $uid), $o, $r); -cmd_exec ./lib/functions.php:7454 * shell_exec() callers that already assemble the command string. -cmd_exec ./lib/functions.php:7494 $process = proc_open($argv, $descriptors, $pipes); -cmd_exec ./lib/functions.php:7566 * shell_exec() and expect a string return value. +cmd_exec ./lib/functions.php:6947 $shell = shell_exec(cacti_escapeshellcmd(read_config_option('path_rrdtool')) . ' -v'); +cmd_exec ./lib/functions.php:6949 $shell = shell_exec(cacti_escapeshellcmd(read_config_option('path_rrdtool')) . ' -v 2>&1'); +cmd_exec ./lib/functions.php:7148 exec('id -nu', $o, $r); +cmd_exec ./lib/functions.php:7158 exec(sprintf('grep :%s: /etc/passwd | cut -d: -f1', (int) $uid), $o, $r); +cmd_exec ./lib/functions.php:7450 * shell_exec() callers that already assemble the command string. +cmd_exec ./lib/functions.php:7490 $process = proc_open($argv, $descriptors, $pipes); +cmd_exec ./lib/functions.php:7562 * shell_exec() and expect a string return value. cmd_exec ./lib/installer.php:3288 $results = shell_exec(cacti_escapeshellcmd(read_config_option('path_php_binary')) . ' -q ' . cmd_exec ./lib/installer.php:3320 shell_exec(cacti_escapeshellcmd(read_config_option('path_php_binary')) . ' -q ' . cmd_exec ./lib/installer.php:3376 $results = shell_exec(cacti_escapeshellcmd(read_config_option('path_php_binary')) . ' -q ' . @@ -70,7 +70,7 @@ cmd_exec ./lib/poller.php:64 * @param (array) $pipes - the array of r/w pip cmd_exec ./lib/poller.php:65 * @param (resource) $proc_fd - the file descriptor returned from proc_open() cmd_exec ./lib/rrd.php:317 $process = proc_open(read_config_option('path_rrdtool') . ' - ' . $debug, $descriptorspec, $pipes); cmd_exec ./lib/rrd.php:91 return popen($command, 'w'); -cmd_exec ./lib/rrd.php:940 $fp = popen($rrdtool_cmd, 'r'); +cmd_exec ./lib/rrd.php:948 $fp = popen($rrdtool_cmd, 'r'); cmd_exec ./lib/rrdcheck.php:798 $process = proc_open($command, $fds, $pipes); cmd_exec ./lib/snmp.php:214 exec($command, $snmp_value); cmd_exec ./lib/snmp.php:314 exec($command, $snmp_value); @@ -89,6 +89,8 @@ cmd_exec ./lib/utility.php:1863 exec('/usr/bin/free', $output, $exit_code); cmd_exec ./lib/utility.php:1914 $json = shell_exec($php . ' -q ' . $php_file); cmd_exec ./lib/utility.php:1982 $json = shell_exec($php . ' -q ' . $php_file); cmd_exec ./lib/utility.php:2114 $json = shell_exec($php . ' -q ' . $php_file); +cmd_exec ./poc/setup/probe.php:82 $out = shell_exec( +cmd_exec ./poc/setup/probe.php:95 shell_exec('rrdtool update ' . escapeshellarg($rrd_file) . ' N:42 2>&1'); cmd_exec ./poller_realtime.php:145 shell_exec("$command_string $extra_args"); cmd_exec ./poller_realtime.php:229 shell_exec($command); cmd_exec ./poller_spikekill.php:247 $response = exec(cacti_escapeshellcmd(read_config_option('path_php_binary')) . ' -q ' . @@ -368,17 +370,17 @@ dynamic_include ./lib/dsstats.php:1009 include_once($config['library_path'] . dynamic_include ./lib/dsstats.php:967 include_once($config['base_path'] . '/lib/rrd.php'); dynamic_include ./lib/export.php:465 include_once($config['library_path'] . '/vdef.php'); dynamic_include ./lib/functions.php:421 include_once($config['base_path'] . '/lib/poller.php'); -dynamic_include ./lib/functions.php:4833 include_once($config['base_path'] . '/include/global_session.php'); -dynamic_include ./lib/functions.php:4836 include_once($config['base_path'] . '/include/bottom_footer.php'); -dynamic_include ./lib/functions.php:4861 include_once($config['base_path'] . '/include/top_header.php'); -dynamic_include ./lib/functions.php:4868 include_once($config['base_path'] . '/include/top_graph_header.php'); -dynamic_include ./lib/functions.php:4875 include_once($config['base_path'] . '/include/top_general_header.php'); -dynamic_include ./lib/functions.php:5037 require_once($config['include_path'] . '/vendor/phpmailer/src/Exception.php'); -dynamic_include ./lib/functions.php:5038 require_once($config['include_path'] . '/vendor/phpmailer/src/PHPMailer.php'); -dynamic_include ./lib/functions.php:5039 require_once($config['include_path'] . '/vendor/phpmailer/src/SMTP.php'); -dynamic_include ./lib/functions.php:5526 require_once($config['include_path'] . '/vendor/phpmailer/src/Exception.php'); -dynamic_include ./lib/functions.php:5527 require_once($config['include_path'] . '/vendor/phpmailer/src/PHPMailer.php'); -dynamic_include ./lib/functions.php:5528 require_once($config['include_path'] . '/vendor/phpmailer/src/SMTP.php'); +dynamic_include ./lib/functions.php:4829 include_once($config['base_path'] . '/include/global_session.php'); +dynamic_include ./lib/functions.php:4832 include_once($config['base_path'] . '/include/bottom_footer.php'); +dynamic_include ./lib/functions.php:4857 include_once($config['base_path'] . '/include/top_header.php'); +dynamic_include ./lib/functions.php:4864 include_once($config['base_path'] . '/include/top_graph_header.php'); +dynamic_include ./lib/functions.php:4871 include_once($config['base_path'] . '/include/top_general_header.php'); +dynamic_include ./lib/functions.php:5033 require_once($config['include_path'] . '/vendor/phpmailer/src/Exception.php'); +dynamic_include ./lib/functions.php:5034 require_once($config['include_path'] . '/vendor/phpmailer/src/PHPMailer.php'); +dynamic_include ./lib/functions.php:5035 require_once($config['include_path'] . '/vendor/phpmailer/src/SMTP.php'); +dynamic_include ./lib/functions.php:5522 require_once($config['include_path'] . '/vendor/phpmailer/src/Exception.php'); +dynamic_include ./lib/functions.php:5523 require_once($config['include_path'] . '/vendor/phpmailer/src/PHPMailer.php'); +dynamic_include ./lib/functions.php:5524 require_once($config['include_path'] . '/vendor/phpmailer/src/SMTP.php'); dynamic_include ./lib/graph_variables.php:65 include_once($config['library_path'] . '/rrd.php'); dynamic_include ./lib/html.php:1243 include($config['include_path'] . '/global_arrays.php'); dynamic_include ./lib/html_tree.php:479 include_once($config['base_path'] . '/lib/data_query.php'); @@ -416,19 +418,19 @@ dynamic_include ./lib/reports.php:395 include_once($config['base_path'] . '/lib dynamic_include ./lib/reports.php:396 include_once($config['base_path'] . '/lib/html_reports.php'); dynamic_include ./lib/reports.php:678 include_once($config['library_path'] . '/html_tree.php'); dynamic_include ./lib/reports.php:755 include_once($config['base_path'] . '/lib/time.php'); -dynamic_include ./lib/rrd.php:1067 include($config['include_path'] . '/global_arrays.php'); -dynamic_include ./lib/rrd.php:1344 include_once($config['library_path'] . '/cdef.php'); -dynamic_include ./lib/rrd.php:1345 include_once($config['library_path'] . '/vdef.php'); -dynamic_include ./lib/rrd.php:1346 include_once($config['library_path'] . '/graph_variables.php'); -dynamic_include ./lib/rrd.php:1347 include_once($config['library_path'] . '/boost.php'); -dynamic_include ./lib/rrd.php:1348 include_once($config['library_path'] . '/xml.php'); -dynamic_include ./lib/rrd.php:1349 include($config['include_path'] . '/global_arrays.php'); -dynamic_include ./lib/rrd.php:2630 include($rrdtheme); -dynamic_include ./lib/rrd.php:3235 include_once($config['library_path'] . '/time.php'); -dynamic_include ./lib/rrd.php:4006 include($themefile); -dynamic_include ./lib/rrd.php:590 include ($config['include_path'] . '/global_arrays.php'); -dynamic_include ./lib/rrd.php:909 include($config['include_path'] . '/global_arrays.php'); -dynamic_include ./lib/rrd.php:983 include_once($config['library_path'] . '/boost.php'); +dynamic_include ./lib/rrd.php:1075 include($config['include_path'] . '/global_arrays.php'); +dynamic_include ./lib/rrd.php:1347 include_once($config['library_path'] . '/cdef.php'); +dynamic_include ./lib/rrd.php:1348 include_once($config['library_path'] . '/vdef.php'); +dynamic_include ./lib/rrd.php:1349 include_once($config['library_path'] . '/graph_variables.php'); +dynamic_include ./lib/rrd.php:1350 include_once($config['library_path'] . '/boost.php'); +dynamic_include ./lib/rrd.php:1351 include_once($config['library_path'] . '/xml.php'); +dynamic_include ./lib/rrd.php:1352 include($config['include_path'] . '/global_arrays.php'); +dynamic_include ./lib/rrd.php:2636 include($rrdtheme); +dynamic_include ./lib/rrd.php:3242 include_once($config['library_path'] . '/time.php'); +dynamic_include ./lib/rrd.php:4019 include($themefile); +dynamic_include ./lib/rrd.php:598 include ($config['include_path'] . '/global_arrays.php'); +dynamic_include ./lib/rrd.php:917 include($config['include_path'] . '/global_arrays.php'); +dynamic_include ./lib/rrd.php:991 include_once($config['library_path'] . '/boost.php'); dynamic_include ./lib/rrdcheck.php:716 include_once($config['base_path'] . '/lib/rrd.php'); dynamic_include ./lib/rrdcheck.php:746 include_once($config['library_path'] . '/poller.php'); dynamic_include ./lib/snmp.php:49 include_once($config['include_path'] . '/vendor/phpsnmp/extension.php'); From d4a6b07c2ab00a66af53edf4eb8556becd7ed94f Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Sun, 7 Jun 2026 23:48:53 -0700 Subject: [PATCH 5/5] fix(support): add defensive check for legacy lockout status formats --- support.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/support.php b/support.php index ed5b43c559..de306cfadd 100644 --- a/support.php +++ b/support.php @@ -1193,7 +1193,12 @@ function show_tech_summary() : void { if ($lockout != '') { $lockout = json_decode($lockout, true); - $unlock_time = $lockout['time'] + (30 * 60); + if (is_array($lockout) && isset($lockout['time'])) { + $unlock_time = $lockout['time'] + (30 * 60); + } else { + $unlock_time = time() + (30 * 60); + } + $unlock_hms = date('H:i', $unlock_time); print '';
" . __('Variable Name') . '" . __('Value') . '
' . htmle($s['Variable_name']) . '' . (is_numeric($s['Value']) ? number_format_i18n($s['Value'], -1) : htmle($s['Value'])) . '' . __('Cacti Lockout Status') . '' . __('Date') . '' . date('r') . '' . __('Cacti Version') . '' . CACTI_VERSION . '' . __('Cacti OS') . '' . CACTI_SERVER_OS . '' . __('RSA Fingerprint') . '' . read_config_option('rsa_fingerprint') . '' . __('NET-SNMP Version') . '' . $snmp_version . '' . __('RRDtool Version') . ' ' . __('Configured') . '' . get_rrdtool_version() . '+' . __('RRDtool Version') . ' ' . __('Found') . '' . $rrdtool_release . ' '; + + foreach ($rrdtool_errors as $rrdtool_error) { + print $br . $rrdtool_error; + $br = '
'; + } + print '
' . __('Devices') . '' . number_format_i18n($host_count, -1) . '' . __('Graphs') . '' . number_format_i18n($graph_count, -1) . '' . __('Data Sources') . ''; + $data_total = 0; + + if (cacti_sizeof($data_count)) { + foreach ($data_count as $item) { + print $input_types[$item['type_id']] . ': ' . number_format_i18n($item['total'], -1) . '
'; + $data_total += $item['total']; + } + print __('Total: %s', number_format_i18n($data_total, -1)); + } else { + print "0"; + } + print '
' . __('Interval') . '' . read_config_option('poller_interval') . '' . __('Type') . '' . $type . '' . __('Items') . ''; + $total = 0; + + if (cacti_sizeof($poller_item)) { + foreach ($poller_item as $item) { + print __('Action[%s]', $item['action']) . ': ' . number_format_i18n($item['total'], -1) . '
'; + $total += $item['total']; + } + print __('Total: %s', number_format_i18n($total, -1)); + } else { + print "" . __('No items to poll') . ''; + } + print '
' . __('Concurrent Processes') . '' . $processes . '' . __('Max Threads') . '' . $threads . '' . __('PHP Servers') . '' . htmle($script_servers) . '' . __('Minimum Connections:') . '' . $db_connections . '
' . + __('Assumes 100 spare connections for Web page users and other various connections.') . '
' . + __('The minimum required can vary greatly if there is heavy user Graph viewing activity.') . '
' . + __('Each browser tab can use upto 10 connections depending on the browser.') . '
' . __('Script Timeout') . '' . read_config_option('script_timeout') . '' . __('Max OID') . '' . read_config_option('max_get_size') . '' . __('Last Run Statistics') . '' . read_config_option('stats_poller') . '$name' . number_format_i18n($value / 1000, 2) . ' MB$name' . __('%s GB', number_format_i18n($value, 2, 1000)) . '' . __('Max Total Memory Possible') . '' . __('%s GB', number_format_i18n($maxPossibleMyMemory, 2, 1000)) . '' . __('Reduce MySQL/MariaDB Memory to less than 80% of System Memory. Preserve additional Cache Memory for RRDfiles if the Database is on the same system as the RRDfiles. See Core and Client Totals below for explanation of calculation method.') . '' . __('Max Total Memory Possible') . '' . __('%s GB', number_format_i18n($maxPossibleMyMemory, 2, 1000)) . '' . __('Max Total Memory Possible') . '' . __('%s GB', number_format_i18n($maxPossibleMyMemory, 2, 1000)) . '' . __('Max Core Memory Possible') . '' . __('%s GB', number_format_i18n($systemMemory, 2, 1000)) . '  (' . __('Reduce Total Core Memory') . '' . __('Max Core Memory Possible') . '' . __('%s GB', number_format_i18n($systemMemory, 2, 1000)) . '' . __('Calculation Formula') . 'SELECT @@GLOBAL.key_buffer_size +
@@GLOBAL.query_cache_size +
@@GLOBAL.tmp_table_size +
@@GLOBAL.innodb_buffer_pool_size +
@@GLOBAL.innodb_log_buffer_size
' . __('Max Connection Memory Possible') . '' . __('%s GB', number_format_i18n($clientMemory, 2, 1000)) . '  (' . __('Reduce Total Client Memory') . ')' . __('Max Connection Memory Possible') . '' . __('%s GB', number_format_i18n($clientMemory, 2, 1000)) . '' . __('Calculation Formula') . 'SELECT @@GLOBAL.max_connections * (
@@GLOBAL.sort_buffer_size +
@@GLOBAL.read_buffer_size +
@@GLOBAL.read_rnd_buffer_size +
@@GLOBAL.join_buffer_size +
@@GLOBAL.thread_stack +
@@GLOBAL.binlog_cache_size)
' . __('Max Core Memory Possible') . '' . __('%s GB', number_format_i18n($systemMemory, 2, 1000)) . '' . __('Calculation Formula') . 'SELECT @@GLOBAL.key_buffer_size +
@@GLOBAL.query_cache_size +
@@GLOBAL.tmp_table_size +
@@GLOBAL.innodb_buffer_pool_size +
@@GLOBAL.innodb_log_buffer_size
' . __('Max Connection Memory Possible') . '' . __('%s GB', number_format_i18n($clientMemory, 2, 1000)) . '' . __('Calculation Formula') . 'SELECT @@GLOBAL.max_connections * (
@@GLOBAL.sort_buffer_size +
@@GLOBAL.read_buffer_size +
@@GLOBAL.read_rnd_buffer_size +
@@GLOBAL.join_buffer_size +
@@GLOBAL.thread_stack +
@@GLOBAL.binlog_cache_size)
' . __('PHP Version') . '' . PHP_VERSION . '' . __('PHP OS') . '' . PHP_OS . '' . __('PHP uname') . ''; + + if (function_exists('php_uname')) { + print php_uname(); + } else { + print __('N/A'); + } + print '' . __('PHP SNMP') . ''; + + if (function_exists('snmpget')) { + print __('Installed. Note: If you are planning on using SNMPv3, you must remove php-snmp and use the Net-SNMP toolset.'); + } else { + print __('Not Installed'); + } + print 'max_execution_time' . ini_get('max_execution_time') . 'memory_limit' . ini_get('memory_limit'); + + // Calculate memory suggestion based off of data source count + $memory_suggestion = $data_total * 32768; + + // Set minimum - 16M + if ($memory_suggestion < 16777216) { + $memory_suggestion = 16777216; + } + + // Set maximum - 512M + if ($memory_suggestion > 536870912) { + $memory_suggestion = 536870912; + } + + // Suggest values in 8M increments + $memory_suggestion = round($memory_suggestion / 8388608) * 8388608; + + if (memory_bytes(ini_get('memory_limit')) < $memory_suggestion) { + print "
"; + + if ((ini_get('memory_limit') == -1)) { + print __("You've set memory limit to 'unlimited'.") . '
'; + } + + print __('It is highly suggested that you alter you php.ini memory_limit to %s or higher.', memory_readable($memory_suggestion)) . '
' . + __('This suggested memory value is calculated based on the number of data source present and is only to be used as a suggestion, actual values may vary system to system based on requirements.'); + + print '

'; + } + + print '