apparmor/utils/Reports.pm
Jesse Michael d8ae032328 used perltidy to clean up the formatting for the perl scripts in the
utils package and manually fixed some places where perltidy's
reformatting made it harder to read.  the options used were--

-i=4    # 4-space indentation
-l=0    # unlimited line length (for now)
-pt=2   # slightly tightened parens
-ce     # cuddled elses
-nolq   # don't outdent long quotes
-nsfs   # don't add spaces in front of semi-colons in for ( ) statements
-isbc   # only indent block comments that have whitespace in front of them
-otr    # don't place a break between a comma and an opening brace

the code will be refactored to make it possible to switch to using 
80-column line-breaks without resorting to really nasty formatting 
constructs.
2007-03-20 21:58:38 +00:00

2261 lines
65 KiB
Perl
Executable file

# $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;