From e59eb510e3ba5c282d2c2d2e0eafb4a5246b3f12 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 5 Mar 2019 13:10:04 -0500 Subject: [PATCH 01/11] update and fix the donate popup --- web/includes/actions/donate.php | 36 +++++++++++++++------------- web/skins/classic/js/classic.js | 2 +- web/skins/classic/views/donate.php | 31 ++++++++++++------------ web/skins/classic/views/js/donate.js | 10 -------- 4 files changed, 36 insertions(+), 43 deletions(-) diff --git a/web/includes/actions/donate.php b/web/includes/actions/donate.php index 156491ede..cfd60c8f0 100644 --- a/web/includes/actions/donate.php +++ b/web/includes/actions/donate.php @@ -27,28 +27,32 @@ if ( $action == 'donate' && isset($_REQUEST['option']) ) { $option = $_REQUEST['option']; switch( $option ) { case 'go' : - // Ignore this, the caller will open the page itself - break; + // Ignore this, the caller will open the page itself + break; case 'hour' : case 'day' : case 'week' : case 'month' : - $nextReminder = time(); - if ( $option == 'hour' ) { - $nextReminder += 60*60; - } elseif ( $option == 'day' ) { - $nextReminder += 24*60*60; - } elseif ( $option == 'week' ) { - $nextReminder += 7*24*60*60; - } elseif ( $option == 'month' ) { - $nextReminder += 30*24*60*60; - } - dbQuery("UPDATE Config SET Value = '".$nextReminder."' WHERE Name = 'ZM_DYN_DONATE_REMINDER_TIME'"); - break; + $nextReminder = time(); + if ( $option == 'hour' ) { + $nextReminder += 60*60; + } elseif ( $option == 'day' ) { + $nextReminder += 24*60*60; + } elseif ( $option == 'week' ) { + $nextReminder += 7*24*60*60; + } elseif ( $option == 'month' ) { + $nextReminder += 30*24*60*60; + } + dbQuery("UPDATE Config SET Value = '".$nextReminder."' WHERE Name = 'ZM_DYN_DONATE_REMINDER_TIME'"); + break; case 'never' : case 'already' : - dbQuery("UPDATE Config SET Value = '0' WHERE Name = 'ZM_DYN_SHOW_DONATE_REMINDER'"); - break; + dbQuery("UPDATE Config SET Value = '0' WHERE Name = 'ZM_DYN_SHOW_DONATE_REMINDER'"); + break; + default : + Warning("Unknown value for option in donate: $option"); + break; } // end switch option + $view = 'none'; } ?> diff --git a/web/skins/classic/js/classic.js b/web/skins/classic/js/classic.js index 8d0d59728..0e62ecbf0 100644 --- a/web/skins/classic/js/classic.js +++ b/web/skins/classic/js/classic.js @@ -33,7 +33,7 @@ var popupSizes = { 'cycle': {'addWidth': 32, 'minWidth': 384, 'addHeight': 62}, 'device': {'width': 260, 'height': 150}, 'devices': {'width': 400, 'height': 240}, - 'donate': {'width': 500, 'height': 280}, + 'donate': {'width': 500, 'height': 480}, 'download': {'width': 350, 'height': 215}, 'event': {'addWidth': 108, 'minWidth': 496, 'addHeight': 230, 'minHeight': 540}, 'eventdetail': {'width': 600, 'height': 420}, diff --git a/web/skins/classic/views/donate.php b/web/skins/classic/views/donate.php index b18e2b010..456fc1e47 100644 --- a/web/skins/classic/views/donate.php +++ b/web/skins/classic/views/donate.php @@ -18,25 +18,24 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -if ( !canEdit( 'System' ) ) -{ - $view = "error"; - return; +if ( !canEdit('System') ) { + $view = 'error'; + return; } $options = array( - "go" => translate('DonateYes'), - "hour" => translate('DonateRemindHour'), - "day" => translate('DonateRemindDay'), - "week" => translate('DonateRemindWeek'), - "month" => translate('DonateRemindMonth'), - "never" => translate('DonateRemindNever'), - "already" => translate('DonateAlready'), + "go" => translate('DonateYes'), + "hour" => translate('DonateRemindHour'), + "day" => translate('DonateRemindDay'), + "week" => translate('DonateRemindWeek'), + "month" => translate('DonateRemindMonth'), + "never" => translate('DonateRemindNever'), + "already" => translate('DonateAlready'), ); $focusWindow = true; -xhtmlHeaders(__FILE__, translate('Donate') ); +xhtmlHeaders(__FILE__, translate('Donate')); ?>
@@ -46,17 +45,17 @@ xhtmlHeaders(__FILE__, translate('Donate') );
- +

- +

- - + +
diff --git a/web/skins/classic/views/js/donate.js b/web/skins/classic/views/js/donate.js index 6ff1ae75b..0cfb42fae 100644 --- a/web/skins/classic/views/js/donate.js +++ b/web/skins/classic/views/js/donate.js @@ -1,13 +1,3 @@ -function submitForm( element ) { - var form = element.form; - if ( form.option.selectedIndex == 0 ) { - form.view.value = currentView; - } else { - form.view.value = 'none'; - } - form.submit(); -} - if ( action == "donate" && option == "go" ) { zmWindow(); } From e6220e9d07cac046b39fd00c17125f1eb1075cde Mon Sep 17 00:00:00 2001 From: Matthew Noorenberghe Date: Sun, 10 Mar 2019 20:56:08 -0700 Subject: [PATCH 02/11] Fix eslint issues in cycle.js --- web/skins/classic/views/js/cycle.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/web/skins/classic/views/js/cycle.js b/web/skins/classic/views/js/cycle.js index 9422890c8..4e6de5320 100644 --- a/web/skins/classic/views/js/cycle.js +++ b/web/skins/classic/views/js/cycle.js @@ -26,10 +26,11 @@ function cycleNext() { window.location.replace('?view=cycle&mid='+monitorData[monIdx].id+'&mode='+mode, cycleRefreshTimeout); } function cyclePrev() { - if ( monIdx ) + if (monIdx) { monIdx -= 1; - else - monIdx = monitorData.length-1; + } else { + monIdx = monitorData.length - 1; + } window.location.replace('?view=cycle&mid='+monitorData[monIdx].id+'&mode='+mode, cycleRefreshTimeout); } @@ -82,9 +83,9 @@ function changeScale() { var scale = $('scale').get('value'); $('width').set('value', 'auto'); $('height').set('value', 'auto'); - Cookie.write('zmCycleScale', scale, { duration: 10*365 }); - Cookie.write('zmCycleWidth', 'auto', { duration: 10*365 }); - Cookie.write('zmCycleHeight', 'auto', { duration: 10*365 }); + Cookie.write('zmCycleScale', scale, {duration: 10*365}); + Cookie.write('zmCycleWidth', 'auto', {duration: 10*365}); + Cookie.write('zmCycleHeight', 'auto', {duration: 10*365}); var newWidth = ( monitorData[monIdx].width * scale ) / SCALE_BASE; var newHeight = ( monitorData[monIdx].height * scale ) / SCALE_BASE; From ac547e0d5d1da2d9935af59ce23681567a367c3b Mon Sep 17 00:00:00 2001 From: Matthew Noorenberghe Date: Sun, 10 Mar 2019 20:52:28 -0700 Subject: [PATCH 03/11] Don't scroll to the top of the page when force/cancel alarm is clicked --- web/skins/classic/views/js/watch.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/web/skins/classic/views/js/watch.js b/web/skins/classic/views/js/watch.js index a0594825b..a30a05fee 100644 --- a/web/skins/classic/views/js/watch.js +++ b/web/skins/classic/views/js/watch.js @@ -448,10 +448,16 @@ function cmdEnableAlarms() { function cmdForceAlarm() { alarmCmdReq.send(alarmCmdParms+"&command=forceAlarm"); + if (window.event) { + window.event.preventDefault(); + } } function cmdCancelForcedAlarm() { alarmCmdReq.send(alarmCmdParms+"&command=cancelForcedAlarm"); + if (window.event) { + window.event.preventDefault(); + } return false; } From 056b96f7fcb1a4747e015a9b6193636c0c8f5c5f Mon Sep 17 00:00:00 2001 From: Matthew Noorenberghe Date: Sun, 10 Feb 2019 21:27:57 -0800 Subject: [PATCH 04/11] API: Monitor and Event 'index' SQLi. Fixes #2099 --- .../Controller/Component/FilterComponent.php | 84 +++++++++++++++++-- web/api/app/Controller/EventsController.php | 5 +- web/api/app/Controller/MonitorsController.php | 7 +- 3 files changed, 86 insertions(+), 10 deletions(-) diff --git a/web/api/app/Controller/Component/FilterComponent.php b/web/api/app/Controller/Component/FilterComponent.php index 326c1ce45..798452793 100644 --- a/web/api/app/Controller/Component/FilterComponent.php +++ b/web/api/app/Controller/Component/FilterComponent.php @@ -1,14 +1,88 @@ ', + '>', + '>=', + 'IS', + 'IS NOT', + //'IS NOT NULL', + //'IS NULL', + //'->', + //'->>', + '<<', + '<', + '<=', + 'LIKE', + '-', + '%', + 'MOD', + //'NOT', + //'!', + //'NOT BETWEEN ... AND ...', + '!=', + '<>', + 'NOT LIKE', + 'NOT REGEXP', + // `or` operators aren't safe as they can + // be used to skip an existing condition + // enforcing access to only certain + // monitors/events. + //'||', + //'OR', + '+', + 'REGEXP', + '>>', + 'RLIKE', + 'SOUNDS LIKE', + //'*', + '-', + //'XOR', + ); + // Build a CakePHP find() condition based on the named parameters // that are passed in public function buildFilter($namedParams) { - if ($namedParams) { - $conditions = array(); - + $conditions = array(); + if ($namedParams) { foreach ($namedParams as $attribute => $value) { + // We need to sanitize $attribute to avoid SQL injection. + $lhs = trim($attribute); + $matches = NULL; + if (preg_match('/^(?P[a-z0-9]+)(?P.+)?$/i', $lhs, $matches) !== 1) { + throw new Exception('Invalid argument before `:`: ' . $lhs); + } + $operator = trim($matches['operator']); + + // Only allow operators on our allow list. No operator + // specified defaults to `=` by cakePHP. + if ($operator != '' && !in_array($operator, $this->twoOperandSQLOperands)) { + throw new Exception('Invalid operator: ' . $operator); + } + + $lhs = '`' . $matches['field'] . '` ' . $operator; // If the named param contains an array, we want to turn it into an IN condition // Otherwise, we add it right into the $conditions array if (is_array($value)) { @@ -18,10 +92,10 @@ class FilterComponent extends Component { array_push($array, $term); } - $query = array($attribute => $array); + $query = array($lhs => $array); array_push($conditions, $query); } else { - $conditions[$attribute] = $value; + $conditions[$lhs] = $value; } } diff --git a/web/api/app/Controller/EventsController.php b/web/api/app/Controller/EventsController.php index cf8ca8f37..4f706bec8 100644 --- a/web/api/app/Controller/EventsController.php +++ b/web/api/app/Controller/EventsController.php @@ -44,9 +44,8 @@ class EventsController extends AppController { } if ( $this->request->params['named'] ) { - //$this->FilterComponent = $this->Components->load('Filter'); - //$conditions = $this->FilterComponent->buildFilter($this->request->params['named']); - $conditions = $this->request->params['named']; + $this->FilterComponent = $this->Components->load('Filter'); + $conditions = $this->FilterComponent->buildFilter($this->request->params['named']); } else { $conditions = array(); } diff --git a/web/api/app/Controller/MonitorsController.php b/web/api/app/Controller/MonitorsController.php index 185a06c84..fd0d1e94a 100644 --- a/web/api/app/Controller/MonitorsController.php +++ b/web/api/app/Controller/MonitorsController.php @@ -40,8 +40,7 @@ class MonitorsController extends AppController { if ( $this->request->params['named'] ) { $this->FilterComponent = $this->Components->load('Filter'); - //$conditions = $this->FilterComponent->buildFilter($this->request->params['named']); - $conditions = $this->request->params['named']; + $conditions = $this->FilterComponent->buildFilter($this->request->params['named']); } else { $conditions = array(); } @@ -315,6 +314,10 @@ class MonitorsController extends AppController { throw new NotFoundException(__('Invalid monitor')); } + if (preg_match('/^[a-z]+$/i', $daemon) !== 1) { + throw new BadRequestException(__('Invalid command')); + } + $monitor = $this->Monitor->find('first', array( 'fields' => array('Id', 'Type', 'Device'), 'conditions' => array('Id' => $id) From abb6ef1688b145670d351b14370e6f52aec8483e Mon Sep 17 00:00:00 2001 From: Matthew Noorenberghe Date: Sun, 10 Feb 2019 21:34:49 -0800 Subject: [PATCH 05/11] API: Escape 'named' params for SQLi in two more Event endpoints. Fixes #2099 --- web/api/app/Controller/EventsController.php | 54 ++++++++++++++++----- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/web/api/app/Controller/EventsController.php b/web/api/app/Controller/EventsController.php index 4f706bec8..959ccc596 100644 --- a/web/api/app/Controller/EventsController.php +++ b/web/api/app/Controller/EventsController.php @@ -233,18 +233,34 @@ class EventsController extends AppController { public function search() { $this->Event->recursive = -1; + // Unmodified conditions to pass to find() + $find_conditions = array(); + // Conditions to be filtered by buildFilter $conditions = array(); foreach ($this->params['named'] as $param_name => $value) { - // Transform params into mysql - if ( preg_match('/interval/i', $value, $matches) ) { - $condition = array("$param_name >= (date_sub(now(), $value))"); + // Transform params into conditions + if ( preg_match('/^\s?interval\s?/i', $value) ) { + if (preg_match('/^[a-z0-9]+$/i', $param_name) !== 1) { + throw new Exception('Invalid field name: ' . $param_name); + } + $matches = NULL; + $value = preg_replace('/^\s?interval\s?/i', '', $value); + if (preg_match('/^(?P[ -.:0-9\']+)\s+(?P[_a-z]+)$/i', trim($value), $matches) !== 1) { + throw new Exception('Invalid interval: ' . $value); + } + $expr = trim($matches['expr']); + $unit = trim($matches['unit']); + array_push($find_conditions, "$param_name >= DATE_SUB(NOW(), INTERVAL $expr $unit)"); } else { - $condition = array($param_name => $value); + $conditions[$param_name] = $value; } - array_push($conditions, $condition); } + $this->FilterComponent = $this->Components->load('Filter'); + $conditions = $this->FilterComponent->buildFilter($conditions); + array_push($conditions, $find_conditions); + $results = $this->Event->find('all', array( 'conditions' => $conditions )); @@ -260,18 +276,32 @@ class EventsController extends AppController { // consoleEvents/1 hour/AlarmFrames >=: 1/AlarmFrames <=: 20.json public function consoleEvents($interval = null) { + $matches = NULL; + // https://dev.mysql.com/doc/refman/5.5/en/expressions.html#temporal-intervals + // Examples: `'1-1' YEAR_MONTH`, `'-1 10' DAY_HOUR`, `'1.999999' SECOND_MICROSECOND` + if (preg_match('/^(?P[ -.:0-9\']+)\s+(?P[_a-z]+)$/i', trim($interval), $matches) !== 1) { + throw new Exception('Invalid interval: ' . $interval); + } + $expr = trim($matches['expr']); + $unit = trim($matches['unit']); + $this->Event->recursive = -1; $results = array(); + $this->FilterComponent = $this->Components->load('Filter'); + $conditions = $this->FilterComponent->buildFilter($conditions); + array_push($conditions, array("StartTime >= DATE_SUB(NOW(), INTERVAL $expr $unit)")); - $moreconditions = ''; - foreach ($this->request->params['named'] as $name => $param) { - $moreconditions = $moreconditions . ' AND '.$name.$param; - } - - $query = $this->Event->query("SELECT MonitorId, COUNT(*) AS Count FROM Events WHERE (StartTime >= (DATE_SUB(NOW(), interval $interval)) $moreconditions) GROUP BY MonitorId;"); + $query = $this->Event->find('all', array( + 'fields' => array( + 'MonitorId', + 'COUNT(*) AS Count', + ), + 'conditions' => $conditions, + 'group' => 'MonitorId', + )); foreach ($query as $result) { - $results[$result['Events']['MonitorId']] = $result[0]['Count']; + $results[$result['Event']['MonitorId']] = $result[0]['Count']; } $this->set(array( From 3c31dd63cedf2365639600979b3fb5fe010c6a68 Mon Sep 17 00:00:00 2001 From: Matthew Noorenberghe Date: Sun, 10 Mar 2019 20:40:32 -0700 Subject: [PATCH 06/11] Use zm_session_start() for API auth. Fixes #2547 --- web/includes/auth.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/includes/auth.php b/web/includes/auth.php index 5ce960388..6b061f7fc 100644 --- a/web/includes/auth.php +++ b/web/includes/auth.php @@ -211,7 +211,7 @@ global $user; if ( ZM_OPT_USE_AUTH ) { $close_session = 0; if ( !is_session_started() ) { - session_start(); + zm_session_start(); $close_session = 1; } From 6ee689f4bfa57e04002c64ff145af13b0bf11fe4 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 12 Mar 2019 11:09:57 -0400 Subject: [PATCH 07/11] Dahua control improvements (#2552) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adding a presetHome method The Dahua protocol does not appear to support a preset Home feature. We could allow the user to assign a preset slot as the "home" slot. Dahua does appear to support naming presets which may lend itself to this sort of thing. At this point, we'll just send the camera back to center and zoom wide. (0°,0°,0) * Adjusting naming of private methods and adding POD * Adding relative focus methods This patch also adds the return value of the get request used to send the command to the camera. Furthermore, it fixes a small bug in the authentication of requests sent to the camera after the connection is opened. I really have no idea why the problem occurred and it appeared to have no practical effect on the execution of the command. It showed up when I enabled debug, so this attempts to fix it or at least quiet it. * Adding POD for the new relative focus methods Also doing a bit of cleanup on POD in general. * Adding some documentation to demystify control motion types This was not clear to me at the outset, and I could not locate existing documentation which explained the prioritization and exclusion aspects. Maybe it will help someone else. * Renaming private methods and adding more POD This patch renames private methods by prefixing an underscore to them. This at least helps visually distinguish them as private when reading through the code. It also adds more documentation on public methods. * Grouping all relative motion methods together This makes for easier reading. * Adding in a reset method from Issue #2414 This method was supplied by kobold81 who got it from bobylapointe69300 who posted it in the following forum post: https://forums.zoneminder.com/viewtopic.php?f=9&t=27000&p=104601&hilit=dahua#p104601 This patch also includes some POD cleanup. * Adding continuous movement methods and fixing reset This patch adds the continuous movement methods provided in the patch for Issue #2414. Note that these are not truly continuous as they result in movement for less than a ms. Adapting the code to support truly continuous movement would invovle some considerable work. This patch also correct the reset method and adds a reboot method. The two are different creatures with different effects. POD added as well. * Removing redundant get request code Two slightly different versions of code are currently used to post the get request to the camera in order to execute commands. This patch modifies the open method in order to allow its re-use and removes redundant code. Note: This is the first installment on changes towards improving the way the HTTP transactions are handled. * Making authenication failures fatal Authentication failures result in camera commands not being executed. They may as well be fatal and return the general reason to the user directly. * Work on persistent sessions I think this will have to wait since it appears that the camera expects some sort of keepalive/heartbeat signal to keep the session open. * Restoring accidentally deleted code --- .../lib/ZoneMinder/Control/Dahua.pm | 396 +++++++++++++++--- 1 file changed, 329 insertions(+), 67 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Dahua.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Dahua.pm index 6cb55d3a6..10a37e102 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Dahua.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Dahua.pm @@ -39,6 +39,8 @@ sub AUTOLOAD my $class = ref($self) || croak( "$self not object" ); my $name = $AUTOLOAD; $name =~ s/.*://; + ## This seems odd... if the method existed would we even be here? + ## https://perldoc.perl.org/perlsub.html#Autoloading if ( exists($self->{$name}) ) { return( $self->{$name} ); @@ -46,9 +48,17 @@ sub AUTOLOAD Fatal( "Can't access $name member of object of class $class" ); } +# FIXME: Do we really have to open a new connection every time? + +#Digest usernbme="bdmin", reblm="Login to 4K05DB3PAJE98BE", nonae="1720242756", +#uri="/agi-bin/ptz.agi?bation=getStbtus&ahbnnel=1", response="10dd925b26ebd559353734635b859b8b", +#opbque="1a99677524b4ae63bbe3a132b2e9b38e3b163ebd", qop=buth, na=00000001, anonae="ab1bb5d43aa5d542" + sub open { + #Debug("&open invoked by: " . (caller(1))[3]); my $self = shift; + my $cgi = shift || '/cgi-bin/configManager.cgi?action=getConfig&name=Ptz'; $self->loadMonitor(); # The Dahua camera firmware API supports the concept of having multiple @@ -73,60 +83,55 @@ sub open } use LWP::UserAgent; - $self->{ua} = LWP::UserAgent->new; + $self->{ua} = LWP::UserAgent->new(keep_alive => 1); $self->{ua}->agent("ZoneMinder Control Agent/".$ZoneMinder::Base::ZM_VERSION); $self->{state} = 'closed'; # credentials: ("ip:port" (no prefix!), realm (string), username (string), password (string) - Debug("sendCmd credentials control address:'".$ADDRESS - ."' realm:'" . $REALM - . "' username:'" . $USERNAME - . "' password:'".$PASSWORD - ."'" - ); $self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD); # Detect REALM - my $get_config_url = $PROTOCOL . $ADDRESS . "/cgi-bin/configManager.cgi?action=getConfig&name=Ptz"; - my $req = HTTP::Request->new(GET=>$get_config_url); + my $url = $PROTOCOL . $ADDRESS . $cgi; + my $req = HTTP::Request->new(GET=>$url); my $res = $self->{ua}->request($req); if ($res->is_success) { $self->{state} = 'open'; - return; + return 1; } 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+(.*)$/; + Debug("Tokens: " . $tokens); + ## FIXME: This is necessary because the Dahua spec does not match reality if ($tokens =~ /\w+="([^"]+)"/i) { if ($REALM ne $1) { $REALM = $1; Debug("Changing REALM to '" . $REALM . "'"); $self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD); - my $req = HTTP::Request->new(GET=>$get_config_url); + my $req = HTTP::Request->new(GET=>$url); $res = $self->{ua}->request($req); + if ($res->is_success()) { $self->{state} = 'open'; - return; + Debug('Authentication succeeded...'); + return 1; } Debug('Authentication still failed after updating REALM' . $res->status_line); $headers = $res->headers(); foreach my $k ( keys %$headers ) { Debug("Initial Header $k => $$headers{$k}"); } # end foreach - } else { - Error('Authentication failed, not a REALM problem'); + } else { ## NOTE: Each of these else conditions is fatal as the command will not be + ## executed. No use going further. + Fatal('Authentication failed: Check username and password.'); } } else { - Error('Failed to match realm in tokens'); + Fatal('Authentication failed: Incorrect realm.'); } # end if } else { - Error('No WWW-Authenticate Header'); + Fatal('Authentication failed: No www-authenticate header returned.'); } # end if headers } # end if $res->status_line() eq '401 Unauthorized' } @@ -146,45 +151,40 @@ sub printMsg Debug( $msg."[".$msg_len."]" ); } -sub sendGetRequest { +sub _sendGetRequest { my $self = shift; my $url_path = shift; - my $result = undef; + # Attempt to reuse the connection + + # FIXME: I think we need some sort of keepalive/heartbeat sent to the camera + # in order to keep the session alive. As it is, it appears that the + # ua's authentication times out or some such. + # + # This might be of some use: + # {"method":"global.keepAlive","params":{"timeout":300,"active":false},"id":1518,"session":"dae233a51c0693519395209b271411b6"}[!http] + # The web browser interface POSTs commands as JSON using js my $url = $PROTOCOL . $ADDRESS . $url_path; - my $req = HTTP::Request->new(GET=>$url); - + my $req = HTTP::Request->new(GET => $url); my $res = $self->{ua}->request($req); if ($res->is_success) { - $result = !undef; + return 1; } else { - if ($res->status_line() eq '401 Unauthorized') { - Debug("Error check failed, trying again: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD); - Debug("Content was " . $res->content() ); - my $res = $self->{ua}->request($req); - if ($res->is_success) { - $result = !undef; - } else { - Error("Content was " . $res->content() ); - } - } - if ( ! $result ) { - Error("Error check failed: '".$res->status_line()); - } + return($self->open($url_path)); # if we have to, open a new connection } - return($result); } -sub sendPtzCommand +sub _sendPtzCommand { my $self = shift; my $action = shift; my $command_code = shift; - my $arg1 = shift; - my $arg2 = shift; - my $arg3 = shift; + my $arg1 = shift || 0; + my $arg2 = shift || 0; + my $arg3 = shift || 0; + my $arg4 = shift || 0; my $channel = $self->{dahua_channel_number}; @@ -194,10 +194,12 @@ sub sendPtzCommand $url_path .= "code=" . $command_code . "&"; $url_path .= "arg1=" . $arg1 . "&"; $url_path .= "arg2=" . $arg2 . "&"; - $url_path .= "arg3=" . $arg3; - $self->sendGetRequest($url_path); + $url_path .= "arg3=" . $arg3 . "&"; + $url_path .= "arg4=" . $arg4; + return $self->_sendGetRequest($url_path); } -sub sendMomentaryPtzCommand + +sub _sendMomentaryPtzCommand { my $self = shift; my $command_code = shift; @@ -206,92 +208,195 @@ sub sendMomentaryPtzCommand my $arg3 = shift; my $duration_ms = shift; - $self->sendPtzCommand("start", $command_code, $arg1, $arg2, $arg3); + $self->_sendPtzCommand("start", $command_code, $arg1, $arg2, $arg3); my $duration_ns = $duration_ms * 1000; usleep($duration_ns); - $self->sendPtzCommand("stop", $command_code, $arg1, $arg2, $arg3); + $self->_sendPtzCommand("stop", $command_code, $arg1, $arg2, $arg3); +} + +sub _sendAbsolutePositionCommand +{ + my $self = shift; + my $arg1 = shift; + my $arg2 = shift; + my $arg3 = shift; + my $arg4 = shift; + + $self->_sendPtzCommand("start", "PositionABS", $arg1, $arg2, $arg3, $arg4); +} + +sub moveConLeft +{ + my $self = shift; + Debug("Move Up Left"); + $self->_sendMomentaryPtzCommand("Left", 0, 1, 0, 0); +} + +sub moveConRight +{ + my $self = shift; + Debug( "Move Right" ); + $self->_sendMomentaryPtzCommand("Right", 0, 1, 0, 0); +} + +sub moveConUp +{ + my $self = shift; + Debug( "Move Up" ); + $self->_sendMomentaryPtzCommand("Up", 0, 1, 0, 0); +} + +sub moveConDown +{ + my $self = shift; + Debug( "Move Down" ); + $self->_sendMomentaryPtzCommand("Down", 0, 1, 0, 0); +} + +sub moveConUpRight +{ + my $self = shift; + Debug( "Move Diagonally Up Right" ); + $self->_sendMomentaryPtzCommand("RightUp", 1, 1, 0, 0); +} + +sub moveConDownRight +{ + my $self = shift; + Debug( "Move Diagonally Down Right" ); + $self->_sendMomentaryPtzCommand("RightDown", 1, 1, 0, 0); +} + +sub moveConUpLeft +{ + my $self = shift; + Debug( "Move Diagonally Up Left" ); + $self->_sendMomentaryPtzCommand("LeftUp", 1, 1, 0, 0); +} + +sub moveConDownLeft +{ + my $self = shift; + Debug( "Move Diagonally Up Right" ); + $self->_sendMomentaryPtzCommand("LeftDown", 1, 1, 0, 0); +} + +sub zoomConTele +{ + my $self = shift; + Debug( "Zoom Tele" ); + $self->_sendMomentaryPtzCommand("ZoomTele", 0, 1, 0, 0); +} + +sub zoomConWide +{ + my $self = shift; + Debug( "Zoom Wide" ); + $self->_sendMomentaryPtzCommand("ZoomWide", 0, 1, 0, 0); } sub moveRelUpLeft { my $self = shift; Debug("Move Up Left"); - $self->sendMomentaryPtzCommand("LeftUp", 4, 4, 0, 500); + $self->_sendMomentaryPtzCommand("LeftUp", 4, 4, 0, 500); } sub moveRelUp { my $self = shift; Debug("Move Up"); - $self->sendMomentaryPtzCommand("Up", 0, 4, 0, 500); + $self->_sendMomentaryPtzCommand("Up", 0, 4, 0, 500); } sub moveRelUpRight { my $self = shift; Debug("Move Up Right"); - $self->sendMomentaryPtzCommand("RightUp", 0, 4, 0, 500); + $self->_sendMomentaryPtzCommand("RightUp", 0, 4, 0, 500); } sub moveRelLeft { my $self = shift; Debug("Move Left"); - $self->sendMomentaryPtzCommand("Left", 0, 4, 0, 500); + $self->_sendMomentaryPtzCommand("Left", 0, 4, 0, 500); } sub moveRelRight { my $self = shift; Debug("Move Right"); - $self->sendMomentaryPtzCommand("Right", 0, 4, 0, 500); + $self->_sendMomentaryPtzCommand("Right", 0, 4, 0, 500); } sub moveRelDownLeft { my $self = shift; Debug("Move Down Left"); - $self->sendMomentaryPtzCommand("LeftDown", 4, 4, 0, 500); + $self->_sendMomentaryPtzCommand("LeftDown", 4, 4, 0, 500); } sub moveRelDown { my $self = shift; Debug("Move Down"); - $self->sendMomentaryPtzCommand("Down", 0, 4, 0, 500); + $self->_sendMomentaryPtzCommand("Down", 0, 4, 0, 500); } sub moveRelDownRight { my $self = shift; Debug("Move Down Right"); - $self->sendMomentaryPtzCommand("RightDown", 4, 4, 0, 500); + $self->_sendMomentaryPtzCommand("RightDown", 4, 4, 0, 500); } sub zoomRelTele { my $self = shift; Debug("Zoom Relative Tele"); - $self->sendMomentaryPtzCommand("ZoomTele", 0, 0, 0, 500); + $self->_sendMomentaryPtzCommand("ZoomTele", 0, 0, 0, 500); } sub zoomRelWide { my $self = shift; Debug("Zoom Relative Wide"); - $self->sendMomentaryPtzCommand("ZoomWide", 0, 0, 0, 500); + $self->_sendMomentaryPtzCommand("ZoomWide", 0, 0, 0, 500); } +sub focusRelNear +{ + my $self = shift; + + my $response = $self->_sendPtzCommand("start", "FocusNear", 0, 1, 0, 0); + Debug("focusRelNear response: " . $response); +} + +sub focusRelFar +{ + my $self = shift; + + my $response = $self->_sendPtzCommand("start", "FocusFar", 0, 1, 0, 0); + Debug("focusRelFar response: " . $response); +} + +sub moveStop +{ + my $self = shift; + Debug( "Move Stop" ); + # The command does not matter here, just the stop... + $self->_sendPtzCommand("stop", "Up", 0, 0, 1, 0); +} sub presetClear { my $self = shift; my $params = shift; my $preset_id = $self->getParam($params, 'preset'); - $self->sendPtzCommand("start", "ClearPreset", 0, $preset_id, 0); + $self->_sendPtzCommand("start", "ClearPreset", 0, $preset_id, 0); } - sub presetSet { my $self = shift; @@ -308,8 +413,8 @@ sub presetSet my $control_preset_row = $sth->fetchrow_hashref(); my $new_label_name = $control_preset_row->{'Label'}; - $self->sendPtzCommand("start", "SetPreset", 0, $preset_id, 0); - $self->sendPtzCommand("start", "SetPresetName", $preset_id, $new_label_name, 0); + $self->_sendPtzCommand("start", "SetPreset", 0, $preset_id, 0); + $self->_sendPtzCommand("start", "SetPresetName", $preset_id, $new_label_name, 0); } sub presetGoto @@ -318,12 +423,39 @@ sub presetGoto my $params = shift; my $preset_id = $self->getParam($params, 'preset'); - $self->sendPtzCommand("start", "GotoPreset", 0, $preset_id, 0); + $self->_sendPtzCommand("start", "GotoPreset", 0, $preset_id, 0); +} + +sub presetHome +{ + my $self = shift; + + $self->_sendAbsolutePositionCommand( 0, 0, 0, 1 ); +} + +sub reset +{ + my $self = shift; + Debug( "Camera Reset" ); + $self->_sendPtzCommand("Reset", 0, 0, 0, 0); +} + +sub reboot +{ + my $self = shift; + Debug( "Camera Reboot" ); + my $cmd = "cgi-bin/magicBox.cgi?action=reboot"; + $self->_sendGetRequest($cmd); } 1; + __END__ +=pod + +=encoding utf8 + =head1 NAME ZoneMinder::Control::Dahua - Perl module for Dahua cameras @@ -337,10 +469,6 @@ place this in /usr/share/perl5/ZoneMinder/Control This module is an implementation of the Dahua IP camera HTTP control API. -=head2 EXPORT - -None by default. - =head1 COPYRIGHT AND LICENSE Copyright (C) 2018 ZoneMinder LLC @@ -359,4 +487,138 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +=head1 Private Methods + + Methods intended for use internally but documented here for future developers. + +=head2 _sendAbsolutePositionCommand( $arg1, $arg2, $arg3, $arg4 ) + + Where: + + $arg1 = Horizontal angle 0° to 360° + $arg2 = Vertical angle 0° to -90° + $arg3 = Zoom multiplier + $arg4 = Speed 1 to 8 + + This is an private method used to send an absolute position command to the + camera. + +=head1 Public Methods + + Methods made available to control.pl via ZoneMinder::Control + +=head2 Notes: + +=over 1 + + Which methods are invoked depends on which types of movement are selected in + the camera control type. For example: if the 'Can Move Continuous' option is + checked, then methods including 'Con' in their names are invoked. Likewise if + the 'Can Move Relative" option is checked, then methods including 'Rel' in + their names are invoked. + + + At present, these types of movement are prioritized and exclusive. This applies + to all types of movement, not just PTZ, but focus, iris, etc. as well. The options + are tested in the following order: + + 1. Continuous + + 2. Relative + + 3. Absolute + + These types are exclusive meaning that the first one that matches is the one + ZoneMinder will use to control with. It would be nice to allow the user to + select the type used given that some cameras support all three types of + movement. + +=back + +=head2 new + + This method instantiates a new control object based upon this control module + and sets the 'id' attribute to the value passed in. + +=head2 open + + This method opens an HTTP connection to the camera. It handles authentication, + etc. Upon success it sets the 'state' attribute to 'open.' + +=head2 close + + This method effectively closes the HTTP connection to the camera. It sets the + 'state' attribute to 'close.' + +=head2 printMsg + + This method appears to be used for debugging. + +=head2 moveCon + + This set of methods invoke continuous movement in the direction indicated by + the portion of their name. They accept no arguments and move the + camera at a speed of 1 for 0ms. The speed index of 1 is the lowest of the + accepted range of 1-8. + + NOTE: + + This is not true continuous movmement as currently implemented. + +=head2 focusCon + + This set of methods invoke continuous focus in the range direction indicated + by the portion of their name. They accept no arguments. + + NOTE: + + This is not true continuous movmement as currently implemented. + +=head2 moveRel + + This set of methods invoke relatvie movement in the direction indicated by + the portion of their name. They accept no arguments and move the + camera at a speed of 4 for 500ms. The speed index of 4 is half-way between + the accepted range of 1-8. + +=head2 focusRel + + This set of methods invoke realtive focus in the range direction indicated by + the portion of their name. They accept no arguments. + + NOTE: + + This only just does work. The Dahua API specifies "multiples" as the input. + We pass in a 1 for that as it does not seem to matter what number (0-8) is + provided, the camera focus behaves the same. + +=head2 moveStop + + This method attempts to stop the camera. The problem is that if continuous + motion is occurring in multiple directions, this will only stop the motion + in the 'Up' direction. Dahua does not support an "all-stop" command. + +=head2 presetHome + + This method "homes" the camera to a preset position. It accepts no arguments. + When either continuous or relative movement is enabled, pressing the center + button on the movement controls invokes this method. + + NOTE: + + The Dahua protocol does not appear to support a preset Home feature. We could + allow the user to assign a preset slot as the "home" slot. Dahua does appear + to support naming presets which may lend itself to this sort of thing. At + this point, we'll just send the camera back to center and zoom wide. (0°,0°,0) + +=head2 reset + + This method will reset the PTZ controls to their "default." It is not clear + what that is. + +=head2 reboot + + This method performs a reboot of the camera. This will take the camera offline + for the time it takes to reboot. + =cut From 9482207f5c7b4f9fd65e8bf1f8296bb6d2a27f2b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 18 Mar 2019 11:24:28 -0400 Subject: [PATCH 08/11] revert namespace stuff in index.php --- web/index.php | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/web/index.php b/web/index.php index 7a5abb79b..99e77d285 100644 --- a/web/index.php +++ b/web/index.php @@ -17,7 +17,6 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -namespace ZM; error_reporting(E_ALL); @@ -194,7 +193,7 @@ isset($view) || $view = NULL; isset($request) || $request = NULL; isset($action) || $action = NULL; -Logger::Debug("View: $view Request: $request Action: $action User: " . ( isset($user) ? $user['Username'] : 'none' )); +ZM\Logger::Debug("View: $view Request: $request Action: $action User: " . ( isset($user) ? $user['Username'] : 'none' )); if ( ZM_ENABLE_CSRF_MAGIC && ( $action != 'login' ) && @@ -205,17 +204,17 @@ if ( ( $view != 'archive' ) ) { require_once( 'includes/csrf/csrf-magic.php' ); - #Logger::Debug("Calling csrf_check with the following values: \$request = \"$request\", \$view = \"$view\", \$action = \"$action\""); + #ZM\Logger::Debug("Calling csrf_check with the following values: \$request = \"$request\", \$view = \"$view\", \$action = \"$action\""); csrf_check(); } # Need to include actions because it does auth if ( $action ) { if ( file_exists('includes/actions/'.$view.'.php') ) { - Logger::Debug("Including includes/actions/$view.php"); + ZM\Logger::Debug("Including includes/actions/$view.php"); require_once('includes/actions/'.$view.'.php'); } else { - Warning("No includes/actions/$view.php for action $action"); + ZM\Warning("No includes/actions/$view.php for action $action"); } } @@ -227,7 +226,7 @@ if ( ZM_OPT_USE_AUTH and !isset($user) and ($view != 'login') ) { header('HTTP/1.1 401 Unauthorized'); exit; } - Logger::Debug('Redirecting to login'); + ZM\Logger::Debug('Redirecting to login'); $view = 'none'; $redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=login'; $request = null; @@ -239,7 +238,7 @@ if ( ZM_OPT_USE_AUTH and !isset($user) and ($view != 'login') ) { CSPHeaders($view, $cspNonce); if ( $redirect ) { - Logger::Debug("Redirecting to $redirect"); + ZM\Logger::Debug("Redirecting to $redirect"); header('Location: '.$redirect); return; } @@ -247,7 +246,7 @@ if ( $redirect ) { if ( $request ) { foreach ( getSkinIncludes('ajax/'.$request.'.php', true, true) as $includeFile ) { if ( !file_exists($includeFile) ) - Fatal("Request '$request' does not exist"); + ZM\Fatal("Request '$request' does not exist"); require_once $includeFile; } return; @@ -256,7 +255,7 @@ if ( $request ) { if ( $includeFiles = getSkinIncludes('views/'.$view.'.php', true, true) ) { foreach ( $includeFiles as $includeFile ) { if ( !file_exists($includeFile) ) - Fatal("View '$view' does not exist"); + ZM\Fatal("View '$view' does not exist"); require_once $includeFile; } // If the view overrides $view to 'error', and the user is not logged in, then the From b794c2ca203bd11bf804ea0b5ac49aaa6000cf03 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 18 Mar 2019 12:01:51 -0400 Subject: [PATCH 09/11] fix crash by checking username without checking if it is NULL --- src/zm_user.cpp | 12 ++++++++---- src/zmu.cpp | 46 ++++++++++++++++++++-------------------------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/zm_user.cpp b/src/zm_user.cpp index da0c66416..46ee2cdf1 100644 --- a/src/zm_user.cpp +++ b/src/zm_user.cpp @@ -248,15 +248,19 @@ User *zmLoadAuthUser( const char *auth, bool use_remote_addr ) { //Function to check Username length bool checkUser ( const char *username) { - if ( strlen(username) > 32) { + if ( ! username ) return false; - } + if ( strlen(username) > 32 ) + return false; + return true; } //Function to check password length bool checkPass (const char *password) { - if ( strlen(password) > 64) { + if ( !password ) return false; - } + if ( strlen(password) > 64 ) + return false; + return true; } diff --git a/src/zmu.cpp b/src/zmu.cpp index a8ee61273..2b5d95c40 100644 --- a/src/zmu.cpp +++ b/src/zmu.cpp @@ -394,7 +394,7 @@ int main(int argc, char *argv[]) { //fprintf( stderr, "?? getopt returned character code 0%o ??\n", c ); break; } - } + } // end getopt loop if ( optind < argc ) { fprintf(stderr, "Extraneous options, "); @@ -425,44 +425,38 @@ int main(int argc, char *argv[]) { if ( config.opt_use_auth ) { if ( strcmp(config.auth_relay, "none") == 0 ) { - if ( !checkUser(username)) { - fprintf(stderr, "Error, username greater than allowed 32 characters\n"); - exit_zmu(-1); - } if ( !username ) { fprintf(stderr, "Error, username must be supplied\n"); exit_zmu(-1); } - if ( username ) { - user = zmLoadUser(username); - } - } else { - if ( !(username && password) && !auth ) { - fprintf(stderr, "Error, username and password or auth string must be supplied\n"); - exit_zmu(-1); - } if ( !checkUser(username)) { fprintf(stderr, "Error, username greater than allowed 32 characters\n"); exit_zmu(-1); } - if ( !checkPass(password)) { - fprintf(stderr, "Error, password greater than allowed 64 characters\n"); + + user = zmLoadUser(username); + } else { + + if ( !(username && password) && !auth ) { + fprintf(stderr, "Error, username and password or auth string must be supplied\n"); exit_zmu(-1); } - //if ( strcmp( config.auth_relay, "hashed" ) == 0 ) - { - if ( auth ) { - user = zmLoadAuthUser(auth, false); - } + if ( auth ) { + user = zmLoadAuthUser(auth, false); } - //else if ( strcmp( config.auth_relay, "plain" ) == 0 ) - { - if ( username && password ) { - user = zmLoadUser(username, password); + if ( username && password ) { + if ( !checkUser(username)) { + fprintf(stderr, "Error, username greater than allowed 32 characters\n"); + exit_zmu(-1); } - } - } + if ( !checkPass(password)) { + fprintf(stderr, "Error, password greater than allowed 64 characters\n"); + exit_zmu(-1); + } + user = zmLoadUser(username, password); + } // end if username && password + } // end if relay or not if ( !user ) { fprintf(stderr, "Error, unable to authenticate user\n"); return exit_zmu(-1); From d86b1ea49c895e4c4778988c7487a37369efefc6 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 18 Mar 2019 13:48:55 -0400 Subject: [PATCH 10/11] Implement a date filter to zmaudit so that it only looks in directories by date. --- scripts/zmaudit.pl.in | 73 +++++++++++++++++++++++-------------------- scripts/zmdc.pl.in | 5 +-- 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index 10917a729..67d1d23ce 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -62,6 +62,7 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my $report = 0; my $interactive = 0; my $continuous = 0; +my $date = undef; my $level = 1; my $monitor_id = 0; my $version; @@ -73,6 +74,7 @@ logInit(); GetOptions( continuous =>\$continuous, + 'date=s' =>\$date, force =>\$force, interactive =>\$interactive, level =>\$level, @@ -110,24 +112,24 @@ if ( -e ZM_AUDIT_PID ) { } } # end if ZM_AUDIT_PID exists -if ( open( my $PID, '>', ZM_AUDIT_PID ) ) { - print( $PID $$ ); - close( $PID ); +if ( open(my $PID, '>', ZM_AUDIT_PID) ) { + print($PID $$); + close($PID); } else { - Error( "Can't open pid file at " . ZM_PID ); + Error("Can't open pid file at " . ZM_PID); } sub HupHandler { - Info("Received HUP, reloading"); + Info('Received HUP, reloading'); &ZoneMinder::Logger::logHupHandler(); } sub TermHandler { - Info("Received TERM, exiting"); + Info('Received TERM, exiting'); Term(); } sub Term { unlink ZM_AUDIT_PID; - exit( 0 ); + exit(0); } $SIG{HUP} = \&HupHandler; $SIG{TERM} = \&TermHandler; @@ -154,7 +156,7 @@ MAIN: while( $loop ) { # if we are running continuously, then just skip to the next # interval, otherwise we are a one off run, so wait a second and # retry until someone kills us. - sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ); + sleep($Config{ZM_AUDIT_CHECK_INTERVAL}); } else { sleep 1; } # end if @@ -182,53 +184,53 @@ MAIN: while( $loop ) { Error("No Storage Area found with Id $storage_id"); Term(); } - Info("Auditing Storage Area $Storage_Areas[0]{Id} $Storage_Areas[0]{Name} at $Storage_Areas[0]{Path}"); + Info("Auditing Storage Area $Storage_Areas[0]{Id} $Storage_Areas[0]{Name} at $Storage_Areas[0]{Path}".( $date ? " limited to $date" : '')); } elsif ( $server_id ) { @Storage_Areas = ZoneMinder::Storage->find( ServerId => $server_id ); if ( ! @Storage_Areas ) { - Error("No Storage Area found with ServerId =" . $server_id); + Error('No Storage Area found with ServerId =' . $server_id); Term(); } foreach my $Storage ( @Storage_Areas ) { - Info('Auditing ' . $Storage->Name() . ' at ' . $Storage->Path() . ' on ' . $Storage->Server()->Name() ); + Info('Auditing ' . $Storage->Name() . ' at ' . $Storage->Path() . ' on ' . $Storage->Server()->Name().( $date ? " limited to $date" : '')); } } else { @Storage_Areas = ZoneMinder::Storage->find(); - Info("Auditing All Storage Areas"); + Info('Auditing All Storage Areas'.( $date ? " limited to $date" : '')); } my %Monitors; my $db_monitors; my $monitorSelectSql = $monitor_id ? 'SELECT * FROM Monitors WHERE Id=?' : 'SELECT * FROM Monitors ORDER BY Id'; - my $monitorSelectSth = $dbh->prepare_cached( $monitorSelectSql ) - or Fatal( "Can't prepare '$monitorSelectSql': ".$dbh->errstr() ); + my $monitorSelectSth = $dbh->prepare_cached($monitorSelectSql) + or Fatal("Can't prepare '$monitorSelectSql': ".$dbh->errstr()); my $eventSelectSql = 'SELECT Id, (unix_timestamp() - unix_timestamp(StartTime)) AS Age FROM Events WHERE MonitorId = ?'.(@Storage_Areas ? ' AND StorageId IN ('.join(',',map { '?'} @Storage_Areas).')' : '' ). ' ORDER BY Id'; - my $eventSelectSth = $dbh->prepare_cached( $eventSelectSql ) - or Fatal( "Can't prepare '$eventSelectSql': ".$dbh->errstr() ); + my $eventSelectSth = $dbh->prepare_cached($eventSelectSql) + or Fatal("Can't prepare '$eventSelectSql': ".$dbh->errstr()); $cleaned = 0; my $res = $monitorSelectSth->execute( $monitor_id ? $monitor_id : () ) - or Fatal( "Can't execute: $monitorSelectSql ".$monitorSelectSth->errstr() ); + or Fatal("Can't execute: $monitorSelectSql ".$monitorSelectSth->errstr()); while( my $monitor = $monitorSelectSth->fetchrow_hashref() ) { $Monitors{$$monitor{Id}} = $monitor; my $db_events = $db_monitors->{$monitor->{Id}} = {}; my $res = $eventSelectSth->execute( $monitor->{Id}, map { $$_{Id} } @Storage_Areas ) - or Fatal( "Can't execute: ".$eventSelectSth->errstr() ); + or Fatal("Can't execute: ".$eventSelectSth->errstr()); while ( my $event = $eventSelectSth->fetchrow_hashref() ) { $db_events->{$event->{Id}} = $event->{Age}; } - Debug( 'Got '.int(keys(%$db_events))." events for monitor $monitor->{Id}" ); + Debug('Got '.int(keys(%$db_events))." events for monitor $monitor->{Id}"); } # end while monitors my $fs_monitors; foreach my $Storage ( @Storage_Areas ) { - Debug('Checking events in ' . $Storage->Path() ); + Debug('Checking events in ' . $Storage->Path()); if ( ! chdir( $Storage->Path() ) ) { - Error( 'Unable to change dir to ' . $Storage->Path() ); + Error('Unable to change dir to ' . $Storage->Path()); next; } # end if @@ -243,7 +245,7 @@ MAIN: while( $loop ) { next; } - Debug( "Found filesystem monitor '$monitor'" ); + Debug("Found filesystem monitor '$monitor'"); $fs_monitors->{$monitor} = {} if ! $fs_monitors->{$monitor}; my $fs_events = $fs_monitors->{$monitor}; @@ -251,10 +253,11 @@ MAIN: while( $loop ) { ( my $monitor_dir ) = ( $monitor =~ /^(.*)$/ ); { - my @day_dirs = glob("$monitor_dir/[0-9][0-9]/[0-9][0-9]/[0-9][0-9]"); + my ( $y, $m, $d ) = $date =~ /^(\d\d\d\d)\-(\d\d)\-(\d\d)$/; + my @day_dirs = glob($date ? "$monitor_dir/$y/$m/$d" : "$monitor_dir/[0-9][0-9]/[0-9][0-9]/[0-9][0-9]"); Debug(qq`Checking for Deep Events under $$Storage{Path} using glob("$monitor_dir/[0-9][0-9]/[0-9][0-9]/[0-9][0-9]") returned `. scalar @day_dirs . ' events'); foreach my $day_dir ( @day_dirs ) { - Debug( "Checking day dir $day_dir" ); + Debug("Checking day dir $day_dir"); ( $day_dir ) = ( $day_dir =~ /^(.*)$/ ); # De-taint if ( !chdir($day_dir) ) { Error("Can't chdir to '$$Storage{Path}/$day_dir': $!"); @@ -267,7 +270,7 @@ MAIN: while( $loop ) { my %event_ids_by_path; my @event_links = sort { $b <=> $a } grep { -l $_ } readdir( DIR ); - Debug("Have " . @event_links . ' event links'); + Debug('Have ' . @event_links . ' event links'); closedir(DIR); my $count = 0; @@ -383,11 +386,12 @@ MAIN: while( $loop ) { Debug("Checking for Medium Scheme Events under $$Storage{Path}/$monitor_dir"); { - my @event_dirs = glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*"); - Debug(qq`glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*") returned ` . scalar @event_dirs . ' entries.' ); + my $glob = $date ? "$monitor_dir/$date/*":"$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*"; + my @event_dirs = glob($glob); + Debug(qq`glob($glob) returned ` . scalar @event_dirs . ' entries.' ); foreach my $event_dir ( @event_dirs ) { if ( ! -d $event_dir ) { - Debug( "$event_dir is not a dir. Skipping" ); + Debug("$event_dir is not a dir. Skipping"); next; } my ( $date, $event_id ) = $event_dir =~ /^$monitor_dir\/(\d{4}\-\d{2}\-\d{2})\/(\d+)$/; @@ -410,11 +414,11 @@ MAIN: while( $loop ) { if ( ! $$Storage{Scheme} ) { Error("Storage Scheme not set on $$Storage{Name}"); if ( ! chdir( $monitor_dir ) ) { - Error( "Can't chdir directory '$$Storage{Path}/$monitor_dir': $!" ); + Error("Can't chdir directory '$$Storage{Path}/$monitor_dir': $!"); next; } if ( ! opendir( DIR, "." ) ) { - Error( "Can't open directory '$$Storage{Path}/$monitor_dir': $!" ); + Error("Can't open directory '$$Storage{Path}/$monitor_dir': $!"); next; } my @temp_events = sort { $b <=> $a } grep { -d $_ && $_ =~ /^\d+$/ } readdir( DIR ); @@ -429,7 +433,7 @@ MAIN: while( $loop ) { } # end foreach event chdir( $Storage->Path() ); } # if USE_DEEP_STORAGE - Debug( 'Got '.int(keys(%$fs_events))." filesystem events for monitor $monitor_dir" ); + Debug('Got '.int(keys(%$fs_events))." filesystem events for monitor $monitor_dir"); delete_empty_subdirs($$Storage{Path}.'/'.$monitor_dir); } # end foreach monitor @@ -463,7 +467,7 @@ MAIN: while( $loop ) { my $age = $Event->age(); if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) { - aud_print( "Filesystem event $fs_event_id at $$Event{Path} does not exist in database and is $age seconds old" ); + aud_print("Filesystem event $fs_event_id at $$Event{Path} does not exist in database and is $age seconds old"); if ( confirm() ) { $Event->delete_files(); $cleaned = 1; @@ -473,7 +477,7 @@ MAIN: while( $loop ) { } # end if ! in db events } # end foreach fs event } else { - aud_print( "Filesystem monitor '$monitor_id' in $$Storage{Path} does not exist in database" ); + aud_print("Filesystem monitor '$monitor_id' in $$Storage{Path} does not exist in database"); if ( confirm() ) { my $command = "rm -rf $monitor_id"; @@ -488,7 +492,7 @@ MAIN: while( $loop ) { next if ( !-l $link ); next if ( -e $link ); - aud_print( "Filesystem monitor link '$link' does not point to valid monitor directory" ); + aud_print("Filesystem monitor link '$link' does not point to valid monitor directory"); if ( confirm() ) { ( $link ) = ( $link =~ /^(.*)$/ ); # De-taint my $command = qq`rm "$link"`; @@ -1049,6 +1053,7 @@ yet. =head1 OPTIONS -c, --continuous - Run continuously + -d, --date - Limit search for events on given date. Use YYYY-MM-DD format -f, --force - Run even if pid file exists -i, --interactive - Ask before applying any changes -m, --monitor_id - Only consider the given monitor diff --git a/scripts/zmdc.pl.in b/scripts/zmdc.pl.in index 67bbc294b..61b9b1b82 100644 --- a/scripts/zmdc.pl.in +++ b/scripts/zmdc.pl.in @@ -93,8 +93,9 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my @daemons = ( 'zmc', 'zma', - 'zmfilter.pl', 'zmaudit.pl', + 'zmcontrol.pl', + 'zmfilter.pl', 'zmtrigger.pl', 'zmx10.pl', 'zmwatch.pl', @@ -105,7 +106,7 @@ my @daemons = ( ); if ( $Config{ZM_OPT_USE_EVENTNOTIFICATION} ) { - push @daemons,'zmeventnotification.pl'; + push @daemons, 'zmeventnotification.pl'; } my $command = shift @ARGV; From e486b035f09bd3bce46aacd64546162f1204d44a Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 18 Mar 2019 13:50:00 -0400 Subject: [PATCH 11/11] Revert "Implement a date filter to zmaudit so that it only looks in directories by date." This reverts commit d86b1ea49c895e4c4778988c7487a37369efefc6. --- scripts/zmaudit.pl.in | 73 ++++++++++++++++++++----------------------- scripts/zmdc.pl.in | 5 ++- 2 files changed, 36 insertions(+), 42 deletions(-) diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index 67d1d23ce..10917a729 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -62,7 +62,6 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my $report = 0; my $interactive = 0; my $continuous = 0; -my $date = undef; my $level = 1; my $monitor_id = 0; my $version; @@ -74,7 +73,6 @@ logInit(); GetOptions( continuous =>\$continuous, - 'date=s' =>\$date, force =>\$force, interactive =>\$interactive, level =>\$level, @@ -112,24 +110,24 @@ if ( -e ZM_AUDIT_PID ) { } } # end if ZM_AUDIT_PID exists -if ( open(my $PID, '>', ZM_AUDIT_PID) ) { - print($PID $$); - close($PID); +if ( open( my $PID, '>', ZM_AUDIT_PID ) ) { + print( $PID $$ ); + close( $PID ); } else { - Error("Can't open pid file at " . ZM_PID); + Error( "Can't open pid file at " . ZM_PID ); } sub HupHandler { - Info('Received HUP, reloading'); + Info("Received HUP, reloading"); &ZoneMinder::Logger::logHupHandler(); } sub TermHandler { - Info('Received TERM, exiting'); + Info("Received TERM, exiting"); Term(); } sub Term { unlink ZM_AUDIT_PID; - exit(0); + exit( 0 ); } $SIG{HUP} = \&HupHandler; $SIG{TERM} = \&TermHandler; @@ -156,7 +154,7 @@ MAIN: while( $loop ) { # if we are running continuously, then just skip to the next # interval, otherwise we are a one off run, so wait a second and # retry until someone kills us. - sleep($Config{ZM_AUDIT_CHECK_INTERVAL}); + sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ); } else { sleep 1; } # end if @@ -184,53 +182,53 @@ MAIN: while( $loop ) { Error("No Storage Area found with Id $storage_id"); Term(); } - Info("Auditing Storage Area $Storage_Areas[0]{Id} $Storage_Areas[0]{Name} at $Storage_Areas[0]{Path}".( $date ? " limited to $date" : '')); + Info("Auditing Storage Area $Storage_Areas[0]{Id} $Storage_Areas[0]{Name} at $Storage_Areas[0]{Path}"); } elsif ( $server_id ) { @Storage_Areas = ZoneMinder::Storage->find( ServerId => $server_id ); if ( ! @Storage_Areas ) { - Error('No Storage Area found with ServerId =' . $server_id); + Error("No Storage Area found with ServerId =" . $server_id); Term(); } foreach my $Storage ( @Storage_Areas ) { - Info('Auditing ' . $Storage->Name() . ' at ' . $Storage->Path() . ' on ' . $Storage->Server()->Name().( $date ? " limited to $date" : '')); + Info('Auditing ' . $Storage->Name() . ' at ' . $Storage->Path() . ' on ' . $Storage->Server()->Name() ); } } else { @Storage_Areas = ZoneMinder::Storage->find(); - Info('Auditing All Storage Areas'.( $date ? " limited to $date" : '')); + Info("Auditing All Storage Areas"); } my %Monitors; my $db_monitors; my $monitorSelectSql = $monitor_id ? 'SELECT * FROM Monitors WHERE Id=?' : 'SELECT * FROM Monitors ORDER BY Id'; - my $monitorSelectSth = $dbh->prepare_cached($monitorSelectSql) - or Fatal("Can't prepare '$monitorSelectSql': ".$dbh->errstr()); + my $monitorSelectSth = $dbh->prepare_cached( $monitorSelectSql ) + or Fatal( "Can't prepare '$monitorSelectSql': ".$dbh->errstr() ); my $eventSelectSql = 'SELECT Id, (unix_timestamp() - unix_timestamp(StartTime)) AS Age FROM Events WHERE MonitorId = ?'.(@Storage_Areas ? ' AND StorageId IN ('.join(',',map { '?'} @Storage_Areas).')' : '' ). ' ORDER BY Id'; - my $eventSelectSth = $dbh->prepare_cached($eventSelectSql) - or Fatal("Can't prepare '$eventSelectSql': ".$dbh->errstr()); + my $eventSelectSth = $dbh->prepare_cached( $eventSelectSql ) + or Fatal( "Can't prepare '$eventSelectSql': ".$dbh->errstr() ); $cleaned = 0; my $res = $monitorSelectSth->execute( $monitor_id ? $monitor_id : () ) - or Fatal("Can't execute: $monitorSelectSql ".$monitorSelectSth->errstr()); + or Fatal( "Can't execute: $monitorSelectSql ".$monitorSelectSth->errstr() ); while( my $monitor = $monitorSelectSth->fetchrow_hashref() ) { $Monitors{$$monitor{Id}} = $monitor; my $db_events = $db_monitors->{$monitor->{Id}} = {}; my $res = $eventSelectSth->execute( $monitor->{Id}, map { $$_{Id} } @Storage_Areas ) - or Fatal("Can't execute: ".$eventSelectSth->errstr()); + or Fatal( "Can't execute: ".$eventSelectSth->errstr() ); while ( my $event = $eventSelectSth->fetchrow_hashref() ) { $db_events->{$event->{Id}} = $event->{Age}; } - Debug('Got '.int(keys(%$db_events))." events for monitor $monitor->{Id}"); + Debug( 'Got '.int(keys(%$db_events))." events for monitor $monitor->{Id}" ); } # end while monitors my $fs_monitors; foreach my $Storage ( @Storage_Areas ) { - Debug('Checking events in ' . $Storage->Path()); + Debug('Checking events in ' . $Storage->Path() ); if ( ! chdir( $Storage->Path() ) ) { - Error('Unable to change dir to ' . $Storage->Path()); + Error( 'Unable to change dir to ' . $Storage->Path() ); next; } # end if @@ -245,7 +243,7 @@ MAIN: while( $loop ) { next; } - Debug("Found filesystem monitor '$monitor'"); + Debug( "Found filesystem monitor '$monitor'" ); $fs_monitors->{$monitor} = {} if ! $fs_monitors->{$monitor}; my $fs_events = $fs_monitors->{$monitor}; @@ -253,11 +251,10 @@ MAIN: while( $loop ) { ( my $monitor_dir ) = ( $monitor =~ /^(.*)$/ ); { - my ( $y, $m, $d ) = $date =~ /^(\d\d\d\d)\-(\d\d)\-(\d\d)$/; - my @day_dirs = glob($date ? "$monitor_dir/$y/$m/$d" : "$monitor_dir/[0-9][0-9]/[0-9][0-9]/[0-9][0-9]"); + my @day_dirs = glob("$monitor_dir/[0-9][0-9]/[0-9][0-9]/[0-9][0-9]"); Debug(qq`Checking for Deep Events under $$Storage{Path} using glob("$monitor_dir/[0-9][0-9]/[0-9][0-9]/[0-9][0-9]") returned `. scalar @day_dirs . ' events'); foreach my $day_dir ( @day_dirs ) { - Debug("Checking day dir $day_dir"); + Debug( "Checking day dir $day_dir" ); ( $day_dir ) = ( $day_dir =~ /^(.*)$/ ); # De-taint if ( !chdir($day_dir) ) { Error("Can't chdir to '$$Storage{Path}/$day_dir': $!"); @@ -270,7 +267,7 @@ MAIN: while( $loop ) { my %event_ids_by_path; my @event_links = sort { $b <=> $a } grep { -l $_ } readdir( DIR ); - Debug('Have ' . @event_links . ' event links'); + Debug("Have " . @event_links . ' event links'); closedir(DIR); my $count = 0; @@ -386,12 +383,11 @@ MAIN: while( $loop ) { Debug("Checking for Medium Scheme Events under $$Storage{Path}/$monitor_dir"); { - my $glob = $date ? "$monitor_dir/$date/*":"$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*"; - my @event_dirs = glob($glob); - Debug(qq`glob($glob) returned ` . scalar @event_dirs . ' entries.' ); + my @event_dirs = glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*"); + Debug(qq`glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*") returned ` . scalar @event_dirs . ' entries.' ); foreach my $event_dir ( @event_dirs ) { if ( ! -d $event_dir ) { - Debug("$event_dir is not a dir. Skipping"); + Debug( "$event_dir is not a dir. Skipping" ); next; } my ( $date, $event_id ) = $event_dir =~ /^$monitor_dir\/(\d{4}\-\d{2}\-\d{2})\/(\d+)$/; @@ -414,11 +410,11 @@ MAIN: while( $loop ) { if ( ! $$Storage{Scheme} ) { Error("Storage Scheme not set on $$Storage{Name}"); if ( ! chdir( $monitor_dir ) ) { - Error("Can't chdir directory '$$Storage{Path}/$monitor_dir': $!"); + Error( "Can't chdir directory '$$Storage{Path}/$monitor_dir': $!" ); next; } if ( ! opendir( DIR, "." ) ) { - Error("Can't open directory '$$Storage{Path}/$monitor_dir': $!"); + Error( "Can't open directory '$$Storage{Path}/$monitor_dir': $!" ); next; } my @temp_events = sort { $b <=> $a } grep { -d $_ && $_ =~ /^\d+$/ } readdir( DIR ); @@ -433,7 +429,7 @@ MAIN: while( $loop ) { } # end foreach event chdir( $Storage->Path() ); } # if USE_DEEP_STORAGE - Debug('Got '.int(keys(%$fs_events))." filesystem events for monitor $monitor_dir"); + Debug( 'Got '.int(keys(%$fs_events))." filesystem events for monitor $monitor_dir" ); delete_empty_subdirs($$Storage{Path}.'/'.$monitor_dir); } # end foreach monitor @@ -467,7 +463,7 @@ MAIN: while( $loop ) { my $age = $Event->age(); if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) { - aud_print("Filesystem event $fs_event_id at $$Event{Path} does not exist in database and is $age seconds old"); + aud_print( "Filesystem event $fs_event_id at $$Event{Path} does not exist in database and is $age seconds old" ); if ( confirm() ) { $Event->delete_files(); $cleaned = 1; @@ -477,7 +473,7 @@ MAIN: while( $loop ) { } # end if ! in db events } # end foreach fs event } else { - aud_print("Filesystem monitor '$monitor_id' in $$Storage{Path} does not exist in database"); + aud_print( "Filesystem monitor '$monitor_id' in $$Storage{Path} does not exist in database" ); if ( confirm() ) { my $command = "rm -rf $monitor_id"; @@ -492,7 +488,7 @@ MAIN: while( $loop ) { next if ( !-l $link ); next if ( -e $link ); - aud_print("Filesystem monitor link '$link' does not point to valid monitor directory"); + aud_print( "Filesystem monitor link '$link' does not point to valid monitor directory" ); if ( confirm() ) { ( $link ) = ( $link =~ /^(.*)$/ ); # De-taint my $command = qq`rm "$link"`; @@ -1053,7 +1049,6 @@ yet. =head1 OPTIONS -c, --continuous - Run continuously - -d, --date - Limit search for events on given date. Use YYYY-MM-DD format -f, --force - Run even if pid file exists -i, --interactive - Ask before applying any changes -m, --monitor_id - Only consider the given monitor diff --git a/scripts/zmdc.pl.in b/scripts/zmdc.pl.in index 61b9b1b82..67bbc294b 100644 --- a/scripts/zmdc.pl.in +++ b/scripts/zmdc.pl.in @@ -93,9 +93,8 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my @daemons = ( 'zmc', 'zma', - 'zmaudit.pl', - 'zmcontrol.pl', 'zmfilter.pl', + 'zmaudit.pl', 'zmtrigger.pl', 'zmx10.pl', 'zmwatch.pl', @@ -106,7 +105,7 @@ my @daemons = ( ); if ( $Config{ZM_OPT_USE_EVENTNOTIFICATION} ) { - push @daemons, 'zmeventnotification.pl'; + push @daemons,'zmeventnotification.pl'; } my $command = shift @ARGV;