diff --git a/CMakeLists.txt b/CMakeLists.txt
index bf0a267025f..e3007986087 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -119,6 +119,8 @@ mark_as_advanced(
ZM_PATH_UNAME
ZM_PATH_IP
ZM_PATH_IFCONFIG
+ ZM_PATH_NETSTAT
+ ZM_PATH_PKEXEC
ZM_CONFIG_DIR
ZM_CONFIG_SUBDIR
ZM_DETECT_SYSTEMD
@@ -198,6 +200,10 @@ set(ZM_PATH_IP "" CACHE PATH
"Full path to compatible ip binary. Leave empty for automatic detection.")
set(ZM_PATH_IFCONFIG "" CACHE PATH
"Full path to compatible ifconfig binary. Leave empty for automatic detection.")
+set(ZM_PATH_NETSTAT "" CACHE PATH
+ "Full path to compatible netstat binary. Leave empty for automatic detection.")
+set(ZM_PATH_PKEXEC "" CACHE PATH
+ "Full path to compatible pkexec binary. Leave empty for automatic detection.")
set(ZM_CONFIG_DIR "${CMAKE_INSTALL_FULL_SYSCONFDIR}/zm" CACHE PATH
"Location of ZoneMinder configuration, default system config directory")
set(ZM_CONFIG_SUBDIR "${ZM_CONFIG_DIR}/conf.d" CACHE PATH
@@ -820,6 +826,28 @@ if(ZM_PATH_IFCONFIG STREQUAL "")
endif()
endif()
+# Find the path to an netstat compatible executable
+if(ZM_PATH_NETSTAT STREQUAL "")
+ find_program(NETSTAT_EXECUTABLE netstat)
+ if(NETSTAT_EXECUTABLE)
+ set(ZM_PATH_NETSTAT "${NETSTAT_EXECUTABLE}")
+ mark_as_advanced(NETSTAT_EXECUTABLE)
+ else()
+ message(WARNING "Unable to find a compatible netstat binary.")
+ endif()
+endif()
+
+# Find the path to an pkexec compatible executable
+if(ZM_PATH_PKEXEC STREQUAL "")
+ find_program(PKEXEC_EXECUTABLE pkexec)
+ if(PKEXEC_EXECUTABLE)
+ set(ZM_PATH_PKEXEC "${PKEXEC_EXECUTABLE}")
+ mark_as_advanced(PKEXEC_EXECUTABLE)
+ else()
+ message(WARNING "Unable to find a compatible pkexec binary.")
+ endif()
+endif()
+
# Some variables that zm expects
set(ZM_PID "${ZM_RUNDIR}/zm.pid")
set(ZM_CONFIG "${ZM_CONFIG_DIR}/zm.conf")
diff --git a/conf.d/01-system-paths.conf.in b/conf.d/01-system-paths.conf.in
index 3a047b00867..d3a2be1dfd8 100644
--- a/conf.d/01-system-paths.conf.in
+++ b/conf.d/01-system-paths.conf.in
@@ -70,6 +70,14 @@ ZM_PATH_IP="@ZM_PATH_IP@"
# ZoneMinder will find the ifconfig binary automatically on most systems
ZM_PATH_IFCONFIG="@ZM_PATH_IFCONFIG@"
+# Full path to optional netstat binary or just netstat to use PATH
+# ZoneMinder will find the netstat binary automatically on most systems
+ZM_PATH_NETSTAT="@ZM_PATH_NETSTAT@"
+
+# Full path to optional pkexec binary or just pkexec to use PATH
+# ZoneMinder will find the pkexec binary automatically on most systems
+ZM_PATH_PKEXEC="@ZM_PATH_PKEXEC@"
+
#Full path to shutdown binary
ZM_PATH_SHUTDOWN="@ZM_PATH_SHUTDOWN@"
diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec
index ca838487996..8c268f1476d 100644
--- a/distros/redhat/zoneminder.spec
+++ b/distros/redhat/zoneminder.spec
@@ -230,6 +230,8 @@ mv -f CxxUrl-%{CxxUrl_version} ./dep/CxxUrl
-DZM_PATH_ARP_SCAN="/usr/sbin/arp-scan" \
-DZM_PATH_IP="/usr/sbin/ip" \
-DZM_PATH_IFCONFIG="/usr/sbin/ifconfig" \
+ -DZM_PATH_NETSTAT="/usr/bin/netstat" \
+ -DZM_PATH_PKEXEC="/usr/bin/pkexec" \
.
%cmake_build
diff --git a/web/includes/functions.php b/web/includes/functions.php
index 9dae2966bed..e38519d4946 100644
--- a/web/includes/functions.php
+++ b/web/includes/functions.php
@@ -2240,64 +2240,113 @@ function i18n() {
}
function get_networks() {
- $interfaces = array();
+ $interfaces = array();
- if (defined('ZM_PATH_IP') and ZM_PATH_IP and file_exists(ZM_PATH_IP)) {
- exec(ZM_PATH_IP.' link', $output, $status);
- if ( $status ) {
- $html_output = implode('
', $output);
- ZM\Error("Unable to list network interfaces, status is '$status'. Output was:
$html_output");
- } else {
- foreach ( $output as $line ) {
- if ( preg_match('/^\d+: ([[:alnum:]]+):/', $line, $matches ) ) {
- if ( $matches[1] != 'lo' ) {
+ if (defined('ZM_PATH_IP') and ZM_PATH_IP and file_exists(ZM_PATH_IP)) {
+ exec(ZM_PATH_IP.' link', $output, $status);
+ if ( $status ) {
+ $html_output = implode('
', $output);
+ ZM\Error("Unable to list network interfaces, status is '$status'. Output was:
$html_output");
+ } else {
+ foreach ( $output as $line ) {
+ if ( preg_match('/^\d+: ([[:alnum:]]+):/', $line, $matches ) ) {
+ if ( $matches[1] != 'lo' ) {
+ $interfaces[$matches[1]] = $matches[1];
+ } else {
+ ZM\Debug("Skipping loopback interface $line");
+ }
+ }
+ }
+ }
+ $output = '';
+ exec(ZM_PATH_IP.' route', $output, $status);
+ if ($status) {
+ $html_output = implode('
', $output);
+ ZM\Error("Unable to list network interfaces, status is '$status'. Output was:
$html_output");
+ } else {
+ foreach ($output as $line) {
+ if ( preg_match('/^default via [.[:digit:]]+ dev ([[:alnum:]]+)/', $line, $matches) ) {
+ $interfaces['default'] = $matches[1];
+ } else if ( preg_match('/^([.[:digit:]]+\/[[:digit:]]+) dev ([[:alnum:]]+)/', $line, $matches) ) {
+ $interfaces[$matches[2]] .= ' ' . $matches[1];
+ ZM\Debug("Matched $line: $matches[2] .= $matches[1]");
+ } else {
+ ZM\Debug("Didn't match $line");
+ }
+ } # end foreach line of output
+ }
+ } else if (defined('ZM_PATH_IFCONFIG') and ZM_PATH_IFCONFIG and file_exists(ZM_PATH_IFCONFIG)) {
+ exec(ZM_PATH_IFCONFIG, $output, $status);
+ if ($status) {
+ $html_output = implode("\n", $output);
+ ZM\Error("Unable to list network interfaces, status is '$status'. Output was:$html_output");
+ } else {
+ array_walk($output, function($value, $key) use (&$interfaces) {
+ $retval = preg_match("/^([A-Za-z0-9]*):\s+flags=([0-9]*)<([A-Z,]*)>.*/ims", $value, $matches);
+ ZM\Debug(print_r($matches, true));
+ if (($retval == 1) && (strlen($matches[3]) > 0) &&
+ (strpos($matches[3], "LOOPBACK") === false) && (strpos($matches[3], "RUNNING") !== false))
+ {
$interfaces[$matches[1]] = $matches[1];
+ }
+ });
+
+ if (defined('ZM_PATH_NETSTAT') and ZM_PATH_NETSTAT and file_exists(ZM_PATH_NETSTAT)) {
+ $output = '';
+ // Get default route iface
+ exec(ZM_PATH_NETSTAT . " -r4 | grep '^default' | head -n 1 | awk '{print $4}'", $defaultIface, $status);
+
+ if ($status) {
+ $html_output = implode("\n", $output);
+ ZM\Error("Unable to get default ip route, status is '$status'. Output was:$html_output");
} else {
- ZM\Debug("No match for $line");
+ if (isset($defaultIface) && isset($defaultIface[0])) {
+ $interfaces['default'] = $defaultIface[0];
+ }
}
}
- }
- }
- $routes = array();
- exec(ZM_PATH_IP.' route', $output, $status);
- if ($status) {
- $html_output = implode('
', $output);
- ZM\Error("Unable to list network interfaces, status is '$status'. Output was:
$html_output");
- } else {
- foreach ($output as $line) {
- if ( preg_match('/^default via [.[:digit:]]+ dev ([[:alnum:]]+)/', $line, $matches) ) {
- $interfaces['default'] = $matches[1];
- } else if ( preg_match('/^([.[:digit:]]+\/[[:digit:]]+) dev ([[:alnum:]]+)/', $line, $matches) ) {
- $interfaces[$matches[2]] .= ' ' . $matches[1];
- ZM\Debug("Matched $line: $matches[2] .= $matches[1]");
- } else {
- ZM\Debug("Didn't match $line");
- }
- } # end foreach line of output
- }
- } else if (defined('ZM_PATH_IFCONFIG') and ZM_PATH_IFCONFIG and file_exists(ZM_PATH_IFCONFIG)) {
- exec(ZM_PATH_IFCONFIG, $output, $status);
- if ($status) {
- $html_output = implode("\n", $output);
- ZM\Error("Unable to list network interfaces, status is '$status'. Output was:$html_output");
- } else {
- preg_match("/^([eth|enp][A-z0-9]*)\s+Link\s+encap:([A-z]*)\s+HWaddr\s+([A-z0-9:]*).*".
- "inet addr:([0-9.]+).*Bcast:([0-9.]+).*Mask:([0-9.]+).*".
- "MTU:([0-9.]+).*Metric:([0-9.]+).*".
- "RX packets:([0-9.]+).*errors:([0-9.]+).*dropped:([0-9.]+).*overruns:([0-9.]+).*frame:([0-9.]+).*".
- "TX packets:([0-9.]+).*errors:([0-9.]+).*dropped:([0-9.]+).*overruns:([0-9.]+).*carrier:([0-9.]+).*".
- "RX bytes:([0-9.]+).*\((.*)\).*TX bytes:([0-9.]+).*\((.*)\)".
- "/ims", implode("\n", $output), $regex);
-
- ZM\Debug(print_r( $regex,true));
- }
- }
- return $interfaces;
-}
-
-# Returns an array of subnets like 192.168.1.0/24 for a given interface.
-# Will ignore mdns networks.
+ }
+ }
+ return $interfaces;
+}
+
+function parse_netstat_line(string $line) {
+ $line = trim($line);
+ $pattern = '/^(?:' .
+ '(\d{1,3}(?:\.\d{1,3}){3})\s+' .
+ '\d{1,3}(?:\.\d{1,3}){3}\s+' .
+ '\d{1,3}(?:\.\d{1,3}){3}\s+' .
+ '[A-Za-z]+\s+' .
+ '\d+\s+' .
+ '\d+\s+' .
+ '\d+\s+' .
+ '(\S+)' .
+ ')|(?:' .
+ '(\d{1,3}(?:\.\d{1,3}){3}\/\d{1,2})\s+' .
+ '\S+?\s*' .
+ '[A-Za-z]+\s+' .
+ '(\S+)' .
+ ')$/';
+ if (!preg_match($pattern, $line, $matches)) {
+ return null;
+ }
+
+ if (!empty($matches[1])) {
+ return [
+ 'ip' => $matches[1],
+ 'iface' => $matches[2],
+ ];
+ }
+
+ return [
+ 'ip' => $matches[3],
+ 'iface' => $matches[4],
+ ];
+}
+
+# Returns an array of subnets like 192.168.1.0/24 (192.168.1.0 on some platforms) for a given interface.
+# Will ignore mdns networks.
function get_subnets($interface) {
$subnets = array();
if (defined('ZM_PATH_IP') and ZM_PATH_IP and file_exists(ZM_PATH_IP)) {
@@ -2320,7 +2369,29 @@ function get_subnets($interface) {
}
} # end foreach line of output
}
+ } else if (defined('ZM_PATH_NETSTAT') and ZM_PATH_NETSTAT and file_exists(ZM_PATH_NETSTAT)) {
+ exec(ZM_PATH_NETSTAT.' -r4', $output, $status);
+ if ( $status ) {
+ $html_output = implode('
', $output);
+ ZM\Error("Unable to list network interfaces, status is '$status'. Output was:
$html_output");
+ } else {
+ foreach ($output as $line) {
+ $netstat_match = parse_netstat_line($line);
+ if (null !== $netstat_match) {
+ if (($netstat_match['ip'] == '169.254.0.0/16') || ($netstat_match['ip'] == '169.254.0.0')) {
+ # Ignore mdns
+ } else if ($netstat_match['iface'] == $interface) {
+ $subnets[] = $netstat_match['ip'];
+ } else {
+ ZM\Debug("Wrong interface " . $netstat_match['iface'] . " != " . $interface);
+ }
+ } else {
+ ZM\Debug("Didn't match $line");
+ }
+ } # end foreach line of output
+ }
}
+
return $subnets;
} # end function get_subnets($interface)
diff --git a/web/includes/monitor_probe.php b/web/includes/monitor_probe.php
index 3e0610b0294..55db04d20e7 100644
--- a/web/includes/monitor_probe.php
+++ b/web/includes/monitor_probe.php
@@ -833,7 +833,7 @@ function get_arp_scan_results($network) {
ZM\Error('arp-scan compatible binary not found or not executable by the web user account. Verify ZM_PATH_ARP_SCAN points to a valid arp-scan tool.');
return $results;
}
- $arp_scan_command = '/usr/bin/pkexec '.ZM_PATH_ARP_SCAN.' '.$network;
+ $arp_scan_command = ZM_PATH_PKEXEC.' '.ZM_PATH_ARP_SCAN.' '.$network;
$result = exec(escapeshellcmd($arp_scan_command), $output, $status);
if ($status) {
ZM\Error("Unable to probe network cameras, command was $arp_scan_command, status is '$status' output: ".implode(PHP_EOL, $output));
diff --git a/web/skins/classic/views/monitorprobe.php b/web/skins/classic/views/monitorprobe.php
index b9745fcfbde..36c719d473d 100644
--- a/web/skins/classic/views/monitorprobe.php
+++ b/web/skins/classic/views/monitorprobe.php
@@ -293,8 +293,8 @@ function get_arp_results() {
ZM\Error('ARP compatible binary not found or not executable by the web user account. Verify ZM_PATH_ARP points to a valid arp tool.');
return $results;
}
- if (count($result)==1) {
- $arp_command .= ' -n';
+ if (count($result) == 1) {
+ $arp_command .= ' -na';
}
$result = exec(escapeshellcmd($arp_command), $output, $status);
@@ -321,7 +321,7 @@ function get_arp_scan_results($network) {
ZM\Error('arp-scan compatible binary not found or not executable by the web user account. Verify ZM_PATH_ARP_SCAN points to a valid arp-scan tool.');
return $results;
}
- $arp_scan_command = '/usr/bin/pkexec '.ZM_PATH_ARP_SCAN.' '.$network.' 2>&1';
+ $arp_scan_command = ZM_PATH_PKEXEC.' '.ZM_PATH_ARP_SCAN.' '.$network.' 2>&1';
$result = exec(escapeshellcmd($arp_scan_command), $output, $status);
if ($status) {
ZM\Error("Unable to probe network cameras, command was $arp_scan_command, status is '$status' output: ".implode(PHP_EOL, $output));
@@ -353,10 +353,10 @@ function probeNetwork() {
foreach ( dbFetchAll("SELECT `Id`, `Name`, `Path` FROM `Monitors` WHERE `Type` = 'Ffmpeg' ORDER BY `Path`") as $monitor ) {
$url_parts = parse_url($monitor['Path']);
if ($url_parts !== false) {
- ZM\Debug("Ffmpeg monitor ${url_parts['host']} = ${monitor['Id']} ${monitor['Name']}");
+ ZM\Debug("Ffmpeg monitor " . $url_parts['host'] . " = " . $monitor['Id'] . " " . $monitor['Name']);
$monitors[gethostbyname($url_parts['host'])] = $monitor;
} else {
- ZM\Debug("Unable to parse ${monitor['Path']}");
+ ZM\Debug("Unable to parse " . $monitor['Path']);
}
}
@@ -450,7 +450,7 @@ function probeNetwork() {