Skip to content
Open
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
28 changes: 28 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down
8 changes: 8 additions & 0 deletions conf.d/01-system-paths.conf.in
Original file line number Diff line number Diff line change
Expand Up @@ -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@"

Expand Down
2 changes: 2 additions & 0 deletions distros/redhat/zoneminder.spec
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
175 changes: 123 additions & 52 deletions web/includes/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -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('<br/>', $output);
ZM\Error("Unable to list network interfaces, status is '$status'. Output was:<br/><br/>$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('<br/>', $output);
ZM\Error("Unable to list network interfaces, status is '$status'. Output was:<br/><br/>$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('<br/>', $output);
ZM\Error("Unable to list network interfaces, status is '$status'. Output was:<br/><br/>$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('<br/>', $output);
ZM\Error("Unable to list network interfaces, status is '$status'. Output was:<br/><br/>$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)) {
Expand All @@ -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('<br/>', $output);
ZM\Error("Unable to list network interfaces, status is '$status'. Output was:<br/><br/>$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)

Expand Down
2 changes: 1 addition & 1 deletion web/includes/monitor_probe.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
12 changes: 6 additions & 6 deletions web/skins/classic/views/monitorprobe.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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));
Expand Down Expand Up @@ -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']);
}
}

Expand Down Expand Up @@ -450,7 +450,7 @@ function probeNetwork() {
<?php
$interfaces = array('', 'select');
$interfaces += get_networks();
$default_interface = $interfaces['default'];
$default_interface = isset($interfaces['default']) ? $interfaces['default'] : '';
unset($interfaces['default']);

echo htmlSelect('interface', $interfaces,
Expand Down
7 changes: 4 additions & 3 deletions web/skins/classic/views/onvifprobe.php
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ function probeProfiles($device_ep, $soapversion, $username, $password) {
<p><label for="interface"><?php echo translate('Interface') ?></label>
<?php
$interfaces = get_networks();
$default_interface = $interfaces['default'];
$default_interface = isset($interfaces['default']) ? $interfaces['default'] : null;
unset($interfaces['default']);

echo htmlSelect('interface', $interfaces,
Expand Down Expand Up @@ -240,8 +240,9 @@ function probeProfiles($device_ep, $soapversion, $username, $password) {
foreach ($detprofiles as $profile) {
$monitor = $camera['monitor'];

$sourceString = "${profile['Name']} : ${profile['Encoding']}" .
" (${profile['Width']}x${profile['Height']} @ ${profile['MaxFPS']}fps ${profile['Transport']})";
$sourceString = $profile['Name'] . ' : ' . $profile['Encoding'] .
' (' . $profile['Width'] . 'x' . $profile['Height'] . ' @ ' . $profile['MaxFPS'] . 'fps ' . $profile['Transport'] . ')';

// copy technical details
$monitor['Width'] = $profile['Width'];
$monitor['Height'] = $profile['Height'];
Expand Down
Loading