From 923c8358bc84d76ec80896d04186d7d8ded23d36 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 26 Jun 2017 11:45:34 -0400 Subject: [PATCH 1/4] Don't do csrf for view_video --- web/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/index.php b/web/index.php index c2b17c061..eaa41b05e 100644 --- a/web/index.php +++ b/web/index.php @@ -177,7 +177,7 @@ isset($view) || $view = NULL; isset($request) || $request = NULL; isset($action) || $action = NULL; -if ( ZM_ENABLE_CSRF_MAGIC && $action != 'login' ) { +if ( ZM_ENABLE_CSRF_MAGIC && $action != 'login' && $view != 'view_video' ) { Logger::Debug("Calling csrf_check with the following values: \$request = \"$request\", \$view = \"$view\", \$action = \"$action\""); csrf_check(); } From 3a113899ed06b24056a5ec070ece447b1ad0c71e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 26 Jun 2017 14:29:45 -0400 Subject: [PATCH 2/4] whitespace and braces fixing --- web/index.php | 148 ++++++++++++++++++++++---------------------------- 1 file changed, 66 insertions(+), 82 deletions(-) diff --git a/web/index.php b/web/index.php index 0b0c4f45e..5ccf79d66 100644 --- a/web/index.php +++ b/web/index.php @@ -21,29 +21,26 @@ error_reporting( E_ALL ); $debug = false; -if ( $debug ) -{ - // Use these for debugging, though not both at once! - phpinfo( INFO_VARIABLES ); - //error_reporting( E_ALL ); +if ( $debug ) { + // Use these for debugging, though not both at once! + phpinfo( INFO_VARIABLES ); + //error_reporting( E_ALL ); } // Use new style autoglobals where possible -if ( version_compare( phpversion(), "4.1.0", "<") ) -{ - $_SESSION = &$HTTP_SESSION_VARS; - $_SERVER = &$HTTP_SERVER_VARS; +if ( version_compare( phpversion(), '4.1.0', '<') ) { + $_SESSION = &$HTTP_SESSION_VARS; + $_SERVER = &$HTTP_SERVER_VARS; } // Useful debugging lines for mobile devices -if ( false ) -{ - ob_start(); - phpinfo( INFO_VARIABLES ); - $fp = fopen( "/tmp/env.html", "w" ); - fwrite( $fp, ob_get_contents() ); - fclose( $fp ); - ob_end_clean(); +if ( false ) { + ob_start(); + phpinfo( INFO_VARIABLES ); + $fp = fopen( '/tmp/env.html', 'w' ); + fwrite( $fp, ob_get_contents() ); + fclose( $fp ); + ob_end_clean(); } require_once( 'includes/config.php' ); @@ -53,26 +50,23 @@ require_once( 'includes/Storage.php' ); require_once( 'includes/Event.php' ); require_once( 'includes/Monitor.php' ); -if ( isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == 'on' ) -{ - $protocol = 'https'; +if ( isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ) { + $protocol = 'https'; +} else { + $protocol = 'http'; } -else -{ - $protocol = 'http'; -} -define( "ZM_BASE_PROTOCOL", $protocol ); +define( 'ZM_BASE_PROTOCOL', $protocol ); // Absolute URL's are unnecessary and break compatibility with reverse proxies // define( "ZM_BASE_URL", $protocol.'://'.$_SERVER['HTTP_HOST'] ); // Use relative URL's instead -define( "ZM_BASE_URL", "" ); +define( 'ZM_BASE_URL', '' ); // Check time zone is set if (!ini_get('date.timezone') || !date_default_timezone_set(ini_get('date.timezone'))) { - date_default_timezone_set('UTC'); - Fatal( "ZoneMinder is not installed properly: php's date.timezone is not set to a valid timezone" ); + date_default_timezone_set('UTC'); + Fatal( "ZoneMinder is not installed properly: php's date.timezone is not set to a valid timezone" ); } if ( isset($_GET['skin']) ) @@ -82,7 +76,7 @@ elseif ( isset($_COOKIE['zmSkin']) ) elseif ( defined('ZM_SKIN_DEFAULT') ) $skin = ZM_SKIN_DEFAULT; else - $skin = "classic"; + $skin = 'classic'; $skins = array_map( 'basename', glob('skins/*',GLOB_ONLYDIR) ); if ( ! in_array( $skin, $skins ) ) { @@ -97,7 +91,7 @@ elseif ( isset($_COOKIE['zmCSS']) ) elseif (defined('ZM_CSS_DEFAULT')) $css = ZM_CSS_DEFAULT; else - $css = "classic"; + $css = 'classic'; $css_skins = array_map( 'basename', glob('skins/'.$skin.'/css/*',GLOB_ONLYDIR) ); if ( ! in_array( $css, $css_skins ) ) { @@ -105,9 +99,9 @@ if ( ! in_array( $css, $css_skins ) ) { $css = $css_skins[0]; } -define( "ZM_BASE_PATH", dirname( $_SERVER['REQUEST_URI'] ) ); -define( "ZM_SKIN_NAME", $skin ); -define( "ZM_SKIN_PATH", "skins/$skin" ); +define( 'ZM_BASE_PATH', dirname( $_SERVER['REQUEST_URI'] ) ); +define( 'ZM_SKIN_NAME', $skin ); +define( 'ZM_SKIN_PATH', "skins/$skin" ); $skinBase = array(); // To allow for inheritance of skins if ( !file_exists( ZM_SKIN_PATH ) ) @@ -117,26 +111,25 @@ $skinBase[] = $skin; $currentCookieParams = session_get_cookie_params(); Logger::Debug('Setting cookie parameters to lifetime('.$currentCookieParams['lifetime'].') path('.$currentCookieParams['path'].') domain ('.$currentCookieParams['domain'].') secure('.$currentCookieParams['secure'].') httpOnly(1)'); session_set_cookie_params( - $currentCookieParams["lifetime"], - $currentCookieParams["path"], - $currentCookieParams["domain"], - $currentCookieParams["secure"], + $currentCookieParams['lifetime'], + $currentCookieParams['path'], + $currentCookieParams['domain'], + $currentCookieParams['secure'], true ); -ini_set( "session.name", "ZMSESSID" ); +ini_set( 'session.name', 'ZMSESSID' ); session_start(); -if ( !isset($_SESSION['skin']) || isset($_REQUEST['skin']) || !isset($_COOKIE['zmSkin']) || $_COOKIE['zmSkin'] != $skin ) -{ +if ( !isset($_SESSION['skin']) || isset($_REQUEST['skin']) || !isset($_COOKIE['zmSkin']) || $_COOKIE['zmSkin'] != $skin ) { $_SESSION['skin'] = $skin; - setcookie( "zmSkin", $skin, time()+3600*24*30*12*10 ); + setcookie( 'zmSkin', $skin, time()+3600*24*30*12*10 ); } if ( !isset($_SESSION['css']) || isset($_REQUEST['css']) || !isset($_COOKIE['zmCSS']) || $_COOKIE['zmCSS'] != $css ) { $_SESSION['css'] = $css; - setcookie( "zmCSS", $css, time()+3600*24*30*12*10 ); + setcookie( 'zmCSS', $css, time()+3600*24*30*12*10 ); } if ( ZM_OPT_USE_AUTH ) @@ -154,8 +147,7 @@ require_once( 'includes/functions.php' ); CORSHeaders(); // Check for valid content dirs -if ( !is_writable(ZM_DIR_EVENTS) || !is_writable(ZM_DIR_IMAGES) ) -{ +if ( !is_writable(ZM_DIR_EVENTS) || !is_writable(ZM_DIR_IMAGES) ) { Error( "Cannot write to content dirs('".ZM_DIR_EVENTS."','".ZM_DIR_IMAGES."'). Check that these exist and are owned by the web account user"); } @@ -178,57 +170,49 @@ isset($action) || $action = NULL; if ( ZM_ENABLE_CSRF_MAGIC && $action != 'login' && $action != 'view_video' ) { require_once( 'includes/csrf/csrf-magic.php' ); - Logger::Debug("Calling csrf_check with the following values: \$request = \"$request\", \$view = \"$view\", \$action = \"$action\""); - csrf_check(); + Logger::Debug("Calling csrf_check with the following values: \$request = \"$request\", \$view = \"$view\", \$action = \"$action\""); + csrf_check(); } require_once( 'includes/actions.php' ); # If I put this here, it protects all views and popups, but it has to go after actions.php because actions.php does the actual logging in. if ( ZM_OPT_USE_AUTH && ! isset($user) && $view != 'login' ) { - $view = 'login'; + $view = 'login'; } # Only one request can open the session file at a time, so let's close the session here to improve concurrency. # Any file/page that uses the session must re-open it. session_write_close(); -if ( isset( $_REQUEST['request'] ) ) -{ - foreach ( getSkinIncludes( 'ajax/'.$request.'.php', true, true ) as $includeFile ) - { - if ( !file_exists( $includeFile ) ) - Fatal( "Request '$request' does not exist" ); +if ( isset( $_REQUEST['request'] ) ) { + foreach ( getSkinIncludes( 'ajax/'.$request.'.php', true, true ) as $includeFile ) { + if ( !file_exists( $includeFile ) ) + Fatal( "Request '$request' does not exist" ); + require_once $includeFile; + } + return; +} else { + if ( $includeFiles = getSkinIncludes( 'views/'.$view.'.php', true, true ) ) { + foreach ( $includeFiles as $includeFile ) { + if ( !file_exists( $includeFile ) ) + 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 + // issue is probably resolvable by logging in, so provide the opportunity to do so. + // The login view should handle redirecting to the correct location afterward. + if ( $view == 'error' && !isset($user) ) { + $view = 'login'; + foreach ( getSkinIncludes( 'views/login.php', true, true ) as $includeFile ) require_once $includeFile; } - return; -} -else -{ - if ( $includeFiles = getSkinIncludes( 'views/'.$view.'.php', true, true ) ) - { - foreach ( $includeFiles as $includeFile ) - { - if ( !file_exists( $includeFile ) ) - 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 - // issue is probably resolvable by logging in, so provide the opportunity to do so. - // The login view should handle redirecting to the correct location afterward. - if ( $view == 'error' && !isset($user) ) - { - $view = 'login'; - foreach ( getSkinIncludes( 'views/login.php', true, true ) as $includeFile ) - require_once $includeFile; - } - } - // If the view is missing or the view still returned error with the user logged in, - // then it is not recoverable. - if ( !$includeFiles || $view == 'error' ) - { - foreach ( getSkinIncludes( 'views/error.php', true, true ) as $includeFile ) - require_once $includeFile; - } + } + // If the view is missing or the view still returned error with the user logged in, + // then it is not recoverable. + if ( !$includeFiles || $view == 'error' ) { + foreach ( getSkinIncludes( 'views/error.php', true, true ) as $includeFile ) + require_once $includeFile; + } } ?> From f782aeccd98f7bd7c6a173d437bac9ed19b4f6fd Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 26 Jun 2017 21:09:54 -0400 Subject: [PATCH 3/4] fix view is view_video, not action=niew_video --- web/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/index.php b/web/index.php index 5ccf79d66..0e2ade668 100644 --- a/web/index.php +++ b/web/index.php @@ -168,7 +168,7 @@ isset($view) || $view = NULL; isset($request) || $request = NULL; isset($action) || $action = NULL; -if ( ZM_ENABLE_CSRF_MAGIC && $action != 'login' && $action != 'view_video' ) { +if ( ZM_ENABLE_CSRF_MAGIC && $action != 'login' && $view != 'view_video' ) { require_once( 'includes/csrf/csrf-magic.php' ); Logger::Debug("Calling csrf_check with the following values: \$request = \"$request\", \$view = \"$view\", \$action = \"$action\""); csrf_check(); From 9ba9495ae0610e26202bbea0cdd267215698c486 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 28 Jun 2017 10:53:35 -0400 Subject: [PATCH 4/4] whitespace, braces, move pod doc to bottom. No functional changes --- scripts/zmtelemetry.pl.in | 596 +++++++++++++++++++------------------- 1 file changed, 299 insertions(+), 297 deletions(-) diff --git a/scripts/zmtelemetry.pl.in b/scripts/zmtelemetry.pl.in index 39e7b73ec..3b0ec32fe 100644 --- a/scripts/zmtelemetry.pl.in +++ b/scripts/zmtelemetry.pl.in @@ -21,6 +21,305 @@ # # ========================================================================== +use strict; +use bytes; + +@EXTRA_PERL_LIB@ +use ZoneMinder; +use DBI; +use Getopt::Long; +use autouse 'Pod::Usage'=>qw(pod2usage); +use LWP::UserAgent; +use Sys::MemInfo qw(totalmem); +use Sys::CPU qw(cpu_count); +use POSIX qw(strftime uname); + +$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; +$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; +delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; + +use constant CHECK_INTERVAL => (14*24*60*60); # Interval between version checks + +# Setting these as contants for now. +# Alternatively, we can put these in the dB and then retrieve using the Config hash. +use constant ZM_TELEMETRY_SERVER_ENDPOINT => 'https://zmanon:2b2d0b4skps@telemetry.zoneminder.com/zmtelemetry/testing5'; + +if ( $Config{ZM_TELEMETRY_DATA} ) { + print( 'Update agent starting at '.strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" ); + + my $lastCheck = $Config{ZM_TELEMETRY_LAST_UPLOAD}; + + while( 1 ) { + my $now = time(); + if ( ($now-$lastCheck) > CHECK_INTERVAL ) { + Info( 'Collecting data to send to ZoneMinder Telemetry server.' ); + my $dbh = zmDbConnect(); +# Build the telemetry hash +# We should keep *BSD systems in mind when calling system commands + my %telemetry; + $telemetry{uuid} = getUUID($dbh); + $telemetry{ip} = getIP(); + $telemetry{timestamp} = strftime( '%Y-%m-%dT%H:%M:%S%z', localtime() ); + $telemetry{monitor_count} = countQuery($dbh,'Monitors'); + $telemetry{event_count} = countQuery($dbh,'Events'); + $telemetry{architecture} = runSysCmd('uname -p'); + ($telemetry{kernel}, $telemetry{distro}, $telemetry{version}) = getDistro(); + $telemetry{zm_version} = ZoneMinder::Base::ZM_VERSION; + $telemetry{system_memory} = totalmem(); + $telemetry{processor_count} = cpu_count(); + $telemetry{monitors} = getMonitorRef($dbh); + + Info( 'Sending data to ZoneMinder Telemetry server.' ); + + my $result = jsonEncode( \%telemetry ); + + if ( sendData($result) ) { + $lastCheck = $now; + + my $sql = "update Config set Value = ? where Name = 'ZM_TELEMETRY_LAST_UPLOAD'"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( "$lastCheck" ) or die( "Can't execute: ".$sth->errstr() ); + $sth->finish(); + } + } + sleep( 3600 ); + } + print( 'Update agent exiting at '.strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" ); +} + +############### +# SUBROUTINES # +############### + +# Find, verify, then run the supplied system command +sub runSysCmd { + my $msg = shift; + my @arguments = split(/ /,$msg); + chomp($arguments[0]); + my $path = qx( which $arguments[0] ); + + my $status = $? >> 8; + my $result = ''; + if ( !$path || $status ) { + Warning( "Cannot find the $arguments[0] executable." ); + } else { + chomp($path); + $arguments[0] = $path; + my $cmd = join(' ',@arguments); + $result = qx( $cmd ); + chomp($result); + } + + return $result; +} + +# Upload message data to ZoneMinder telemetry server +sub sendData { + my $msg = shift; + + my $ua = LWP::UserAgent->new; + my $server_endpoint = ZM_TELEMETRY_SERVER_ENDPOINT; + + if ( $Config{ZM_UPDATE_CHECK_PROXY} ) { + $ua->proxy( 'https', $Config{ZM_UPDATE_CHECK_PROXY} ); + } + + Debug("Posting telemetry data to: $server_endpoint"); + +# set custom HTTP request header fields + my $req = HTTP::Request->new(POST => $server_endpoint); + $req->header('content-type' => 'application/x-www-form-urlencoded'); + $req->header('content-length' => length($msg)); + $req->header('connection' => 'Close'); + + $req->content($msg); + + my $resp = $ua->request($req); + my $resp_msg = $resp->decoded_content; + my $resp_code = $resp->code; + if ($resp->is_success) { + Info("Telemetry data uploaded successfully."); + Debug("Telemetry server upload success response message: $resp_msg"); + } else { + Warning("Telemetry server returned HTTP POST error code: $resp_code"); + Debug("Telemetry server upload failure response message: $resp_msg"); + } + return $resp->is_success; +} + +# Retrieves the UUID from the database. Creates a new UUID if one does not exist. +sub getUUID { + my $dbh = shift; + my $uuid= ""; + +# Verify the current UUID is valid and not nil + if (( $Config{ZM_TELEMETRY_UUID} =~ /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i ) && ( $Config{ZM_TELEMETRY_UUID} ne "00000000-0000-0000-0000-000000000000" )) { + $uuid = $Config{ZM_TELEMETRY_UUID}; + } else { + my $sql = 'SELECT uuid()'; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + $uuid = $Config{ZM_TELEMETRY_UUID} = $sth->fetchrow_array(); + $sth->finish(); + + $sql = "UPDATE Config set Value = ? WHERE Name = 'ZM_TELEMETRY_UUID'"; + $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + $res = $sth->execute( "$uuid" ) or die( "Can't execute: ".$sth->errstr() ); + $sth->finish(); + } + Debug("Using UUID of: $uuid"); + + return $uuid; +} + +# Retrieves the local server's external IP address +sub getIP { + my $ipaddr = '0.0.0.0'; + my $ua = LWP::UserAgent->new; + my $server_endpoint = 'https://wiki.zoneminder.com/ip.php'; + + my $req = HTTP::Request->new(GET => $server_endpoint); + my $resp = $ua->request($req); + + if ($resp->is_success) { + $ipaddr = $resp->decoded_content; + } + + Debug("Found external ip address of: $ipaddr"); + + return $ipaddr; +} + +# As the name implies, just your average mysql count query +sub countQuery { + my $dbh = shift; + my $table = shift; + + my $sql = "SELECT count(*) FROM $table"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + my $count = $sth->fetchrow_array(); + $sth->finish(); + + return $count +} + +# Returns a reference to an array of hashes containing data from all monitors +sub getMonitorRef { + my $dbh = shift; + + my $sql = 'SELECT Id,Name,Type,Function,Width,Height,Colours,MaxFPS,AlarmMaxFPS FROM Monitors'; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + my $arrayref = $sth->fetchall_arrayref({}); + + return $arrayref; +} + +sub getDistro { + my $kernel = ''; + my $distro = ''; + my $version = ''; + my @uname = uname(); + + if ( $uname[0] =~ /Linux/ ) { + Debug('Linux distro detected.'); + ($kernel, $distro, $version) = linuxDistro(); + } elsif ( $uname[0] =~ /.*BSD/ ) { + Debug('BSD distro detected.'); + $kernel = $uname[3]; + $distro = $uname[0]; + $version = $uname[2]; + } elsif ( $uname[0] =~ /Darwin/ ) { + Debug('Mac OS distro detected.'); + $kernel = $uname[3]; + $distro = runSysCmd('sw_vers -productName'); + $version = runSysCmd('sw_vers -productVersion'); + } elsif ( $uname[0] =~ /SunOS|Solaris/ ) { + Debug('Sun Solaris detected.'); + $kernel = $uname[3]; + $distro = $uname[1]; + $version = $uname[2]; + } else { + Warning('ZoneMinder was unable to determine the host system. Please report.'); + $kernel = 'Unknown'; + $distro = 'Unknown'; + $version = 'Unknown'; + } + + return ($kernel, $distro, $version); +} + +sub linuxDistro { + my @uname = uname(); + my $kernel = $uname[2]; + my $distro = 'Unknown Linux Distro'; + my $version = 'Unknown Linux Version'; + my $found = 0; + +# os-release is the standard for many new distros based on systemd + if ( -f '/etc/os-release' ) { + open(my $RELFILE,'<','/etc/os-release') or die( "Can't Open file: $!\n" ); + while (<$RELFILE>) { + if ( /^NAME=(")?(.*)(?(1)\1|).*$/ ) { + $distro = $2; + $found = 1; + } + if ( /^VERSION_ID=(")?(.*)(?(1)\1|).*$/ ) { + $version = $2; + $found = 1; + } + } + close $RELFILE; +# exists on many distros but does not always contain useful information, such as redhat + } elsif ( -f '/etc/lsb-release' ) { + open(my $RELFILE,'<','/etc/lsb-release') or die( "Can't Open file: $!\n" ); + while (<$RELFILE>) { + if ( /^DISTRIB_DESCRIPTION=(")?(.*)(?(1)\1|).*$/ ) { + $distro = $2; + $found = 1; + } + if ( /^DISTRIB_RELEASE=(")?(.*)(?(1)\1|).*$/ ) { + $version = $2; + $found = 1; + } + } + close $RELFILE; + } + +# If all else fails, search through a list of known release files until we find one + if ( !$found ) { + my @releasefile = ('/etc/SuSE-release', '/etc/redhat-release', '/etc/redhat_version', + '/etc/fedora-release', '/etc/slackware-release', '/etc/slackware-version', + '/etc/debian_release', '/etc/debian_version', '/etc/mandrake-release', + '/etc/yellowdog-release', '/etc/gentoo-release'); + foreach (@releasefile) { + if ( -f $_ ) { + open(my $RELFILE,'<',$_) or die( "Can't Open file: $!\n" ); + while (<$RELFILE>) { + if ( /(.*).* (\d+\.?\d*) .*/ ) { + $distro = $1; + $version = $2; + $found = 1; + } + } + close $RELFILE; + last; + } + } + } + + if ( !$found ) { + Warning('ZoneMinder was unable to determine Linux distro. Please report.'); + } + + return ($kernel, $distro, $version); +} + + +1; +__END__ + =head1 NAME zmtelemetry.pl - Send usage information to the ZoneMinder development team @@ -44,300 +343,3 @@ console under Options. none currently =cut -use strict; -use bytes; - -@EXTRA_PERL_LIB@ -use ZoneMinder; -use DBI; -use Getopt::Long; -use autouse 'Pod::Usage'=>qw(pod2usage); -use LWP::UserAgent; -use Sys::MemInfo qw(totalmem); -use Sys::CPU qw(cpu_count); -use POSIX qw(strftime uname); - -$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; -$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; -delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; - -use constant CHECK_INTERVAL => (14*24*60*60); # Interval between version checks - -# Setting these as contants for now. -# Alternatively, we can put these in the dB and then retrieve using the Config hash. -use constant ZM_TELEMETRY_SERVER_ENDPOINT => 'https://zmanon:2b2d0b4skps@telemetry.zoneminder.com/zmtelemetry/testing5'; - -if ( $Config{ZM_TELEMETRY_DATA} ) -{ - print( "Update agent starting at ".strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" ); - - my $lastCheck = $Config{ZM_TELEMETRY_LAST_UPLOAD}; - - while( 1 ) { - my $now = time(); - if ( ($now-$lastCheck) > CHECK_INTERVAL ) { - Info( "Collecting data to send to ZoneMinder Telemetry server." ); - my $dbh = zmDbConnect(); - # Build the telemetry hash - # We should keep *BSD systems in mind when calling system commands - my %telemetry; - $telemetry{uuid} = getUUID($dbh); - $telemetry{ip} = getIP(); - $telemetry{timestamp} = strftime( "%Y-%m-%dT%H:%M:%S%z", localtime() ); - $telemetry{monitor_count} = countQuery($dbh,"Monitors"); - $telemetry{event_count} = countQuery($dbh,"Events"); - $telemetry{architecture} = runSysCmd("uname -p"); - ($telemetry{kernel}, $telemetry{distro}, $telemetry{version}) = getDistro(); - $telemetry{zm_version} = ZoneMinder::Base::ZM_VERSION; - $telemetry{system_memory} = totalmem(); - $telemetry{processor_count} = cpu_count(); - $telemetry{monitors} = getMonitorRef($dbh); - - Info( "Sending data to ZoneMinder Telemetry server." ); - - my $result = jsonEncode( \%telemetry ); - - if ( sendData($result) ) { - $lastCheck = $now; - - my $sql = "update Config set Value = ? where Name = 'ZM_TELEMETRY_LAST_UPLOAD'"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( "$lastCheck" ) or die( "Can't execute: ".$sth->errstr() ); - $sth->finish(); - } - } - sleep( 3600 ); - } - print( "Update agent exiting at ".strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" ); -} - -############### -# SUBROUTINES # -############### - -# Find, verify, then run the supplied system command -sub runSysCmd { - my $msg = shift; - my @arguments = split(/ /,$msg); - chomp($arguments[0]); - my $path = qx( which $arguments[0] ); - - my $status = $? >> 8; - my $result = ""; - if ( !$path || $status ) { - Warning( "Cannot find the $arguments[0] executable." ); - } else { - chomp($path); - $arguments[0] = $path; - my $cmd = join(" ",@arguments); - $result = qx( $cmd ); - chomp($result); - } - -return $result; -} - -# Upload message data to ZoneMinder telemetry server -sub sendData { - my $msg = shift; - - my $ua = LWP::UserAgent->new; - my $server_endpoint = ZM_TELEMETRY_SERVER_ENDPOINT; - - if ( $Config{ZM_UPDATE_CHECK_PROXY} ) { - $ua->proxy( "https", $Config{ZM_UPDATE_CHECK_PROXY} ); - } - - Debug("Posting telemetry data to: $server_endpoint"); - - # set custom HTTP request header fields - my $req = HTTP::Request->new(POST => $server_endpoint); - $req->header('content-type' => 'application/x-www-form-urlencoded'); - $req->header('content-length' => length($msg)); - $req->header('connection' => 'Close'); - - $req->content($msg); - - my $resp = $ua->request($req); - my $resp_msg = $resp->decoded_content; - my $resp_code = $resp->code; - if ($resp->is_success) { - Info("Telemetry data uploaded successfully."); - Debug("Telemetry server upload success response message: $resp_msg"); - } else { - Warning("Telemetry server returned HTTP POST error code: $resp_code"); - Debug("Telemetry server upload failure response message: $resp_msg"); - } -return $resp->is_success; -} - -# Retrieves the UUID from the database. Creates a new UUID if one does not exist. -sub getUUID { - my $dbh = shift; - my $uuid= ""; - - # Verify the current UUID is valid and not nil - if (( $Config{ZM_TELEMETRY_UUID} =~ /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i ) && ( $Config{ZM_TELEMETRY_UUID} ne "00000000-0000-0000-0000-000000000000" )) { - $uuid = $Config{ZM_TELEMETRY_UUID}; - } else { - my $sql = "SELECT uuid()"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - $uuid = $Config{ZM_TELEMETRY_UUID} = $sth->fetchrow_array(); - $sth->finish(); - - $sql = "UPDATE Config set Value = ? WHERE Name = 'ZM_TELEMETRY_UUID'"; - $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - $res = $sth->execute( "$uuid" ) or die( "Can't execute: ".$sth->errstr() ); - $sth->finish(); - } - Debug("Using UUID of: $uuid"); - -return $uuid; -} - -# Retrieves the local server's external IP address -sub getIP { - my $ipaddr = "0.0.0.0"; - my $ua = LWP::UserAgent->new; - my $server_endpoint = "https://wiki.zoneminder.com/ip.php"; - - my $req = HTTP::Request->new(GET => $server_endpoint); - my $resp = $ua->request($req); - - if ($resp->is_success) { - $ipaddr = $resp->decoded_content; - } - - Debug("Found external ip address of: $ipaddr"); - -return $ipaddr; -} - -# As the name implies, just your average mysql count query -sub countQuery { - my $dbh = shift; - my $table = shift; - - my $sql = "SELECT count(*) FROM $table"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - my $count = $sth->fetchrow_array(); - $sth->finish(); - -return $count -} - -# Returns a reference to an array of hashes containing data from all monitors -sub getMonitorRef { - my $dbh = shift; - - my $sql = "SELECT Id,Name,Type,Function,Width,Height,Colours,MaxFPS,AlarmMaxFPS FROM Monitors"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - my $arrayref = $sth->fetchall_arrayref({}); - -return $arrayref; -} - -sub getDistro { - my $kernel = ""; - my $distro = ""; - my $version = ""; - my @uname = uname(); - - if ( $uname[0] =~ /Linux/ ) { - Debug("Linux distro detected."); - ($kernel, $distro, $version) = linuxDistro(); - } elsif ( $uname[0] =~ /.*BSD/ ) { - Debug("BSD distro detected."); - $kernel = $uname[3]; - $distro = $uname[0]; - $version = $uname[2]; - } elsif ( $uname[0] =~ /Darwin/ ) { - Debug("Mac OS distro detected."); - $kernel = $uname[3]; - $distro = runSysCmd("sw_vers -productName"); - $version = runSysCmd("sw_vers -productVersion"); - } elsif ( $uname[0] =~ /SunOS|Solaris/ ) { - Debug("Sun Solaris detected."); - $kernel = $uname[3]; - $distro = $uname[1]; - $version = $uname[2]; - } else { - Warning("ZoneMinder was unable to determine the host system. Please report."); - $kernel = "Unknown"; - $distro = "Unknown"; - $version = "Unknown"; - } - -return ($kernel, $distro, $version); -} - -sub linuxDistro { - my @uname = uname(); - my $kernel = $uname[2]; - my $distro = "Unknown Linux Distro"; - my $version = "Unknown Linux Version"; - my $found = 0; - - # os-release is the standard for many new distros based on systemd - if ( -f "/etc/os-release" ) { - open(my $RELFILE,"<","/etc/os-release") or die( "Can't Open file: $!\n" ); - while (<$RELFILE>) { - if ( /^NAME=(")?(.*)(?(1)\1|).*$/ ) { - $distro = $2; - $found = 1; - } - if ( /^VERSION_ID=(")?(.*)(?(1)\1|).*$/ ) { - $version = $2; - $found = 1; - } - } - close $RELFILE; - # exists on many distros but does not always contain useful information, such as redhat - } elsif ( -f "/etc/lsb-release" ) { - open(my $RELFILE,"<","/etc/lsb-release") or die( "Can't Open file: $!\n" ); - while (<$RELFILE>) { - if ( /^DISTRIB_DESCRIPTION=(")?(.*)(?(1)\1|).*$/ ) { - $distro = $2; - $found = 1; - } - if ( /^DISTRIB_RELEASE=(")?(.*)(?(1)\1|).*$/ ) { - $version = $2; - $found = 1; - } - } - close $RELFILE; - } - - # If all else fails, search through a list of known release files until we find one - if ( !$found ) { - my @releasefile = ("/etc/SuSE-release", "/etc/redhat-release", "/etc/redhat_version", - "/etc/fedora-release", "/etc/slackware-release", "/etc/slackware-version", - "/etc/debian_release", "/etc/debian_version", "/etc/mandrake-release", - "/etc/yellowdog-release", "/etc/gentoo-release"); - foreach (@releasefile) { - if ( -f $_ ) { - open(my $RELFILE,"<",$_) or die( "Can't Open file: $!\n" ); - while (<$RELFILE>) { - if ( /(.*).* (\d+\.?\d*) .*/ ) { - $distro = $1; - $version = $2; - $found = 1; - } - } - close $RELFILE; - last; - } - } - } - - if ( !$found ) { - Warning("ZoneMinder was unable to determine Linux distro. Please report."); - } - -return ($kernel, $distro, $version); -} - -