mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-11-07 13:13:05 -05:00
Merge pull request #5343 from nabbi/fix/b120-remote-control
fix-medium: forward PTZ and monitor action requests to the server that records the monitor
This commit is contained in:
4 files changed
+157
No files matched your search
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
// Tests remoteControlUrl() in web/includes/control_functions.php: in a
|
||||
// multi-server setup a PTZ or other control request for a monitor that another
|
||||
// server records is handed to that server, which runs its zmcontrol.pl.
|
||||
//
|
||||
// Before, the server that got the request sent the zmcontrol option string to
|
||||
// the other server's monitors/daemonControl API, which only starts and stops
|
||||
// zmcontrol.pl, so the camera never moved and the request answered "Failed".
|
||||
//
|
||||
// Run: php tests/php/test_remote_control_url.php
|
||||
|
||||
define('ZM_SERVER_ID', 1);
|
||||
|
||||
$stubdir = sys_get_temp_dir().'/zm_remote_control_stubs_'.getmypid();
|
||||
@mkdir($stubdir, 0700, true);
|
||||
file_put_contents($stubdir.'/logger.php', "<?php\nnamespace ZM;\nfunction Warning(\$s) {}\nfunction Error(\$s) {}\nfunction Debug(\$s) {}\n");
|
||||
set_include_path($stubdir.PATH_SEPARATOR.get_include_path());
|
||||
require_once __DIR__.'/../../web/includes/control_functions.php';
|
||||
|
||||
function get_auth_relay() { return 'auth=0123456789abcdef&user=admin'; }
|
||||
|
||||
class StubServer {
|
||||
private $id;
|
||||
public function __construct($id) { $this->id = $id; }
|
||||
public function Id() { return $this->id; }
|
||||
public function UrlToIndex() { return 'http://zm2.example:8080/zm/index.php'; }
|
||||
}
|
||||
|
||||
class StubMonitor {
|
||||
private $serverId;
|
||||
private $serverKnown;
|
||||
public function __construct($serverId, $serverKnown = true) {
|
||||
$this->serverId = $serverId;
|
||||
$this->serverKnown = $serverKnown;
|
||||
}
|
||||
public function ServerId() { return $this->serverId; }
|
||||
public function Server() { return new StubServer($this->serverKnown ? $this->serverId : null); }
|
||||
}
|
||||
|
||||
$failures = 0;
|
||||
$passes = 0;
|
||||
|
||||
function check($name, $got, $want) {
|
||||
global $failures, $passes;
|
||||
if ($got === $want) {
|
||||
$passes++;
|
||||
echo "ok - $name\n";
|
||||
} else {
|
||||
$failures++;
|
||||
echo "FAIL - $name (got ".var_export($got, true).", want ".var_export($want, true).")\n";
|
||||
}
|
||||
}
|
||||
|
||||
$request = array(
|
||||
'view' => 'request', 'request' => 'control', 'id' => '4', 'control' => 'moveConLeft',
|
||||
'xge' => '50', 'yge' => '50', 'token' => 'jwt', 'username' => 'admin', 'password' => 'secret',
|
||||
);
|
||||
|
||||
check('monitor on this server is handled here', remoteControlUrl(new StubMonitor(1), $request), null);
|
||||
check('monitor with no server is handled here', remoteControlUrl(new StubMonitor(0), $request), null);
|
||||
check('monitor on a server ZoneMinder does not list is handled here',
|
||||
remoteControlUrl(new StubMonitor(2, false), $request), null);
|
||||
check('a request forwarded once is handled where it lands',
|
||||
remoteControlUrl(new StubMonitor(2), $request + array('forwarded' => '1')), null);
|
||||
|
||||
$url = remoteControlUrl(new StubMonitor(2), $request);
|
||||
check('monitor on another server goes to that server',
|
||||
strtok($url, '?'), 'http://zm2.example:8080/zm/index.php');
|
||||
parse_str(parse_url($url, PHP_URL_QUERY), $sent);
|
||||
check('the control request is passed on', array(
|
||||
isset($sent['view']) ? $sent['view'] : null,
|
||||
isset($sent['request']) ? $sent['request'] : null,
|
||||
isset($sent['id']) ? $sent['id'] : null,
|
||||
isset($sent['control']) ? $sent['control'] : null,
|
||||
isset($sent['xge']) ? $sent['xge'] : null,
|
||||
isset($sent['forwarded']) ? $sent['forwarded'] : null,
|
||||
), array('request', 'control', '4', 'moveConLeft', '50', '1'));
|
||||
check('the caller\'s credentials are not passed on', array(
|
||||
isset($sent['token']), isset($sent['password'])), array(false, false));
|
||||
check('the auth relay says who is asking', array(
|
||||
isset($sent['auth']) ? $sent['auth'] : null, isset($sent['user']) ? $sent['user'] : null),
|
||||
array('0123456789abcdef', 'admin'));
|
||||
|
||||
$action = array('view' => 'request', 'request' => 'control', 'action' => 'monitorAction', 'id' => '3', 'aid' => '7');
|
||||
parse_str(parse_url(remoteControlUrl(new StubMonitor(2), $action), PHP_URL_QUERY), $sent);
|
||||
check('a monitor action keeps its action and ids', array($sent['action'], $sent['id'], $sent['aid']),
|
||||
array('monitorAction', '3', '7'));
|
||||
|
||||
array_map('unlink', glob($stubdir.'/*'));
|
||||
@rmdir($stubdir);
|
||||
|
||||
echo "\n$passes passed, $failures failed\n";
|
||||
exit($failures ? 1 : 0);
|
||||
@@ -37,6 +37,9 @@ if ( isset($_REQUEST['action']) and ($_REQUEST['action'] == 'monitorAction') ) {
|
||||
if ( !canView('Control', $target->Id()) )
|
||||
ajaxError('Insufficient permissions on the target monitor');
|
||||
|
||||
if ( $url = remoteControlUrl($target, $_REQUEST) )
|
||||
relayControlReply($url);
|
||||
|
||||
$ctrlCommand = $monitorAction->controlCommand();
|
||||
if ( !$ctrlCommand )
|
||||
ajaxError('Action has no usable command');
|
||||
@@ -52,6 +55,9 @@ if ( isset($_REQUEST['action']) and ($_REQUEST['action'] == 'monitorAction') ) {
|
||||
if ( canView('Control', $_REQUEST['id']) ) {
|
||||
$monitor = new ZM\Monitor(validCardinal($_REQUEST['id']));
|
||||
|
||||
if ( $url = remoteControlUrl($monitor, $_REQUEST) )
|
||||
relayControlReply($url);
|
||||
|
||||
$ctrlCommand = buildControlCommand($monitor);
|
||||
|
||||
if ( !$ctrlCommand ) {
|
||||
@@ -73,4 +79,15 @@ if ( canView('Control', $_REQUEST['id']) ) {
|
||||
}
|
||||
|
||||
ajaxError('Unrecognised action or insufficient permissions');
|
||||
|
||||
// Hands the request to the server that records the monitor and answers with its reply.
|
||||
function relayControlReply($url) {
|
||||
$reply = forwardControlRequest($url);
|
||||
if ( $reply === false )
|
||||
ajaxError('Failed');
|
||||
if ( function_exists('ajaxCleanup') )
|
||||
ajaxCleanup();
|
||||
header('Content-type: application/json');
|
||||
exit($reply);
|
||||
}
|
||||
?>
|
||||
@@ -872,6 +872,12 @@ class Monitor extends ZM_Object {
|
||||
}
|
||||
socket_close($socket);
|
||||
} else if ($this->ServerId()) {
|
||||
# The API only starts and stops zmcontrol.pl there; a camera command has to
|
||||
# go to that server's web request (ajax/control.php forwards those).
|
||||
if (!in_array($command, array('quit', 'start', 'stop'))) {
|
||||
Error('Control command for monitor '.$this->{'Id'}.' not sent: it is recorded by server '.$this->{'ServerId'});
|
||||
return false;
|
||||
}
|
||||
$result = $this->Server()->SendToApi('/monitors/daemonControl/'.$this->{'Id'}.'/'.$command.'/zmcontrol.pl.json');
|
||||
return $result;
|
||||
} else {
|
||||
|
||||
@@ -2,6 +2,47 @@
|
||||
|
||||
require_once('logger.php');
|
||||
|
||||
// In a multi-server setup only the server that records a monitor can drive its
|
||||
// camera: zmcontrol.pl runs there. When that is another server, returns the URL
|
||||
// that hands it this same control request, or null to handle it here. The other
|
||||
// server checks the permissions and builds the command itself. A request that
|
||||
// was forwarded once is handled where it lands, so two servers that disagree
|
||||
// about the monitor's server don't pass it back and forth.
|
||||
function remoteControlUrl($monitor, $request) {
|
||||
if ( !defined('ZM_SERVER_ID') or !ZM_SERVER_ID ) return null;
|
||||
if ( !$monitor->ServerId() or ($monitor->ServerId() == ZM_SERVER_ID) ) return null;
|
||||
if ( !empty($request['forwarded']) ) return null;
|
||||
|
||||
$server = $monitor->Server();
|
||||
if ( !$server->Id() ) return null;
|
||||
|
||||
// The other server learns who is asking from the auth relay, not from the
|
||||
// credentials this request came with.
|
||||
$params = array('view'=>'request', 'request'=>'control');
|
||||
foreach ( $request as $name => $value ) {
|
||||
if ( !in_array($name, array('view', 'request', 'token', 'auth', 'user', 'username', 'password', 'pass', 'forwarded')) )
|
||||
$params[$name] = $value;
|
||||
}
|
||||
$params['forwarded'] = 1;
|
||||
$url = $server->UrlToIndex().'?'.http_build_query($params);
|
||||
$auth_relay = get_auth_relay();
|
||||
if ( $auth_relay ) $url .= '&'.$auth_relay;
|
||||
return $url;
|
||||
}
|
||||
|
||||
// Sends a control request to the server that records the monitor and returns
|
||||
// its JSON reply, or false when that server can't be reached.
|
||||
function forwardControlRequest($url) {
|
||||
$context = stream_context_create(array(
|
||||
'ssl'=>array('verify_peer'=>false, 'verify_peer_name'=>false),
|
||||
));
|
||||
$reply = @file_get_contents($url, false, $context);
|
||||
if ( $reply === false ) {
|
||||
ZM\Error('Could not forward the control request to '.strtok($url, '?'));
|
||||
}
|
||||
return $reply;
|
||||
}
|
||||
|
||||
function buildControlCommand($monitor) {
|
||||
$ctrlCommand = '';
|
||||
$control = $monitor->Control();
|
||||
|
||||
Reference in new issue
Block a user