From 7b59995a483ba6e30a557c2f06479e0b9ea9048e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 21 Dec 2024 14:32:08 -0500 Subject: [PATCH 1/3] Use parent's credentials in HikVision --- scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm | 6 ------ 1 file changed, 6 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm index b4f0c7d7c..ffe098a77 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm @@ -60,12 +60,6 @@ my $DefaultFocusSpeed = 50; # Should be between 1 and 100 my $DefaultIrisSpeed = 50; # Should be between 1 and 100 my $uri; -sub credentials { - my $self = shift; - $$self{username} = shift; - $$self{password} = shift; -Debug("Setting credentials to $$self{username}/$$self{password}"); -} sub open { my $self = shift; From 27dfdd1b4e5a2dbe4755b759c84fb289171ff599 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 21 Dec 2024 14:32:32 -0500 Subject: [PATCH 2/3] Upgrade Amcrest to use parent's code --- .../lib/ZoneMinder/Control/Amcrest_HTTP.pm | 104 +++++------------- 1 file changed, 25 insertions(+), 79 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm index 443cca804..6643694dc 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm @@ -42,10 +42,6 @@ our @ISA = qw(ZoneMinder::Control); use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); -our $username = ''; -our $password = ''; -our $realm = ''; - sub new { my $class = shift; my $id = shift; @@ -54,12 +50,6 @@ sub new { return $self; } - -sub credentials { - my $self = shift; - ($username, $password) = @_; -} - sub open { my $self = shift; @@ -80,30 +70,30 @@ sub open { } my $uri = URI->new($self->{Monitor}->{ControlAddress}); - $realm = 'Login to ' . $self->{Monitor}->{ControlDevice}; + $$self{realm} = 'Login to ' . $self->{Monitor}->{ControlDevice}; if ($self->{Monitor}->{ControlAddress}) { if ( $uri->userinfo()) { - ( $username, $password ) = $uri->userinfo() =~ /^(.*):(.*)$/; + @$self{'username', 'password'} = $uri->userinfo() =~ /^(.*):(.*)$/; } else { - $username = $self->{Monitor}->{User}; - $password = $self->{Monitor}->{Pass}; + $$self{username} = $self->{Monitor}->{User}; + $$self{password} = $self->{Monitor}->{Pass}; } $$self{address} = $uri->host_port(); - $self->{ua}->credentials($uri->host_port(), $realm, $username, $password); + $self->{ua}->credentials($uri->host_port(), @$self{'realm', 'username', 'password'}); # Testing seems to show that we need the username/password in each url as well as credentials $$self{base_url} = $uri->canonical(); - Debug('Using initial credentials for '.$uri->host_port().", $realm, $username, $password, base_url: $$self{base_url} auth:".$uri->authority()); + Debug('Using initial credentials for '.$uri->host_port().join(',', '', @$self{'realm', 'username', 'password'}).", base_url: $$self{base_url} auth:".$uri->authority()); } } elsif ( $self->{Monitor}{Path}) { my $uri = URI->new($self->{Monitor}{Path}); Debug("Using Path for credentials: $self->{Monitor}{Path} " . $uri->userinfo()); if ( $uri->userinfo()) { - ( $username, $password ) = $uri->userinfo() =~ /^(.*):(.*)$/; + @$self{'username', 'password'} = $uri->userinfo() =~ /^(.*):(.*)$/; } else { - $username = $self->{Monitor}->{User}; - $password = $self->{Monitor}->{Pass}; - $uri->userinfo($username.':'.$password); + $$self{username} = $self->{Monitor}->{User}; + $$self{password} = $self->{Monitor}->{Pass}; + $uri->userinfo($$self{username}.':'.$$self{password}); } $uri->scheme('http'); $uri->port(80); @@ -111,8 +101,8 @@ sub open { $$self{base_url} = $uri->canonical(); $$self{address} = $uri->host_port(); - Debug("User auth $username $password " . $uri->authority() . ' ' . $uri->host_port()); - $self->{ua}->credentials($uri->host_port(), $realm, $username, $password); + Debug("User auth $$self{username} $$self{password} " . $uri->authority() . ' ' . $uri->host_port()); + $self->{ua}->credentials($uri->host_port(), @$self{'realm', 'username', 'password'); chomp $$self{base_url}; Debug("Base_url is ".$$self{base_url}); } else { @@ -121,57 +111,17 @@ sub open { my $url = $$self{base_url}.'cgi-bin/magicBox.cgi?action=getDeviceType'; # Detect REALM, has to be /cgi-bin/ptz.cgi because just / accepts no auth - my $res = $self->get($url); + my $res = $self->get_realm($url); if ( $res->is_success ) { $self->{state} = 'open'; - return; - } - - if ( $res->status_line() eq '401 Unauthorized' ) { - - my $headers = $res->headers(); - foreach my $k ( keys %$headers ) { - Debug("Initial Header $k => $$headers{$k}"); - } - - if ( $$headers{'www-authenticate'} ) { - my ( $auth, $tokens ) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/; - if ( $tokens =~ /realm="([^"]+)"/i ) { - if ( $realm ne $1 ) { - $realm = $1; - Debug("Changing REALM to ($realm)"); - $self->{ua}->credentials($$self{address}, $realm, $username, $password); - $res = $self->get($url); - if ( $res->is_success() ) { - $self->{state} = 'open'; - return !undef; - } elsif ( $res->status_line eq '400 Bad Request' ) { - # In testing, this second request fails with Bad Request, I assume because we didn't actually give it a command. - $self->{state} = 'open'; - return !undef; - } else { - Error('Authentication still failed after updating REALM' . $res->status_line); - $headers = $res->headers(); - foreach my $k ( keys %$headers ) { - Debug("Header $k => $$headers{$k}"); - } # end foreach - } - } else { - Error('Authentication failed, not a REALM problem'); - } - } else { - Error('Failed to match realm in tokens'); - } # end if - } else { - Debug('No headers line'); - } # end if headers + return !undef; } else { - Error("Failed to get $$self{base_url}cgi-bin/magicBox.cgi?action=getDeviceType ".$res->status_line()); - + Error("Failed to get $url ".$res->status_line()); } # end if $res->status_line() eq '401 Unauthorized' $self->{state} = 'closed'; + return undef; } sub close { @@ -179,15 +129,6 @@ sub close { $self->{state} = 'closed'; } -sub get { - my $self = shift; - my $url = shift; - Debug("Getting $url"); - my $response = $self->{ua}->get($url); - Debug('Response: '. $response->status_line . ' ' . $response->content); - return $response; -} - sub sendCmd { my $self = shift; my $cmd = shift; @@ -195,7 +136,7 @@ sub sendCmd { $self->printMsg($cmd, 'Tx'); - my $res = $self->{ua}->get($$self{base_url}.$cmd); + my $res = $self->get($$self{base_url}.$cmd); if ( $res->is_success ) { $result = !undef; @@ -203,14 +144,19 @@ sub sendCmd { Info('Camera control: \''.$res->status_line().'\' for URL '.$$self{base_url}.$cmd); # TODO: Add code to retrieve $res->message_decode or some such. Then we could do things like check the camera status. } else { + # Have seen on some HikVision cams that whatever cookie LWP uses times out and it never refreshes, so we have to actually create a new LWP object. + $self->{ua} = LWP::UserAgent->new(); + $self->{ua}->cookie_jar( {} ); + $self->{ua}->credentials($$self{address}, $$self{realm}, $$self{username}, $$self{password}); + # Try again - $res = $self->{ua}->get($$self{base_url}.$cmd); + $res = $self->get($$self{base_url}.$cmd); if ( $res->is_success ) { # Command to camera appears successful, write Info item to log Info('Camera control 2: \''.$res->status_line().'\' for URL '.$$self{base_url}.$cmd); } else { Error('Camera control command FAILED: \''.$res->status_line().'\' for URL '.$$self{base_url}.$cmd); - $res = $self->{ua}->get('http://'.$self->{Monitor}->{ControlAddress}.'/'.$cmd); + $res = $self->get('http://'.$self->{Monitor}->{ControlAddress}.'/'.$cmd); } } @@ -447,7 +393,7 @@ sub set_config { my $url = $$self{base_url}.'/cgi-bin/configManager.cgi?action=setConfig'. join('&', map { $_.'='.uri_encode($$diff{$_}) } keys %$diff); - my $response = $self->{ua}->get($url); + my $response = $self->get($url); Debug($response->content); return $response->is_success(); } From 24ad12fa2270434761d62b0b3e575fc02d8e73c2 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 21 Dec 2024 14:34:35 -0500 Subject: [PATCH 3/3] Don't include empty fields in filter query string, just to shorten it a bit --- web/includes/Filter.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/web/includes/Filter.php b/web/includes/Filter.php index f2a8f5b9f..97088498b 100644 --- a/web/includes/Filter.php +++ b/web/includes/Filter.php @@ -90,9 +90,12 @@ class Filter extends ZM_Object { $this->_querystring .= $term->querystring($objectname, $separator); } # end foreach term $this->_querystring .= $separator.urlencode($objectname.'[Query][sort_asc]').'='.$this->sort_asc(); - $this->_querystring .= $separator.urlencode($objectname.'[Query][sort_field]').'='.$this->sort_field(); - $this->_querystring .= $separator.urlencode($objectname.'[Query][skip_locked]').'='.$this->skip_locked(); - $this->_querystring .= $separator.urlencode($objectname.'[Query][limit]').'='.$this->limit(); + if ($this->sort_field()) + $this->_querystring .= $separator.urlencode($objectname.'[Query][sort_field]').'='.$this->sort_field(); + if ($this->skip_locked()) + $this->_querystring .= $separator.urlencode($objectname.'[Query][skip_locked]').'='.$this->skip_locked(); + if ($this->limit()) + $this->_querystring .= $separator.urlencode($objectname.'[Query][limit]').'='.$this->limit(); if ( $this->Id() ) { $this->_querystring .= $separator.$objectname.urlencode('[Id]').'='.$this->Id(); }