Merge branch 'master' of github.com:ZoneMinder/zoneminder

pull/3718/head
Isaac Connor 2023-05-31 09:16:32 -04:00
commit c02cbd86f8
14 changed files with 302 additions and 11 deletions

View File

@ -7,6 +7,8 @@ configure_file(logrotate.conf.in "${CMAKE_CURRENT_BINARY_DIR}/logrotate.conf" @O
configure_file(syslog.conf.in "${CMAKE_CURRENT_BINARY_DIR}/syslog.conf" @ONLY)
configure_file(com.zoneminder.systemctl.policy.in "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.policy" @ONLY)
configure_file(com.zoneminder.systemctl.rules.in "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.rules" @ONLY)
configure_file(com.zoneminder.dnsmasq.policy.in "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.dnsmasq.policy" @ONLY)
configure_file(com.zoneminder.dnsmasq.rules.in "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.dnsmasq.rules" @ONLY)
configure_file(com.zoneminder.arp-scan.policy.in "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.arp-scan.policy" @ONLY)
configure_file(com.zoneminder.arp-scan.rules.in "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.arp-scan.rules" @ONLY)
configure_file(zoneminder.service.in "${CMAKE_CURRENT_BINARY_DIR}/zoneminder.service" @ONLY)

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policyconfig PUBLIC
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
"http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
<policyconfig>
<vendor>The ZoneMinder Project</vendor>
<vendor_url>https://www.zoneminder.com/</vendor_url>
<action id="com.zoneminder.policykit.pkexec.run-systemctl">
<description>Allow the ZoneMinder webuser to start/stop the dnsmasq service</description>
<message>The ZoneMinder webuser is trusted to start/stop dnsmasq</message>
<defaults>
<allow_any>yes</allow_any>
<allow_inactive>yes</allow_inactive>
<allow_active>yes</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.exec.path">/bin/systemctl</annotate>
</action>
</policyconfig>

View File

@ -0,0 +1,9 @@
// Allow www-data to manage dnsmasq.service;
// fall back to implicit authorization otherwise.
polkit.addRule(function(action, subject) {
if (action.id == "org.freedesktop.systemd1.manage-units" &&
action.lookup("unit") == "dnsmasq.service" &&
subject.user == "@WEB_USER@") {
return polkit.Result.YES;
}
});

View File

@ -5,7 +5,7 @@
<policyconfig>
<vendor>The ZoneMinder Project</vendor>
<vendor_url>http://www.zoneminder.com/</vendor_url>
<vendor_url>https://www.zoneminder.com/</vendor_url>
<action id="com.zoneminder.policykit.pkexec.run-zmsystemctl">
<description>Allow the ZoneMinder webuser to run zmsystemctl.pl</description>

View File

@ -3,5 +3,4 @@ polkit.addRule(function(action, subject) {
subject.user != "@WEB_USER@") {
return polkit.Result.NO;
}
});

View File

@ -1095,5 +1095,9 @@ public static function getStatuses() {
if (isset($gp_permissions['Edit'])) return 'Edit';
return $u->Monitors();
}
public function link_to($text='') {
return '<a href="?view=monitor&mid='.$this->Id().'">'.($text ? $text : $this->Name()).'</a>';
}
} // end class Monitor
?>

View File

@ -108,5 +108,50 @@ if ( $action == 'delete' ) {
#generateAuthHash(ZM_AUTH_HASH_IPS, true);
}
return;
} else if ($action == 'save') {
if (isset($_REQUEST['object'])) {
if ($_REQUEST['object'] == 'dnsmasq') {
$config = isset($_REQUEST['config']) ? $_REQUEST['config'] : [];
$conf = '';
foreach ($config as $name=>$value) {
if ($name == 'dhcp-host') {
foreach ($value as $mac=>$ip) {
$conf .= $name.'='.$mac.','.$ip.PHP_EOL;
}
} else if (
($name == 'bind-interfaces')
or
($name == 'dhcp-authoritative')
) {
if ($value=='yes') {
$conf .= $name.PHP_EOL;
}
} else if ($name == 'dhcp-range') {
$conf .= $name.'='.$value['min'].','.$value['max'].','.$value['expires'].PHP_EOL;
} else {
if (is_array($value)) {
foreach ($value as $v) {
}
} else {
$conf .= $name.'='.$value.PHP_EOL;
}
}
}
file_put_contents(ZM_PATH_DNSMASQ_CONF, $conf);
exec('sudo -n /bin/systemctl restart dnsmasq.service');
}
}
} else if ($action == 'start') {
if (isset($_REQUEST['object'])) {
if ($_REQUEST['object'] == 'dnsmasq') {
exec('sudo -n /bin/systemctl start dnsmasq.service');
}
}
} else if ($action == 'stop') {
if (isset($_REQUEST['object'])) {
if ($_REQUEST['object'] == 'dnsmasq') {
exec('sudo -n /bin/systemctl stop dnsmasq.service');
}
}
} // end if object vs action
?>

View File

@ -258,9 +258,8 @@ if ( defined('ZM_TIMEZONE') and ZM_TIMEZONE )
ini_set('date.timezone', ZM_TIMEZONE);
function process_configfile($configFile) {
if ( is_readable($configFile) ) {
$configvals = array();
$configvals = array();
if (is_readable($configFile)) {
$cfg = fopen($configFile, 'r') or error_log('Could not open config file: '.$configFile);
while ( !feof($cfg) ) {
$str = fgets($cfg, 256);
@ -273,11 +272,10 @@ function process_configfile($configFile) {
}
}
fclose($cfg);
return $configvals;
} else {
error_log('WARNING: ZoneMinder configuration file found but is not readable. Check file permissions on '.$configFile);
return false;
}
return $configvals;
}
?>

View File

@ -2459,4 +2459,9 @@ function getHomeView() {
}
return 'console';
}
function systemd_isactive($service) {
$output = shell_exec("systemctl is-active $service");
return (trim($output) == 'active');
}
?>

View File

@ -587,7 +587,7 @@ function probeNetwork() {
$results = array();
$monitors = array();
foreach ( ZM\Monitor::find(['Type'=>'Ffmpeg']) as $monitor) {
foreach (ZM\Monitor::find(['Type'=>'Remote']) as $monitor) {
if ( preg_match('/^(.+)@(.+)$/', $monitor->Host(), $matches) ) {
//echo "1: ".$matches[2]." = ".gethostbyname($matches[2])."<br/>";
$monitors[gethostbyname($matches[2])] = $monitor;
@ -596,7 +596,7 @@ function probeNetwork() {
$monitors[gethostbyname($monitor->Host())] = $monitor;
}
}
foreach ( ZM\Monitor::find(['Type'=>'Ffmpeg']) as $monitor) {
foreach (ZM\Monitor::find(['Type'=>'Ffmpeg']) as $monitor) {
$url_parts = parse_url($monitor->Path());
if ($url_parts !== false) {
#ZM\Debug("Ffmpeg monitor ${url_parts['host']} = ${monitor['Id']} ${monitor['Name']}");

View File

@ -105,3 +105,27 @@ form {
text-align: left;
}
}
.Config .config .row > label {
width: 300px;
text-align: right;
}
.Config .config .row {
min-height:36px;
text-align: left;
display: flex;
align-content: space-around;
}
#leasesTable td,
#leasesTable th {
text-align: left;
}
input[name="config[dhcp-range][min]"],
input[name="config[dhcp-range][max]"] {
width: 120px;
min-width: 120px;
}
input[name="config[dhcp-range][expires]"] {
width: 100px;
min-width: 100px;
}

View File

@ -0,0 +1,177 @@
<?php
if (!canView('System')) {
$view = 'error';
return;
}
?>
<div class="Config">
<h2>DHCP Server Config</h2>
<form name="contentForm" action="?view=options" method="post">
<input type="hidden" name="object" value="dnsmasq"/>
<input type="hidden" name="tab" value="dnsmasq"/>
<?php
if (canEdit('System')) {
?>
<div id="contentButtons">
<button type="submit" name="action" value="save"><?php echo translate('Save') ?></button>
</div>
<?php
}
?>
<div class="service"><h2><?php echo translate('Service') ?></h2>
<?php
$active = systemd_isactive('dnsmasq');
echo 'Dnsmasq service is '.($active ? 'active' : 'inactive').'.';
if (canEdit('System')) {
if ($active) {
echo '<button type="submit" name="action" value="stop">'.translate('Stop').'</button>';
} else {
echo '<button type="submit" name="action" value="start">'.translate('Start').'</button>';
}
}
?>
</div>
<div class="config"><h2><?php echo translate('Configuration') ?></h2>
<div class="container">
<?php
$dnsmasq_config = [
'interface'=>'',
'bind-interfaces'=>'',
'dhcp-range'=>'192.168.9.50,192.168.9.150,12h',
#'dhcp-rapid-commit'=>'',
'dhcp-authoritative'=>''
];
if (defined('ZM_PATH_DNSMASQ_CONF') and file_exists(ZM_PATH_DNSMASQ_CONF))
$dnsmasq_config += process_dnsmasq_configfile(ZM_PATH_DNSMASQ_CONF);
ZM\Debug(print_r($dnsmasq_config, true));
foreach ($dnsmasq_config as $name=>$value) {
if ($name == 'interface') {
$interfaces = get_networks();
$default_interface = $interfaces['default'];
unset($interfaces['default']);
echo '<div class="row"><label class="form-label" for="interface">'.translate('Interface').'</label>'.PHP_EOL.
'<span class="value">'.
htmlSelect('config[interface]', $interfaces,
(isset($dnsmasq_config['interface']) ? $dnsmasq_config['interface'] : $default_interface),
).'</span></div>'.PHP_EOL;
} else if ($name == 'bind-interfaces') {
echo '<div class="row"><label class="form-label" for="bind-interfaces">'.translate('Bind Interfaces').'</label>'.PHP_EOL.
'<span class="value">'.
html_radio('config[bind-interfaces]', ['yes'=>translate('Yes'), 'no'=>translate('No')], $dnsmasq_config[$name], ['default'=>'yes']).
'</span></div>'.PHP_EOL;
} else if ($name == 'dhcp-authoritative') {
echo '<div class="row"><label class="form-label" for="dhcp-authoritative">'.translate('DHCP Authoritative').'</label>'.PHP_EOL.
'<span class="value">'.
html_radio('config[dhcp-authoritative]', ['yes'=>translate('Yes'), 'no'=>translate('No')], $dnsmasq_config[$name], ['default'=>'yes']).
'</span></div>'.PHP_EOL;
} else if ($name == 'dhcp-range') {
$values = explode(',', $value);
echo '<div class="row"><label class="form-label" for="dhcp-range"> '.translate('DHCP Range').'</label><span class="value">';
echo '<input type="text" name="config[dhcp-range][min]" value="'.$values[0].'"/>';
echo ' to <input type="text" name="config[dhcp-range][max]" value="'.$values[1].'"/>';
echo ' <input type="text" name="config[dhcp-range][expires]" value="'.$values[2].'"/></span></div>'.PHP_EOL;
} else if ($name == 'dhcp-host') {
# Handled below
} else {
echo '<div class="row"><label class="form-label">'.$name.'</label><span class="value">'.PHP_EOL;
echo '<input type="text" name="config['.validHtmlStr($name).']" value="'.validHtmlStr($value).'"/></span></div>'.PHP_EOL;
}
}
?>
</div>
</div><!--Config-->
<div class="leases"><h2>Leases</h2>
<?php
function process_dnsmasq_configfile($configFile) {
$configvals = array();
if (is_readable($configFile)) {
$cfg = fopen($configFile, 'r') or ZM\Error('Could not open config file: '.$configFile);
while ( !feof($cfg) ) {
$str = fgets($cfg, 256);
if ( preg_match('/^\s*(#.*)?$/', $str) ) {
continue;
} else if ( preg_match('/^\s*([^=\s]+)\s*(=\s*[\'"]*(.*?)[\'"]*\s*)?$/', $str, $matches) ) {
$configvals[$matches[1]] = isset($matches[2]) ? $matches[2] : 'yes';
} else {
ZM\Error("Malformed line in config $configFile\n$str");
}
}
fclose($cfg);
} else {
Error('WARNING: dnsmasq configuration file found but is not readable. Check file permissions on '.$configFile);
}
return $configvals;
}
function read_leasefile($file) {
$leases = [];
$contents = file_get_contents($file);
foreach (explode("\n", $contents) as $line) {
$row = explode(' ', $line);
if (count($row) != 5) continue;
$lease = [
'expiry' => $row[0],
'mac' => $row[1],
'ip' => $row[2],
'name' => $row[3],
'id' => $row[4]
];
$leases[] = $lease;
}
return $leases;
}
?>
<table id="leasesTable" class="table bootstraptable"
data-check-on-init="true"
data-mobile-responsive="true"
data-min-width="562"
>
<thead>
<tr>
<th data-sortable="true" data-field="hostname" class="hostname"><?php echo translate('Hostname') ?></th>
<th data-sortable="true" data-field="mac" class="mac"><?php echo translate('Mac Address') ?></th>
<th data-sortable="true" data-field="ip" class="ip"><?php echo translate('IP Address') ?></th>
<th data-sortable="true" data-field="expires" class="expires"><?php echo translate('Expires') ?></th>
<th data-sortable="true" data-field="Monitor" class="expires"><?php echo translate('Monitor') ?></th>
</tr>
</thead>
<tbody>
<?php
$monitors_by_ip = array();
foreach (ZM\Monitor::find(['Type'=>'Remote']) as $monitor) {
if (preg_match('/^(.+)@(.+)$/', $monitor->Host(), $matches)) {
$monitors_by_ip[gethostbyname($matches[2])] = $monitor;
} else {
$monitors_by_ip[gethostbyname($monitor->Host())] = $monitor;
}
}
foreach (ZM\Monitor::find(['Type'=>'Ffmpeg']) as $monitor) {
$url_parts = parse_url($monitor->Path());
if ($url_parts !== false) {
$monitors_by_ip[gethostbyname($url_parts['host'])] = $monitor;
} else {
ZM\Debug('Unable to parse '.$monitor->Path());
}
}
$leases = read_leasefile('/var/lib/misc/dnsmasq.leases');
foreach ($leases as $lease) {
if (!isset($monitors_by_ip[$lease['ip']])) {
ZM\Debug("No monitor for".$lease['ip']);
}
echo '
<tr>
<td class="hostname">'.$lease['name'].'</td>
<td class="mac">'.$lease['mac'].'</td>
<td class="ip"><input type="text" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" name="config[dhcp-host]['.$lease['mac'].']" value="'.$lease['ip'].'"/></td>
<td class="expiry">'.$dateTimeFormatter->format($lease['expiry']).'</td>
<td class="Monitor">'.(isset($monitors_by_ip[$lease['ip']]) ? $monitors_by_ip[$lease['ip']]->link_to() : '').'</td>
</tr>';
} # end foreach
?>
</tbody>
</table>
</div>
</form>
</script>

View File

@ -57,6 +57,8 @@ function initPage() {
NewStorageBtn.prop('disabled', !canEdit.System);
NewServerBtn.prop('disabled', !canEdit.System);
$j('.bootstraptable').bootstrapTable({icons: icons}).show();
}
$j(document).ready(function() {

View File

@ -29,6 +29,9 @@ $tabs = array();
$tabs['skins'] = translate('Display');
$tabs['system'] = translate('System');
$tabs['config'] = translate('Config');
if (defined('ZM_PATH_DNSMASQ_CONF') and file_exists(ZM_PATH_DNSMASQ_CONF)) {
$tabs['dnsmasq'] = translate('DHCP');
}
$tabs['API'] = translate('API');
$tabs['servers'] = translate('Servers');
$tabs['storage'] = translate('Storage');
@ -109,8 +112,6 @@ foreach (array_map('basename', glob('skins/'.$skin.'/css/*', GLOB_ONLYDIR)) as $
</div>
</form>
<?php
} else if ($tab == 'users') {
include('_options_users.php');
} else if ($tab == 'control') {
if (canView('Control')) {
$redirect = '?view=controlcaps';
@ -262,6 +263,10 @@ foreach (array_map('basename', glob('skins/'.$skin.'/css/*', GLOB_ONLYDIR)) as $
</div>
</form>
<?php
} else if ($tab == 'dnsmasq' and file_exists('skins/classic/views/_options_dnsmasq.php')) {
include('_options_dnsmasq.php');
} else if ($tab == 'users') {
include('_options_users.php');
} else if ($tab == 'API') {
include('_options_api.php');
} // $tab == API