NULL' : ' 2> /dev/null'); if (defined('CACTI_CLI_ONLY') && CACTI_WEB) { die('
This script is only meant to run at the command line.'); } // this should be auto-detected, set it manually if needed $config['cacti_server_os'] = (str_starts_with(PHP_OS, 'WIN')) ? 'win32' : 'unix'; $config['is_web'] = CACTI_WEB; // load cacti version from file $cacti_version_file = __DIR__ . '/cacti_version'; if (!file_exists($cacti_version_file)) { die('ERROR: failed to find cacti version file'); } $cacti_version = file_get_contents($cacti_version_file, false); if ($cacti_version === false) { die('ERROR: failed to load cacti version file'); } $cacti_version = trim($cacti_version); // define documentation table of contents define('CACTI_DOCUMENTATION_TOC', 'docs/Table-of-Contents.html'); // By default, we assume that it is not // an AJAX request. global $is_request_ajax; $is_request_ajax = false; // If HTTP_X_REQUESTED_WITH is equal to xmlhttprequest // We assume this is an ajax call if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strcasecmp($_SERVER['HTTP_X_REQUESTED_WITH'], 'xmlhttprequest') == 0) { $is_request_ajax = true; } elseif (isset($_REQUEST['header']) && $_REQUEST['header'] == 'false') { $is_request_ajax = true; } elseif (isset($_REQUEST['headercontent'])) { $is_request_ajax = false; } // Default database settings $database_type = 'mysql'; $database_default = 'cacti'; $database_hostname = 'localhost'; $database_username = 'cactiuser'; $database_password = 'cactiuser'; $database_port = 3306; $database_retries = 2; $database_ssl = false; $database_ssl_key = ''; $database_ssl_cert = ''; $database_ssl_ca = ''; $database_ssl_capath = ''; $database_ssl_verify_server_cert = true; $database_persist = true; // Default session name - Session name must contain alpha characters $cacti_session_name = 'Cacti'; if (isset($_COOKIE['CactiTab'])) { $cacti_session_name = 'CactiTabId:' . $_COOKIE['CactiTab']; } // define default url path $url_path = '/cacti/'; // disable log rotation setting $disable_log_rotation = false; // Include configuration, or use the defaults if (file_exists(__DIR__ . '/config.php')) { if (!is_readable(__DIR__ . '/config.php')) { die('Configuration file include/config.php is present, but unreadable.' . PHP_EOL); } include(__DIR__ . '/config.php'); } // Combined test-bootstrap gate: PHP_TESTING alone is not enough. The // CACTI_TEST_BOOTSTRAP env var (local_only so per-request SAPI params under // FPM/FastCGI cannot set it) must also be present. A stray PHP_TESTING define // in a deployed environment therefore cannot swap to the default config, skip // schema validation, or disable real DB connection logic. Fail-closed. if (!function_exists('cacti_is_test_bootstrap')) { function cacti_is_test_bootstrap(): bool { return defined('PHP_TESTING') && getenv('CACTI_TEST_BOOTSTRAP', true) === '1'; } } if (cacti_is_test_bootstrap() && file_exists(__DIR__ . '/config.php.dist')) { if (!is_readable(__DIR__ . '/config.php.dist')) { die('Configuration file include/config.php is present, but unreadable.' . PHP_EOL); } include(__DIR__ . '/config.php.dist'); } if (isset($config['cacti_version'])) { // @phpstan-ignore-line die('Invalid include/config.php file detected.' . PHP_EOL); } // Define global paths require_once(__DIR__ . '/global_path.php'); // Should we allow proxy ip headers? $config['proxy_headers'] = $proxy_headers ?? []; // Reverse proxies allowed to assert a forwarded, pre-authenticated user $config['trusted_proxies'] = $trusted_proxies ?? []; // Set the poller_id if (isset($poller_id)) { $config['poller_id'] = $poller_id; define('POLLER_ID', $poller_id); } else { $config['poller_id'] = 1; define('POLLER_ID', 1); } $db_var_defaults = [ 'database_type' => 'mysql', 'database_default' => null, 'database_hostname' => null, 'database_username' => null, 'database_password' => null, 'database_port' => '3306', 'database_retries' => 2, 'database_ssl' => false, 'database_ssl_key' => '', 'database_ssl_cert' => '', 'database_ssl_ca' => '', 'database_ssl_capath' => '', 'database_ssl_verify_server_cert' => true, ]; $db_var_prefixes = ['']; if ($config['poller_id'] > 1 || isset($rdatabase_hostname)) { $db_var_prefixes[] = 'r'; } $db_missing_vars = ''; foreach ($db_var_prefixes as $db_var_prefix) { foreach ($db_var_defaults as $db_var_name => $db_var_default) { $db_var_full = $db_var_prefix . $db_var_name; if (!isset(${$db_var_full})) { if ($db_var_default !== null) { ${$db_var_full} = $db_var_default; } else { $db_missing_vars .= (($db_missing_vars == '') ? 'missing ' : ', ') . $db_var_full; } } } } if (!empty($db_missing_vars)) { die("config.php is $db_missing_vars" . PHP_EOL); } // set the local for international users setlocale(LC_CTYPE, 'en_US.UTF-8'); $colors = []; // required for Windows if ($config['cacti_server_os'] == 'win32') { putenv('MIB_DIRS=c:/usr/share/snmp/mibs'); } if (!empty($path_csrf_secret)) { $config['path_csrf_secret'] = $path_csrf_secret; } else { $config['path_csrf_secret'] = CACTI_PATH_INCLUDE . '/vendor/csrf/csrf-secret.php'; } define('CACTI_CSRF_SECRET', $config['path_csrf_secret']); // built-in snmp support if ((isset($php_snmp_support) && $php_snmp_support == false) || !function_exists('snmpget')) { $config['php_snmp_support'] = false; } else { $config['php_snmp_support'] = class_exists('SNMP'); } define('CACTI_PHP_SNMP', $config['php_snmp_support']); // PHP binary location if (isset($php_path)) { $config['php_path'] = $php_path; } // Set various debug fields $config['DEBUG_READ_CONFIG_OPTION'] = defined('DEBUG_READ_CONFIG_OPTION'); $config['DEBUG_READ_CONFIG_OPTION_DB_OPEN'] = defined('DEBUG_READ_CONFIG_OPTION_DB_OPEN'); $config['DEBUG_SQL_CMD'] = defined('DEBUG_SQL_CMD'); $config['DEBUG_SQL_FLOW'] = defined('DEBUG_SQL_FLOW'); $config['DEBUG_SQL_CONNECT'] = defined('DEBUG_SQL_CONNECT'); // check for an empty database port if (empty($database_port)) { // @phpstan-ignore-line $database_port = 3306; } if (isset($input_whitelist)) { $config['input_whitelist'] = $input_whitelist; define('CACTI_WHITELIST', $input_whitelist); } // define any additional paths as constants foreach ($config as $key => $value) { if (str_ends_with($key, '_path')) { $path_name = substr($key, 0, -5); $constant_name = 'CACTI_PATH_' . mb_strtoupper($path_name); if (!defined($constant_name)) { define($constant_name, $value); } } } if (isset($i18n_handler)) { $config['i18n_language_handler'] = $i18n_handler; } if (isset($i18n_force_language)) { $config['i18n_force_language'] = $i18n_force_language; } if (isset($i18n_log)) { $config['i18n_log'] = $i18n_log; } if (isset($i18n_text_log)) { $config['i18n_text_log'] = $i18n_text_log; } // include base modules require_once(CACTI_PATH_LIBRARY . '/database.php'); require_once(CACTI_PATH_LIBRARY . '/functions.php'); require_once(CACTI_PATH_LIBRARY . '/graph_template_input.php'); require_once(CACTI_PATH_LIBRARY . '/renderer.php'); require_once(CACTI_PATH_LIBRARY . '/headers_secure.php'); require_once(CACTI_PATH_INCLUDE . '/global_constants.php'); define('CACTI_VERSION', format_cacti_version($cacti_version, CACTI_VERSION_FORMAT_SHORT)); define('CACTI_VERSION_FULL', format_cacti_version($cacti_version, CACTI_VERSION_FORMAT_FULL)); require_once(CACTI_PATH_LIBRARY . '/htmx.php'); require_once(CACTI_PATH_LIBRARY . '/html.php'); require_once(CACTI_PATH_LIBRARY . '/html_utility.php'); require_once(CACTI_PATH_LIBRARY . '/html_validate.php'); require_once(CACTI_PATH_LIBRARY . '/html_filter.php'); $filename = get_current_page(); if (isset($no_http_headers) && $no_http_headers == true) { $config['is_web'] = false; if (!str_contains(php_sapi_name(), 'cli') || !isset($_SERVER['argv'])) { print 'FATAL: This file can only be called from the command line.' . PHP_EOL; exit; } } if ($config['is_web'] && ini_get('session.auto_start') == 1) { print 'FATAL: PHP setting session.auto_start NOT supported. Disable in your php.ini file and then restart your Web Service' . PHP_EOL; exit; } // Test-mode DB sentinel: any method invocation on the sentinel throws, // so production-mode code paths can never silently treat a non-handle as // a real connection. Gated by the PHP_TESTING constant AND the // CACTI_TEST_BOOTSTRAP env var so a stale define alone cannot disable // real DB connection logic in a deployed environment. // Pass false to class_exists() so this guard never triggers autoload. if (!class_exists('Cacti_TestDbSentinel', false)) { final class Cacti_TestDbSentinel { /** * @param string $name * @param array $args * @return never */ public function __call($name, $args) { throw new \RuntimeException('PHP_TESTING DB sentinel called: ' . $name); } } } // Helper for "is this a real DB handle" checks that must reject the sentinel. // Defined inline so include/global.php remains self-contained. if (!function_exists('_cacti_is_real_db_conn')) { function _cacti_is_real_db_conn(mixed $x): bool { return is_object($x) && !($x instanceof Cacti_TestDbSentinel); } } // Resolve the test-bootstrap predicate once; both PHP_TESTING and // CACTI_TEST_BOOTSTRAP=1 must be set for the sentinel branches to engage. $is_test_bootstrap = cacti_is_test_bootstrap(); // set poller mode global $local_db_cnn_id, $remote_db_cnn_id, $conn_mode; $config['connection'] = 'online'; $ps = $config['is_web'] ? '

' : ''; $sp = $config['is_web'] ? '

' : PHP_EOL; $ul = $config['is_web'] ? '' : ''; $il = $config['is_web'] ? '' : ''; /* Git checkouts and interrupted upgrades do not ship include/vendor. Without this check the autoload require below produces a bare fatal error. */ // The installer can repair a missing vendor tree before application classes // are loaded. All normal entry points still fail closed with an actionable // message rather than emitting a raw require() fatal. $vendor_autoload = CACTI_PATH_INCLUDE . '/vendor/autoload.php'; if (!is_file($vendor_autoload) && !defined('IN_CACTI_INSTALL')) { print $ps . 'FATAL: The include/vendor directory is not populated. Cacti requires its Composer dependencies before it can start. From the Cacti directory, run:' . $sp; print $ps . 'composer install' . $sp; print $ps . 'Then reload this page. See the README for details.' . $sp; exit(1); } if ($config['poller_id'] > 1 || isset($rdatabase_hostname)) { if (!$is_test_bootstrap) { $local_db_cnn_id = db_connect_real($database_hostname, $database_username, $database_password, $database_default, $database_type, $database_port, $database_retries, $database_ssl, $database_ssl_key, $database_ssl_cert, $database_ssl_ca, $database_ssl_capath, $database_ssl_verify_server_cert); } else { $local_db_cnn_id = new Cacti_TestDbSentinel(); } if (!isset($rdatabase_retries)) { $rdatabase_retries = 2; } if (!isset($rdatabase_ssl)) { $rdatabase_ssl = false; } if (!isset($rdatabase_ssl_key)) { $rdatabase_ssl_key = false; } if (!isset($rdatabase_ssl_cert)) { $rdatabase_ssl_cert = false; } if (!isset($rdatabase_ssl_ca)) { $rdatabase_ssl_ca = false; } if (!isset($rdatabase_ssl_capath)) { $rdatabase_ssl_capath = false; } if (!isset($rdatabase_ssl_verify_server_cert)) { $rdatabase_ssl_verify_server_cert = true; } // Check for recovery if (_cacti_is_real_db_conn($local_db_cnn_id)) { $boost_records = db_fetch_cell('SELECT COUNT(*) FROM poller_output_boost', '', true, $local_db_cnn_id); if ($boost_records > 0) { $config['connection'] = 'recovery'; } } // gather the existing cactidb version (skip when running under the test sentinel). // Seed '' first so the key is always set, matching pre-sentinel behavior where a // failed db_fetch_cell returned ''; a remote poller with the local DB down then // reads a defined value instead of raising an 'Undefined array key' warning. $config['cacti_db_version'] = ''; if (_cacti_is_real_db_conn($local_db_cnn_id)) { $config['cacti_db_version'] = db_fetch_cell('SELECT cacti FROM version LIMIT 1', '', false, $local_db_cnn_id); } /** * If we have not been forced offline by the $conn_mode global and since we are * a remote poller, let's attempt to get back online. */ if ($conn_mode != 'offline') { if (!$is_test_bootstrap) { $remote_db_cnn_id = db_connect_real($rdatabase_hostname, $rdatabase_username, $rdatabase_password, $rdatabase_default, $rdatabase_type, $rdatabase_port, $database_retries, $rdatabase_ssl, $rdatabase_ssl_key, $rdatabase_ssl_cert, $rdatabase_ssl_ca, $rdatabase_ssl_capath, $rdatabase_ssl_verify_server_cert); } else { $remote_db_cnn_id = new Cacti_TestDbSentinel(); } } if ($config['is_web'] && _cacti_is_real_db_conn($remote_db_cnn_id) && $config['connection'] != 'recovery' && $config['cacti_db_version'] != 'new_install' && !defined('IN_CACTI_INSTALL')) { // Connection worked, so now override the default settings so that it will always utilize the remote connection $database_default = $rdatabase_default; $database_hostname = $rdatabase_hostname; $database_username = $rdatabase_username; $database_password = $rdatabase_password; $database_port = $rdatabase_port; $database_ssl = $rdatabase_ssl; $database_ssl_key = $rdatabase_ssl_key; $database_ssl_cert = $rdatabase_ssl_cert; $database_ssl_ca = $rdatabase_ssl_ca; $database_ssl_capath = $rdatabase_ssl_capath; $database_ssl_verify_server_cert = $rdatabase_ssl_verify_server_cert; } elseif (_cacti_is_real_db_conn($remote_db_cnn_id)) { if ($config['connection'] != 'recovery') { $config['connection'] = 'online'; } } else { $config['connection'] = 'offline'; } } else { if (!$is_test_bootstrap) { if (!db_connect_real($database_hostname, $database_username, $database_password, $database_default, $database_type, $database_port, $database_retries, $database_ssl, $database_ssl_key, $database_ssl_cert, $database_ssl_ca, $database_ssl_capath, $database_ssl_verify_server_cert)) { print $ps . 'FATAL: Connection to Cacti database failed. Please ensure: ' . $ul; print $li . 'the PHP MySQL module is installed and enabled.' . $il; print $li . 'the database is running.' . $il; print $li . 'the credentials in config.php are valid.' . $il; print $lu . $sp; if (isrv('display_db_errors') && !empty($config['DATABASE_ERROR'])) { // @phpstan-ignore-line print $ps . 'The following database errors occurred: ' . $ul; foreach ($config['DATABASE_ERROR'] as $e) { // @phpstan-ignore-line print $li . $e['Code'] . ': ' . $e['Error'] . $il; } print $lu . $sp; } exit; } } else { $local_db_cnn_id = new Cacti_TestDbSentinel(); } if (!$is_test_bootstrap) { if (!db_table_exists('settings') || !db_table_exists('version')) { print $ps . 'FATAL: Connection to Cacti database succeeded but `settings` table not found. Please ensure: ' . $ul; print $li . 'the PHP MySQL module is installed and enabled.' . $il; print $li . 'the database is running.' . $il; print $li . 'the cacti.sql has been imported.' . $il; print $li . 'the credentials in config.php are valid and correct.' . $il; print $lu . $sp; if (isrv('display_db_errors') && !empty($config['DATABASE_ERROR'])) { // @phpstan-ignore-line print $ps . 'The following database errors occurred: ' . $ul; foreach ($config['DATABASE_ERROR'] as $e) { // @phpstan-ignore-line print $li . $e['Code'] . ': ' . $e['Error'] . $il; } print $lu . $sp; } exit; } } // gather the existing cactidb version (skip when running under the test sentinel). // Seed '' first so the key is always set even under the test bootstrap. $config['cacti_db_version'] = ''; if (!$is_test_bootstrap) { $config['cacti_db_version'] = db_fetch_cell('SELECT cacti FROM version LIMIT 1'); } } define('CACTI_CONNECTION', $config['connection']); // check cacti log is available $log_filename = cacti_log_file(); if (!is_resource_writable($log_filename)) { print $ps . 'FATAL: System log file is not available for writing. Please ensure: ' . $ul; print $li . 'the log folder is correctly set.' . $il; if (CACTI_CLI) { print $li . 'the script was run as the website user. ' . $il; } print $li . 'the log folder is writable by the website user.' . $il; print $li . 'there is enough disk space.' . $il; print $lu . $sp; if (CACTI_CLI) { print $ps . 'To run as the website user, use sudo -u php -q