Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

--- develop ---

* issue: PHPStan level 8 typing pass - fixed an incorrect script_path/script_function in the exported WMI query resource XML, restrictive drp_action/menu-index guards in wmi_accounts.php and wmi_queries.php, several unguarded array offset accesses on DB fetch results, and html_start_box() argument-type mismatches
* test: Expand Security/Unit/Integration Pest coverage for setup.php lifecycle, hook registration, and table/column provisioning
* chore: Harmonize CI workflow, issue/PR templates, and PHP-compatibility test structure with the shared Cacti plugin baseline
* feat: Add a PowerShell/CIM transport (PowerShellCim_Transport) for Windows collector hosts, with automatic transport selection based on the Cacti server OS
Expand Down
29 changes: 19 additions & 10 deletions functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,14 @@ function display_tabs() {
* form_save() path that invokes it).
*
* @param string $query The WQL query text to check (e.g.
* 'SELECT * FROM Win32_Something').
* 'SELECT * FROM Win32_Something').
*
* @return bool True when an existing saved query already targets the
* same WMI class.
*/
function plugin_wmi_query_exists($query) {
$tokens = preg_split('/\s+/', $query);
$tokens = is_array($tokens) ? $tokens : [];
$next_ic = false;
$exists = false;

Expand Down Expand Up @@ -316,7 +317,7 @@ function plugin_wmi_create_dataquery_xml($id) {
* download action for a given saved query.
*
* @param int $id The wmi_wql_queries.id to generate the resource XML
* for.
* for.
*
* @return string The generated Script Server resource XML, or '' when
* the query id does not exist.
Expand All @@ -328,8 +329,8 @@ function plugin_wmi_create_resource_xml($id) {
if (isset($wmic['id'])) {
$data = "<WMIQuery>\n";
$data .= ' <name>' . $wmic['name'] . "</name>\n";
$data .= " <script_path>|path_cacti|/scripts/wmic-script.php</script_path>\n";
$data .= " <script_function>wmic_script</script_function>\n";
$data .= " <script_path>|path_cacti|/scripts/wmi-script.php</script_path>\n";
$data .= " <script_function>wmi_script</script_function>\n";
$data .= ' <description>WMI Query for ' . $wmic['name'] . "</description>\n";
$data .= " <script_server>php</script_server>\n";
$data .= ' <arg_prepend>|host_hostname| |host_id| ' . $wmic['name'] . "</arg_prepend>\n";
Expand Down Expand Up @@ -376,14 +377,14 @@ function plugin_wmi_create_resource_xml($id) {
*
* @param int $host_id The Cacti host id to query.
* @param int $wmi_query_id The wmi_wql_queries.id to run against the
* host.
* host.
*
* @return bool|void True when the query succeeded and results were
* cached; false when the host's WMI account/query
* could not be found or the query failed; no explicit
* return value when the host itself could not be
* found (stale cache rows are still cleaned up in that
* case).
* cached; false when the host's WMI account/query
* could not be found or the query failed; no explicit
* return value when the host itself could not be
* found (stale cache rows are still cleaned up in that
* case).
*
* @global array $config Cacti global configuration array; used to load
* this plugin's linux_wmi.php client.
Expand All @@ -396,6 +397,8 @@ function run_store_wmi_query($host_id, $wmi_query_id) {
WHERE id = ?',
[$host_id]);

$host_info = is_array($host_info) ? $host_info : [];

// Prepared old entries for removal
db_execute_prepared('UPDATE host_wmi_cache
SET present = 0
Expand All @@ -409,11 +412,15 @@ function run_store_wmi_query($host_id, $wmi_query_id) {
WHERE id = ?',
[$host_info['wmi_account']]);

$auth_info = is_array($auth_info) ? $auth_info : [];

$wmi_query = db_fetch_row_prepared('SELECT *
FROM wmi_wql_queries
WHERE id = ?',
[$wmi_query_id]);

$wmi_query = is_array($wmi_query) ? $wmi_query : [];

if (!cacti_sizeof($auth_info)) {
return false;
}
Expand Down Expand Up @@ -464,6 +471,8 @@ function run_store_wmi_query($host_id, $wmi_query_id) {
$data = [];
}

$indexes = is_array($indexes) ? $indexes : [];

if (cacti_sizeof($data)) {
$sql = [];

Expand Down
33 changes: 29 additions & 4 deletions linux_wmi.php
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,12 @@ public function error(): ?string {
}

private function default_runner(): callable {
/**
* @param array<int, string> $args
* @param array<string, string> $env
*
* @return array{exit: int, stdout: array<int, string>, stderr: string}
*/
return function (string $binary, array $args, string $stdin, array $env): array {
$descriptors = [
0 => ['pipe', 'r'],
Expand All @@ -247,7 +253,7 @@ private function default_runner(): callable {
// onto the inherited one to keep PATH and friends.
$child_env = array_merge(getenv(), $env);

$process = proc_open(array_merge([$binary], $args), $descriptors, $pipes, null, $child_env);
$process = proc_open(array_values(array_merge([$binary], $args)), $descriptors, $pipes, null, $child_env);

if (!is_resource($process)) {
return ['exit' => -1, 'stdout' => [], 'stderr' => 'proc_open failed'];
Expand Down Expand Up @@ -442,7 +448,7 @@ public function fetch(): array|false {
}

$this->results = array_map(
fn (string $line): array => explode($this->separator, $line),
fn (string $line): array => explode($this->effective_separator(), $line),
$output
);

Expand Down Expand Up @@ -511,10 +517,16 @@ public function retrieve_account(): bool {
}

public function decode(string $info): string {
$raw = base64_decode($info, true);

if ($raw === false) {
return '';
}

// allowed_classes => false forbids object instantiation, so no gadget
// chain can run; the blob is our own base64(serialize()) from encode().
// nosemgrep: php.lang.security.unserialize-use.unserialize-use
$decoded = unserialize(base64_decode($info, true), ['allowed_classes' => false]);
$decoded = unserialize($raw, ['allowed_classes' => false]);

return is_array($decoded) && isset($decoded['password']) ? (string) $decoded['password'] : '';
}
Expand All @@ -536,11 +548,24 @@ private function request(): Wmi_Request {
$this->password,
$this->querynspace,
$this->command,
$this->separator,
$this->effective_separator(),
$this->binary
);
}

/**
* The separator used for both the transport request and parsing its
* output. Falls back to the default when $separator (a public,
* mutable property) is empty, since an empty delimiter would make
* explode() throw (PHP 8) when parsing the results, and would make the
* wmic/PowerShell transport emit unsplit rows.
*
* @return non-empty-string
*/
private function effective_separator(): string {
return $this->separator !== '' ? $this->separator : '|+|';
}

/**
* The data rows: results with the class-name and column-header lines
* removed.
Expand Down
Loading
Loading