Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
34f3869
fix: keep punctuation in the port name filter
somethingwithproof Sep 5, 2026
748964b
fix: leave an empty hardware address empty
somethingwithproof Sep 5, 2026
b1e0660
fix: stop re-querying addresses that have no PTR record
somethingwithproof Sep 5, 2026
aa13a92
test: exercise the real xform_mac_address instead of a copy
somethingwithproof Sep 5, 2026
e795cfe
fix: preserve trailing NUL bytes in binary MACs
somethingwithproof Sep 6, 2026
914b819
fix: reject empty MAC lookups and preserve DNS policy
somethingwithproof Sep 6, 2026
837858d
test: cover resolver and MAC edge cases
somethingwithproof Sep 6, 2026
06cd21f
ci: adopt plugin_evidence Pest CI model, PHP 8.2 floor, PascalCase te…
TheWitness Sep 20, 2026
3486e04
build: rely on Cacti core for Net_DNS2 instead of a plugin-level depe…
TheWitness Sep 20, 2026
b09eac0
Merge upstream/develop into fix/mactrack-342-334-regressions
TheWitness Sep 20, 2026
26f02bf
ci: align code-quality.yml with the PHP 8.2 floor and drop the broken…
TheWitness Sep 20, 2026
2072751
fix(ci): restore vendor ownership for Pest; fix(tests): set include_p…
TheWitness Sep 20, 2026
3bc6ff2
style: apply php-cs-fixer coding standards
TheWitness Sep 20, 2026
ec8f645
revert: drop this PR's code-quality.yml changes
TheWitness Sep 20, 2026
44c6ddc
Merge remote-tracking branch 'origin/develop' into fix/mactrack-342-3…
TheWitness Sep 20, 2026
7ed70a2
fix(tests): correct dot1x signature expectation and macauth-schedule …
TheWitness Sep 20, 2026
5abb62e
fix: wire resolver to the guarded hostname-resolution helper; load Ne…
TheWitness Sep 20, 2026
a271e69
fix: fall through to system resolver on NODATA PTR; escape site/devic…
TheWitness Sep 20, 2026
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

--- develop ---

* issue#349: Keep punctuation in the port name filter
* issue#350: Leave an empty hardware address empty
* issue#353: Stop re-querying addresses that have no PTR record
* issue#357: Prevent repeated database setup from duplicating the Default site
* security: Restrict Net_DNS2 cache deserialization to DNS response classes
* docs: Align the documented PHP floor with the PHP 8.2 CI/test contract
Expand Down
55 changes: 55 additions & 0 deletions lib/mactrack_dns_resolution.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
+-------------------------------------------------------------------------+
*/

/**
* Resolve one address according to the configured DNS policy.
*
* A NOERROR/NODATA response (no PTR records, but not an exception) falls
* through to the system resolver the same as a query failure does; only a
* PTR record actually found in the answer is used without falling back.
*
* @param mixed $resolver
* @param bool $use_resolver
* @param string $ip_address
* @param callable|null $system_resolver
* @return string
*/
function mactrack_resolve_hostname($resolver, $use_resolver, $ip_address, $system_resolver = null) {
if ($system_resolver === null) {
$system_resolver = 'gethostbyaddr';
}

$dns_hostname = '';

if ($use_resolver && is_object($resolver) && is_callable([$resolver, 'query'])) {
try {
$response = $resolver->query($ip_address, 'PTR');

foreach ($response->answer as $answer) {
if (isset($answer->ptrdname)) {
$dns_hostname = $answer->ptrdname;

break;
}
}

if ($dns_hostname === '') {
$dns_hostname = call_user_func($system_resolver, $ip_address);
}
} catch (Net_DNS2_Exception $e) {
$dns_hostname = call_user_func($system_resolver, $ip_address);
}
} else {
$dns_hostname = call_user_func($system_resolver, $ip_address);
}

if ($dns_hostname === false || $dns_hostname === '' || $dns_hostname === $ip_address) {
return $ip_address;
}

return $dns_hostname;
}
88 changes: 76 additions & 12 deletions lib/mactrack_functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ function get_standard_arp_table($site, &$device) {
if (cacti_sizeof($atEntries)) {
foreach ($atEntries as $atEntry) {
// check the mac_track_arp table if no IP address is found
if ($atEntry['atNetAddress'] == '') {
if ($atEntry['atNetAddress'] == '' && $atEntry['atPhysAddress'] !== '') {
$atEntry['atNetAddress'] = db_check_for_ip($atEntry['atPhysAddress']);
mactrack_debug('atNetAddress ****:' . $atEntry['atPhysAddress'] . '(' . $atEntry['atNetAddress'] . ')');
}
Expand Down Expand Up @@ -954,8 +954,8 @@ function build_InterfacesTable(&$device, &$ifIndexes, $getLinkPorts = false, $ge
(isset($ifHighSpeed[$ifIndex]) ? $ifHighSpeed[$ifIndex] : '') . "', '" .
(isset($ifDuplex[$ifIndex]) ? $ifDuplex[$ifIndex] : '') . "', " .
db_qstr($desc) . ", '" .
(isset($ifMtu[$ifIndex]) ? $ifMtu[$ifIndex] : '') . "', '" .
$mac_address . "', '" .
(isset($ifMtu[$ifIndex]) ? $ifMtu[$ifIndex] : '') . "', " .
db_qstr($mac_address) . ", '" .
(isset($ifAdminStatus[$ifIndex]) ? $ifAdminStatus[$ifIndex] : '') . "', '" .
(isset($ifOperStatus[$ifIndex]) ? $ifOperStatus[$ifIndex] : '') . "', '" .
(isset($ifLastChange[$ifIndex]) ? $ifLastChange[$ifIndex] : '') . "', '" .
Expand Down Expand Up @@ -2127,10 +2127,25 @@ function xform_net_address($ip_address) {
* @param mixed $mac_address
*/
function xform_mac_address($mac_address) {
$mac_address = trim((string) $mac_address);
$mac_address = (string) $mac_address;

// A six-byte SNMP OctetString is binary. Every byte, including whitespace
// and NUL values, is significant and must reach bin2hex() unchanged.
if (strlen($mac_address) === 6) {
// Some agents use an all-zero or all-whitespace OctetString to report
// that an interface has no hardware address. Do not store those sentinels
// as synthetic MAC addresses.
if ($mac_address === str_repeat("\0", 6) || trim($mac_address) === '') {
return '';
}
} else {
$mac_address = trim($mac_address);
}

// An interface with no hardware address stores an empty string, not the
// placeholder the dead branch below used to build and throw away.
if ($mac_address === '') {
return 'NOT USER';
return '';
}

if (strlen($mac_address) > 10) {
Expand All @@ -2152,8 +2167,51 @@ function xform_mac_address($mac_address) {
}

$mac_address = str_replace(':', '', $mac_address);
$mac_address = strtoupper($mac_address);

return preg_match('/^[0-9A-F]{12}$/D', $mac_address) === 1 ? $mac_address : '';
}

/**
* Preserve printable port-description punctuation used by switches while
* rejecting control characters and unbounded request values.
*
* @param mixed $value
* @return string
*/
function mactrack_sanitize_port_name_filter($value) {
$value = preg_replace('/[\x00-\x1F\x7F]/', '', (string) $value) ?? '';

return strtoupper($mac_address);
return mb_strcut($value, 0, 255, 'UTF-8');
}

/**
* Build the parameterized port-name predicate used by viewer queries.
*
* @param string $column
* @param int $filter_type
* @param string $value
* @return array
*/
function mactrack_port_name_filter_clause($column, $filter_type, $value) {
switch ((int) $filter_type) {
case 2:
return ["$column = ?", [$value]];
case 3:
return ["$column LIKE ?", ['%' . $value . '%']];
case 4:
return ["$column LIKE ?", [$value . '%']];
case 5:
return ["$column NOT LIKE ?", ['%' . $value . '%']];
case 6:
return ["$column NOT LIKE ?", [$value . '%']];
case 7:
return ["$column = ''", []];
case 8:
return ["$column != ''", []];
default:
return ['', []];
}
}

/**
Expand Down Expand Up @@ -2477,22 +2535,28 @@ function db_store_device_port_results(&$device, $port_array, $scan_date) {

// db_check_auth - This function checks whether the mac address exists in the mac_track+macauth table
function db_check_auth($mac_address) {
if ($mac_address === null || $mac_address === '') {
return false;
}

$query = db_fetch_cell_prepared('SELECT mac_id
FROM mac_track_macauth
WHERE mac_address
LIKE ?',
['%' . $mac_address . '%']);
WHERE mac_address = ?',
[$mac_address]);

return $query;
}

// db_check_for_ip - This function checks whether the mac address has a matching IP address in the mac_track_arp table
function db_check_for_ip($mac_address) {
if ($mac_address === null || $mac_address === '') {
return false;
}

$query = db_fetch_cell_prepared('SELECT ip_address
FROM mac_track_arp
WHERE mac_address
LIKE ?',
['%' . $mac_address . '%']);
WHERE mac_address = ?',
[$mac_address]);

return $query;
}
Expand Down
8 changes: 5 additions & 3 deletions lib/mactrack_h3c_3com.php
Original file line number Diff line number Diff line change
Expand Up @@ -417,10 +417,12 @@ function get_h3c_3com_arp_table($site, &$device) {

if (cacti_sizeof($atifIndexes)) {
foreach ($atifIndexes as $key => $atifIndex) {
$tmpmac = xform_mac_address($mac_address[$key]);
// The two walks are keyed independently, so this index can be absent.
// An empty MAC is the column default, so it must not be looked up.
$tmpmac = xform_mac_address($mac_address[$key] ?? '');

$port = db_fetch_cell_prepared('SELECT port_number FROM mac_track_ports
WHERE mac_address=? ORDER BY scan_date DESC LIMIT 1', [$tmpmac]);
$port = $tmpmac !== '' ? db_fetch_cell_prepared('SELECT port_number FROM mac_track_ports
WHERE mac_address=? ORDER BY scan_date DESC LIMIT 1', [$tmpmac]) : false;

if ($port) {
$atEntries[$i]['atifIndex'] = $port;
Expand Down
25 changes: 5 additions & 20 deletions mactrack_resolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@

include('../../include/cli_check.php');
include_once($config['base_path'] . '/plugins/mactrack/lib/mactrack_functions.php');
include_once($config['base_path'] . '/plugins/mactrack/lib/mactrack_dns_resolution.php');
// Net_DNS2 autoloads its own classes with include paths relative to the library
// root, so that root has to be reachable independently of the current directory.
set_include_path($config['base_path'] . '/plugins/mactrack' . PATH_SEPARATOR . get_include_path());
Expand Down Expand Up @@ -214,26 +215,10 @@
mactrack_debug(cacti_sizeof($unresolved_ips) . ' IP\'s require resolving this pass');

foreach ($unresolved_ips as $key => $unresolved_ip) {
$dns_hostname = $unresolved_ip['ip_address'];

if ($use_resolver) {
try {
$resp = $resolver->query($dns_hostname, 'PTR');
$dns_hostname = $resp->answer[0]->ptrdname;
} catch (Net_DNS2_Exception $e) {
$dns_hostname = gethostbyaddr($unresolved_ip['ip_address']);

if ($dns_hostname === false) {
mactrack_debug('Unable to resolve IP Address: ' . $unresolved_ip['ip_address']);
}
}
} else {
$dns_hostname = gethostbyaddr($unresolved_ip['ip_address']);

if ($dns_hostname === false) {
$dns_hostname = $unresolved_ip['ip_address'];
mactrack_debug('Unable to resolve IP Address: ' . $unresolved_ip['ip_address']);
}
$dns_hostname = mactrack_resolve_hostname($resolver, $use_resolver, $unresolved_ip['ip_address'], 'gethostbyaddr');

if ($dns_hostname === $unresolved_ip['ip_address']) {
mactrack_debug('Unable to resolve IP Address: ' . $unresolved_ip['ip_address']);
}

$unresolved_ips[$key]['dns_hostname'] = $dns_hostname;
Expand Down
4 changes: 2 additions & 2 deletions mactrack_view_arp.php
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ function mactrack_ip_address_filter() {

if (get_request_var('site_id') == $site['site_id']) {
print ' selected';
} print '>' . $site['site_name'] . '</option>';
} print '>' . html_escape($site['site_name']) . '</option>';
}
}
?>
Expand Down Expand Up @@ -497,7 +497,7 @@ function mactrack_ip_address_filter() {

if (get_request_var('device_id') == $filter_device['device_id']) {
print ' selected';
} print '>' . $filter_device['device_name'] . '(' . $filter_device['hostname'] . ')' . '</option>';
} print '>' . html_escape($filter_device['device_name'] . '(' . $filter_device['hostname'] . ')') . '</option>';
}
}
?>
Expand Down
4 changes: 2 additions & 2 deletions mactrack_view_devices.php
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ function mactrack_device_filter2() {

if (get_request_var('site_id') == $site['site_id']) {
print ' selected';
} print '>' . $site['site_name'] . '</option>';
} print '>' . html_escape($site['site_name']) . '</option>';
}
}
?>
Expand Down Expand Up @@ -501,7 +501,7 @@ function mactrack_device_filter2() {

if (get_request_var('device_type_id') == $device_type['device_type_id']) {
print ' selected';
} print '>' . $display_text . '</option>';
} print '>' . html_escape($display_text) . '</option>';
}
}
?>
Expand Down
Loading
Loading