diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Netcat.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Netcat.pm
index 977662ceb..2d9573299 100644
--- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Netcat.pm
+++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Netcat.pm
@@ -28,6 +28,9 @@ package ZoneMinder::Control::Netcat;
use 5.006;
use strict;
use warnings;
+use MIME::Base64;
+use Digest::SHA;
+use DateTime;
require ZoneMinder::Base;
require ZoneMinder::Control;
@@ -36,7 +39,7 @@ our @ISA = qw(ZoneMinder::Control);
our %CamParams = ();
-our $profileToken;
+our ($profileToken, $address, $port, %identity);
# ==========================================================================
#
@@ -52,7 +55,6 @@ our $profileToken;
#
#
# Possible future improvements (for anyone to improve upon):
-# - Onvif authentication
# - Build the SOAP commands at runtime rather than use templates
# - Implement previously mentioned advanced features
#
@@ -60,9 +62,10 @@ our $profileToken;
# more dependencies to ZoneMinder is always a concern.
#
# On ControlAddress use the format :
-# ADDRESS:PORT
+# [USERNAME:PASSWORD@]ADDRESS:PORT
# eg : 10.1.2.1:8899
# 10.0.100.1:8899
+# username:password@10.0.100.1:8899
#
# Use port 8899 for the Netcat camera
#
@@ -84,6 +87,8 @@ sub open {
$profileToken = $self->{Monitor}->{ControlDevice};
if ($profileToken eq '') { $profileToken = '000'; }
+ parseControlAddress($self->{Monitor}->{ControlAddress});
+
use LWP::UserAgent;
$self->{ua} = LWP::UserAgent->new;
$self->{ua}->agent('ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION);
@@ -91,6 +96,39 @@ sub open {
$self->{state} = 'open';
}
+sub parseControlAddress
+{
+ my $controlAddress = shift;
+ my ($usernamepassword, $addressport) = split /@/, $controlAddress;
+ if ( !defined $addressport ) {
+ # If value of "Control address" does not consist of two parts, then only address is given
+ $addressport = $usernamepassword;
+ } else {
+ my ($username , $password) = split /:/, $usernamepassword;
+ %identity = (username => "$username", password => "$password");
+ }
+ ($address, $port) = split /:/, $addressport;
+}
+
+sub digestBase64
+{
+ my ($nonce, $date, $password) = @_;
+ my $shaGenerator = Digest::SHA->new(1);
+ $shaGenerator->add($nonce . $date . $password);
+ return encode_base64($shaGenerator->digest, "");
+}
+
+sub authentificationHeader
+{
+ my ($username, $password) = @_;
+ my $nonce;
+ $nonce .= chr(int(rand(254))) for (0 .. 20);
+ my $nonceBase64 = encode_base64($nonce, "");
+ my $currentDate = DateTime->now()->iso8601().'Z';
+
+ return '' . $username . '' . digestBase64($nonce, $currentDate, $password) . '' . $nonceBase64 . '' . $currentDate . '';
+}
+
sub printMsg {
my $self = shift;
my $msg = shift;
@@ -108,10 +146,10 @@ sub sendCmd {
printMsg($cmd, 'Tx');
- my $server_endpoint = 'http://'.$self->{Monitor}->{ControlAddress}.'/'.$cmd;
+ my $server_endpoint = 'http://' . $address . ':' . $port . "/$cmd";
my $req = HTTP::Request->new(POST => $server_endpoint);
$req->header('content-type' => $content_type);
- $req->header('Host' => $self->{Monitor}->{ControlAddress});
+ $req->header('Host' => $address . ':' . $port);
$req->header('content-length' => length($msg));
$req->header('accept-encoding' => 'gzip, deflate');
$req->header('connection' => 'Close');
@@ -130,10 +168,10 @@ sub sendCmd {
sub getCamParams {
my $self = shift;
my $msg = '000';
- my $server_endpoint = 'http://'.$self->{Monitor}->{ControlAddress}.'/onvif/imaging';
+ my $server_endpoint = 'http://' . $address . ':' . $port . '/onvif/imaging';
my $req = HTTP::Request->new(POST => $server_endpoint);
$req->header('content-type' => 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/GetImagingSettings"');
- $req->header('Host' => $self->{Monitor}->{ControlAddress});
+ $req->header('Host' => $address . ':' . $port);
$req->header('content-length' => length($msg));
$req->header('accept-encoding' => 'gzip, deflate');
$req->header('connection' => 'Close');
@@ -165,7 +203,7 @@ sub autoStop {
if ( $autostop ) {
Debug('Auto Stop');
my $cmd = 'onvif/PTZ';
- my $msg = '' . $profileToken . 'truefalse';
+ my $msg = '' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '' . $profileToken . 'truefalse';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
usleep($autostop);
$self->sendCmd($cmd, $msg, $content_type);
@@ -177,7 +215,7 @@ sub reset {
Debug('Camera Reset');
my $self = shift;
my $cmd = '';
- my $msg = '';
+ my $msg = '' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver10/device/wsdl/SystemReboot"';
$self->sendCmd($cmd, $msg, $content_type);
}
@@ -187,7 +225,7 @@ sub moveConUp {
Debug('Move Up');
my $self = shift;
my $cmd = 'onvif/PTZ';
- my $msg ='' . $profileToken . '';
+ my $msg ='' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '' . $profileToken . '';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd($cmd, $msg, $content_type);
$self->autoStop($self->{Monitor}->{AutoStopTimeout});
@@ -209,7 +247,7 @@ sub moveConLeft {
Debug('Move Left');
my $self = shift;
my $cmd = 'onvif/PTZ';
- my $msg ='' . $profileToken . '';
+ my $msg ='' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '' . $profileToken . '';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd($cmd, $msg, $content_type);
$self->autoStop($self->{Monitor}->{AutoStopTimeout});
@@ -220,7 +258,7 @@ sub moveConRight {
Debug('Move Right');
my $self = shift;
my $cmd = 'onvif/PTZ';
- my $msg ='' . $profileToken . '';
+ my $msg ='' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '' . $profileToken . '';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd($cmd, $msg, $content_type);
$self->autoStop($self->{Monitor}->{AutoStopTimeout});
@@ -231,7 +269,7 @@ sub zoomConTele {
Debug('Zoom Tele');
my $self = shift;
my $cmd = 'onvif/PTZ';
- my $msg ='' . $profileToken . '';
+ my $msg ='' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '' . $profileToken . '';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd($cmd, $msg, $content_type);
$self->autoStop($self->{Monitor}->{AutoStopTimeout});
@@ -242,7 +280,7 @@ sub zoomConWide {
Debug('Zoom Wide');
my $self = shift;
my $cmd = 'onvif/PTZ';
- my $msg ='' . $profileToken . '';
+ my $msg ='' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '' . $profileToken . '';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd($cmd, $msg, $content_type);
$self->autoStop($self->{Monitor}->{AutoStopTimeout});
@@ -254,7 +292,7 @@ sub moveConUpRight {
Debug('Move Diagonally Up Right');
my $self = shift;
my $cmd = 'onvif/PTZ';
- my $msg ='' . $profileToken . '';
+ my $msg ='' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '' . $profileToken . '';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd($cmd, $msg, $content_type);
$self->autoStop($self->{Monitor}->{AutoStopTimeout});
@@ -266,7 +304,7 @@ sub moveConDownRight {
Debug('Move Diagonally Down Right');
my $self = shift;
my $cmd = 'onvif/PTZ';
- my $msg ='' . $profileToken . '';
+ my $msg ='' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '' . $profileToken . '';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd($cmd, $msg, $content_type);
$self->autoStop($self->{Monitor}->{AutoStopTimeout});
@@ -290,7 +328,7 @@ sub moveConDownLeft {
Debug('Move Diagonally Down Left');
my $self = shift;
my $cmd = 'onvif/PTZ';
- my $msg ='' . $profileToken . '';
+ my $msg ='' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '' . $profileToken . '';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd($cmd, $msg, $content_type);
$self->autoStop($self->{Monitor}->{AutoStopTimeout});
@@ -301,7 +339,7 @@ sub moveStop {
Debug('Move Stop');
my $self = shift;
my $cmd = 'onvif/PTZ';
- my $msg ='' . $profileToken . 'truefalse';
+ my $msg ='' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '' . $profileToken . 'truefalse';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd($cmd, $msg, $content_type);
}
@@ -313,7 +351,7 @@ sub presetSet {
my $preset = $self->getParam($params, 'preset');
Debug("Set Preset $preset");
my $cmd = 'onvif/PTZ';
- my $msg ='' . $profileToken . ''.$preset.'';
+ my $msg ='' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '' . $profileToken . ''.$preset.'';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/SetPreset"';
$self->sendCmd($cmd, $msg, $content_type);
}
@@ -325,7 +363,7 @@ sub presetGoto {
my $preset = $self->getParam($params, 'preset');
Debug("Goto Preset $preset");
my $cmd = 'onvif/PTZ';
- my $msg ='' . $profileToken . ''.$preset.'';
+ my $msg ='' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '' . $profileToken . ''.$preset.'';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/GotoPreset"';
$self->sendCmd( $cmd, $msg, $content_type );
}
@@ -367,7 +405,7 @@ sub irisAbsOpen {
$CamParams{Brightness} = $max if ($CamParams{Brightness} > $max);
my $cmd = 'onvif/imaging';
- my $msg ='000'.$CamParams{Brightness}.'true';
+ my $msg ='' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '000'.$CamParams{Brightness}.'true';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"';
$self->sendCmd( $cmd, $msg, $content_type );
}
@@ -386,7 +424,7 @@ sub irisAbsClose
$CamParams{Brightness} = $min if ($CamParams{Brightness} < $min);
my $cmd = 'onvif/imaging';
- my $msg ='000'.$CamParams{Brightness}.'true';
+ my $msg ='' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '000'.$CamParams{Brightness}.'true';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"';
$self->sendCmd( $cmd, $msg, $content_type );
}
@@ -404,7 +442,7 @@ sub whiteAbsIn {
$CamParams{Contrast} = $max if ($CamParams{Contrast} > $max);
my $cmd = 'onvif/imaging';
- my $msg ='000'.$CamParams{Contrast}.'true';
+ my $msg ='' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '000'.$CamParams{Contrast}.'true';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"';
}
@@ -421,7 +459,7 @@ sub whiteAbsOut {
$CamParams{Contrast} = $min if ($CamParams{Contrast} < $min);
my $cmd = 'onvif/imaging';
- my $msg ='000'.$CamParams{Contrast}.'true';
+ my $msg ='' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '000'.$CamParams{Contrast}.'true';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"';
}