feat: add AUDIT logging level for tracking administrative changes
Add a new AUDIT logging level (-5) between PANIC (-4) and NOLOG (shifted to -6) across C++, PHP, and Perl loggers. AUDIT entries use code 'AUD' and syslog priority LOG_NOTICE. They record who changed what, from where, for monitors, filters, users, config, roles, groups, zones, states, servers, storage, events, snapshots, control caps, and login/logout. AUDIT entries have their own retention period (ZM_LOG_AUDIT_DATABASE_LIMIT, default 1 year) separate from regular log pruning. The log pruning in zmstats.pl and zmaudit.pl now excludes AUDIT rows from regular pruning and prunes them independently. Critical safety: the C++ termination logic is changed from 'if (level <= FATAL)' to 'if (level == FATAL || level == PANIC)' to prevent AUDIT-level log calls from killing the process. Includes db migration zm_update-1.39.1.sql to shift any stored NOLOG config values from -5 to -6. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>pull/4639/head^2
parent
c0016fa00b
commit
e6ace6fcf4
|
|
@ -0,0 +1,15 @@
|
|||
--
|
||||
-- Add AUDIT logging level between PANIC (-4) and NOLOG.
|
||||
-- AUDIT is now -5; NOLOG shifts from -5 to -6.
|
||||
-- Migrate any stored NOLOG config values from -5 to -6.
|
||||
--
|
||||
|
||||
UPDATE Config SET Value = '-6'
|
||||
WHERE Name IN ('ZM_LOG_LEVEL_SYSLOG','ZM_LOG_LEVEL_TERM',
|
||||
'ZM_LOG_LEVEL_FILE','ZM_LOG_LEVEL_WEBLOG','ZM_LOG_LEVEL_DATABASE')
|
||||
AND Value = '-5';
|
||||
|
||||
UPDATE Config SET DefaultValue = '-6'
|
||||
WHERE Name IN ('ZM_LOG_LEVEL_SYSLOG','ZM_LOG_LEVEL_TERM',
|
||||
'ZM_LOG_LEVEL_FILE','ZM_LOG_LEVEL_WEBLOG','ZM_LOG_LEVEL_DATABASE')
|
||||
AND DefaultValue = '-5';
|
||||
|
|
@ -1179,7 +1179,7 @@ our @options = (
|
|||
`,
|
||||
type => {
|
||||
db_type => 'integer',
|
||||
hint => 'None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1',
|
||||
hint => 'None=-6|Audit=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1',
|
||||
pattern => qr|^(\d+)$|,
|
||||
format => q( $1 )
|
||||
},
|
||||
|
|
@ -1205,7 +1205,7 @@ our @options = (
|
|||
`,
|
||||
type => {
|
||||
db_type => 'integer',
|
||||
hint => 'None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1',
|
||||
hint => 'None=-6|Audit=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1',
|
||||
pattern => qr|^(\d+)$|,
|
||||
format => q( $1 )
|
||||
},
|
||||
|
|
@ -1235,7 +1235,7 @@ our @options = (
|
|||
`,
|
||||
type => {
|
||||
db_type => 'integer',
|
||||
hint => 'None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1',
|
||||
hint => 'None=-6|Audit=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1',
|
||||
pattern => qr|^(\d+)$|,
|
||||
format => q( $1 )
|
||||
},
|
||||
|
|
@ -1243,7 +1243,7 @@ our @options = (
|
|||
},
|
||||
{
|
||||
name => 'ZM_LOG_LEVEL_WEBLOG',
|
||||
default => '-5',
|
||||
default => '-6',
|
||||
description => 'Save logging output to the weblog',
|
||||
help => q`
|
||||
ZoneMinder logging is now more integrated between
|
||||
|
|
@ -1262,7 +1262,7 @@ our @options = (
|
|||
`,
|
||||
type => {
|
||||
db_type => 'integer',
|
||||
hint => 'None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1',
|
||||
hint => 'None=-6|Audit=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1',
|
||||
pattern => qr|^(\d+)$|,
|
||||
format => q( $1 )
|
||||
},
|
||||
|
|
@ -1293,7 +1293,7 @@ our @options = (
|
|||
`,
|
||||
type => {
|
||||
db_type => 'integer',
|
||||
hint => 'None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1',
|
||||
hint => 'None=-6|Audit=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1',
|
||||
pattern => qr|^(\d+)$|,
|
||||
format => q( $1 )
|
||||
},
|
||||
|
|
@ -1321,6 +1321,21 @@ our @options = (
|
|||
type => $types{string},
|
||||
category => 'logging',
|
||||
},
|
||||
{
|
||||
name => 'ZM_LOG_AUDIT_DATABASE_LIMIT',
|
||||
default => '1 year',
|
||||
description => 'Maximum retention period for audit log entries',
|
||||
help => q`
|
||||
Audit log entries record administrative changes such as monitor
|
||||
configuration, user management, filter changes, and system settings.
|
||||
These entries are typically retained longer than regular log entries
|
||||
for compliance and troubleshooting. Set to a row count (integer) or
|
||||
time interval such as '1 year', '6 month', '90 day'. Set to empty
|
||||
to retain audit logs indefinitely.
|
||||
`,
|
||||
type => $types{string},
|
||||
category => 'logging',
|
||||
},
|
||||
{
|
||||
name => 'ZM_LOG_FFMPEG',
|
||||
default => 'yes',
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ our %EXPORT_TAGS = (
|
|||
ERROR
|
||||
FATAL
|
||||
PANIC
|
||||
AUDIT
|
||||
NOLOG
|
||||
) ],
|
||||
functions => [ qw(
|
||||
|
|
@ -79,6 +80,7 @@ our %EXPORT_TAGS = (
|
|||
Error
|
||||
Fatal
|
||||
Panic
|
||||
Audit
|
||||
) ]
|
||||
);
|
||||
|
||||
|
|
@ -122,7 +124,8 @@ use constant {
|
|||
ERROR => -2,
|
||||
FATAL => -3,
|
||||
PANIC => -4,
|
||||
NOLOG => -5
|
||||
AUDIT => -5,
|
||||
NOLOG => -6
|
||||
};
|
||||
|
||||
our %codes = (
|
||||
|
|
@ -141,6 +144,7 @@ our %codes = (
|
|||
&ERROR => 'ERR',
|
||||
&FATAL => 'FAT',
|
||||
&PANIC => 'PNC',
|
||||
&AUDIT => 'AUD',
|
||||
&NOLOG => 'OFF'
|
||||
);
|
||||
|
||||
|
|
@ -159,7 +163,8 @@ our %priorities = (
|
|||
&WARNING => 'warning',
|
||||
&ERROR => 'err',
|
||||
&FATAL => 'err',
|
||||
&PANIC => 'err'
|
||||
&PANIC => 'err',
|
||||
&AUDIT => 'notice'
|
||||
);
|
||||
|
||||
our $logger;
|
||||
|
|
@ -764,6 +769,8 @@ sub Panic {
|
|||
confess($_[0]);
|
||||
}
|
||||
|
||||
sub Audit { fetch()->logPrint(AUDIT, @_, caller); }
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
|
|
|
|||
|
|
@ -844,22 +844,23 @@ FROM `Frames` WHERE `EventId`=?';
|
|||
File::Find::find( { wanted=>\&deleteSwapImage, untaint=>1 }, $swap_image_root );
|
||||
};
|
||||
|
||||
# Prune the Logs table if required
|
||||
# Prune the Logs table if required (excluding AUDIT entries)
|
||||
if ( $Config{ZM_LOG_DATABASE_LIMIT} ) {
|
||||
my $audit_level = ZoneMinder::Logger::AUDIT;
|
||||
if ( $Config{ZM_LOG_DATABASE_LIMIT} =~ /^\d+$/ ) {
|
||||
# Number of rows
|
||||
my $selectLogRowCountSql = 'SELECT count(*) AS `Rows` FROM `Logs`';
|
||||
my $selectLogRowCountSql = 'SELECT count(*) AS `Rows` FROM `Logs` WHERE `Level` != ?';
|
||||
my $selectLogRowCountSth = $dbh->prepare_cached( $selectLogRowCountSql )
|
||||
or Fatal("Can't prepare '$selectLogRowCountSql': ".$dbh->errstr());
|
||||
$res = $selectLogRowCountSth->execute()
|
||||
$res = $selectLogRowCountSth->execute($audit_level)
|
||||
or Fatal("Can't execute: ".$selectLogRowCountSth->errstr());
|
||||
my $row = $selectLogRowCountSth->fetchrow_hashref();
|
||||
my $logRows = $row->{Rows};
|
||||
if ( $logRows > $Config{ZM_LOG_DATABASE_LIMIT} ) {
|
||||
my $deleteLogByRowsSql = 'DELETE low_priority FROM `Logs` ORDER BY `TimeKey` ASC LIMIT ?';
|
||||
my $deleteLogByRowsSql = 'DELETE low_priority FROM `Logs` WHERE `Level` != ? ORDER BY `TimeKey` ASC LIMIT ?';
|
||||
my $deleteLogByRowsSth = $dbh->prepare_cached( $deleteLogByRowsSql )
|
||||
or Fatal("Can't prepare '$deleteLogByRowsSql': ".$dbh->errstr());
|
||||
$res = $deleteLogByRowsSth->execute( $logRows - $Config{ZM_LOG_DATABASE_LIMIT} )
|
||||
$res = $deleteLogByRowsSth->execute( $audit_level, $logRows - $Config{ZM_LOG_DATABASE_LIMIT} )
|
||||
or Fatal("Can't execute: ".$deleteLogByRowsSth->errstr());
|
||||
if ( $deleteLogByRowsSth->rows() ) {
|
||||
aud_print('Deleted '.$deleteLogByRowsSth->rows().' log table entries by count');
|
||||
|
|
@ -867,7 +868,7 @@ FROM `Frames` WHERE `EventId`=?';
|
|||
}
|
||||
} else {
|
||||
# Time of record
|
||||
|
||||
|
||||
# 7 days is invalid. We need to remove the s
|
||||
if ( $Config{ZM_LOG_DATABASE_LIMIT} =~ /^(.*)s$/ ) {
|
||||
$Config{ZM_LOG_DATABASE_LIMIT} = $1;
|
||||
|
|
@ -876,16 +877,53 @@ FROM `Frames` WHERE `EventId`=?';
|
|||
do {
|
||||
my $deleteLogByTimeSql =
|
||||
'DELETE FROM `Logs`
|
||||
WHERE `TimeKey` < unix_timestamp(now() - interval '.$Config{ZM_LOG_DATABASE_LIMIT}.') LIMIT 10';
|
||||
WHERE `Level` != ? AND `TimeKey` < unix_timestamp(now() - interval '.$Config{ZM_LOG_DATABASE_LIMIT}.') LIMIT 10';
|
||||
my $deleteLogByTimeSth = $dbh->prepare_cached( $deleteLogByTimeSql )
|
||||
or Fatal("Can't prepare '$deleteLogByTimeSql': ".$dbh->errstr());
|
||||
$res = $deleteLogByTimeSth->execute()
|
||||
$res = $deleteLogByTimeSth->execute($audit_level)
|
||||
or Fatal("Can't execute: ".$deleteLogByTimeSth->errstr());
|
||||
$deleted_rows = $deleteLogByTimeSth->rows();
|
||||
aud_print("Deleted $deleted_rows log table entries by time");
|
||||
} while ( $deleted_rows );
|
||||
}
|
||||
} # end if ZM_LOG_DATABASE_LIMIT
|
||||
|
||||
# Prune AUDIT log entries separately with their own retention period
|
||||
if ( $Config{ZM_LOG_AUDIT_DATABASE_LIMIT} ) {
|
||||
my $audit_level = ZoneMinder::Logger::AUDIT;
|
||||
my $audit_limit = $Config{ZM_LOG_AUDIT_DATABASE_LIMIT};
|
||||
if ( $audit_limit =~ /^\d+$/ ) {
|
||||
# Number of rows
|
||||
my $sth = $dbh->prepare_cached('SELECT count(*) AS `Rows` FROM `Logs` WHERE `Level` = ?')
|
||||
or Fatal("Can't prepare audit log count: ".$dbh->errstr());
|
||||
$res = $sth->execute($audit_level)
|
||||
or Fatal("Can't execute audit log count: ".$sth->errstr());
|
||||
my $row = $sth->fetchrow_hashref();
|
||||
my $logRows = $row->{Rows};
|
||||
if ( $logRows > $audit_limit ) {
|
||||
my $del_sth = $dbh->prepare_cached('DELETE low_priority FROM `Logs` WHERE `Level` = ? ORDER BY `TimeKey` ASC LIMIT ?')
|
||||
or Fatal("Can't prepare audit log delete: ".$dbh->errstr());
|
||||
$res = $del_sth->execute($audit_level, $logRows - $audit_limit)
|
||||
or Fatal("Can't execute audit log delete: ".$del_sth->errstr());
|
||||
if ( $del_sth->rows() ) {
|
||||
aud_print('Deleted '.$del_sth->rows().' audit log entries by count');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
# Time of record
|
||||
$audit_limit =~ s/s$//;
|
||||
my $deleted_rows;
|
||||
do {
|
||||
my $del_sth = $dbh->prepare_cached(
|
||||
'DELETE FROM `Logs` WHERE `Level` = ? AND `TimeKey` < unix_timestamp(now() - interval '.$audit_limit.') LIMIT 10')
|
||||
or Fatal("Can't prepare audit log time delete: ".$dbh->errstr());
|
||||
$res = $del_sth->execute($audit_level)
|
||||
or Fatal("Can't execute audit log time delete: ".$del_sth->errstr());
|
||||
$deleted_rows = $del_sth->rows();
|
||||
aud_print("Deleted $deleted_rows audit log entries by time");
|
||||
} while ( $deleted_rows );
|
||||
}
|
||||
} # end if ZM_LOG_AUDIT_DATABASE_LIMIT
|
||||
$loop = $continuous;
|
||||
|
||||
my $eventcounts_sql = '
|
||||
|
|
|
|||
|
|
@ -99,19 +99,20 @@ while (!$zm_terminate) {
|
|||
$event_ids = $dbh->selectcol_arrayref('SELECT EventId FROM Events_Month WHERE StartDateTime < DATE_SUB(NOW(), INTERVAL 1 month)');
|
||||
zmDbDo('DELETE FROM Events_Month WHERE EventId IN ('.join(',', map { '?' } @$event_ids).')', @$event_ids) if $event_ids and @$event_ids;
|
||||
|
||||
# Prune the Logs table if required
|
||||
# Prune the Logs table if required (excluding AUDIT entries)
|
||||
if ( $Config{ZM_LOG_DATABASE_LIMIT} ) {
|
||||
my $audit_level = ZoneMinder::Logger::AUDIT;
|
||||
if ( $Config{ZM_LOG_DATABASE_LIMIT} =~ /^\d+$/ ) {
|
||||
# Number of rows
|
||||
my $selectLogRowCountSql = 'SELECT count(*) AS `Rows` FROM `Logs`';
|
||||
my $selectLogRowCountSql = 'SELECT count(*) AS `Rows` FROM `Logs` WHERE `Level` != ?';
|
||||
my $selectLogRowCountSth = $dbh->prepare_cached( $selectLogRowCountSql )
|
||||
or Fatal("Can't prepare '$selectLogRowCountSql': ".$dbh->errstr());
|
||||
my $res = $selectLogRowCountSth->execute()
|
||||
my $res = $selectLogRowCountSth->execute($audit_level)
|
||||
or Fatal("Can't execute: ".$selectLogRowCountSth->errstr());
|
||||
my $row = $selectLogRowCountSth->fetchrow_hashref();
|
||||
my $logRows = $row->{Rows};
|
||||
if ( $logRows > $Config{ZM_LOG_DATABASE_LIMIT} ) {
|
||||
my $rows = zmDbDo('DELETE low_priority FROM `Logs` ORDER BY `TimeKey` ASC LIMIT ?', $logRows - $Config{ZM_LOG_DATABASE_LIMIT});
|
||||
my $rows = zmDbDo('DELETE low_priority FROM `Logs` WHERE `Level` != ? ORDER BY `TimeKey` ASC LIMIT ?', $audit_level, $logRows - $Config{ZM_LOG_DATABASE_LIMIT});
|
||||
Debug('Deleted '.$rows.' log table entries by count') if defined $rows;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -123,12 +124,39 @@ while (!$zm_terminate) {
|
|||
}
|
||||
my $rows;
|
||||
do {
|
||||
$rows = zmDbDo('DELETE low_priority FROM `Logs` WHERE `TimeKey` < unix_timestamp(now() - interval '.$Config{ZM_LOG_DATABASE_LIMIT}.') LIMIT 100');
|
||||
$rows = zmDbDo('DELETE low_priority FROM `Logs` WHERE `Level` != ? AND `TimeKey` < unix_timestamp(now() - interval '.$Config{ZM_LOG_DATABASE_LIMIT}.') LIMIT 100', $audit_level);
|
||||
Debug("Deleted $rows log table entries by time") if $rows;
|
||||
} while ($rows and ($rows == 100) and !$zm_terminate);
|
||||
}
|
||||
} # end if ZM_LOG_DATABASE_LIMIT
|
||||
|
||||
# Prune AUDIT log entries separately with their own retention period
|
||||
if ( $Config{ZM_LOG_AUDIT_DATABASE_LIMIT} ) {
|
||||
my $audit_level = ZoneMinder::Logger::AUDIT;
|
||||
my $audit_limit = $Config{ZM_LOG_AUDIT_DATABASE_LIMIT};
|
||||
if ( $audit_limit =~ /^\d+$/ ) {
|
||||
# Number of rows
|
||||
my $sth = $dbh->prepare_cached('SELECT count(*) AS `Rows` FROM `Logs` WHERE `Level` = ?')
|
||||
or Fatal("Can't prepare audit log count: ".$dbh->errstr());
|
||||
my $res = $sth->execute($audit_level)
|
||||
or Fatal("Can't execute audit log count: ".$sth->errstr());
|
||||
my $row = $sth->fetchrow_hashref();
|
||||
my $logRows = $row->{Rows};
|
||||
if ( $logRows > $audit_limit ) {
|
||||
my $rows = zmDbDo('DELETE low_priority FROM `Logs` WHERE `Level` = ? ORDER BY `TimeKey` ASC LIMIT ?', $audit_level, $logRows - $audit_limit);
|
||||
Debug('Deleted '.$rows.' audit log entries by count') if defined $rows;
|
||||
}
|
||||
} else {
|
||||
# Time of record
|
||||
$audit_limit =~ s/s$//;
|
||||
my $rows;
|
||||
do {
|
||||
$rows = zmDbDo('DELETE low_priority FROM `Logs` WHERE `Level` = ? AND `TimeKey` < unix_timestamp(now() - interval '.$audit_limit.') LIMIT 100', $audit_level);
|
||||
Debug("Deleted $rows audit log entries by time") if $rows;
|
||||
} while ($rows and ($rows == 100) and !$zm_terminate);
|
||||
}
|
||||
} # end if ZM_LOG_AUDIT_DATABASE_LIMIT
|
||||
|
||||
{
|
||||
my $rows;
|
||||
do {
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ Logger::Logger() :
|
|||
smCodes[ERROR] = "ERR";
|
||||
smCodes[FATAL] = "FAT";
|
||||
smCodes[PANIC] = "PNC";
|
||||
smCodes[AUDIT] = "AUD";
|
||||
smCodes[NOLOG] = "OFF";
|
||||
|
||||
smSyslogPriorities[INFO] = LOG_INFO;
|
||||
|
|
@ -81,6 +82,7 @@ Logger::Logger() :
|
|||
smSyslogPriorities[ERROR] = LOG_ERR;
|
||||
smSyslogPriorities[FATAL] = LOG_ERR;
|
||||
smSyslogPriorities[PANIC] = LOG_ERR;
|
||||
smSyslogPriorities[AUDIT] = LOG_NOTICE;
|
||||
|
||||
char code[4] = "";
|
||||
// Extra comparison against DEBUG1 to ensure GCC knows we are printing a single byte.
|
||||
|
|
@ -420,7 +422,7 @@ void Logger::closeSyslog() {
|
|||
|
||||
void Logger::logPrint(bool hex, const char *filepath, int line, int level, const char *fstring, ...) {
|
||||
if (level > mEffectiveLevel) return;
|
||||
if (level < PANIC || level > DEBUG9)
|
||||
if (level < AUDIT || level > DEBUG9)
|
||||
Panic("Invalid logger level %d", level);
|
||||
|
||||
log_mutex.lock();
|
||||
|
|
@ -544,12 +546,12 @@ void Logger::logPrint(bool hex, const char *filepath, int line, int level, const
|
|||
}
|
||||
|
||||
log_mutex.unlock();
|
||||
if (level <= FATAL) {
|
||||
if (level == FATAL || level == PANIC) {
|
||||
zm_terminate = true;
|
||||
dbQueue.stop();
|
||||
zmDbClose();
|
||||
logTerm();
|
||||
if (level <= PANIC) abort();
|
||||
if (level == PANIC) abort();
|
||||
exit(-1);
|
||||
}
|
||||
} // end logPrint
|
||||
|
|
|
|||
|
|
@ -34,8 +34,9 @@
|
|||
class Logger {
|
||||
public:
|
||||
enum {
|
||||
NOOPT = -6,
|
||||
NOLOG, // -5
|
||||
NOOPT = -7,
|
||||
NOLOG, // -6
|
||||
AUDIT, // -5
|
||||
PANIC, // -4
|
||||
FATAL, // -3
|
||||
ERROR, // -2
|
||||
|
|
@ -229,6 +230,7 @@ inline Logger::Level logDebugging() {
|
|||
#define Error(params...) logPrintf(Logger::ERROR, ##params)
|
||||
#define Fatal(params...) logPrintf(Logger::FATAL, ##params)
|
||||
#define Panic(params...) logPrintf(Logger::PANIC, ##params)
|
||||
#define Audit(params...) logPrintf(Logger::AUDIT, ##params)
|
||||
#define Mark() Info("Mark/%s/%d", __FILE__, __LINE__)
|
||||
#define Log() Info("Log")
|
||||
#ifdef __GNUC__
|
||||
|
|
|
|||
|
|
@ -31,7 +31,10 @@ if ($action == 'delete') {
|
|||
foreach ($_REQUEST['markMids'] as $markMid) {
|
||||
if (canEdit('Monitors', $markMid)) {
|
||||
$monitor = ZM\Monitor::find_one(['Id'=>$markMid]);
|
||||
if ($monitor) $monitor->delete();
|
||||
if ($monitor) {
|
||||
$monitor->delete();
|
||||
ZM\AuditAction('delete', 'monitor', $markMid, 'Name: '.$monitor->Name());
|
||||
}
|
||||
} else {
|
||||
$error_message .= 'You do not have permission to delete monitor '.$markMid.'<br/>';
|
||||
} // end if canedit this monitor
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ if ( $action == 'Save' ) {
|
|||
global $error_message;
|
||||
$error_message .= "Error saving control: " . $Control->get_last_error().'</br>';
|
||||
} else {
|
||||
ZM\AuditAction((!empty($_REQUEST['cid']) ? 'update' : 'create'), 'control', $Control->Id(), 'Name: '.($Control->Name() ?? ''));
|
||||
$redirect = '?view=options&tab=control';
|
||||
}
|
||||
} // end if action
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ if ( canEdit('Events') ) {
|
|||
|
||||
if ( ($action == 'rename') && isset($_REQUEST['eventName']) ) {
|
||||
dbQuery('UPDATE Events SET Name=? WHERE Id=?', array($_REQUEST['eventName'], $_REQUEST['eid']));
|
||||
ZM\AuditAction('rename', 'event', $_REQUEST['eid'], 'Name: '.$_REQUEST['eventName']);
|
||||
} else if ( $action == 'eventdetail' ) {
|
||||
dbQuery('UPDATE Events SET Cause=?, Notes=? WHERE Id=?',
|
||||
array(
|
||||
|
|
@ -37,6 +38,7 @@ if ( canEdit('Events') ) {
|
|||
$_REQUEST['eid']
|
||||
)
|
||||
);
|
||||
ZM\AuditAction('update', 'event', $_REQUEST['eid'], 'Detail update');
|
||||
$refreshParent = true;
|
||||
$closePopup = true;
|
||||
} else if ( $action == 'archive' ) {
|
||||
|
|
@ -45,6 +47,7 @@ if ( canEdit('Events') ) {
|
|||
dbQuery('UPDATE Events SET Archived=? WHERE Id=?', array(0, $_REQUEST['eid']));
|
||||
} else if ( $action == 'delete' ) {
|
||||
deleteEvent($_REQUEST['eid']);
|
||||
ZM\AuditAction('delete', 'event', $_REQUEST['eid'], '');
|
||||
$refreshParent = true;
|
||||
}
|
||||
} // end if canEdit(Events)
|
||||
|
|
|
|||
|
|
@ -48,9 +48,11 @@ if ( $action == 'archive' ) {
|
|||
$dbConn->commit();
|
||||
$refreshParent = true;
|
||||
} else if ( $action == 'delete' ) {
|
||||
foreach ( getAffectedIds('eids') as $markEid ) {
|
||||
$deletedEids = getAffectedIds('eids');
|
||||
foreach ( $deletedEids as $markEid ) {
|
||||
deleteEvent($markEid);
|
||||
}
|
||||
ZM\AuditAction('delete', 'events', 0, 'Count: '.count($deletedEids));
|
||||
$refreshParent = true;
|
||||
} else {
|
||||
ZM\Warning("Unsupported action $action in events");
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ if (isset($_REQUEST['object']) and ($_REQUEST['object'] == 'filter')) {
|
|||
$filter->control('stop');
|
||||
}
|
||||
$filter->delete();
|
||||
ZM\AuditAction('delete', 'filter', $_REQUEST['Id'], 'Name: '.$filter->Name());
|
||||
} else {
|
||||
$error_message .= 'You do not have permission to delete the filter.<br/>';
|
||||
}
|
||||
|
|
@ -89,6 +90,7 @@ if (isset($_REQUEST['object']) and ($_REQUEST['object'] == 'filter')) {
|
|||
$error_message = $filter->get_last_error();
|
||||
return;
|
||||
}
|
||||
ZM\AuditAction('save', 'filter', $filter->Id(), 'Name: '.$filter->Name().' Action: '.$action);
|
||||
if ($action == 'Save' or $action == 'SaveAs' ) {
|
||||
// We update the request id so that the newly saved filter is auto-selected
|
||||
$_REQUEST['Id'] = $filter->Id();
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ if ($action == 'save') {
|
|||
$oldDecodingEnabled = $monitor->DecodingEnabled();
|
||||
if ( $newFunction != $oldFunction || $newEnabled != $oldEnabled || $newDecodingEnabled != $oldDecodingEnabled ) {
|
||||
$monitor->save(array('Function'=>$newFunction, 'Enabled'=>$newEnabled, 'DecodingEnabled'=>$newDecodingEnabled));
|
||||
ZM\AuditAction('update', 'monitor', $mid, "Function: $oldFunction->$newFunction Enabled: $oldEnabled->$newEnabled");
|
||||
|
||||
if ( daemonCheck() && ($monitor->Type() != 'WebSite') ) {
|
||||
$monitor->zmcControl(($newFunction != 'None') ? 'restart' : 'stop');
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ if ( $action == 'save' ) {
|
|||
dbQuery('INSERT INTO `Groups_Monitors` (`GroupId`,`MonitorId`) VALUES (?,?)', array($group_id, $mid));
|
||||
}
|
||||
}
|
||||
ZM\AuditAction((!empty($_REQUEST['gid']) ? 'update' : 'create'), 'group', $group->Id(), 'Name: '.$_REQUEST['newGroup']['Name']);
|
||||
$redirect = '?view=groups';
|
||||
}
|
||||
?>
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ if ( $action == 'delete' ) {
|
|||
if ( !empty($_REQUEST['gid']) ) {
|
||||
foreach ( ZM\Group::find(array('Id'=>$_REQUEST['gid'])) as $Group ) {
|
||||
$Group->delete();
|
||||
ZM\AuditAction('delete', 'group', $Group->Id(), 'Name: '.$Group->Name());
|
||||
}
|
||||
}
|
||||
$redirect = '?view=groups';
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
|
||||
if ( $action == 'logout' ) {
|
||||
ZM\Audit("user=".($user ? $user->Username() : 'unknown')." action=logout id=".($user ? $user->Id() : 0)." from=".($_SERVER['REMOTE_ADDR'] ?? 'local'));
|
||||
userLogout();
|
||||
$view = 'login';
|
||||
} elseif ( $action == 'config' ) {
|
||||
|
|
|
|||
|
|
@ -226,6 +226,7 @@ if ($action == 'save') {
|
|||
} // end foreach zone
|
||||
} // end if rotation or just size change
|
||||
} // end if changes in width or height
|
||||
ZM\AuditAction('update', 'monitor', $mid, 'Changed: '.implode(', ', array_keys($changes)));
|
||||
} else {
|
||||
$error_message .= $monitor->get_last_error();
|
||||
} // end if successful save
|
||||
|
|
@ -263,6 +264,7 @@ if ($action == 'save') {
|
|||
$error_message .= $zone->get_last_error();
|
||||
ZM\Error('Error adding zone:' . $error_message);
|
||||
}
|
||||
ZM\AuditAction('create', 'monitor', $mid, 'Name: '.($newMonitor['Name'] ?? ''));
|
||||
} else {
|
||||
ZM\Error('Error saving new Monitor.');
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ if ($action == 'save') {
|
|||
}
|
||||
if (!$Monitor->save($_REQUEST['newMonitor'])) {
|
||||
$error_message .= 'Error saving monitor: ' . $Monitor->get_last_error().'<br/>';
|
||||
} else {
|
||||
ZM\AuditAction('update', 'monitor', $mid, 'Bulk update: '.implode(', ', array_keys($_REQUEST['newMonitor'])));
|
||||
}
|
||||
if ($Monitor->Capturing() != 'None' && $Monitor->Type() != 'WebSite') {
|
||||
$Monitor->zmcControl('start');
|
||||
|
|
|
|||
|
|
@ -30,27 +30,35 @@ if ( $action == 'delete' ) {
|
|||
if ( isset($_REQUEST['object']) ) {
|
||||
if ( $_REQUEST['object'] == 'server' ) {
|
||||
if ( !empty($_REQUEST['markIds']) ) {
|
||||
foreach( $_REQUEST['markIds'] as $Id )
|
||||
foreach ( $_REQUEST['markIds'] as $Id ) {
|
||||
dbQuery('DELETE FROM Servers WHERE Id=?', array($Id));
|
||||
ZM\AuditAction('delete', 'server', $Id, '');
|
||||
}
|
||||
}
|
||||
$refreshParent = true;
|
||||
} else if ( $_REQUEST['object'] == 'storage' ) {
|
||||
if ( !empty($_REQUEST['markIds']) ) {
|
||||
foreach( $_REQUEST['markIds'] as $Id )
|
||||
foreach ( $_REQUEST['markIds'] as $Id ) {
|
||||
dbQuery('DELETE FROM Storage WHERE Id=?', array($Id));
|
||||
ZM\AuditAction('delete', 'storage', $Id, '');
|
||||
}
|
||||
}
|
||||
$refreshParent = true;
|
||||
} else if ( $_REQUEST['object'] == 'role' ) {
|
||||
if ( !empty($_REQUEST['markRids']) ) {
|
||||
foreach( $_REQUEST['markRids'] as $Id )
|
||||
foreach ( $_REQUEST['markRids'] as $Id ) {
|
||||
dbQuery('DELETE FROM User_Roles WHERE Id=?', array($Id));
|
||||
ZM\AuditAction('delete', 'role', $Id, '');
|
||||
}
|
||||
}
|
||||
$redirect = '?view=options&tab=roles';
|
||||
} # end if isset($_REQUEST['object'] )
|
||||
} else if ( isset($_REQUEST['markUids']) ) {
|
||||
// deletes users
|
||||
foreach ($_REQUEST['markUids'] as $markUid)
|
||||
foreach ($_REQUEST['markUids'] as $markUid) {
|
||||
dbQuery('DELETE FROM Users WHERE Id = ?', array($markUid));
|
||||
ZM\AuditAction('delete', 'user', $markUid, '');
|
||||
}
|
||||
if ($markUid == $user->Id()) {
|
||||
userLogout();
|
||||
$redirect = '?view=login';
|
||||
|
|
@ -90,6 +98,7 @@ if ( $action == 'delete' ) {
|
|||
} # end if value changed
|
||||
} # end foreach config entry
|
||||
if ( $changed ) {
|
||||
ZM\AuditAction('update', 'config', 0, 'Tab: '.$_REQUEST['tab']);
|
||||
switch ( $_REQUEST['tab'] ) {
|
||||
case 'system' :
|
||||
case 'config' :
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ if ($action == 'Save') {
|
|||
unset($_REQUEST['redirect']);
|
||||
return;
|
||||
}
|
||||
ZM\AuditAction(($rid ? 'update' : 'create'), 'role', $dbRole->Id(), 'Name: '.$dbRole->Name());
|
||||
}
|
||||
|
||||
# Save group permissions
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ if ($action == 'save') {
|
|||
} else {
|
||||
dbQuery('INSERT INTO Servers SET '.implode(', ', $changes));
|
||||
}
|
||||
ZM\AuditAction((!empty($_REQUEST['id']) ? 'update' : 'create'), 'server', $_REQUEST['id'] ?? 0, 'Changed: '.implode(', ', array_keys($changes)));
|
||||
$refreshParent = true;
|
||||
}
|
||||
$redirect = '?view=options&tab=servers';
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ if ( $action == 'create' ) {
|
|||
}
|
||||
$snapshot = new ZM\Snapshot();
|
||||
$snapshot->save(array('CreatedBy'=>$user->Id()));
|
||||
ZM\AuditAction('create', 'snapshot', $snapshot->Id(), '');
|
||||
|
||||
foreach ( $_REQUEST['monitor_ids'] as $monitor_id ) {
|
||||
if (!validCardinal($monitor_id)) {
|
||||
|
|
@ -73,12 +74,14 @@ if ( isset($_REQUEST['id']) ) {
|
|||
$changes = $snapshot->changes($_REQUEST['snapshot']);
|
||||
if ( count($changes) ) {
|
||||
$snapshot->save($changes);
|
||||
ZM\AuditAction('update', 'snapshot', $snapshot->Id(), 'Changed: '.implode(', ', array_keys($changes)));
|
||||
}
|
||||
$redirect = '?view=snapshots';
|
||||
}
|
||||
} else if ( $action == 'delete' ) {
|
||||
if ( canEdit('Events') ) {
|
||||
$snapshot->delete();
|
||||
ZM\AuditAction('delete', 'snapshot', $_REQUEST['id'], '');
|
||||
$redirect = '?view=snapshots';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ if (!canEdit('System')) {
|
|||
if ($action == 'state') {
|
||||
if (!empty($_REQUEST['runState'])) {
|
||||
packageControl($_REQUEST['runState']);
|
||||
ZM\AuditAction('apply', 'state', 0, 'State: '.$_REQUEST['runState']);
|
||||
$refreshParent = true;
|
||||
}
|
||||
} else if ($action == 'save') {
|
||||
|
|
@ -39,10 +40,13 @@ if ($action == 'state') {
|
|||
if ( $_REQUEST['newState'] )
|
||||
$_REQUEST['runState'] = $_REQUEST['newState'];
|
||||
dbQuery('REPLACE INTO `States` SET `Name`=?, `Definition`=?', array($_REQUEST['runState'], $definition));
|
||||
ZM\AuditAction('save', 'state', 0, 'Name: '.$_REQUEST['runState']);
|
||||
}
|
||||
} else if ($action == 'delete') {
|
||||
if (isset($_REQUEST['runState']))
|
||||
if (isset($_REQUEST['runState'])) {
|
||||
dbQuery('DELETE FROM `States` WHERE `Name`=?', array($_REQUEST['runState']));
|
||||
ZM\AuditAction('delete', 'state', 0, 'Name: '.$_REQUEST['runState']);
|
||||
}
|
||||
}
|
||||
$redirect = '?view='.getHomeView();
|
||||
?>
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ if ($action == 'save') {
|
|||
|
||||
if (count($changes)) {
|
||||
if ($storage->save($changes)) {
|
||||
ZM\AuditAction(($_REQUEST['id'] ? 'update' : 'create'), 'storage', $storage->Id(), 'Changed: '.implode(', ', array_keys($changes)));
|
||||
} else {
|
||||
$error_message .= $storage->get_last_error();
|
||||
} // end if successful save
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ if ($action == 'Save') {
|
|||
unset($_REQUEST['redirect']);
|
||||
return;
|
||||
}
|
||||
ZM\AuditAction(($uid ? 'update' : 'create'), 'user', $dbUser->Id(), 'Username: '.$dbUser->Username().' Changed: '.implode(', ', array_keys($changes)));
|
||||
|
||||
if ($uid) {
|
||||
if ($user and ($dbUser->Username() == $user->Username())) {
|
||||
|
|
@ -120,6 +121,7 @@ if ($action == 'Save') {
|
|||
unset($_REQUEST['redirect']);
|
||||
return;
|
||||
}
|
||||
ZM\AuditAction('self_update', 'user', $dbUser->Id(), 'Changed: '.implode(', ', array_keys($changes)));
|
||||
|
||||
# We are the logged in user, need to update the $user object and generate a new auth_hash
|
||||
$user = ZM\User::find_one(['Enabled'=>1, 'Id'=>$uid]);
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ if ( !empty($_REQUEST['mid']) && canEdit('Monitors', $_REQUEST['mid']) ) {
|
|||
} else {
|
||||
dbQuery('INSERT INTO Zones SET MonitorId=?, '.implode(', ', $changes), array($mid));
|
||||
}
|
||||
ZM\AuditAction(($zid > 0 ? 'update' : 'create'), 'zone', $zid, 'MonitorId: '.$mid);
|
||||
if ( daemonCheck() && ($monitor->Type() != 'WebSite') ) {
|
||||
$monitor->zmcControl('reload');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ if ($action == 'delete') {
|
|||
# Could use true but store the object instead for easy access later
|
||||
$monitors_to_restart[] = $monitor;
|
||||
$error_message .= $zone->delete();
|
||||
ZM\AuditAction('delete', 'zone', $markZid, 'MonitorId: '.$monitor->Id());
|
||||
} # end foreach Zone
|
||||
|
||||
if (daemonCheck()) {
|
||||
|
|
|
|||
|
|
@ -604,6 +604,7 @@ if (ZM_OPT_USE_AUTH) {
|
|||
$password = $_REQUEST['password'];
|
||||
|
||||
ZM\Info("Login successful for user \"$username\"");
|
||||
ZM\Audit("user=$username action=login id=".$user->Id()." from=".($_SERVER['REMOTE_ADDR'] ?? 'local'));
|
||||
$password_type = password_type($user->Password());
|
||||
|
||||
if ( $password_type == 'mysql' or $password_type == 'mysql+bcrypt' ) {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ class Logger {
|
|||
const ERROR = -2;
|
||||
const FATAL = -3;
|
||||
const PANIC = -4;
|
||||
const NOLOG = -5; // Special artificial level to prevent logging
|
||||
const AUDIT = -5;
|
||||
const NOLOG = -6; // Special artificial level to prevent logging
|
||||
|
||||
private $initialised = false;
|
||||
|
||||
|
|
@ -46,6 +47,7 @@ class Logger {
|
|||
self::ERROR => 'ERR',
|
||||
self::FATAL => 'FAT',
|
||||
self::PANIC => 'PNC',
|
||||
self::AUDIT => 'AUD',
|
||||
self::NOLOG => 'OFF',
|
||||
);
|
||||
private static $syslogPriorities = array(
|
||||
|
|
@ -55,6 +57,7 @@ class Logger {
|
|||
self::ERROR => LOG_ERR,
|
||||
self::FATAL => LOG_ERR,
|
||||
self::PANIC => LOG_ERR,
|
||||
self::AUDIT => LOG_NOTICE,
|
||||
);
|
||||
private static $phpErrorLevels = array(
|
||||
self::DEBUG => E_USER_NOTICE,
|
||||
|
|
@ -63,6 +66,7 @@ class Logger {
|
|||
self::ERROR => E_USER_WARNING,
|
||||
self::FATAL => E_USER_ERROR,
|
||||
self::PANIC => E_USER_ERROR,
|
||||
self::AUDIT => E_USER_NOTICE,
|
||||
);
|
||||
|
||||
private function __construct() {
|
||||
|
|
@ -512,6 +516,20 @@ function Panic( $string ) {
|
|||
exit(1);
|
||||
}
|
||||
|
||||
function Audit($string) {
|
||||
Logger::fetch()->logPrint(Logger::AUDIT, $string);
|
||||
}
|
||||
|
||||
function AuditAction($action, $target_type, $target_id, $details) {
|
||||
global $user;
|
||||
$username = $user ? $user->Username() : 'system';
|
||||
$ip = !empty($_SERVER['HTTP_X_FORWARDED_FOR'])
|
||||
? $_SERVER['HTTP_X_FORWARDED_FOR']
|
||||
: ($_SERVER['REMOTE_ADDR'] ?? 'local');
|
||||
Audit("user=$username action=$action target=$target_type"
|
||||
." id=$target_id details=\"$details\" from=$ip");
|
||||
}
|
||||
|
||||
function ErrorHandler( $error, $string, $file, $line ) {
|
||||
if ( ! (error_reporting() & $error) ) {
|
||||
// This error code is not included in error_reporting
|
||||
|
|
|
|||
Loading…
Reference in New Issue