diff --git a/include/global.php b/include/global.php index 572607b1f4..1d26ec84fb 100644 --- a/include/global.php +++ b/include/global.php @@ -300,6 +300,32 @@ 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 { + 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($x) { + 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 = (defined('PHP_TESTING') && getenv('CACTI_TEST_BOOTSTRAP') === '1'); + // set poller mode global $local_db_cnn_id, $remote_db_cnn_id, $conn_mode; @@ -313,8 +339,10 @@ $il = $config['is_web'] ? '' : ''; if ($config['poller_id'] > 1 || isset($rdatabase_hostname)) { - if (!defined('PHP_TESTING')) { + 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)) { @@ -346,7 +374,7 @@ } // Check for recovery - if (is_object($local_db_cnn_id)) { + 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); @@ -355,20 +383,24 @@ } } - // gather the existing cactidb version - $config['cacti_db_version'] = db_fetch_cell('SELECT cacti FROM version LIMIT 1', '', false, $local_db_cnn_id); + // gather the existing cactidb version (skip when running under the test sentinel) + 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 (!defined('PHP_TESTING')) { + 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'] && is_object($remote_db_cnn_id) && $config['connection'] != 'recovery' && $config['cacti_db_version'] != 'new_install' && !defined('IN_CACTI_INSTALL')) { + 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; @@ -381,7 +413,7 @@ $database_ssl_ca = $rdatabase_ssl_ca; $database_ssl_capath = $rdatabase_ssl_capath; $database_ssl_verify_server_cert = $rdatabase_ssl_verify_server_cert; - } elseif (is_object($remote_db_cnn_id)) { + } elseif (_cacti_is_real_db_conn($remote_db_cnn_id)) { if ($config['connection'] != 'recovery') { $config['connection'] = 'online'; } @@ -389,7 +421,7 @@ $config['connection'] = 'offline'; } } else { - if (!defined('PHP_TESTING')) { + 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; @@ -408,9 +440,11 @@ exit; } + } else { + $local_db_cnn_id = new Cacti_TestDbSentinel(); } - if (!defined('PHP_TESTING')) { + 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; @@ -432,8 +466,10 @@ } } - // gather the existing cactidb version - $config['cacti_db_version'] = db_fetch_cell('SELECT cacti FROM version LIMIT 1'); + // gather the existing cactidb version (skip when running under the test sentinel) + if (!$is_test_bootstrap) { + $config['cacti_db_version'] = db_fetch_cell('SELECT cacti FROM version LIMIT 1'); + } } define('CACTI_CONNECTION', $config['connection']);