Commit Graph
321 Commits
Author SHA1 Message Date
TheWitnessandTheWitness 3ab4c69fb7 PHPStan Level 8 - Phase 1 Conversion (#8135)
* PHPStan Level 8 - Phase 1 Conversion

Reduces the real (no-baseline) PHPStan level 8 error count from ~2,724 to 425
and regenerates phpstan-baseline.neon accordingly (8,941 -> 1,921 lines).

Core type-safety changes
- lib/database.php: narrow db_fetch_row()/db_fetch_row_prepared() to return
  array (was bool|array) and db_fetch_assoc()/db_fetch_assoc_prepared() to
  return array (was mixed); the _prepared workers coerce the connection-failure
  path to an empty array so the declared type is honest. empty()/cacti_sizeof()
  checks are unaffected.
- lib/database.php: narrow $db_conn guards from is_object() to instanceof PDO in
  the transaction/quote/insert-id helpers so the PDO calls resolve.
- lib/mib_cache.php: the fluent MibCache methods mib()/object()/table()/row()
  now return self instead of a bare object so chained calls type-check.

Type-safety casts and guards (mechanical, behaviour-preserving)
- Cast string|false/|null builtin results feeding string parameters
  (json_decode, base64_encode, trim, htmle, preg_replace, explode, etc.).
- Cast int|string / int|false values feeding int id/pid parameters.
- Guard resource|false fopen()/popen() handles before fwrite/fclose/fread, and
  wrap foreach over array|false/list|false results with `?: []`.

Genuine bugs found and fixed
- cli/add_tree.php / lib/api_tree.php: site tree nodes could not be created via
  the CLI - the --site-id option was never parsed, $nodeTypes had no 'site'
  entry (passing null to api_tree_item_save(int $type) -> TypeError), and
  api_tree_item_save() never persisted site_id. All three fixed.
- tree.php: leaves_exist() is typed : int but returned db_fetch_assoc_prepared()
  (an array of rows) for a SELECT COUNT(*); now uses db_fetch_cell_prepared()
  and returns the scalar count.
- lib/mib_cache.php: $entries_per_object was a float (division / ceil) used as
  an array key (PHP 8.1 implicit float-to-int key deprecation); now cast to int.

Config
- .phpstan.neon: add notIdentical.alwaysTrue and booleanAnd.alwaysFalse to the
  existing always-true/false ignore family (same "PHPStan gets confused"
  dynamic-constant cause already covered by identical.alwaysTrue,
  notEqual.alwaysTrue, booleanAnd.leftAlwaysTrue, etc.).
- phpstan-baseline.neon: regenerated; the remaining 425 entries are the
  untouched long tail for Phase 2.

* Remove redundant @phpstan-ignore comments

Follow-up cleanup for the level 8 Phase 1 work. Removes 14 inline
@phpstan-ignore comments that no longer suppress any reported error:

- cli/repair_database.php: `@phpstan-ignore booleanAnd.alwaysFalse` is now
  covered by the global booleanAnd.alwaysFalse ignore added to .phpstan.neon.
- 13 others (host.php, include/global.php, include/session.php,
  lib/api_automation.php x2, lib/functions.php x2, lib/html_utility.php x2,
  lib/installer.php, lib/package.php, lib/reports.php, lib/rrd.php) no longer
  match any reported error after the type-safety fixes (e.g. the
  db_fetch_row_prepared() return-type narrowing removed the offset-assign
  errors the api_automation ignores were suppressing).

Verified with reportUnmatchedIgnoredErrors enabled: 0 unmatched ignores
remain and the full level 8 run still reports 0 errors.

* ci: satisfy validator ratchet and refresh security sink baselines

- lib/api_tree.php: give the new tree-item site_id a real numeric
  constraint ('^[-0-9]+$', matching html_reports.php) instead of an
  empty regex, so the validator constraint ratchet passes without
  bumping its baseline.
- tests/security/baselines: refresh the three sink/hotspot signatures
  whose code text changed from the (string) casts added in this branch
  (lib/poller.php setsid, cli/rrdresize.php json_encode, lib/import.php
  simplexml_load_string).

* ci(js): allow DOMPurify 3.4.16 and update sync_js pin test

The dependabot bump (#8134) moved package.json/package-lock.json to
DOMPurify 3.4.16 on develop but left tests/js/sync_js.test.mjs pinned to
3.4.15, so the PR merge commit fails the 'DOMPurify is pinned' contract.
Bring package.json/package-lock.json to 3.4.16 (matching develop) and
update the pin test to expect 3.4.16.

* test: update sql.php null-handling assertion for the (string) cast

This branch changed scripts/sql.php from trim(\ ?? '') to
trim((string) \) (the phpstan-safe form that coerces both null and
false from shell_exec()). Update SqlScriptsTest's null-return assertion
to match the new cast instead of the removed '?? ''.

* fix: address Copilot review - ownership IDs, fopen guards, mib_cache bound

- chown()/chgrp(): pass integer UID/GID from fileowner()/filegroup()
  (guarding the false lookup) instead of string-casting them, which made
  them resolve account/group names like '1001' and fail. Covers
  lib/api_device.php, lib/boost.php and poller_maintenance.php (x2).
- fopen() failure now short-circuits instead of reporting success:
  lib/package.php returns false, lib/clog_webapi.php and utilities.php
  render an error, and snmpagent_mibcachechild.php skips the cache write
  and sentinel unlink so a concurrent writer's lock is preserved.
- lib/mib_cache.php: keep the float quotient as the loop bound and cast
  only the array offset, so a non-even entries/columns split no longer
  truncates the loop and drops a cached row.

* revert: keep db_fetch_* returning false on error; baseline Level 8 instead

Per review, restoring the intentional fail-safe: db_fetch_row[_prepared]()
return bool|array and db_fetch_assoc[_prepared]() return mixed again, so a
genuine SQL error (not just a missing connection) is signalled as false and
the === false checks in cdef_is_in_use(), boost_process_* and aggregate.php
work as before. Dropped the is_array() ? : [] coercion.

Level 8 stays green by regenerating phpstan-baseline.neon to capture the
re-introduced bool|array/mixed call-site errors (the ~1,850 the narrowing
had eliminated). Tightening those callers to drop them from the baseline is
Phase 2 work.

---------

Co-authored-by: TheWitness <copilot@users.noreply.github.com>
2026-10-02 10:08:56 -04:00
TheWitness d4f2bd1f3b Restore legacy unique_keys[] support in the schema API (#8116)
* Restore legacy unique_keys[] support in the schema API

develop expresses a unique index as a `keys[]` entry with `'unique' => true`,
and both db_table_create() and db_update_table() were rewritten to only read
that form. Plugins that still use the legacy `unique_keys[]` definition style
(supported on 1.2.x) therefore get no UNIQUE index created, and any existing
one dropped on the next schema refresh.

Fold a legacy `unique_keys[]` definition into `keys[]` with the unique flag at
the top of both functions, so the existing unique-aware key handling applies
to it. New-style `keys[]` with `'unique' => true` continues to work unchanged.

* CHANGELOG: reference PR #8116

* Fix CHANGELOG format and add unique_keys[] regression coverage

CHANGELOG: use the -issue#8116 entry form (the -bug prefix and trailing (#NNNN) failed the CHANGELOG format check) and place it in the issue block so the security-issue-feature ordering holds. Adds DbUpdateTableUniqueKeysTest covering db_table_create() emitting the UNIQUE index and db_update_table() adding a missing one and preserving an existing one for a legacy unique_keys[] definition. FakeMySQLPDO now projects Seq_in_index/Column_name/Non_unique for named secondary indexes so the reconciliation path can be exercised.
2026-09-29 23:08:41 -04:00
TheWitness a724239758 fix: Allow api_plugin_db_table_create to do updates (and correct default drift) (#8113)
* fix: allow api_plugin_db_table_create to update, and correct default drift

Mirrors the 1.2.x change so api_plugin_db_table_create() refreshes an
existing plugin table via db_update_table() instead of silently doing
nothing, recording the change in plugin_db_changes as a 'create'.

Also teaches db_update_table() to compare the column default, so a
refresh reconciles a column whose live default has drifted from the
canonical definition (e.g. DEFAULT NULL where the definition specifies
DEFAULT ''). Date/time defaults are left to the type comparison to
avoid churn.

* fix: do not re-record plugin_db_changes when refreshing an existing table

Mirrors the 1.2.x fix: plugin_db_changes has no unique key, so REPLACE INTO
on the update path duplicated the 'create' ownership record on every call and
could mark a pre-existing/shared table as plugin-created (dropped on uninstall
by api_plugin_db_changes_remove()). Update existing tables without inserting
another ownership record.
2026-09-29 21:45:46 -04:00
Thomas Vincent cb5ef7ba4a fix: commit transactions on MySQL as well as MariaDB (#7928)
* fix: commit transactions on MySQL as well as MariaDB

db_commit_transaction() gated the commit on SELECT @@in_transaction. That
variable is MariaDB only; MySQL answers 'Unknown system variable
in_transaction', so the guard was false on every MySQL install and the commit
never ran. The transaction was left open and the function reported failure.

Verified against both engines: MariaDB returns 0, MySQL 8 raises ERROR 1193.

Every caller was affected, including device removal, which reports 'Unable to
remove Device N because its database transaction could not be committed' and
leaves the device in place.

Use PDO::inTransaction(), which tracks the state itself and answers the same
question on both engines.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* Update transaction regression assertion

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-09-02 16:54:17 -07:00
Thomas Vincent ac0b2c9515 Fix confirmed poller, database, automation, and UI defects (#7861)
* issue#7024: do not delete still-running poller_time rows on cycle start

The overrun path deleted every poller_time row for the poller, including the
in-flight ones it had just counted, so overrunning processes lost their
tracking. Restrict the delete to completed rows.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7028: restore identifier and connection safety in the db layer

_db_replace interpolated caller-supplied column keys as backtick identifiers
without escaping, and db_commit_transaction checked @@in_transaction on the
default connection rather than the one passed in. Both were correct on 1.2.x
and regressed on develop.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7341: size the CSV export maxrows to the requested span

The export hardcoded --maxrows=10000, truncating wide date ranges regardless
of the data source profile step.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7421: offset automation_get_next_host through the 32-bit integer

The hand-rolled octet walk subtracted the wrong magnitude per octet and
emitted out-of-range octets for ranges wider than a /16.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7030: skip the poller_output ENGINE=MEMORY conversion when already applied

The output-table refresh ran ALTER TABLE poller_output ENGINE=MEMORY every
cycle even though the swapped-in copy already inherits the engine, adding a
metadata-locking DDL to the hot path. Probe the current engine and convert
only when needed.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7342: prefer snmpEngineTime only when it covers system uptime

update_system_mibs preferred snmpEngineTime (x100) over sysUpTime whenever it
was present, so an SNMP engine that restarts independently of the system (for
example OpenBSD snmpd) made the stored uptime drop, which reads as a reboot
and forces a perpetual reindex with mass 'U' results. Capture both values and
use snmpEngineTime only when it is at least the system uptime, which still
covers the 32-bit sysUpTime wrap.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7403: scale lmsensors readings in the get path like the query path

The get path returned the raw milli-unit SNMP counter while the query path
divided voltage and temperature by 1000 (and corrected negative voltages), so
a get gave values 1000x too large. Apply the same scaling before returning.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7029: propagate the reconnected handle back to the caller

db_check_reconnect() closed the dead connection, opened a new one, and
returned true without giving the new handle back, so the db_execute_prepared
retry ran its query against the connection that was just closed. Take $db_conn
by reference and assign the fresh handle; the one caller that passed a literal
false now passes a variable.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* test: refresh sink inventory baseline

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* test: refresh architectural hotspots baseline

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7474: return valid after correcting the theme to selected_theme

is_valid_theme() corrected an invalid theme to selected_theme but left $valid
false, so callers saw the theme as invalid even though it had been fixed.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7462: allow SELECT aliases as sortable automation columns

site_name and host_template_name are aliases (s.name AS site_name), valid in
ORDER BY but not real columns, so api_automation_column_exists() rejected them
and the site-name sort silently failed.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7456: bind the OS value in the automation OS filter

The OS filter built 'os = ?' but bound the network value, so filtering by OS
matched on the wrong input.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7491: filter host_template_id against its own column

html_make_device_where() compared h.location to the host_template_id value
instead of h.host_template_id.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7479: apply the hostname and description filters in get_hosts

The REST get_hosts() honored host_id/poller/site/template/location but ignored
the hostname and description filters its callers pass; add them as bound LIKE.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7475: authorize a direct client-IP poller match before the hostname guard

A poller configured by IP with no hostname-based pollers was rejected because
the empty-hostname guard ran before the direct-match check, even though the
client address exactly matched a poller's configured address.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* Guard two divisors that PHP 8 makes fatal

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* docs: consolidate functional fix changelog

* test: cover consolidated functional fixes

* docs: identify consolidated functional fixes

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-09-01 21:01:28 -07:00
Thomas Vincent 11117e812d fix(php85): remove test and authenticator deprecations (#7632) 2026-08-29 17:38:51 -07:00
Thomas VincentandTheWitness 4238aadcb1 security: escape LIKE wildcards in poller_automation and plugins.php filters (#7670)
* security: add db_like_escape and bind the PTR hostname in poller_automation

poller_automation.php concatenated the gethostbyaddr() result into a LIKE clause, so an attacker controlling the reverse-DNS zone injected SQL through a crafted PTR record. The new db_like_escape() helper neutralises the LIKE metacharacters, and the reverse-DNS hostname is now passed as a bound parameter (LIKE ?). The escape semantics are covered by unit tests and verified against a live MariaDB.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* security: bind and LIKE-escape the plugins.php filter (GHSA-jjfq-mj5r-2r95)

The plugins.php filter reached its LIKE clauses through db_qstr(), which escapes quotes but not the LIKE metacharacters, so a filter of '%' matched every row and bypassed the state filter. The filter LIKE clauses are now bound placeholders and the value is LIKE-escaped once with db_like_escape() and reused for each placeholder; the count and list queries are prepared.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
2026-08-16 21:15:44 -04:00
Thomas Vincent cde946d9dd fix: repair regressions found in recent develop audit (#7614)
* Fix regressions found in recent commit audit

* fix(database): fetch only the requested first row

* fix(tests): use native PDO fetch behavior

* test(orb): clarify unavailable runtime skips

* test(database): assert malformed metadata query path

* revert(tests): leave macOS Orb integration out of scope
2026-08-08 11:26:44 -07:00
Thomas Vincent 8bcc4b8bbf perf(database): batch db_update_table schema alters (#7589)
* perf(database): batch table schema alters

* fix(database): harden batched alter generation
2026-08-06 17:30:45 -07:00
Thomas Vincent cc8a9d49b4 fix: correct KILL CONNECTION typo in db_close (#7432)
* fix: correct KILL CONNECTION typo in db_close

CONNECTIION_ID() is not a function, so the guarded force-close of
non-persistent connections at db_close() has always errored silently and
never ran. Correct it to CONNECTION_ID() so the intended teardown behaviour
works; persistent connections stay exempt via the existing guard.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* test(database): add regression coverage for KILL CONNECTION fix

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-07-28 18:55:41 -07:00
Thomas VincentandTheWitness c3df72d11e fix(db): map empty value to NULL for nullable numeric columns in sql_save (#7337)
* fix(db): map empty value to NULL for nullable numeric columns in sql_save

Nullable numeric columns previously coerced an empty incoming value to 0
instead of SQL NULL; the NOT-NULL/no-default branch is unchanged since it
has no safe NULL fallback.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(db): preserve numeric zero when mapping empty to NULL in sql_save

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* test(security): refresh sink/hotspot baselines for upstream line shifts

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
2026-07-24 22:54:08 -04:00
Thomas VincentandTheWitness 04f0088427 fix: memoize db_get_table_column_types to cut redundant SHOW COLUMNS (#7381)
* fix: memoize db_get_table_column_types to cut redundant SHOW COLUMNS

sql_save() calls db_get_table_column_types() for every write, and it ran
SHOW COLUMNS with no caching, so bulk operations re-query the same tables
hundreds of times (creating 30 graphs issued ~540 identical lookups). Cache
the result per request and clear it in the column-mutating DDL helpers so the
installer/upgrade path stays correct.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* test: cover db_get_table_column_types memoization and invalidation

Prove the cache is transparent (identical output cached vs fresh), can be
cleared, keys per connection, and that every column-mutating DDL helper clears
it so sql_save() sees the new schema after an ALTER.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* test(security): refresh sink/hotspot baselines for upstream line shifts

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
2026-07-24 21:24:02 -04:00
Thomas VincentandTheWitness 364be26ffa chore(test): gate test-only DB short-circuit behind CACTI_TEST_BOOTSTRAP env (#7128)
* chore(test): gate test-only DB short-circuit behind CACTI_TEST_BOOTSTRAP env

The previous `!defined('PHP_TESTING')` guard skipped db_connect_real
whenever PHP_TESTING was set anywhere in the include chain. A stray
define from a CLI tool or third-party script could disable real DB
connection logic in a deployed environment.

Switch the guard to require both `PHP_TESTING` and an explicit
`CACTI_TEST_BOOTSTRAP=1` env var, and replace the silent-no-connect
path with a `Cacti_TestDbSentinel` whose every method throws. Any
production code path that accidentally treats the sentinel as a real
DB handle will fail loudly instead of silently misbehaving.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(test): apply Copilot review feedback for test DB sentinel guard

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* test: collapse remaining test-bootstrap guard to $is_test_bootstrap

The previous review fix introduced a local $is_test_bootstrap flag but left
two call sites still inlining defined('PHP_TESTING') && getenv(...). Replace
the long-form check and guard the single-poller cacti_db_version fetch so the
sentinel branch finishes initialising without invoking the DB.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* chore(test): arm CACTI_TEST_BOOTSTRAP in test bootstraps and fail closed on PHP_TESTING alone

Set the env var where PHP_TESTING is defined so legitimate test runs short-circuit the DB connect again, gate the config.php.dist swap and schema-validation skip on the combined predicate, read it with local_only, and always seed cacti_db_version.

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>
2026-06-24 12:34:16 -04:00
Thomas VincentandTheWitness 20298c6cd6 fix(defensive): harden helper library input and output handling (#7197)
* fix(defensive): harden helper library input and output handling

lib/html_utility.php: update_order_string() wrote sort_direction from
the request directly into the session without enforcing ASC/DESC.
Normalise to strtoupper and allowlist-check before every session write;
default to ASC for any unrecognised value.

lib/functions.php:
- sanitize_uri(): add protocol-relative URL guard (leading '//' after
  ltrim). The function stripped most dangerous chars but had no entry
  for '//', which browsers treat as a scheme-relative redirect target.
- is_valid_pathname(): the regex allows literal dots so '../..' passed
  validation. Reject any path containing '..' before the regex check.

lib/database.php: db_qstr() fallback (no PDO connection) was missing
\x1a (Ctrl+Z) from its escape map. PDO quote() handles it; the fallback
now matches.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(defensive): correct hardening edge cases flagged in review

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(defensive): reject control-char redirect and drive-relative traversal

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>
2026-06-24 10:41:39 -04:00
Thomas VincentandTheWitness bb952a5967 validate graph template-id list and harden percentile math (#7198)
* fix: validate graph template-id list and harden percentile math

Add db_in_clause() for integer-validated IN() construction and route the graph template-id filter through it so multi-select values resolve to integers; pass the title regex filter through db_qstr(); and add a bounds guard plus gap-value filtering to the nth-percentile statistics.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix: drop non-numeric ids in db_in_clause and skip missing dq_ lookups

- intval() coerced garbage and missing rows to 0, a meaningful template id

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix: skip non-numeric cg_ ids and route tree filters through db_in_clause

- raw IN interpolation produced IN () on an empty template set, a SQL syntax error

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>

* fix: add missing PCRE delimiters to graph_template_id regexp filter

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* ci: re-trigger flaky poller-timing check

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
2026-06-24 10:09:45 -04:00
Thomas VincentandTheWitness 7a8ab8b4eb feat(db): add optional per-statement timeout to db_* functions (#7209)
* feat(db): add optional per-statement timeout to db_* functions

Adds a trailing $timeout argument (seconds, decimals allowed) to
db_execute*/db_fetch_* that caps server-side execution time. MariaDB
uses SET STATEMENT MAX_STATEMENT_TIME, MySQL uses the MAX_EXECUTION_TIME
optimizer hint on SELECTs; 0 (default) keeps current behavior. Forum
request t=64124.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* ci: ignore phpstan function.alreadyNarrowedType identifier

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
2026-06-23 16:48:01 -04:00
TheWitnessandCopilot 9d8bf959f5 fix: Fixing Unit tests (#6790)
* fix: Fixing Unit tests

* fix: Include helper remove the defines

* Enable Pest tests for validation

* Update tests/Unit/ScriptsHardeningTest.php

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update tests/Helpers/CactiStubs.php

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update tests/Unit/SsHstatsTest.php

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update rector.php

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update tests/Unit/HtmlEscapeTest.php

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix: Remove comments that are no longer required

* fix: Move xdebug to the correct location

* fix: Be consistent with includes

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-07 12:57:00 -05:00
Thomas VincentandTheWitness 3c70f3a4e8 hardening: escape db_dump_data exec arguments (#6769)
* hardening: escape db_dump_data exec arguments

Use cacti_escapeshellcmd for dump binary, cacti_escapeshellarg for
username, password, database, tables, and output_file to prevent
command injection when config or parameters contain shell metacharacters.

* fix: escape each table name individually in db_dump_data()

cacti_escapeshellarg() on a space-separated table list passes it as a
single quoted argument to mysqldump, which then fails to match any
tables. Split on whitespace and escape each name separately, joining
with spaces. Empty string (all-tables) continues to work unchanged.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
2026-03-06 22:47:45 -05:00
3e1dc3c886 fix: replace strtolower/strtoupper with locale-independent wrappers (#6717)
* 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>
2026-03-05 19:51:02 -05:00
TheWitness 39846b4df2 Diagnosis and Resolution of Recursion Errors in Debug (#6686)
* Adding background capture of top processes

* Run this periodically

* Slight tweak to remove warings

* Re-introduce memory issue

* Switch to debug and attempt to locate the page

* Chagne to 2 second granularity to better trace

* Allow test to run now, keeping at debug

* Fixing infinite recursion on connection went away

* Change logging profile for 2002 and 2006 errors

* Correcting php-cs-fixer issue

* Add priority to syslog message

* One more pass to check for completion

* Reenable Debug for run

* Per copilot check_all_pages.sh not saving logs first

* Re-enable execption mode

* Fix php-cs-fixer issue

* Removing debug flag now

* Checking with non-persistent connections

* Final changes before merge
2026-03-02 11:49:10 -05:00
Thomas Vincent b400242618 fix: use strict comparison for $db_conn sentinel in db_table_exists and db_column_exists (#6679)
Both functions use $db_conn == false to detect the "use global
connection" sentinel. Loose comparison also matches 0, '', and null,
which silently falls back to the global connection instead of surfacing
a caller bug.

Replace with === false.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-03-01 11:22:03 -05:00
TheWitness 102e575e1c Partial rollback of database change (#6666)
* Partial rollback of database change

* Roll back array_map change

* More rollback searching for memory exhaustion solution

* One more try for solving memory issue

* Memory leak identified, rolling back one at a time

* One more roll back in search of the leak

* Seeing if we can preserve exception mode

* Switching to silent error mode due to memory leak
2026-02-28 11:01:40 -05:00
Thomas VincentandTheWitness a36c7a2503 fix: escape values in array_to_sql_or() to prevent SQL injection (#6648)
* fix: escape values in array_to_sql_or() to prevent SQL injection

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* style: apply php-cs-fixer formatting

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
2026-02-25 11:39:18 -05:00
TheWitness 9e4820a362 Removing src-* files over composer process (#6646)
* Removing src-* files over composer process

* Fixing PHP-CS-Fixer and PHP-Stan issues

* Minor fixups to format
2026-02-25 15:36:10 +00:00
Thomas VincentandTheWitness 4fb1ad6cfd fix: switch PDO to ERRMODE_EXCEPTION with proper error handling (#6627)
* fix: switch PDO to ERRMODE_EXCEPTION with proper error handling

- Fix $flage typo to $flags so PDO error mode flag is actually applied
  to connections via constructor
- Switch from ERRMODE_SILENT to ERRMODE_EXCEPTION for fail-fast error
  detection instead of silent query failures returning false
- Move prepare() inside existing try/catch in db_execute_prepared() so
  SQL syntax errors are caught consistently
- Wrap db_cacti_initialized() direct PDO calls in try/catch so
  uninitialized databases show the friendly setup message instead of
  crashing with an uncaught PDOException

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix: remove redundant setAttribute after constructor flag fix

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
2026-02-24 08:10:29 -05:00
TheWitness 27919c50d7 Updating Copyrights to 2026 (#6578) 2026-01-28 16:11:10 -05:00
TheWitnessandCopilot b993471579 Fix #6539 - Allow Standard Cacti Log Files to be Rotated on Demand,PHPdoc, and Typing (#6540)
* 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>
2026-01-25 10:13:56 -05:00
TheWitness 3d7cd822b8 QA: Fixing additional files phpdoc specs 2025-11-18 23:22:24 -05:00
TheWitness c0afb6a976 Preparing for phpstan analysis by adding bootstrapFiles 2025-11-18 21:47:55 -05:00
TheWitness b9334a15d9 QA: Updates for files for php-cs-fixer and some php-doc changes 2025-11-18 21:01:15 -05:00
Petr Macek 0118b3cd35 fix #6185 - add test if database contains tables (#6448)
* fix #6185 - add test if database contains tables

* formatting

* change table
2025-11-06 07:52:21 -05:00
TheWitness fc14c73db2 QA: Handle 1020 Errors like 1213 Errors
These errors are transient and should result in a retry
2025-10-16 16:40:57 -04:00
TheWitness 2d6ee98a65 QA: Fix: #6262 - Soft database restart recovery
If the database service restarts during polling or other activities errors occur
2025-06-16 08:43:02 -04:00
Linegod db304b7b18 typo 2025-05-09 13:42:25 -06:00
TheWitness 685d4b9357 Start addressing PHPStan Issues 2025-03-23 14:09:11 -04:00
TheWitness abc07dc997 Leave the old wrapper in place 2025-03-23 10:33:23 -04:00
TheWitness d2624514cc Restore parts of lib/rrd.php move/rename a function 2025-03-23 10:23:11 -04:00
TheWitness e5c4015e0e QA: Remove most references to $config
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.
2025-03-22 16:48:59 -04:00
MSS db4eb59dc5 Fixes related to Windows (#6180)
* Update database.php

fix typo:
cacti_log('ERROR: Column: ' . $key . ' contains and invalid value: ' . $value, false, 'DBCALL');

to:
cacti_log('ERROR: Column: ' . $key . ' contains an invalid value: ' . $value, false, 'DBCALL');

* Update dsstats.php

dsstats_get_and_store_ds_avgpeak_values('daily', 'child', 0);
should be:
dsstats_get_and_store_ds_avgpeak_values('daily', 'child', 1);

* Update ping.php

* Update main.css

* Update poller.php

* Update poller_automation.php

* Update rrdcleaner.php

* Update poller_automation.php

* Update poller.php

* Update poller.php

* Update poller_automation.php

* Update rrdcleaner.php
2025-03-21 16:29:48 -04:00
TheWitness 2c779273f9 Copyright Updates and PHP-CS-Fixer updates 2025-03-09 14:34:19 -04:00
Jimmy ConnerandTheWitness 65ab16f5cf Start converting to newer PHP Syntax and Functions (#6103)
* 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>
2025-02-23 15:14:54 -05:00
Jimmy Conner f80747e857 Fix implode error in db_table_create
Only relevant when multiple primary keys
2025-02-21 23:15:46 -06:00
TheWitness 512ba34307 QA: Unable to add defaults of ON UPDATE ... 2024-12-30 22:18:46 -05:00
TheWitness 2b39ba4cd2 QA: Remove debugging
* This time without the buggered up audit_schema.sql
2024-12-29 19:28:51 -05:00
TheWitness fa145e1bd7 Revert "QA: Remove debugging"
This reverts commit 009fe40930.
2024-12-29 19:28:04 -05:00
TheWitness 009fe40930 QA: Remove debugging 2024-12-29 19:26:06 -05:00
TheWitness e4f677c224 QA: Recognize mariadb-dump in addition to mysqldump 2024-12-29 18:49:28 -05:00
TheWitness 8ae86b8991 QA: Fix: #5995 - String substitution error leading to errors 2024-12-15 09:51:23 -05:00
TheWitness cf2a148599 Fix: #5973 - Database function don't handle unique indexes
* Upon setting up new table structures, noticed this defect.  Lumping in with prior related fixes.
* Update the cli script to handle this case as well in a compatible way.
2024-12-09 12:38:26 -05:00
TheWitness 4f783471fa Fix: #5973 - Issues with db_update_table() function
* Multiple Issues with database API function db_update_table() preventing correct update of an existing table
2024-12-09 11:23:14 -05:00