apparmor/utils/Reports.pm

2262 lines
65 KiB
Perl
Raw Normal View History

# $Id$
# ------------------------------------------------------------------
#
# Copyright (C) 2005-2006 Novell/SUSE
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public
# License published by the Free Software Foundation.
#
# ------------------------------------------------------------------
package Immunix::Reports;
################################################################################
# /usr/lib/perl5/site_perl/Reports.pm
#
# - Parses /var/log/messages for SubDomain messages
# - Writes results to .html or comma-delimited (.csv) files (Optional)
#
# Requires:
# Immunix::Events;
# Time::Local (temporary)
#
# Input (Optional):
# -Start Date|End Date (Month, Day, Year, Time)
# -Program Name
# -Profile Name
# -PID
# -Denied Resources
#
################################################################################
use strict;
use DBI;
use DBD::SQLite;
use Locale::gettext;
use POSIX;
use ycp;
setlocale(LC_MESSAGES, "");
textdomain("Reports");
my $eventDb = '/var/log/apparmor/events.db';
my $numEvents = 1000;
sub month2Num {
my $lexMon = shift;
my $months = {
"Jan" => '01',
"Feb" => '02',
"Mar" => '03',
"Apr" => '04',
"May" => '05',
"Jun" => '06',
"Jul" => '07',
"Aug" => '08',
"Sep" => '09',
"Oct" => '10',
"Nov" => '11',
"Dec" => '12'
};
my $numMonth = $months->{$lexMon};
return $numMonth;
}
sub num2Month {
my $monthNum = shift;
my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
my $lexMonth = $months[ ($monthNum - 1) ];
return $lexMonth;
}
# Converts Epoch Time to Formatted Date String
sub getDate {
my $epTime = shift;
my $date = localtime($epTime);
my ($day, $mon, $mondate, $time, $year) = split(/\s+/, $date);
my ($hour, $min, $sec) = split(/:/, $time);
$mon = month2Num($mon);
# we want 2 digits for easier reading
$mon = sprintf("%02d", $mon);
$mondate = sprintf("%02d", $mondate);
my $newDate = "$year-$mon-$mondate $time";
return $newDate;
}
sub round {
my $num = shift;
$num = sprintf("%.2f", $num);
return ("$num");
}
# round up
sub pageRound {
my $num = shift;
my $pnum = int($num);
if ($pnum < $num) {
$pnum++;
}
return $pnum;
}
sub checkFileExists {
my $file = shift;
if ($file && -e $file) {
return 1;
} else {
return 0;
}
}
# Translate mode & sdmode for parsing
sub rewriteModes {
my $filts = shift;
# Mode wrangling - Rewrite for better matches
if ($filts->{'mode'} && $filts->{'mode'} ne "All") {
my @mode = ();
my $tmpMode = undef;
@mode = split(//, $filts->{'mode'});
if (@mode > 0) {
$tmpMode = join("|", @mode);
} else {
delete($filts->{'mode'});
}
if ($tmpMode) {
$filts->{'mode'} = $tmpMode;
}
}
# Rewrite sdmode for more flexible matches
if ($filts->{'sdmode'} && $filts->{'sdmode'} ne "All") {
my @tmpMode = ();
if ($filts->{'sdmode'} =~ /[pP]/) { push(@tmpMode, 'PERMIT'); }
if ($filts->{'sdmode'} =~ /[rR]/) { push(@tmpMode, 'REJECT'); }
if ($filts->{'sdmode'} =~ /[aA]/) { push(@tmpMode, 'AUDIT'); }
if (@tmpMode > 0) {
$filts->{'sdmode'} = join('|', @tmpMode);
} else {
delete($filts->{'sdmode'});
}
}
return $filts;
}
sub enableEventD {
# make sure the eventd is enabled before we do any reports
my $need_enable = 0;
if (open(SDCONF, "/etc/apparmor/subdomain.conf")) {
while (<SDCONF>) {
if (/^\s*APPARMOR_ENABLE_AAEVENTD\s*=\s*(\S+)\s*$/) {
my $flag = lc($1);
# strip quotes from the value if present
$flag = $1 if $flag =~ /^"(\S+)"$/;
$need_enable = 1 if $flag ne "yes";
}
}
close(SDCONF);
}
# if the eventd isn't enabled, we'll turn it on the first time they
# run a report and start it up - if something fails for some reason,
# we should just fall through and the db check should correctly tell
# the caller that the db isn't initialized correctly
if ($need_enable) {
my $old = "/etc/apparmor/subdomain.conf";
my $new = "/etc/apparmor/subdomain.conf.$$";
if (open(SDCONF, $old)) {
if (open(SDCONFNEW, ">$new")) {
my $foundit = 0;
while (<SDCONF>) {
if (/^\s*APPARMOR_ENABLE_AAEVENTD\s*=/) {
print SDCONFNEW "APPARMOR_ENABLE_AAEVENTD=\"yes\"\n";
$foundit = 1;
} else {
print SDCONFNEW;
}
}
unless ($foundit) {
print SDCONFNEW "APPARMOR_ENABLE_AAEVENTD=\"yes\"\n";
}
close(SDCONFNEW);
# if we were able to overwrite the old config
# config file with the new stuff, we'll kick
# the init script to start up aa-eventd
if (rename($new, $old)) {
if (-e "/sbin/rcaaeventd") {
system("/sbin/rcaaeventd restart >/dev/null 2>&1");
} else {
system("/sbin/rcapparmor restart >/dev/null 2>&1");
}
}
}
close(SDCONF);
}
}
return $need_enable;
}
# Check that events db exists and is populated
# - Returns 1 for good db, 0 for bad db
sub checkEventDb {
my $count = undef;
my $eventDb = '/var/log/apparmor/events.db';
# make sure the event daemon is enabled
if (enableEventD()) {
my $now = time;
# wait until the event db appears or we hit 1 min
while (!-e $eventDb) {
sleep 2;
return 0 if ((time - $now) >= 60);
}
# wait until it stops changing or we hit 1 min - the event
# daemon flushes events to the db every five seconds.
my $last_modified = 0;
my $modified = (stat($eventDb))[9];
while ($last_modified != $modified) {
sleep 10;
last if ((time - $now) >= 60);
$last_modified = $modified;
$modified = (stat($eventDb))[9];
}
}
my $query = "SELECT count(*) FROM events ";
# Pull stuff from db
my $dbh = DBI->connect("dbi:SQLite:dbname=$eventDb", "", "", { RaiseError => 1, AutoCommit => 1 });
eval {
my $sth = $dbh->prepare($query);
$sth->execute;
$count = $sth->fetchrow_array();
$sth->finish;
};
if ($@) {
ycp::y2error(sprintf(gettext("DBI Execution failed: %s."), $DBI::errstr));
return;
}
$dbh->disconnect();
if ($count && $count > 0) {
return 1;
} else {
return 0;
}
}
# Called from ag_reports_parse
sub getNumPages {
my $args = shift;
my $db = ();
my $numPages = 0;
my $count = 0;
my $type = undef;
my $eventRep = "/var/log/apparmor/reports/events.rpt";
# Figure out whether we want db count or file parse
if ($args->{'type'}) {
if ($args->{'type'} eq 'sir' || $args->{'type'} eq 'ess-multi') {
$type = 'db';
} elsif ($args->{'type'} eq 'ess') {
return 1; # ess reports have one page by definition
} else {
$type = 'arch'; # archived or file
}
}
# Parse sdmode & mode labels
if ($args->{'sdmode'}) {
$args->{'sdmode'} =~ s/\&//g;
$args->{'sdmode'} =~ s/\://g;
$args->{'sdmode'} =~ s/\s//g;
$args->{'sdmode'} =~ s/AccessType//g;
if ($args->{'sdmode'} eq "All") {
delete($args->{'sdmode'});
}
}
if ($args->{'mode'}) {
$args->{'mode'} =~ s/\&//g;
$args->{'mode'} =~ s/Mode\://g;
$args->{'mode'} =~ s/\s//g;
if ($args->{'mode'} eq "All") {
delete($args->{'mode'});
}
}
########################################
$args = rewriteModes($args);
if ($type && $type eq 'db') {
my $start = undef;
my $end = undef;
if ($args->{'startTime'} && $args->{'startTime'} > 0) {
$start = $args->{'startTime'};
}
if ($args->{'endTime'} && $args->{'endTime'} > 0) {
$end = $args->{'endTime'};
}
my $query = "SELECT count(*) FROM events ";
# We need filter information for getting a correct count
#my $filts = getSirFilters($args); # these should be sent from YaST
my $filts = undef;
if ($args->{'prog'}) { $filts->{'prog'} = $args->{'prog'}; }
if ($args->{'profile'}) { $filts->{'profile'} = $args->{'profile'}; }
if ($args->{'pid'}) { $filts->{'pid'} = $args->{'pid'}; }
if ($args->{'resource'}) { $filts->{'resource'} = $args->{'resource'}; }
if ($args->{'severity'}) { $filts->{'severity'} = $args->{'severity'}; }
if ($args->{'sdmode'}) { $filts->{'sdmode'} = $args->{'sdmode'}; }
if ($args->{'mode'}) { $filts->{'mode'} = $args->{'mode'}; }
for (sort(keys(%$filts))) {
if ($filts->{$_} eq '-' || $filts->{$_} eq 'All') {
delete($filts->{$_});
}
}
my $midQuery = getQueryFilters($filts, $start, $end);
$query .= "$midQuery";
# Pull stuff from db
my $dbh = DBI->connect("dbi:SQLite:dbname=$eventDb", "", "", { RaiseError => 1, AutoCommit => 1 });
eval {
my $sth = $dbh->prepare($query);
$sth->execute;
$count = $sth->fetchrow_array();
$sth->finish;
};
if ($@) {
ycp::y2error(sprintf(gettext("DBI Execution failed: %s."), $DBI::errstr));
return;
}
$dbh->disconnect();
#ycp::y2milestone("Numpages Query: $query"); # debug
$numPages = pageRound($count / $numEvents);
if ($numPages < 1) { $numPages = 1; }
} elsif ($type && $type eq 'arch') {
if (open(REP, "<$eventRep")) {
while (<REP>) {
if (/^Page/) {
$numPages++;
} else {
$count++;
}
}
close REP;
} else {
ycp::y2error(sprintf(gettext("Couldn't open file: %s."), $eventRep));
}
} else {
ycp::y2error(gettext("No type value passed. Unable to determine page count."));
return ("1");
}
if ($numPages < 1) { $numPages = 1; }
my $numCheck = int($count / $numEvents);
if ($numPages < $numCheck) {
$numPages = $numCheck;
}
return ($numPages);
}
sub getEpochFromNum {
my $date = shift;
my $place = shift || undef; # Used to set default $sec if undef
my ($numMonth, $numDay, $time, $year) = split(/\s+/, $date);
my ($hour, $min, $sec) = '0';
my $junk = undef;
if ($time =~ /:/) {
($hour, $min, $sec, $junk) = split(/\:/, $time);
if (!$hour || $hour eq "") { $hour = '0'; }
if (!$min || $min eq "") { $min = '0'; }
if (!$sec || $sec eq "") {
if ($place eq 'end') {
$sec = '59';
} else {
$sec = '0';
}
}
}
$numMonth--; # Months start from 0 for epoch translation
if (!$year) { $year = (split(/\s+/, localtime))[4]; }
my $epochDate = timelocal($sec, $min, $hour, $numDay, $numMonth, $year);
return $epochDate;
}
sub getEpochFromStr {
my $lexDate = shift;
my ($lexMonth, $dateDay, $fullTime, $year) = split(/\s+/, $lexDate);
#my ($lexDay, $lexMonth, $dateDay, $fullTime, $year) = split(/\s+/, $lexDate);
my ($hour, $min, $sec) = split(/\:/, $fullTime);
if (!$year) { $year = (split(/\s+/, localtime))[4]; }
my $numMonth = month2Num($lexMonth);
my $epochDate = timelocal($sec, $min, $hour, $dateDay, $numMonth, $year);
return $epochDate;
}
# Replaces old files with new files
sub updateFiles {
my ($oldFile, $newFile) = @_;
if (unlink("$oldFile")) {
if (!rename("$newFile", "$oldFile")) {
if (!system('/bin/mv', "$newFile", "$oldFile")) {
ycp::y2error(sprintf(gettext("Failed copying %s."), $oldFile));
return 1;
}
}
} else {
system('/bin/rm', "$oldFile");
system('/bin/mv', "$newFile", "$oldFile");
}
return 0;
}
# This is a holder, that was originally part of exportLog()
# Used by /usr/bin/reportgen.pl
sub exportFormattedText {
my ($repName, $logFile, $db) = @_;
my $date = localtime;
open(LOG, ">$logFile") || die "Couldn't open $logFile";
# Date Profile PID Mesg
print LOG "$repName: Log generated by Novell AppArmor, $date\n\n";
printf LOG "%-21s%-32s%-8s%-51s", "Host", "Date", "Program", "Profile", "PID", "Severity", "Mode", "Detail", "Access Type";
print LOG "\n";
for (sort (@$db)) {
print LOG "$_->{'host'},$_->{'time'},$_->{'prog'},$_->{'profile'},";
print LOG "$_->{'pid'},$_->{'severity'},$->{'mode'},$_->{'resource'},$_->{'sdmode'}\n";
}
close LOG;
}
sub exportLog {
my ($exportLog, $db, $header) = @_;
if (open(LOG, ">$exportLog")) {
my $date = localtime();
if ($exportLog =~ /csv/) {
# $header comes from reportgen.pl (scheduled reports)
if ($header) { print LOG "$header\n\n"; }
for (@$db) {
no strict;
# host time prog profile pid severity resource sdmode mode
#print LOG "$_->{'host'},$_->{'time'},$_->{'prog'},$_->{'profile'},$_->{'pid'},";
print LOG "$_->{'host'},$_->{'date'},$_->{'prog'},$_->{'profile'},$_->{'pid'},";
print LOG "$_->{'severity'},$_->{'mode'},$_->{'resource'},$_->{'sdmode'}\n";
}
} elsif ($exportLog =~ /html/) {
print LOG "<html><body bgcolor='fffeec'>\n\n";
print LOG "<font face='Helvetica,Arial,Sans-Serif'>\n";
# $header comes from reportgen.pl (scheduled reports)
if ($header) {
print LOG "$header\n\n";
} else {
print LOG "<br><h3>$exportLog</h3><br>\n<h4>Log generated by Novell AppArmor, $date</h4>\n\n";
}
print LOG "<hr><br><table border='1' cellpadding='2'>\n";
#print LOG "<tr bgcolor='edefff'><th>Date</th><th>Profile</th><th>PID</th><th>Message</th></tr>\n";
print LOG "<tr bgcolor='edefff'><th>Host</th><th>Date</th><th>Program</th><th>Profile</th><th>PID</th>"
. "<th>Severity</th><th>Mode</th><th>Detail</th><th>Access Type</th></tr>\n";
my $idx = 1;
for (@$db) {
no strict;
$idx++;
if ($idx % 2 == 0) {
#"<td>&nbsp;$_->{'time'}&nbsp;</td>" .
print LOG "<tr><td>&nbsp;$_->{'host'}&nbsp;</td>"
. "<td>&nbsp;$_->{'date'}&nbsp;</td>"
. "<td>&nbsp;$_->{'prog'}&nbsp;</td>"
. "<td>&nbsp;$_->{'profile'}&nbsp;</td>"
. "<td>&nbsp;$_->{'pid'}&nbsp;</td>"
. "<td>&nbsp;$_->{'severity'}&nbsp;</td>"
. "<td>&nbsp;$_->{'mode'}&nbsp;</td>"
. "<td>&nbsp;$_->{'resource'}&nbsp;</td>"
. "<td>&nbsp;$_->{'sdmode'}&nbsp;</td></tr>\n";
} else {
# Shade every other row
print LOG "<tr='edefef'><td>&nbsp;$_->{'host'}&nbsp;</td>"
. "<td>&nbsp;$_->{'date'}&nbsp;</td>"
. "<td>&nbsp;$_->{'prog'}&nbsp;</td>"
. "<td>&nbsp;$_->{'profile'}&nbsp;</td>"
. "<td>&nbsp;$_->{'pid'}&nbsp;</td>"
. "<td>&nbsp;$_->{'severity'}&nbsp;</td>"
. "<td>&nbsp;$_->{'mode'}&nbsp;</td>"
. "<td>&nbsp;$_->{'resource'}&nbsp;</td>"
. "<td>&nbsp;$_->{'sdmode'}&nbsp;</td></tr>\n";
}
}
print LOG "<br></table></font></body></html>\n\n";
}
close LOG;
} else {
ycp::y2error(sprintf(gettext("Export Log Error: Couldn't open %s"), $exportLog));
}
# return($error);
}
# Pulls info on single report from apparmor xml file
sub getXmlReport {
my ($repName, $repConf) = @_;
my $repFlag = 0;
my %rep = ();
if (defined($repName) && ref($repName)) {
if ($repName->{'base'}) {
$repName = $repName->{'base'};
} elsif ($repName->{'name'}) {
$repName = $repName->{'name'};
}
}
if (!$repName) {
ycp::y2error(gettext("Fatal error. No report name given. Exiting."));
}
if (!$repConf || !-e $repConf) {
$repConf = '/etc/apparmor/reports.conf';
if (!-e $repConf) {
ycp::y2error(
sprintf(
gettext(
"Unable to get configuration info for %s.
Unable to find %s."
),
$repName,
$repConf
)
);
exit 1;
}
}
if (open(XML, "<$repConf")) {
while (<XML>) {
chomp;
if (/\<name\>/) {
#my $name = (split(/\"/, $_))[1];
/\<name\>(.+)\<\/name\>/;
my $name = $1;
if ($name eq $repName) {
$rep{'name'} = $name;
$repFlag = 1;
}
} elsif (/\<\/report\>/) {
$repFlag = 0;
} elsif ($repFlag == 1) {
if (/\s*\<\w+\s+(.*)\/\>.*$/) {
my $attrs = $1;
chomp($attrs);
my @attrlist = split(/\s+/, $attrs);
for (@attrlist) {
#Match attributes
if (/\s*(\S+)=\"(\S+)\"/) {
$rep{$1} = $2 unless $2 eq '-';
}
}
} elsif (/\<(\w+)\>([\w+|\/].*)\<\//) {
if ($1) {
$rep{"$1"} = $2 unless $2 eq '-';
} else {
ycp::y2error(sprintf(gettext("Failed to parse: %s."), $_));
}
}
}
}
close XML;
} else {
ycp::y2error(sprintf(gettext("Fatal Error. Couldn't open %s."), $repConf));
exit 1;
}
return \%rep;
}
# Returns info on currently confined processes
sub getCfInfo {
my $ref = ();
my @cfDb = ();
my $cfApp = '/usr/sbin/unconfined';
if (open(CF, "$cfApp |")) {
my $host = `hostname`;
chomp($host);
my $date = localtime;
while (<CF>) {
my $ref = ();
my $all = undef;
$ref->{'host'} = $host;
$ref->{'date'} = $date;
chomp;
($ref->{'pid'}, $ref->{'prog'}, $all) = split(/\s+/, $_, 3);
$all = /\s*((not)*\s*confined\s*(by)*)/;
$ref->{'state'} = $1;
$ref->{'state'} =~ s/\s*by//g;
$ref->{'state'} =~ s/not\s+/not-/g;
($ref->{'prof'}, $ref->{'type'}) = split(/\s+/, $_);
if ($ref->{'prog'} eq "") { $ref->{'prog'} = "-"; }
if ($ref->{'prof'} eq "") { $ref->{'prof'} = "-"; }
if ($ref->{'pid'} eq "") { $ref->{'pid'} = "-"; }
if ($ref->{'state'} eq "") { $ref->{'state'} = "-"; }
if ($ref->{'type'} eq "") { $ref->{'type'} = "-"; }
push(@cfDb, $ref);
}
close CF;
} else {
my $error = sprintf(gettext("Fatal Error. Can't run %s. Exiting."), $cfApp);
ycp::y2error($error);
return $error;
}
return (\@cfDb);
}
# generate stats for ESS reports
sub getEssStats {
my $args = shift;
#my ($host, $targetDir, $startdate, $enddate) = @_;
my @hostDb = ();
my @hostList = ();
my $targetDir = undef;
my $host = undef;
my $startdate = undef;
my $enddate = undef;
if (!$args->{'targetDir'}) {
$targetDir = '/var/log/apparmor/';
}
if ($args->{'host'}) { $host = $args->{'host'}; }
if ($args->{'startdate'}) {
$startdate = $args->{'startdate'};
} else {
$startdate = '1104566401'; # Jan 1, 2005
}
if ($args->{'enddate'}) {
$enddate = $args->{'enddate'};
} else {
$enddate = time;
}
if (!-e $targetDir) {
ycp::y2error(sprintf(gettext("Fatal Error. No directory, %s, found. Exiting."), $targetDir));
return;
}
# Max Sev, Ave. Sev, Num. Rejects, Start Time, End Time
my $ctQuery = "SELECT count(*) FROM events WHERE time >= $startdate AND time <= $enddate";
my $query = "SELECT MAX(severity), AVG(severity), COUNT(id), MIN(time), "
. "MAX(time) FROM events WHERE sdmode='REJECTING' AND "
. "time >= $startdate AND time <= $enddate";
# "MAX(time) FROM events join info WHERE sdmode='REJECTING' AND " .
# Get list of hosts to scan
if (opendir(TDIR, $targetDir)) {
@hostList = grep(/\.db/, readdir(TDIR));
close TDIR;
} else {
ycp::y2error(sprintf(gettext("Fatal Error. Couldn't open %s. Exiting"), $targetDir));
return;
}
# Cycle through for each host
for my $eventDb (@hostList) {
$eventDb = "$targetDir/$eventDb";
my $ess = undef;
my $ret = undef;
my $count = undef;
#my $eventDb = '/var/log/apparmor/events.db';
my $dbh = DBI->connect("dbi:SQLite:dbname=$eventDb", "", "", { RaiseError => 1, AutoCommit => 1 });
# get hostname
my $host = undef;
my $hostQuery = "SELECT * FROM info WHERE name='host'";
eval {
my $sth = $dbh->prepare($hostQuery);
$sth->execute;
$host = $sth->fetchrow_array();
$sth->finish;
};
if ($@) {
ycp::y2error(sprintf(gettext("DBI Execution failed: %s"), $DBI::errstr));
return;
}
# Get number of events
eval {
my $sth = $dbh->prepare($ctQuery);
$sth->execute;
$count = $sth->fetchrow_array();
$sth->finish;
};
if ($@) {
ycp::y2error(sprintf(gettext("DBI Execution failed: %s"), $DBI::errstr));
return;
}
# Get rest of stats
eval { $ret = $dbh->selectall_arrayref("$query"); };
if ($@) {
ycp::y2error(sprintf(gettext("DBI Execution failed: %s"), $DBI::errstr));
return;
}
$dbh->disconnect();
# hostIp, startDate, endDate, sevHi, sevMean, numRejects
if ($host) {
$ess->{'host'} = $host;
} else {
$ess->{'host'} = '';
}
$ess->{'sevHi'} = $$ret[0]->[0];
if (!$ess->{'sevHi'}) {
$ess->{'sevHi'} = 0;
}
$ess->{'sevMean'} = $$ret[0]->[1];
if (!$ess->{'sevMean'} || $ess->{'sevHi'} == 0) {
$ess->{'sevMean'} = 0;
} else {
$ess->{'sevMean'} = round("$ess->{'sevMean'}");
}
$ess->{'numRejects'} = $$ret[0]->[2];
$ess->{'startdate'} = $$ret[0]->[3];
$ess->{'enddate'} = $$ret[0]->[4];
$ess->{'numEvents'} = $count;
# Convert dates
if ($ess->{'startdate'} && $ess->{'startdate'} !~ /:/) {
$ess->{'startdate'} = Immunix::Reports::getDate($ess->{'startdate'});
}
if ($ess->{'enddate'} && $ess->{'enddate'} !~ /:/) {
$ess->{'enddate'} = Immunix::Reports::getDate($ess->{'enddate'});
}
push(@hostDb, $ess);
}
return \@hostDb;
}
# get ESS stats for archived reports (warning -- this can be slow for large files
# debug -- not fully functional yet
sub getArchEssStats {
my $args = shift;
my $prevTime = '0';
my $prevDate = '0';
my $startDate = '1104566401'; # Jan 1, 2005
my $endDate = time;
if ($args->{'startdate'}) { $startDate = $args->{'startdate'}; }
if ($args->{'enddate'}) { $endDate = $args->{'enddate'}; }
# hostIp, startDate, endDate, sevHi, sevMean, numRejects
my @eventDb = getEvents("$startDate", "$endDate");
my @hostIdx = (); # Simple index to all hosts for quick host matching
my @hostDb = (); # Host-keyed Data for doing REJECT stats
# Outer Loop for Raw Event db
for (@eventDb) {
if ($_->{'host'}) {
my $ev = $_; # current event record
# Create new host entry, or add to existing
if (grep(/$ev->{'host'}/, @hostIdx) == 1) {
# Inner loop, but the number of hosts should be small
for (@hostDb) {
if ($_->{'host'} eq $ev->{'host'}) {
# Find earliest start date
if ($_->{'startdate'} > $ev->{'date'}) {
$_->{'startdate'} = $ev->{'date'};
}
# tally all events reported for host
$_->{'numEvents'}++;
if ($ev->{'sdmode'}) {
if ($ev->{'sdmode'} =~ /PERMIT/) {
$_->{'numPermits'}++;
}
if ($ev->{'sdmode'} =~ /REJECT/) {
$_->{'numRejects'}++;
}
if ($ev->{'sdmode'} =~ /AUDIT/) {
$_->{'numAudits'}++;
}
}
# Add stats to host entry
#if ( $ev->{'severity'} && $ev->{'severity'} =~ /\b\d+\b/ ) {}
if ($ev->{'severity'} && $ev->{'severity'} != -1) {
$_->{'sevNum'}++;
$_->{'sevTotal'} = $_->{'sevTotal'} + $ev->{'severity'};
if ($ev->{'severity'} > $_->{'sevHi'}) {
$_->{'sevHi'} = $ev->{'severity'};
}
} else {
$_->{'unknown'}++;
}
}
}
} else {
# New host
my $rec = undef;
push(@hostIdx, $ev->{'host'}); # Add host entry to index
$rec->{'host'} = $ev->{'host'};
$rec->{'startdate'} = $startDate;
#$rec->{'startdate'} = $ev->{'date'};
if ($endDate) {
$rec->{'enddate'} = $endDate;
} else {
$rec->{'enddate'} = time;
}
# Add stats to host entry
if ($ev->{'sev'} && $ev->{'sev'} ne "U") {
$rec->{'sevHi'} = $ev->{'sev'};
$rec->{'sevTotal'} = $ev->{'sev'};
$rec->{'sevNum'} = 1;
$rec->{'unknown'} = 0;
} else {
$rec->{'sevHi'} = 0;
$rec->{'sevTotal'} = 0;
$rec->{'sevNum'} = 0;
$rec->{'unknown'} = 1;
}
# Start sdmode stats
$rec->{'numPermits'} = 0;
$rec->{'numRejects'} = 0;
$rec->{'numAudits'} = 0;
$rec->{'numEvents'} = 1; # tally all events reported for host
if ($ev->{'sdmode'}) {
if ($ev->{'sdmode'} =~ /PERMIT/) { $rec->{'numPermits'}++; }
if ($ev->{'sdmode'} =~ /REJECT/) { $rec->{'numRejects'}++; }
if ($ev->{'sdmode'} =~ /AUDIT/) { $rec->{'numAudits'}++; }
}
push(@hostDb, $rec); # Add new records to host data list
}
} else {
next; # Missing host info -- big problem
}
} # END @eventDb loop
# Process simple REJECT-related stats (for Executive Security Summaries)
for (@hostDb) {
# In the end, we want this info:
# - Hostname, Startdate, Enddate, # Events, # Rejects, Ave. Severity, High Severity
if ($_->{'sevTotal'} > 0 && $_->{'sevNum'} > 0) {
$_->{'sevMean'} = round($_->{'sevTotal'} / $_->{'sevNum'});
} else {
$_->{'sevMean'} = 0;
}
# Convert dates
if ($_->{'startdate'} !~ /:/) {
$_->{'startdate'} = getDate($startDate);
}
if ($_->{'enddate'} !~ /:/) {
$_->{'enddate'} = getDate($_->{'enddate'});
}
# Delete stuff that we may use in later versions (YaST is a silly,
# silly data handler)
delete($_->{'sevTotal'});
delete($_->{'sevNum'});
delete($_->{'numPermits'});
delete($_->{'numAudits'});
delete($_->{'unknown'});
}
return (\@hostDb);
}
# special version of getEvents() for /usr/bin/reportgen.pl
sub grabEvents {
my ($rep, $start, $end) = @_;
my $db = undef;
my $prevDate = "0";
my $prevTime = "0";
my $query = "SELECT * FROM events ";
# Clear unnecessary filters
if ($rep->{'prog'}) { $rep->{'prog'} =~ s/\s+//g; }
if ($rep->{'prof'}) { $rep->{'prof'} =~ s/\s+//g; }
if ($rep->{'mode'}) { $rep->{'mode'} =~ s/\s+//g; }
if ($rep->{'sdmode'}) { $rep->{'sdmode'} =~ s/\s+//g; }
if ($rep->{'sev'}) { $rep->{'sev'} =~ s/\s+//g; }
if ($rep->{'res'}) { $rep->{'res'} =~ s/\s+//g; }
if ($rep->{'prog'} && ($rep->{'prog'} eq "-" || $rep->{'prog'} eq "All")) {
delete($rep->{'prog'});
}
if ($rep->{'prof'} && $rep->{'prof'} eq "-") { delete($rep->{'prof'}); }
if ($rep->{'pid'} && $rep->{'pid'} eq "-") { delete($rep->{'pid'}); }
if ($rep->{'sev'} && ($rep->{'sev'} eq "-" || $rep->{'sev'} eq "All")) {
delete($rep->{'sev'});
}
if ($rep->{'resource'} && $rep->{'resource'} eq "-") {
delete($rep->{'resource'});
}
if ($rep->{'mode'} && ($rep->{'mode'} eq "-" || $rep->{'mode'} eq "All")) {
delete($rep->{'mode'});
}
if ($rep->{'sdmode'}
&& ($rep->{'sdmode'} eq "-" || $rep->{'sdmode'} eq "All"))
{
delete($rep->{'sdmode'});
}
$rep = rewriteModes($rep);
# Set Dates far enough apart to get all entries (ie. no date filter)
my $startDate = '1104566401'; # Jan 1, 2005
my $endDate = time;
if ($start && $start > 0) { $startDate = $start; }
if (ref($rep)) {
my $midQuery = getQueryFilters($rep, $startDate, $endDate);
$query .= "$midQuery";
}
$db = getEvents($query, "$startDate", "$endDate");
return ($db);
}
sub getQueryFilters {
my ($filts, $start, $end) = @_;
my $query = undef;
my $wFlag = 0;
if ($filts) {
# Match any requested filters or drop record
############################################################
if ($filts->{'prog'}) {
$query .= "WHERE events.prog = \'$filts->{'prog'}\' ";
$wFlag = 1;
}
if ($filts->{'profile'} && $_->{'profile'}) {
if ($wFlag == 1) {
$query .= "AND events.profile = \'$filts->{'profile'}\' ";
} else {
$query .= "WHERE events.profile = \'$filts->{'profile'}\' ";
}
$wFlag = 1;
}
if ($filts->{'pid'}) {
if ($wFlag == 1) {
$query .= "AND events.pid = \'$filts->{'pid'}\' ";
} else {
$query .= "WHERE events.pid = \'$filts->{'pid'}\' ";
}
$wFlag = 1;
}
if ($filts->{'severity'}) {
if ($filts->{'severity'} eq "-" || $filts->{'severity'} eq "All") {
delete($filts->{'severity'});
} elsif ($filts->{'severity'} eq "-1"
|| $filts->{'severity'} eq "U")
{
if ($wFlag == 1) {
$query .= "AND events.severity = '-1' ";
} else {
$query .= "WHERE events.severity = '-1' ";
}
$wFlag = 1;
} else {
if ($wFlag == 1) {
$query .= "AND events.severity >= \'$filts->{'severity'}\' ";
} else {
$query .= "WHERE events.severity >= \'$filts->{'severity'}\' ";
}
$wFlag = 1;
}
}
if ($filts->{'resource'}) {
if ($wFlag == 1) {
$query .= "AND events.resource LIKE '%$filts->{'resource'}%' ";
} else {
$query .= "WHERE events.resource LIKE '%$filts->{'resource'}%' ";
}
$wFlag = 1;
}
if ($filts->{'mode'}) {
if ($wFlag == 1) {
$query .= "AND events.mode LIKE '%$filts->{'mode'}%' ";
} else {
$query .= "WHERE events.mode LIKE '%$filts->{'mode'}%' ";
}
$wFlag = 1;
}
if ($filts->{'sdmode'}) {
if ($filts->{'sdmode'} =~ /\|/) {
my @sdmunge = split(/\|/, $filts->{'sdmode'});
for (@sdmunge) { $_ = "\'\%" . "$_" . "\%\'"; }
$filts->{'sdmode'} = join(" OR events.sdmode LIKE ", @sdmunge);
} else {
$filts->{'sdmode'} = "\'\%" . "$filts->{'sdmode'}" . "\%\'";
}
if ($wFlag == 1) {
$query .= "AND events.sdmode LIKE $filts->{'sdmode'} ";
} else {
$query .= "WHERE events.sdmode LIKE $filts->{'sdmode'} ";
}
$wFlag = 1;
}
}
if ($start && $start =~ /\d+/ && $start > 0) {
if ($wFlag == 1) {
$query .= "AND events.time >= $start ";
} else {
$query .= "WHERE events.time >= $start ";
}
$wFlag = 1;
}
if ($end && $end =~ /\d+/ && $end > $start) {
if ($wFlag == 1) {
$query .= "AND events.time <= $end ";
} else {
$query .= "WHERE events.time <= $end ";
}
}
return $query;
}
sub getQuery {
my ($filts, $page, $sortKey, $numEvents) = @_;
if (!$page || $page < 1 || $page !~ /\d+/) { $page = 1; }
if (!$sortKey) { $sortKey = 'time'; }
if (!$numEvents) { $numEvents = '1000'; }
my $limit = (($page * $numEvents) - $numEvents);
my $query = "SELECT * FROM events ";
if ($filts) {
my $midQuery = getQueryFilters($filts);
$query .= "$midQuery";
}
# Finish query
$query .= "Order by $sortKey LIMIT $limit,$numEvents";
return $query;
}
# - This should exec AFTER the initial select (should limit the number of records
# that we'll be mangling
# - There may be a way to do this with a creative query statement generator
sub queryPostProcess {
my $db = shift;
my @newDb = ();
my $prevTime = 0;
my $prevDate = 0;
for (@$db) {
# Shuffle special events into appropriate column variables
############################################################
if ($_->{'attrch'}) { $_->{'sdmode'} .= " $_->{'attrch'}"; }
if ($_->{'type'}) {
if ($_->{'type'} eq 'control_variable') {
# OWLSM gets special treatment
if ($_->{'variable'} eq 'owlsm') {
#if ( $_->{'value'} ) {}
if ($_->{'value'} == '0') {
$_->{'resource'} = "GLOBAL MODULE CHANGE: OWLSM DISABLED";
} elsif ($_->{'value'} == '1') {
$_->{'resource'} = "GLOBAL MODULE CHANGE: OWLSM ENABLED";
} else {
$_->{'resource'} = "Unrecognized OWLSM activity.";
}
} else {
$_->{'resource'} = "$_->{'variable'}";
}
} elsif ($_->{'type'} eq 'capability') {
$_->{'resource'} .= " $_->{'capability'}";
} elsif ($_->{'type'} eq 'attribute_change') {
$_->{'sdmode'} .= " $_->{'attribute'} change";
} elsif ($_->{'type'} eq 'subdomain_insmod') {
$_->{'resource'} = "AppArmor Started";
} elsif ($_->{'type'} eq 'subdomain_rmmod') {
$_->{'resource'} = "AppArmor Stopped";
# DROP logprof-hints
} elsif ($_->{'type'} eq 'unknown_hat') {
next;
# DROP logprof-hints
} elsif ($_->{'type'} eq 'changing_profile') {
next;
# DROP logprof-hints
} elsif ($_->{'type'} eq 'fork') {
next;
} elsif ($_->{'type'} ne 'path') {
$_->{'resource'} .= " $_->{'type'}";
}
}
# Convert Epoch Time to Date
if ($_->{'time'} && $_->{'time'} == $prevTime) {
$_->{'date'} = $prevDate;
} elsif ($_->{'time'}) {
my $newDate = getDate("$_->{'time'}");
$_->{'date'} = $newDate;
$prevDate = $newDate;
$prevTime = $_->{'time'};
} else {
$_->{'date'} = "0000-00-00 00:00:00";
}
# $_->{'time'} = undef; # Don't need 'time', only 'date'
if (!$_->{'host'}) { $_->{'host'} = "-"; }
if (!$_->{'date'}) { $_->{'date'} = "-"; }
if (!$_->{'prog'}) { $_->{'prog'} = "-"; }
if (!$_->{'profile'}) { $_->{'profile'} = "-"; }
if (!$_->{'pid'}) { $_->{'pid'} = "-"; }
if (!$_->{'mode'}) { $_->{'mode'} = "-"; }
if (!$_->{'resource'}) { $_->{'resource'} = "-"; }
if (!$_->{'sdmode'}) { $_->{'sdmode'} = "-"; }
if (!$_->{'severity'}) {
$_->{'severity'} = "-";
} elsif ($_->{'severity'} eq "-1") {
$_->{'severity'} = "U";
} # else {
# $_->{'severity'} = sprintf("%02d", $_->{'severity'});
#}
push(@newDb, $_); # Don't quote the $_ (breaks hash)
}
return \@newDb;
}
# Creates single hashref for the various filters
sub setFormFilters {
my $args = shift;
my $filts = undef;
if ($args) {
if ($args->{'prog'}) { $filts->{'prog'} = $args->{'prog'}; }
if ($args->{'profile'}) { $filts->{'profile'} = $args->{'profile'}; }
if ($args->{'pid'}) { $filts->{'pid'} = $args->{'pid'}; }
if ($args->{'resource'}) { $filts->{'resource'} = $args->{'resource'}; }
if ($args->{'severity'}) { $filts->{'severity'} = $args->{'severity'}; }
if ($args->{'sdmode'}) { $filts->{'sdmode'} = $args->{'sdmode'}; }
if ($args->{'mode'}) { $filts->{'mode'} = $args->{'mode'}; }
}
return $filts;
}
# helper for getSirFilters()
# Makes gui-centric filters querying-friendly
sub rewriteFilters {
my $filts = shift;
# Clear unnecessary filters
for (keys(%$filts)) {
if ($filts->{$_} eq "All") { delete($filts->{$_}); }
}
if ($filts->{'prog'}
&& ($filts->{'prog'} eq "-" || $filts->{'prog'} eq "All"))
{
delete($filts->{'prog'});
}
if ($filts->{'profile'} && ($filts->{'profile'} eq "-")) {
delete($filts->{'profile'});
}
if ($filts->{'pid'} && ($filts->{'pid'} eq "-")) {
delete($filts->{'pid'});
}
if ($filts->{'severity'} && ($filts->{'severity'} eq "-")) {
delete($filts->{'severity'});
}
if ($filts->{'resource'} && ($filts->{'resource'} eq "-")) {
delete($filts->{'resource'});
}
if ($filts->{'mode'}
&& ($filts->{'mode'} eq "-" || $filts->{'mode'} eq "All"))
{
delete($filts->{'mode'});
}
if ($filts->{'sdmode'}
&& ($filts->{'sdmode'} eq "-" || $filts->{'sdmode'} eq "All"))
{
delete($filts->{'sdmode'});
}
############################################################
$filts = rewriteModes($filts);
return $filts;
}
# returns ref to active filters for the specific SIR report
sub getSirFilters {
my $args = shift;
my $repName = undef;
if ($args && $args->{'name'}) {
$repName = $args->{'name'};
} else {
$repName = "Security.Incident.Report";
}
my $repConf = '/etc/apparmor/reports.conf';
my $rec = undef;
my $filts = getXmlReport($repName);
# Clean hash of useless refs
for (sort keys(%$filts)) {
if ($filts->{$_} eq "-") {
delete($filts->{$_});
}
}
# remove non-filter info
if ($filts->{'name'}) { delete($filts->{'name'}); }
if ($filts->{'exportpath'}) { delete($filts->{'exportpath'}); }
if ($filts->{'exporttype'}) { delete($filts->{'exporttype'}); }
if ($filts->{'addr1'}) { delete($filts->{'addr1'}); }
if ($filts->{'addr2'}) { delete($filts->{'addr2'}); }
if ($filts->{'addr3'}) { delete($filts->{'addr3'}); }
if ($filts->{'time'}) { delete($filts->{'time'}); }
if (!$args->{'gui'} || $args->{'gui'} ne "1") {
$filts = rewriteModes($filts);
$filts = rewriteFilters($filts);
}
return $filts;
}
# deprecated (pre-xml)
sub OldgetSirFilters {
my $args = shift;
my $repName = undef;
if ($args && $args->{'name'}) {
$repName = $args->{'name'};
}
my $repConf = '/etc/apparmor/reports.conf';
my $rec = undef;
if (!$repName) {
$repName = "\"Security.Incident.Report\"";
} else {
$repName = "\"$repName\"";
}
if (open(CF, "<$repConf")) {
while (<CF>) {
next if /^#/;
chomp;
my ($cfRptName) = (split(/:/, $_))[0];
$cfRptName =~ s/\s+$//; # remove trailing spaces
next unless ($cfRptName eq "$repName");
# Name : csv.html : prog, prof, pid, res, sev, sdmode, mode : (up to 3) email addresses : last run time
my ($name, $info) = split(/:/, $_, 2);
$info =~ s/\s+//g;
$name =~ s/^\s+//;
$name =~ s/\s+$//;
my ($mailtype, $filters, $email, $lastRun) =
split(/\s*:\s*/, $info, 4);
$rec->{'name'} = $name;
$rec->{'name'} =~ s/\"//g;
($rec->{'prog'}, $rec->{'profile'}, $rec->{'pid'}, $rec->{'resource'}, $rec->{'severity'}, $rec->{'sdmode'}, $rec->{'mode'}) =
split(/\,/, $filters, 7);
}
close CF;
} else {
logError("Couldn't open $repConf. No filters will be used in report generation.");
return;
}
# Clean hash of useless refs
for (sort keys(%$rec)) {
if ($rec->{$_} eq "-") {
delete($rec->{$_});
}
}
$rec = rewriteModes($rec);
if (!$args->{'gui'} || $args->{'gui'} ne "1") {
$rec = rewriteFilters($rec);
}
return $rec;
}
# Main SIR report generator
sub getEvents {
my ($query, $start, $end, $dbFile) = @_;
my @events = ();
my $prevTime = 0;
my $prevDate = '0';
if (!$query || $query !~ /^SELECT/) { $query = "SELECT * FROM events"; }
if ($dbFile && -f $dbFile) { $eventDb = $dbFile; }
my $hostName = `/bin/hostname` || 'unknown';
chomp $hostName unless $hostName eq 'unknown';
if (!$start) { $start = '1104566401'; } # Give default start of 1/1/2005
if (!$end) { $end = time; }
# make sure they don't give us a bad range
($start, $end) = ($end, $start) if $start > $end;
# Events Schema
# - (id,time,counter,pid,sdmode,type,mode,resource,target,profile,prog,severity);
# Pull stuff from db
my $dbh = DBI->connect("dbi:SQLite:dbname=$eventDb", "", "", { RaiseError => 1, AutoCommit => 1 });
my $all = undef;
eval { $all = $dbh->selectall_arrayref("$query"); };
if ($@) {
ycp::y2error(sprintf(gettext("DBI Execution failed: %s."), $DBI::errstr));
return;
}
$dbh->disconnect();
for my $row (@$all) {
my $rec = undef;
($rec->{'id'}, $rec->{'time'}, $rec->{'counter'}, $rec->{'pid'}, $rec->{'sdmode'}, $rec->{'type'}, $rec->{'mode'}, $rec->{'resource'}, $rec->{'target'}, $rec->{'profile'}, $rec->{'prog'}, $rec->{'severity'}) = @$row;
# Give empty record values a default value
if (!$rec->{'host'}) { $rec->{'host'} = $hostName; }
for (keys(%$rec)) {
if (!$rec->{$_}) { $rec->{$_} = '-'; }
}
# Change 'time' to date
if ($rec->{'time'} && $rec->{'time'} == $prevTime) {
$rec->{'date'} = $prevDate;
} elsif ($rec->{'time'}) {
my $newDate = getDate("$rec->{'time'}");
$rec->{'date'} = $newDate;
$prevDate = $newDate;
$prevTime = $rec->{'time'};
} else {
$rec->{'date'} = "0000-00-00-00:00:00";
}
if ($rec->{'severity'} && $rec->{'severity'} eq '-1') {
$rec->{'severity'} = 'U';
}
delete($rec->{'time'});
delete($rec->{'counter'});
push(@events, $rec);
}
return \@events;
}
# Archived Reports Stuff -- Some of this would go away in an ideal world
################################################################################
sub getArchReport {
my $args = shift;
my @rec = ();
my $eventRep = "/var/log/apparmor/reports/events.rpt";
#if ( $args->{'type'} && $args->{'type'} eq 'archRep' ) {}
if ($args->{'logFile'}) {
$eventRep = $args->{'logFile'};
}
if (open(REP, "<$eventRep")) {
my $page = 1;
if ($args->{'page'}) { $page = $args->{'page'}; }
my $id = 1;
my $slurp = 0;
#my $numPages = 0;
my $prevTime = undef;
my $prevDate = undef;
while (<REP>) {
my $db = ();
# Why not get rid of page and just do divide by $i later?
if (/Page/) {
# $numPages++;
chomp;
if ($_ eq "Page $page") {
$slurp = 1;
} else {
$slurp = 0;
}
} elsif ($slurp == 1) {
chomp;
($db->{'host'}, $db->{'time'}, $db->{'prog'}, $db->{'profile'}, $db->{'pid'}, $db->{'severity'}, $db->{'mode'}, $db->{'denyRes'}, $db->{'sdmode'}) = split(/\,/, $_);
# Convert epoch time to date
if ($db->{'time'} == $prevTime) {
$db->{'date'} = $prevDate;
} else {
$prevTime = $db->{'time'};
$prevDate = getDate("$db->{'time'}");
$db->{'date'} = $prevDate;
}
$id++;
$db->{'date'} = $db->{'time'};
delete $db->{'time'};
push(@rec, $db);
}
}
close REP;
} else {
ycp::y2error(sprintf(gettext("Fatal Error. getArchReport() couldn't open %s"), $eventRep));
return ("Couldn't open $eventRep");
}
return (\@rec);
}
sub writeEventReport {
my ($db, $args) = @_; # Filters for date, && regexp
# my $type = shift || undef;
my $eventRep = "/var/log/apparmor/reports/events.rpt";
# Not sure if this is needed anymore, but it messes up archived SIR reports
# if ( $args->{'logFile'} ) { $eventRep = $args->{'logFile'}; }
if (open(REP, ">$eventRep")) {
my $i = 1;
my $page = 1;
my $skip = 0;
# Title for scheduled reports
if ($args->{'title'}) { print REP "$args->{'title'}"; }
print REP "Page $page\n";
$page++;
for (@$db) {
print REP "$_->{'host'},$_->{'date'},$_->{'prog'},$_->{'profile'},$_->{'pid'},$_->{'severity'},$_->{'mode'},$_->{'resource'},$_->{'sdmode'}\n";
if (($i % $numEvents) == 0 && $skip == 0) {
print REP "Page $page\n";
$page++;
$skip = 1;
} else {
$i++;
$skip = 0;
}
}
close REP;
} else {
return ("Couldn't open $eventRep");
}
return 0;
}
sub prepSingleLog {
my $args = shift;
my $dir = '/var/log/apparmor/reports-archived';
my $error = "0";
my @errors = (); # For non-fatal errors
my @repList = ();
my $readFile = "";
my $eventRep = "/var/log/apparmor/reports/all-reports.rpt"; # write summary to this file - changed 04-14-2005
#my $eventRep = "/tmp/events.rpt"; # write summary to this file
if ($args->{'logFile'}) { $readFile = $args->{'logFile'}; }
if ($args->{'repPath'}) { $dir = $args->{'repPath'}; }
my @rawDb = ();
my $numPages = 1;
my $numRecords = 1;
my $skip = 0;
# Open record compilation file
if (open(RREP, "<$dir/$readFile")) {
if (open(WREP, ">$eventRep")) {
# print WREP "Page $numPages\n";
$numPages++;
while (<RREP>) {
next if (/Page/);
next if /^#/;
print WREP "$_";
if (($numRecords % $numEvents) == 0 && $skip == 0) {
print WREP "Page $numPages\n";
$numPages++;
$skip = 1;
} else {
$numRecords++;
$skip = 0;
}
}
close WREP;
} else {
$error = "Problem in prepSingleLog() - couldn't open $eventRep.";
return $error;
}
close RREP;
} else {
$error = "Problem in prepSingleLog() - couldn't open -$dir/$readFile-.";
return $error;
}
return $error;
}
# Cats files in specified directory for easier parsing
sub prepArchivedLogs {
my $args = shift;
my $dir = '/var/log/apparmor/reports-archived';
my $error = "0";
my @errors = (); # For non-fatal errors
my @repList = ();
my @db = ();
my $eventRep = "/var/log/apparmor/reports/all-reports.rpt";
my $useFilters = 0;
if ($args->{'logFile'}) {
$eventRep = $args->{'logFile'};
}
if ($args->{'repPath'}) {
$dir = $args->{'repPath'};
}
# Check to see if we need to use filters
if ($args->{'mode'}
&& ($args->{'mode'} =~ /All/ || $args->{'mode'} =~ /^\s*-\s*$/))
{
delete($args->{'mode'});
}
if ($args->{'sdmode'}
&& ($args->{'sdmode'} =~ /All/ || $args->{'sdmode'} =~ /^\s*-\s*$/))
{
delete($args->{'sdmode'});
}
if ($args->{'resource'}
&& ($args->{'resource'} =~ /All/ || $args->{'resource'} =~ /^\s*-\s*$/))
{
delete($args->{'resource'});
}
if ($args->{'sevLevel'}
&& ($args->{'sevLevel'} =~ /All/ || $args->{'sevLevel'} =~ /^\s*-\s*$/))
{
delete($args->{'sevLevel'});
}
if ( $args->{'prog'}
|| $args->{'profile'}
|| $args->{'pid'}
|| $args->{'denyRes'}
|| $args->{'mode'}
|| $args->{'sdmode'}
|| ($args->{'startdate'} && $args->{'enddate'}))
{
$useFilters = 1;
}
############################################################
# Get list of files in archived report directory
if (opendir(RDIR, $dir)) {
my @firstPass = grep(/csv/, readdir(RDIR));
@repList =
grep(!/Applications.Audit|Executive.Security.Summary/, @firstPass);
close RDIR;
} else {
$error = "Failure in prepArchivedLogs() - couldn't open $dir.";
return ($error); # debug - exit instead?
}
my @rawDb = ();
my $numPages = 1;
my $numRecords = 1;
# Open record compilation file
if (open(AREP, ">$eventRep")) {
for (@repList) {
my $file = $_;
# Cycle through each $file in $dir
if (open(RPT, "<$dir/$file")) {
push(@rawDb, <RPT>);
close RPT;
} else {
$error = "Problem in prepArchivedLogs() - couldn't open $dir/$file.";
push(@errors, $error);
}
}
# sort & store cat'd files
if (@rawDb > 0) {
# Run Filters
if ($useFilters == 1) {
my @tmpDb = parseMultiDb($args, @rawDb);
@db = sort(@tmpDb);
} else {
@db = sort(@rawDb);
}
my $skip = 0;
print AREP "Page $numPages\n";
$numPages++;
for (@db) {
next if /^Page/;
next if /^#/;
print AREP "$_";
if (($numRecords % $numEvents) == 0 && $skip == 0) {
print AREP "Page $numPages\n";
$numPages++;
$skip = 1;
} else {
$numRecords++;
$skip = 0;
}
}
} else {
$error = "DB created from $dir is empty.";
}
close AREP;
} else {
$error = "Problem in prepArchivedLogs() - couldn't open $eventRep.";
push(@errors, $error);
}
return $error;
}
# Similar to parseLog(), but expects @db to be passed
sub parseMultiDb {
my ($args, @db) = @_;
my @newDb = ();
my $error = undef;
my $startDate = undef;
my $endDate = undef;
# deref dates for speed
if ($args->{'startdate'} && $args->{'enddate'}) {
$startDate = getEpochFromNum("$args->{'startdate'}", 'start');
$endDate = getEpochFromNum("$args->{'enddate'}", 'end');
}
$args = rewriteModes($args);
for (@db) {
my $rec = undef;
my $line = $_;
next if /true|false/; # avoid horrible yast bug
next if /^Page/;
next if /^#/;
chomp;
next if (!$_ || $_ eq "");
# Lazy filters -- maybe these should be with the rest below
if ($args->{'prog'}) { next unless /$args->{'prog'}/; }
if ($args->{'profile'}) { next unless /$args->{'profile'}/; }
# Need (epoch) 'time' element here, do we want to store 'date' instead?
($rec->{'host'}, $rec->{'time'}, $rec->{'prog'}, $rec->{'profile'}, $rec->{'pid'}, $rec->{'sevLevel'}, $rec->{'mode'}, $rec->{'resource'}, $rec->{'sdmode'}) = split(/\,/, $_);
# Make sure we get the time/date ref. name right. If it's $args->"time",
# the arg will be converted to a human-friendly "date" ref in writeEventReport().
if ($rec->{'time'} =~ /\:|\-/) {
$rec->{'date'} = $rec->{'time'};
delete $rec->{'time'};
}
# Check filters
if ($args->{'pid'} && $args->{'pid'} ne '-') {
next unless ($args->{'pid'} eq $rec->{'pid'});
}
if ( $args->{'sevLevel'}
&& $args->{'sevLevel'} ne "00"
&& $args->{'sevLevel'} ne '-')
{
if ($args->{'sevLevel'} eq "U") { $args->{'sevLevel'} = '-1'; }
next unless ($args->{'sevLevel'} eq $rec->{'sevLevel'});
}
if ($args->{'mode'} && $args->{'mode'} ne '-') {
next unless ($args->{'mode'} eq $rec->{'mode'});
}
if ($args->{'denyRes'} && $args->{'denyRes'} ne '-') {
next unless ($args->{'denyRes'} eq $rec->{'denyRes'});
}
if ($args->{'sdmode'} && $args->{'sdmode'} ne '-') {
# Needs reversal of comparison for sdmode
next unless ($rec->{'sdmode'} =~ /$args->{'sdmode'}/);
}
push(@newDb, $line);
}
return @newDb;
}
# Grab & filter events from archived reports (.csv files)
sub parseLog {
my $args = shift;
my @db = ();
my $eventRep = "/var/log/apparmor/reports/events.rpt";
if ($args->{'logFile'}) {
$eventRep = $args->{'logFile'};
}
#my $id = keys(%$db);
#my $rec = undef;
my $error = undef;
my $startDate = undef;
my $endDate = undef;
# deref dates for speed
if ($args->{'startdate'} && $args->{'enddate'}) {
$startDate = getEpochFromNum("$args->{'startdate'}", 'start');
$endDate = getEpochFromNum("$args->{'enddate'}", 'end');
}
#if ( $args->{'mode'} && ( $args->{'mode'} =~ /All/ || $args->{'mode'} =~ /\s*\-\s*/) ) {}
if ($args->{'mode'}
&& ($args->{'mode'} =~ /All/ || $args->{'mode'} =~ /^\s*-\s*$/))
{
delete($args->{'mode'});
}
if ($args->{'sdmode'}
&& ($args->{'sdmode'} =~ /All/ || $args->{'sdmode'} =~ /^\s*-\s*$/))
{
delete($args->{'sdmode'});
}
if ($args->{'resource'}
&& ($args->{'resource'} =~ /All/ || $args->{'resource'} =~ /^\s*-\s*$/))
{
delete($args->{'resource'});
}
if ($args->{'sevLevel'}
&& ($args->{'sevLevel'} =~ /All/ || $args->{'sevLevel'} =~ /^\s*-\s*$/))
{
delete($args->{'sevLevel'});
}
$args = rewriteModes($args);
if (open(LOG, "<$eventRep")) {
# Log Parsing
while (<LOG>) {
my $rec = undef;
next if /true|false/; # avoid horrible yast bug
next if /Page/;
next if /^#/;
chomp;
next if (!$_ || $_ eq "");
# Lazy filters -- maybe these should be with the rest below
if ($args->{'prog'}) { next unless /$args->{'prog'}/; }
if ($args->{'profile'}) { next unless /$args->{'profile'}/; }
# Need (epoch) 'time' element here, do we want to store 'date' instead?
($rec->{'host'}, $rec->{'time'}, $rec->{'prog'}, $rec->{'profile'}, $rec->{'pid'}, $rec->{'sevLevel'}, $rec->{'mode'}, $rec->{'resource'}, $rec->{'sdmode'}) = split(/\,/, $_);
# Make sure we get the time/date ref. name right. If it's $args->"time",
# the arg will be converted to a human-friendly "date" ref in writeEventReport().
if ($rec->{'time'} =~ /\:|\-/) {
$rec->{'date'} = $rec->{'time'};
delete $rec->{'time'};
}
# Check filters
if ($args->{'pid'} && $args->{'pid'} ne '-') {
next unless ($args->{'pid'} eq $rec->{'pid'});
}
if ( $args->{'sevLevel'}
&& $args->{'sevLevel'} ne "00"
&& $args->{'sevLevel'} ne '-')
{
next unless ($args->{'sevLevel'} eq $rec->{'sevLevel'});
}
if ($args->{'mode'} && $args->{'mode'} ne '-') {
next unless ($args->{'mode'} eq $rec->{'mode'});
}
if ($args->{'denyRes'} && $args->{'denyRes'} ne '-') {
next unless ($args->{'denyRes'} eq $rec->{'denyRes'});
}
if ($args->{'sdmode'} && $args->{'sdmode'} ne '-') {
# Needs reversal of comparison for sdmode
next unless ($rec->{'sdmode'} =~ /$args->{'sdmode'}/);
}
push(@db, $rec);
}
close LOG;
# Export results to file if requested
if ($args->{'exporttext'} || $args->{'exporthtml'}) {
my $rawLog = undef;
my $expLog = undef;
if ($args->{'exportPath'}) {
$rawLog = $args->{'exportPath'} . '/export-log';
} else {
$rawLog = '/var/log/apparmor/reports-exported/export-log';
}
if ($args->{'exporttext'} && $args->{'exporttext'} eq 'true') {
$expLog = "$rawLog.csv";
exportLog($expLog, \@db); # redo w/ @$db instead of %db?
}
if ($args->{'exporthtml'} && $args->{'exporthtml'} eq 'true') {
$expLog = "$rawLog.html";
exportLog($expLog, \@db); # redo w/ @$db instead of %db?
}
}
# write out files to single sorted file (for state, and to speed up yast)
#if (! $args->{'single'} ) {
# $error = writeEventReport(\@db, $args);
#}
# changed 04-13-05 - should probably do this, regardless
$error = writeEventReport(\@db, $args);
} else {
$error = "Couldn't open $eventRep.";
}
return $error;
}
# OLD STUFF -- delete
# deprecated -- replaced by better SQL queries
sub OLDgetEssStats {
my $args = shift;
my $prevTime = '0';
my $prevDate = '0';
my $startDate = '1104566401'; # Jan 1, 2005
my $endDate = time;
if ($args->{'startdate'}) { $startDate = $args->{'startdate'}; }
if ($args->{'enddate'}) { $endDate = $args->{'enddate'}; }
my $query = "SELECT * FROM events";
# hostIp, startDate, endDate, sevHi, sevMean, numRejects
my $eventDb = getEvents($query, "", "$startDate", "$endDate");
my @hostIdx = (); # Simple index to all hosts for quick host matching
my @hostDb = (); # Host-keyed data for doing REJECT stats
# Outer Loop for Raw Event db
for (@$eventDb) {
my $ev = $_; # current event record
if ($ev->{'host'}) {
# Create new host entry, or add to existing
if (grep(/$ev->{'host'}/, @hostIdx) == 1) {
# Inner loop, but the number of hosts should be small
for my $hdb (@hostDb) {
if ($hdb->{'host'} eq $ev->{'host'}) {
if ($hdb->{'startdate'} gt $ev->{'date'}) {
$hdb->{'startdate'} = $ev->{'date'}; # Find earliest start date
}
$hdb->{'numEvents'}++; # tally all events reported for host
if ($ev->{'sdmode'}) {
if ($ev->{'sdmode'} =~ /PERMIT/) {
$hdb->{'numPermits'}++;
}
if ($ev->{'sdmode'} =~ /REJECT/) {
$hdb->{'numRejects'}++;
}
if ($ev->{'sdmode'} =~ /AUDIT/) {
$hdb->{'numAudits'}++;
}
}
# Add stats to host entry
#if ( $ev->{'severity'} && $ev->{'severity'} =~ /\b\d+\b/ ) {}
if ($ev->{'severity'} && $ev->{'severity'} != -1) {
$hdb->{'sevNum'}++;
$hdb->{'sevTotal'} = $hdb->{'sevTotal'} + $ev->{'severity'};
if ($ev->{'severity'} > $hdb->{'sevHi'}) {
$hdb->{'sevHi'} = $ev->{'severity'};
}
} else {
$hdb->{'unknown'}++;
}
}
}
} else {
# New host
my $rec = undef;
push(@hostIdx, $ev->{'host'}); # Add host entry to index
$rec->{'host'} = $ev->{'host'};
$rec->{'startdate'} = $startDate;
#$rec->{'startdate'} = $ev->{'date'};
if ($endDate) {
$rec->{'enddate'} = $endDate;
} else {
$rec->{'enddate'} = time;
}
# Add stats to host entry
if ($ev->{'sev'} && $ev->{'sev'} ne "U") {
$rec->{'sevHi'} = $ev->{'sev'};
$rec->{'sevTotal'} = $ev->{'sev'};
$rec->{'sevNum'} = 1;
$rec->{'unknown'} = 0;
} else {
$rec->{'sevHi'} = 0;
$rec->{'sevTotal'} = 0;
$rec->{'sevNum'} = 0;
$rec->{'unknown'} = 1;
}
# Start sdmode stats
$rec->{'numPermits'} = 0;
$rec->{'numRejects'} = 0;
$rec->{'numAudits'} = 0;
$rec->{'numEvents'} = 1; # tally all events reported for host
if ($ev->{'sdmode'}) {
if ($ev->{'sdmode'} =~ /PERMIT/) { $rec->{'numPermits'}++; }
if ($ev->{'sdmode'} =~ /REJECT/) { $rec->{'numRejects'}++; }
if ($ev->{'sdmode'} =~ /AUDIT/) { $rec->{'numAudits'}++; }
}
push(@hostDb, $rec); # Add new records to host data list
}
} else {
next; # Missing host info -- big problem
}
} # END @eventDb loop
# Process simple REJECT-related stats (for Executive Security Summaries)
for (@hostDb) {
# In the end, we want this info:
# - Hostname, Startdate, Enddate, # Events, # Rejects, Ave. Severity, High Severity
if ($_->{'sevTotal'} > 0 && $_->{'sevNum'} > 0) {
$_->{'sevMean'} = Immunix::Reports::round($_->{'sevTotal'} / $_->{'sevNum'});
} else {
$_->{'sevMean'} = 0;
}
# Convert dates
if ($_->{'startdate'} !~ /:/) {
$_->{'startdate'} = Immunix::Reports::getDate($startDate);
}
if ($_->{'enddate'} !~ /:/) {
$_->{'enddate'} = Immunix::Reports::getDate($_->{'enddate'});
}
# Delete stuff that we may use in later versions (YaST is a silly, silly data handler)
delete($_->{'sevTotal'});
delete($_->{'sevNum'});
delete($_->{'numPermits'});
delete($_->{'numAudits'});
delete($_->{'unknown'});
}
return (\@hostDb);
}
1;