* security: contain data source paths within the RRA directory (GHSA-9qff-xccr-rrc5)
* security: escape executable path in exec_background on non-win32 (GHSA-cv7j-p6fh-j4q7)
* security: stop disabled accounts authenticating via remember-me cookie and revoke on disable (GHSA-pqrp-v752-mc4f)
* security: prefer base_url over client Host for the force_https redirect (GHSA-3466-fp7g-c9rf)
* changelog: add fragments for GHSA-9qff-xccr-rrc5, GHSA-cv7j-p6fh-j4q7, GHSA-pqrp-v752-mc4f, GHSA-3466-fp7g-c9rf
* security: address review findings on GHSA batch 2 (develop)
- lib/dsstats.php, poller_maintenance.php: route through
get_data_source_path() instead of expanding data_source_path directly,
so the RRA containment check applies to these consumers too (GHSA-9qff)
- lib/functions.php: data_source_path_within_rra() now also validates
the realpath of each existing ancestor segment, closing a symlink
pivot that the purely lexical check missed (GHSA-9qff)
- include/global.php: fail closed instead of falling back to SERVER_NAME
when base_url is unset/invalid, removing the residual open-redirect
path (GHSA-3466)
- lib/html_utility.php: cacti_force_https_host()/
cacti_build_https_redirect_url() now parse a scheme-less base_url
(matching lib/reports.php's own normalization) and preserve a
configured non-default port instead of silently dropping it (GHSA-3466)
- tests: move DataSourcePathContainmentTest.php into
tests/Unit/Security/PathTraversal/, and switch its fixtures from a
fictional CACTI_PATH_RRA path to a real temp directory so the new
realpath()-based checks (including a symlink-pivot regression case)
can actually run
* test: fix CACTI_PATH_RRA constant collision in DataSourcePathContainmentTest
CI showed 2 failures: RrdProxyProtocolTest.php runs earlier in the same
Pest process and already defines CACTI_PATH_RRA to a fictional,
non-existent '/var/lib/cacti/rra', so our own definition guard never
ran and realpath(CACTI_PATH_RRA) returned false for every assertion,
failing the two tests that expect containment to be true.
Now ensure the directory backing CACTI_PATH_RRA actually exists on
disk regardless of who defined the constant first, and gracefully
skip only the fixture-dependent assertions if this process can't
create it (e.g. no permission on a system path).
* test: refresh sink inventory baseline for exec_background rename
5fbe90fb4 renamed \ to \ in lib/poller.php's
exec() calls (GHSA-cv7j-p6fh-j4q7) but never regenerated the tracked
sink inventory, so the 'sink and architectural hotspot guard' CI check
has been failing on this branch since that commit. Regenerated via
tests/security/build_sink_inventory.sh | tr -d '\r' | LC_ALL=C sort;
diff confirms only those two lines changed.
* fix: address CI failures on GHSA batch 2 (develop)
- lib/auth.php: verify the remember-me token before revoking a disabled
account's cache row, so a forged token no longer triggers a database
write (fixes AuthSystemRegressionIntegrationTest)
- apply php-cs-fixer coding standard fixes (comment style, phpdoc
alignment, list-to-short-array syntax) flagged by the locked
integration check
- refresh the architectural hotspots baseline for the exec_background
safe_filename rename
* fix: update PHPStan baseline for reordered check_auth_cookie and get_data_source_path
- lib/auth.php: reordering the disabled-account check after the token
match moved one 'enabled' offset access to a new line, bumping the
file's expected occurrence count from 3 to 4
- lib/functions.php: the GHSA-9qff-xccr-rrc5 containment log line reads
\['data_source_path'], a genuinely new array|bool access
not previously tracked
---------
Co-authored-by: copilot-swe-agent <copilot-swe-agent[bot]@users.noreply.github.com>
* fix(poller): stop the registry pid probe narrowing a wide pid to -1
processes.pid is int(10) unsigned, so a corrupted row can hold 4294967295.
Through PHP 8.4 posix_kill() narrows that to a 32 bit pid_t of -1 and kill(2)
reads it as every process the caller may signal, so cacti_process_pid_exists()
calls a dead registry row live and the two kill sites that ask it first SIGTERM
the caller's whole session. From PHP 8.5 the same value raises a ValueError and
ends the poller instead. Refusing it ahead of the call covers both.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* hardening: bound every process table PID before signalling it
Twelve call sites read a pid out of a process table and handed it straight to
posix_kill(). processes.pid is int(10) unsigned, so a corrupted row can carry
4294967295, which narrows to a 32 bit pid_t of -1 and signals every process
the caller owns, or from PHP 8.5 raises a ValueError that ends the collector.
They now go through cacti_process_kill(), which refuses init, anything that
cannot name a process, and anything wider than pid_t.
poller_recovery read its pid with a bare posix_kill($pid, 0) and treats false
as "stale, clear the row and run". cacti_process_signalable() keeps exactly
that meaning, including EPERM as stale, and adds the bound. Neither
cacti_process_pid_exists() nor cacti_process_still_running() fits there: both
report a pid live when signalling it is merely forbidden, so a recycled
recovery_pid would have wedged recovery until an operator cleared the row.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test: place process guards in poller domain
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(poller): honor platform PID ranges
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test(poller): cover platform signal semantics
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(rrdtool): quote RRDfile paths passed to the command line
__rrd_execute() hands the assembled line to shell_exec(), and its docblock
already required callers to quote each variable argument.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(tests): widen bare RRD path sweep
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(rrdtool): quote binary path as one argument
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(rrd): preserve caller logging context
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Make RRD XML mutations transactional
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Harden RRDtool proxy transport handling
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(rrdproxy): reject invalid command argument lists
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(rrdproxy): preserve binary graph payloads
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Keep RRDtool proxy sessions encrypted
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Harden local RRDtool command and output handoff
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Eliminate graph rendering metadata lookups
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Batch DSStats metadata and last-value reads
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Batch interface speed metadata reads
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Update architectural hotspot baseline for RRD execution
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Batch RRD resolution profile lookups
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Batch DSStats summary aggregates
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Narrow phpseclib key types for the proxy encrypt and decrypt calls
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test(rrd): bound the forked proxy peers so a stall cannot hang the suite
The children accepted and read with no deadline while the parent used a 2s
socket timeout, so any mismatch left the child blocked in read and the parent
blocked in a bare pcntl_waitpid(). Note that accept() ignores SO_RCVTIMEO, so
readiness has to be waited on with socket_select() rather than a socket option.
Also corrects the array-command expectation, which still assumed develop's
escape-everything behaviour rather than rrdtool_build_command() leaving the
operation unquoted.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test(rrd): seed proxy settings where the real read_config_option looks
UnitStubs only declares its stub when lib/functions.php has not been loaded
yet, so during a full run the real database-backed function is the one in
effect and $unit_config_options was being ignored. The parent then dialled an
address the child was not listening on, which is what stalled the handshake.
Seeding the option cache keeps the settings visible to either implementation.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test(rrd): assert the shell-free RRDtool launch instead of the old quoting
__rrd_init now hands proc_open an argv array with bypass_shell, so escaping the
binary path would put the quotes inside the filename. The assertion still
expected the pre-argv line and had been failing unnoticed behind the suite hang.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test(dsstats): derive the expected stamps instead of assuming UTC
dsstats_poller_output() formats with date(), so the hardcoded UTC strings only
matched on a UTC runner. The failure was masked by the suite hang.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(rrd): keep RRDtool control codes intact in stdin commands
Escaping the backslash when converting a shell-quoted argument to an RRDtool
token left legends such as " IO Wait\:" ending in a dangling control code, so
RRDtool refused every graph carrying an escaped colon. Verified against RRDtool
1.7.2 in pipe mode; values with no unambiguous quoted form are now refused
rather than mangled.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test: use Cacti test bootstrap and config
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test: resolve includes through Cacti path constants
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test: match the escaping helpers by token, not by text
lib/rrd.php documents its escaping behaviour in a comment, and the regex
over raw source counted that as a call site.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* ci: refresh sink inventory after develop rebase
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test(rrd): prove argv isolation and concurrent pipe draining
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test(security): refresh sink inventory baseline after rebase onto develop
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix: guard process registration and cleanup against PID reuse
A bare posix_kill($pid, 0) only proves some process owns the pid, so a
recycled one blocked new tasks or drew a SIGTERM meant for something else.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test: spawn probe children without a wrapping shell
proc_open with a string command runs it via sh, so the child's /proc comm was
sh and the identity check correctly refused to signal it on Linux.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* docs: append the changelog entry at the end of the issue block
Matches .github/copilot-instructions.md, which appends rather than inserting
in numeric order.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix: limit the process identity check to CLI callers
The registry holds CLI tasks, so a web request comparing command names would
never match and would report a running task dead.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test: use the full licence header on the new test files
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test: treat CACTI_PATH_LOG as a directory and pass ctor args to the statement class
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix: make process identity checks self-contained
* fix(batchgapfix): guard forced process termination
* fix(processes): preserve live pids on permission errors
* Refresh security baselines after develop rebase
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Wait for the spawned child to exec before the identity check
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Serve fetch() from the buffered rows in the process registry fake
db_table_exists() probes with SHOW TABLES, and db_fetch_row_prepared()
reads a single row with fetch(); the fake drained the cursor on execute()
and answered neither, so every registry query came back empty and the
tests exercised the no-processes-table path instead of the guard.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(changelog): drop entries duplicated by an earlier rebase
Only the issue#7425 line belongs to this branch.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(poller): identify a registered process by its executable, not its name
Children are spawned from path_php_binary, so a versioned child and a parent
started as plain php reported different command names for one interpreter.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test(poller): skip the alias check where both paths share a command name
Whether a second path to the interpreter yields a second name is the runner's
business; asserting it failed where php is already the plain name.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
* fix(boost): forward still-open-round archive rows instead of dropping them
boost_process_poller_output() correctly excluded rows from an
in-progress poll round when building the RRD write, but the
archive-table cleanup right after deleted every row for the
local_data_id anyway, with no time filter. Those rows now get
INSERT IGNORE'd into the live poller_output_boost table before the
archive table is cleared, so a still-open round's data survives.
Also fixes the duplicate-record guard in the same function: $last_item
was initialized once before the results loop and never reassigned, so
the comparison against it could never be true. It is now updated after
each processed row.
Fixes#7519Fixes#7523
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(boost): gate archive-table drop on shard completeness
poller_boost.php decided whether to drop a run's archive tables using
SELECT SUM(status) FROM poller_output_boost_processes, an aggregate
over whatever completion rows landed. If one child crashed before
writing its completion row, the sum from the surviving shards was
still positive and every archive table for the run got dropped,
including the crashed shard's unprocessed data.
The drain loop already computes boost_completed_children() vs
$expected_children to detect a crashed child; the drop now uses that
same completeness check instead, and leaves the archive tables in
place with a warning when it is not met.
Fixes#7520
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(dsstats,rrdcheck): apply Boost's startup barrier to launch loops
dsstats_launch_children()/rrdcheck_launch_children() fork children via
exec_background() (non-blocking) and the callers then wait with a bare
while ($running = ..._processes_running($type)) { sleep(2); }. If the
parent evaluates that before any child has registered in the processes
table, $running reads 0 and the loop body never runs, treating "not
started yet" as "already done" -- the same race Boost's
boost_all_children_registered() barrier was added to close.
dsstats_launch_children()/rrdcheck_launch_children() now return the
launched count, and dsstats_wait_for_children()/
rrdcheck_wait_for_children() wait for the running count to reach it
(bounded by a deadline) before trusting the drain loop's running-count
poll.
Fixes#7526
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* refactor(poller): consolidate poller_item deletes behind a shared helper
Extract poller_item_delete_for_data_source() and poller_item_delete_for_host()
in lib/poller.php and migrate the existing local+remote delete pairs in
lib/api_device.php and lib/api_data_source.php onto them, so the
local-delete-plus-remote-mirror pattern lives in one place instead of eight.
Refs #7535
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(poller): mirror poller_item deletes to the remote poller on handoff
api_data_source_change_host(), data_query_remove_disabled_items(), and
data_source_to_data_template() each moved or removed a data source's
poller_item row locally but never on the remote Data Collector that
still owned it, so that Collector kept polling an item it no longer
should. All three now resolve the owning remote poller before the
local change and purge the row there too via the shared helper.
Fixes#7521Fixes#7524
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(poller): correct poller_item join semantics and cron reset in cmd.php
poller_item and host share ~13 identically-named snmp_*/hostname columns;
SELECT * across the join let PDO FETCH_ASSOC silently collapse them to
host's live values instead of poller_item's cached copies. Select pi.*
plus explicitly aliased host/site disabled columns instead. The now
already-fetched $poller_items also makes the separate script-server-call
COUNT(*) re-join redundant, replaced with an in-PHP count. Also apply the
same cron_interval-aware rrd_next_step reset used by the ranged branch to
the --allhost branch, which previously ignored it.
Fixes#7522, #7525, #7528.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(poller): thread known poller_id through update_poller_cache() callers
update_poller_cache() re-derived poller_id via a host/data_local JOIN on
every call, even when the caller already had it: repopulate_poller_cache()
and push_out_data_input_method() both already select h.poller_id and
discarded it, push_out_host() has the host row cached per data source, and
api_device_enable_devices()/update_poller_cache_from_query() compute it
once outside their loops. Add an optional $poller_id argument (and honor
an already-present 'poller_id' array key) so these callers skip the
redundant query.
Fixes#7527.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(poller): scope global aggregate scans and make process-leveling sargable
Three poller.php queries (total_snmp_ports and the mutually-exclusive
active_profiles pair) had no poller_id filter, unlike the rest of the
file's central-only blocks, so every remote poller redundantly recomputed
and overwrote the same global config options; the snmp_port scan is also
an unindexed full table scan. Gate them behind poller_id == 1, with remote
pollers reading back the value the central poller already stored.
Also rewrite the process-leveling $sql_where's rrd_next_step predicate
from the non-sargable `rrd_next_step - $poller_interval <= 0` to the
algebraically equivalent `rrd_next_step <= $poller_interval`, restoring
use of the poller_id_rrd_next_step index.
Fixes#7529, #7530.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(poller): batch update_data_query_cache()'s poller cache writes
Each changed data source called update_poller_cache($data_source, true),
which flushes immediately via poller_update_poller_cache_from_buffer() --
its own present=0 UPDATE, INSERT ON DUPLICATE KEY UPDATE, and stale-row
DELETE -- on every iteration. Re-indexing a host where every port changed
issued up to 3x as many statements as ports instead of one batched flush.
Accumulate changed rows across the loop and call update_poller_cache()
with commit=false, then flush once via poller_update_poller_cache_from_buffer()
after the loop, matching the batched pattern already used by
push_out_data_input_method().
Fixes#7531.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(boost): add USING BTREE to poller_output_boost_local_data_ids indexes
MEMORY-engine indexes default to HASH unless USING BTREE is specified,
unlike the sibling poller_output/poller_output_boost tables in the same
schema file. boost_output_rrd_data()'s ORDER BY/LIMIT query and
boost_process_local_data_ids()'s ranged DELETE both need to scan
local_data_id in order, which a HASH index can't serve, forcing a
filesort on every boost worker pass.
Verified against a real MariaDB 11 instance: EXPLAIN on the ORDER BY
query shows "Using filesort" with the old HASH schema and no filesort
with USING BTREE, using the primary key for ordering instead.
Also aligns boost_prepare_process_table()'s dead CREATE TABLE IF NOT
EXISTS fallback with the corrected schema, and adds an upgrade step
that rebuilds both indexes as BTREE on existing installs (checked via
information_schema.STATISTICS, since db_install_add_key() only compares
index columns, not algorithm, and would otherwise skip an existing HASH
index with the right column list).
Closes#7532
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(boost): skip the UNION ALL wrapper for the single-archive-table case
boost_process_local_data_ids() always wrapped its flush query in
"SELECT * FROM (... UNION ALL ...) t ORDER BY ...", even when there was
only one archive table -- the steady-state case, since old archive
tables are dropped at the end of each cycle. UNION ALL results wrapped
in a derived table are one of the documented cases MySQL/MariaDB's
derived-table merging doesn't apply to, so every pass paid for a
filesort over an indexless derived table regardless of how well-ordered
the underlying archive table's rows already were.
Extracts the per-table SELECT into boost_archive_select_sql() and uses
it directly, without the wrapper, when there's exactly one archive
table. The UNION ALL/derived-table shape is kept as the fallback for
the genuinely rare case where a prior crashed run left more than one
archive table behind.
Verified against a real MariaDB 11 instance and via a SQLite-backed
integration test that both shapes return correctly filtered and sorted
rows.
Closes#7533
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(boost): batch-prefetch RRD field name lookups instead of per-row queries
Two N+1 patterns in the Boost hot paths:
boost_process_local_data_ids() (poller_boost.php) re-ran the
unused_data_source_names/nt_rrd_field_names lookups on every
local_data_id boundary in the sorted flush result set.
process_poller_output() (lib/poller.php) had the equivalent lookup, but
without even a boundary check -- it re-ran per matching row whenever
the output contained a ':', including consecutive rows for the same
data source.
Adds poller_prefetch_rrd_field_names() (lib/poller.php, shared by both
callers since poller_boost.php already requires lib/poller.php) to
batch-fetch these mappings once per invocation via IN()-batched queries
keyed by the full set of local_data_ids in the batch, chunked at 1000
ids to match the array_chunk(..., 1000) convention used elsewhere
(lib/api_data_source.php, lib/api_graph.php). Both call sites now look
up the prefetched maps in-memory inside their loops.
While consolidating the lookups, fixes a latent bug the old
process_poller_output() code had in its non-templated fallback branch:
a query referencing gti.task_item_id without joining
graph_templates_item, which throws an unknown-column error whenever
that path is hit. The batched query always joins correctly.
Integration test proves both correctness (against a real SQLite-backed
schema) and the query-count reduction: 5 local_data_ids that would
previously cost up to 10 queries for these two lookups now cost exactly
3, regardless of how many ids are in the batch.
Closes#7534
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* refactor(boost): consolidate poller_output_boost inserts into one helper
The INSERT INTO poller_output_boost (...) VALUES ... statement was
hand-written four times: three in cmd.php (behind the same
boost_redirect == 'on' && boost_rrd_update_enable == 'on' guard) using
INSERT IGNORE, and once in boost_poller_on_demand() (lib/boost.php)
using ON DUPLICATE KEY UPDATE. Beyond the duplication, that was a real
data-integrity inconsistency between two paths writing the same shape
of data: silently-drop vs last-write-wins on the same key.
Since boost_redirect gates cmd.php's writes and boost_poller_on_demand()
returns early when boost_redirect is on, the two paths never run
concurrently for the same install; they're alternate write paths
selected by that one setting, not competing writers. A genuine
collision on (local_data_id, rrd_name, time) in either path represents
a retry of an already-collected sample -- a batch resent after a
partial failure, or two writers racing on the same rounded second --
not two independently meaningful readings, since RRD only stores one
value per timestamp regardless. Standardizes on INSERT IGNORE
(first write wins), matching the sibling poller_output insert this
table shadows, which has always used INSERT IGNORE, and 3 of the 4
existing call sites.
Extracts boost_flush_output_batch() (lib/boost.php) as the one shared
helper, handling both the batched INSERT and the max_allowed_packet-safe
chunking cmd.php's three sites previously didn't do at all (they only
chunked by item count).
Integration test exercises a genuine duplicate-key collision against a
real (SQLite-backed) table and asserts the chosen first-write-wins
behavior.
Closes#7536
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(tests): resolve cross-branch test interactions found by full-suite verification
Two test-only bugs surfaced only when all four fix branches for the
poller_item/Boost hardening pass ran together, not when each ran in
isolation:
- BoostArchiveHandoffIntegrationTest extracted the archive-forward INSERT
IGNORE by searching lib/boost.php for the first occurrence of "INSERT
IGNORE INTO poller_output_boost". The Boost SQL-optimization branch adds
a second, earlier occurrence (the new boost_flush_output_batch() helper's
single-line prefix), so the naive search now grabbed the wrong snippet.
Anchor on the multi-line "...poller_output_boost\n\tSELECT *" shape that
only the archive-forward statement has.
- PollerItemRemotePurgeHandOffTest calls push_out_host() (via
api_data_source_change_host()), which needs CACTI_PATH_LIBRARY. The test
never requires include/global.php (which normally defines it), and was
relying on some other file in a full multi-thousand-file suite run having
defined it first as a process-wide side effect. Define it directly
instead of depending on unguaranteed load order.
No production code changed.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* style(poller): satisfy php-cs-fixer strict_param in the script-server count
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(boost): order the single-archive-table flush by the table's PK columns
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(boost): key the max_allowed_packet cache per connection
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(tests): restore the default db_* session after swapping it for a fake handle
Left swapped, the fake handle answered every later read_config_option() in the
run; on PHP 8.4 the extra deprecation traffic routed through CactiErrorHandler
turned that into an uncaught PDOException and killed the pest process.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
* fix(errors): drop E_STRICT references deprecated on PHP 8.4
No diagnostic has raised E_STRICT since PHP 8.0 and composer.json floors at 8.1, so the handler entries were dead weight that made 8.4 emit a deprecation for every error it handled.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* style: align GPL header border and drop PHP 4 comment in aggregate error handler
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(poller): drop the E_STRICT entry from the poller error handlers
Nothing has raised that level since PHP 8.0 and reading the constant is
deprecated on 8.4, so each handled error emitted a second deprecation.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test: name the E_STRICT level numerically with a reason
Spelling the constant would reintroduce the deprecation the change removes.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(errors): drop the E_RECOVERABLE_ERROR guards and fold the poller tests in
The constant has existed since PHP 5.2 and the floor is 8.1, so both conditions are constant.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(poller): fold the always-true constant guards into the error maps
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
* fix: harden CSV encoding and process execution handling
Route CSV export cells through a shared cacti_csv_cell() helper so values with quotes, commas, or leading formula characters round-trip correctly, and add defensive guards on shell construction, unchecked proc_open handles, discarded exec exit codes, and gap values in nth-percentile fetch math.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix: address review feedback on CSV and process hardening
- escape poller_id in cmd_realtime proc_open command to match remote_agent
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix: keep numeric CSV cells unquoted and guard snmptrap and rrdtool process failures
- exempt numeric values from the CSV formula-injection prefix and preserve empty snmptrap positions while bailing out when the RRDtool process fails to spawn
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* ci: ignore phpstan function.alreadyNarrowedType identifier
Clears the level 6 alreadyNarrowedType error on lib/data_query.php:2591 that
develop currently emits, matching the sibling narrowing identifiers already ignored.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
* cleanup: remove unreachable defensive error logs in aggregate graph item validation
* fix: replace strtolower/strtoupper with locale-independent wrappers
PHP 8.5 deprecates locale-sensitive strtolower() and strtoupper().
Add cacti_strtolower() and cacti_strtoupper() wrappers that use
mb_strtolower/mb_strtoupper with explicit UTF-8 encoding, then
replace all 185 call sites across 65 files.
Existing mb_strtolower() calls (email normalization) are left as-is
since they already specify encoding explicitly.
Includes 18 Pest tests covering ASCII, UTF-8, protocol names, config
values, hex strings, NaN detection, and file extension patterns.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix: correct docblock references and month_shift() strpos check
Fix docblocks to reference strtolower()/strtoupper() (the deprecated
PHP builtins) instead of the wrapper names. Fix month_shift() to use
strpos() !== false instead of > 0 so 'month' at position 0 is matched.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* chore: drop unrelated lib/api_aggregate changes
Keep PR scope focused on the issue for this branch.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
* Fix#6539 - Allow Standard Cacti Log Files to be Rotated on Demand from the Cacti UI
* QA: php-cs-fixer updates and documentation for lib/database.php
* QA: Update .gitignore for composer files
* QA: Update .gitignore for composer files
* QA: Back off .gitignore change its duplicated
* QA: Fix typing of db_has_permissions
* QA: Fixing typing issue in db_format_index_create
* Update lib/clog_webapi.php
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* QA: Fix additional code issues
* QA: Applying more defaults to add device
* QA: Fix at the database layer
* QA: Fixes based upon automated testing
* QA: Fix at the database layer
* QA: After new SQL checking
* QA: Skip MacTrack oui database download warnings
* QA: Fixing type issue
* QA: Recommendation from Copilot
* QA: Recommendation from Copilot
* QA: Recommendation from Copilot
* QA: Recommendation from Copilot
* QA: Some minor changes to avoid errors in testing
* QA: Unused variable
* QA: Recommendation from Copilot
* QA: Recommendation from Copilot
* QA: Recommendation from Copilot
* QA: Minor introduced code defect
* QA: Adding some unifying space around concatination
* QA: Consistent comments and new function alias'
* QA: Multi-line comment corrections
* QA: Fix SQL Style
* QA: Missed one function alias
* QA: PHPStan Level 2 and PHPDoc fixes
* QA: PHPStan Level 2 and PHPDoc Fixes
* QA: PHPStan Level 6 Fixes
* QA: Fix lib/xml.php to PHPStan Level 6
* QA: Wrong grep -v
* PHPStan Level 6 for lib/vdef.php
* PHPStan Level 8 for lib/time.php
* QA: Raise PHPStan to Level 6 for lib/database.php
* QA: Remove @deprecated tag from grv()
* QA: Switch to htmle() alias over html_escape()
* QA: More addressing PHPStan issues at Level 6
* QA: Change recommended by Copilot for Readability
* QA: More PHPStan fixes
* QA: More PHPStan fixes
* QA: More PHPStan fixes for lib/utility.php
* QA: More PHPStan fixes at Level 6
* QA: Raise PHPStan to Level 6 More Functions
* QA: Raise PHPStan to Level 6 More Functions
* QA: Raise PHPStan to Level 6 More Functions
* QA: Raise PHPStan to Level 6 More Functions
* QA: Raise PHPStan to Level 6 More Functions
* QA: More PHPStan fixes at Level 6
* QA: Raise PHPStan to Level 6 More Functions
* QA: Raise PHPStan to Level 6 More Functions
* QA: More PHPStan fixes at Level 6
* QA: More PHPStan fixes at Level 6
* QA: Raise PHPStan to Level 6 More Functions
* QA: Raise PHPStan to Level 6 More Functions
* QA: More PHPStan fixes at Level 6
* QA: Raise PHPStan to Level 6 More Functions
* QA: Raise PHPStan to Level 6 More Functions
* QA: More PHPStan fixes at Level 6
* QA: Raise PHPStan to Level 6 More Functions
* QA: Raise PHPStan to Level 6 More Functions
* QA: More PHPStan fixes at Level 6
* QA: More PHPStan fixes at Level 6
* QA: More PHPStan fixes at Level 6
* QA: Raise PHPStan to Level 6 More Functions
* QA: More PHPStan fixes at Level 6
* QA: Some phpseclib3 fixups to update to latest
* QA: More fixups for graphing for magic strings
* QA: More PHPStan fixes at Level 6
* QA: Fixing order string type on get_allowed_graphs()
* QA: Fixing improper use of print
* QA: Optimization of color_templates
* QA: Copilot recommendation
* QA: Copilot recommendation
* QA: Copilot recommendation
* QA: Copilot recommendation
* QA: Copilot recommendation
* QA: Copilot recommendation
* QA: Copilot recommendation
* QA: Copilot recommendation
* QA: Raise PHPStan to Level 6 More Functions
* QA: Issues insalling Cacti
* QA: Issues insalling Cacti
* QA: Issues insalling Cacti
* QA: Issues insalling Cacti
* QA: Issues insalling Cacti
* QA: Issues testing Cacti
* QA: Copilot recommendation
* QA: More PHPStan fixes at Level 6
* QA: More PHPStan fixes at Level 6
* QA: More PHPStan fixes at Level 6
* QA: More PHPStan fixes at Level 6
* QA: More PHPStan fixes at Level 6
* QA: Installer failing
* Undo ownership changes to remove tar errors
* QA: Updates after php-cs-fixer
* QA: Adding code checks to syntax checking
* QA Change syntax flow language
* QA: Reduce the number of cures
* QA: Use sequentional option
* Change runner to self-hosted for check job
* Fix typo in OS version in workflow file
* Add Git installation step in workflow
Added step to install Git if not already installed.
* Add '-y' flag to apt-get install and update commands
Updated apt-get commands to include '-y' for automatic confirmation.
* Change workflow OS version to 'ubuntu-latest'
Updated the workflow to use 'ubuntu-latest' instead of 'ubuntu:latest' and modified the 'runs-on' configuration.
* Remove sequential option from phpcsfixer script
* Remove PHPCS fixer step from workflow
Removed PHP coding standards check from workflow.
* Move code checks to before plugins
Moving these core checks to before the plugins are installed as we really want to check only the base Cacti here.
* Add Composer validation and caching to start of workflow
* QA: Addressing PHPStan additional
* QA: Restore composer lock file
* QA: Addressing PHPStan additional
* QA: Addressing PHPStan additional
* QA: Complete LDAP Project
* QA: php-cs-fixer doing it's thing
* QA: Syntax is already happening in PHPStan
* QA: The md5 function no longer exists
* QA: Update after ldap changes
* QA: Undo that last password hack
* QA: Attempting to switch to MariaDB 11.8
* QA: Attempting to switch to MariaDB 11.8
* QA: Resolving some issues with PHP8.4
* QA: Resolving some issues with PHP8.4
* QA: Add PHP8.5 to the matrix
* QA: Removing PHP8.5 due to dependency issues
* QA: Add the DB version to the testing output
* QA: Switch back to MySQL for the moment
* QA: More fidelity in reporting
* QA: The pipe must be mixed
* QA: Moving audit_schema.php changes from 1.2.x
* QA: Moving audit_schema.php changes from 1.2.x
* QA: Restore mysql monitoring
* QA: php-cs-fixer issues
* QA: Attempt to roll back to MariaDB 10.6
* QA: Attempt to fix check_all_pages.sh
* QA: Make installer functional again
* QA: Fix installer php-cs-fixer issues
* QA: Type to a string
* QA: Get the database version correctly
* QA: Fix issue with reports in check_all_pages.sh
* QA: Check the server version in the right place
* Feature: Support SHA256 as a new hash for passwords
* QA: Function definition and return typing audit
* QA: Refinement of form types
* QA: Refinement of form types
* QA: Consistency of comments
* QA: Issues with php-cs-fixer and more comments
* QA: More typing issues
* QA: php-cs-fixer on merge conflict resolution
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This removes most entries to $config in the base code. A few new constants were defined to achieve this:
* CACTI_CONNECTION - The status of the current connection
* CACTI_CSRF_SECRET - The path to the csrf secret file
* CACTI_PHP_SNMP - True if php-snmp is installed
* CACTI_WHITELIST - The path to the Cacti Whitelist file if defined
I also moved several $config['poller_id'] to the existing POLLER_ID and move a number of $config['is_web'] to the existing CACTI_WEB constants.
There remain a few artifacts in the code. They will be handled in a separate commit.
* Add Rector
* Add Rector
* Fix "Cashed" misspelling
Also harmonize the phrase with the one below it
* Fix implode error in db_table_create
Only relevant when multiple primary keys
* Fix auth issue, parentheses in the wrong location
Was causing too many arguments to be sent to cacti_sizeof which only take 1, instead of get_allowed_devices.
* Fix issue with session_var not properly using class variable
* Fix pass by reference error in get_allowed_devices
get_allowed_devices has a pass by reference on the "total_rows" field. It is used in several places to pass the total_rows back to a counter. Here though, we are passing a number directly to the function, which errors out with "Uncaught Error: get_allowed_devices(): Argument #4 ($total_rows) cannot be passed by reference". So we will just place a dummy variable in front to catch the pass.
This wasn't caught before because the previous parentheses issue.
* Fix undefined variable "field_array" in html_filter
* Fix undefined function 'get_nfilter_filter_request_var'
* Fix undefined variable 'resizable'
I corrected the function parameter, as that was technically the incorrect spelling
* Fix invalid variable $user_id
* Fix undefined vars in spikekill class
* Fix copy and paste error
Oops
* Forgot to use the dummy variables
* Add missing $
Oops
* Start converting to newer PHP Syntax and Functions
* Test fixing the workflow by install composer
* Lets not valid the composer file right now
Seems we have a few things to fix it in
* Validate composer
* Lite formating changes
* lite formatting
* lite formatting
* lite format changes
* lite format changes
* lite format changes
* lite format changes
* lite format changes
* lite format changes
* lite format changes
* lite format changes
---------
Co-authored-by: TheWitness <thewitness@cacti.net>
* When you change the two dsstats modes, the dsstats command cache should be cleared and it is not.
* Use strpos() instead of substr_count() as it's way more efficient.
* Fixing #2572 - Ordering Graphs by Data Source
* Support Sorting of Tree or Graph View by Data Source
* This feature is not yet complete. The API modifications for ordering are not yet complete.
* This first commit covers much of the initial front end changes.
* Proposed API changes
* First working prototype of Graph Ordering
* Fixing bugs and applying to tree
* Updating the ChangeLog for #2572
* QA: Fix GitHub Action - Pre-checks
* Adds multiple percentile data to the Data Source Statistics tables
* Converts the Hourly Cache to InnoDB
* Some normalization rrdtool_function_graph() calling params outside of plugins
* Fix issues with variance and standard deviation calculations in graph_variables.php
* This change updates the various RRDtool function to use the pips if available
* It also adds the RRDtool pipes to the statisticsl function in graph_variables.php
* Lastly it starts to plumb in the advanced mode.
* There will be one more commit to finalize the new mode.
* Some PSR on php doc style function documentation
* DSStats Does not Scale on Large Systems
* DSStats will get invalid data for some devices after reboot requiring purging of stats
This is way less than what I want to do, but these are all essentially bugs.
There is also a change to lib/boost.php to provide better debug logging when testing.
- If Cacti runs out of resources (file descriptions via a ulimit) and DSStats is running, don't flood the Cacti log with errors
- When disabling the snmpagent, you can introduce significant delay to data collection
This should correct the boost and dsstat's issues. There were a few hands on this one and it had not undergone a full regression in a while.
This update should speed things up pretty signifcantly.
First, don't check the last_update time if rrdtool is >= 1.5
Second, we can accurately assume that the filemtime() is the last update time. This speeds things up dramatically.