mirror of
https://gitlab.com/apparmor/apparmor.git
synced 2025-03-05 17:01:00 +01:00

perl utilities to the deprecated to directory; a couple of perl utilities remain, but they are still useful and do not depend on the Immunix module (just the LibAppArmor perl module).
2024 lines
57 KiB
Perl
Executable file
2024 lines
57 KiB
Perl
Executable file
# ------------------------------------------------------------------
|
|
#
|
|
# 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 AppArmor 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 YcpDebug ($$) {
|
|
|
|
my $argList = "";
|
|
#my ($script, $args) = @_;
|
|
my $script = shift;
|
|
my $args = shift;
|
|
|
|
if ($args && ref($args) eq "HASH") {
|
|
|
|
for (sort keys(%$args) ) {
|
|
$argList .= "$_ is ..$args->{$_}.., " if $args->{$_};
|
|
}
|
|
|
|
} elsif ($args && ref($args) eq "ARRAY") {
|
|
|
|
for my $row (@$args) {
|
|
for (sort keys(%$row) ) {
|
|
$argList .= "$_ is ..$row->{$_}.., " if $row->{$_};
|
|
}
|
|
}
|
|
} elsif ( $args ) {
|
|
$argList = $args;
|
|
} else {
|
|
my $prob = ref($args);
|
|
$argList = "Type not supported for printing debug: $prob";
|
|
}
|
|
|
|
ycp::y2milestone("[apparmor $script] vars: $argList");
|
|
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
sub getNetSockList {
|
|
|
|
my @netsockList = (
|
|
"All", "UNIX Domain Sockets", "IP v4", "Radio AX.25",
|
|
"Novell IPX", "Appletalk", "Radio NET/ROM",
|
|
"Multiprotocol Bridge", "ATM PVC", "X.25", "IP v6",
|
|
"Radio X.25 PLP", "DECnet", "NetBEUI", "Security Callback",
|
|
"PF Key Management", "Netlink", "Packet", "Ash",
|
|
"Econet", "ATM SVC", "Filler (Ignore)", "Linux SNA",
|
|
"IRDA", "PPPoX", "Wanpipe", "Linux LLC",
|
|
"Filler (Ignore)", "Filler (Ignore)", "Filler (Ignore)", "TIPC",
|
|
"Bluetooth", "IUCV", "RxRPC"
|
|
);
|
|
|
|
|
|
return \@netsockList;
|
|
}
|
|
|
|
sub netsock_name2num {
|
|
|
|
my $sockName = shift;
|
|
my $sockNum = '';
|
|
my $netsockList = getNetSockList();
|
|
|
|
my $i;
|
|
|
|
for ($i = 0; $i < @$netsockList; $i++) {
|
|
last if $_ eq @$netsockList[$i];
|
|
}
|
|
|
|
if ($i < @$netsockList) {
|
|
$sockNum = $i;
|
|
}
|
|
|
|
return $sockNum;
|
|
}
|
|
|
|
sub netsock_num2name {
|
|
|
|
my $sockNum = shift;
|
|
my $sockName = undef;
|
|
my $netsockList = getNetSockList();
|
|
|
|
if ( $sockNum < @$netsockList) {
|
|
$sockName = @$netsockList[$sockNum];
|
|
}
|
|
|
|
if ( $sockName eq "Filler (Ignore)" ) {
|
|
$sockName = undef;
|
|
}
|
|
|
|
return $sockName;
|
|
}
|
|
|
|
sub matchFailed ($$) {
|
|
|
|
my $args = shift;
|
|
my $rec = shift;
|
|
|
|
# Check filters
|
|
if ($args->{'pid'} && $args->{'pid'} ne '-') {
|
|
return(1) unless ($args->{'pid'} eq $rec->{'pid'});
|
|
}
|
|
if ( $args->{'severity'}
|
|
&& $args->{'severity'} ne "00"
|
|
&& $args->{'severity'} ne '-')
|
|
{
|
|
if ($args->{'severity'} eq "U") { $args->{'severity'} = '-1'; }
|
|
return(1) unless ($args->{'severity'} eq $rec->{'severity'});
|
|
}
|
|
if ($args->{'mode_deny'} && $args->{'mode_deny'} ne '-') {
|
|
return(1) unless ($args->{'mode_deny'} eq $rec->{'mode_deny'});
|
|
}
|
|
if ($args->{'mode_req'} && $args->{'mode_req'} ne '-') {
|
|
return(1) unless ($args->{'mode_req'} eq $rec->{'mode_req'});
|
|
}
|
|
|
|
if ($args->{'resource'} && $args->{'resource'} ne '-') {
|
|
return(1) unless ($args->{'resource'} eq $rec->{'resource'});
|
|
}
|
|
if ($args->{'sdmode'} && $args->{'sdmode'} ne '-') {
|
|
# Needs reversal of comparison for sdmode
|
|
return(1) unless ($rec->{'sdmode'} =~ /$args->{'sdmode'}/);
|
|
}
|
|
if ($args->{'op'} && $args->{'op'} ne '-') {
|
|
return(1) unless ($args->{'op'} eq $rec->{'op'});
|
|
}
|
|
if ($args->{'attr'} && $args->{'attr'} ne '-') {
|
|
return(1) unless ($args->{'attr'} eq $rec->{'attr'});
|
|
}
|
|
if ($args->{'name_alt'} && $args->{'name_alt'} ne '-') {
|
|
return(1) unless ($args->{'name_alt'} eq $rec->{'name_alt'});
|
|
}
|
|
if ($args->{'net_family'} && $args->{'net_family'} ne '-') {
|
|
return(1) unless ($args->{'net_family'} eq $rec->{'net_family'});
|
|
}
|
|
if ($args->{'net_proto'} && $args->{'net_proto'} ne '-') {
|
|
return(1) unless ($args->{'net_proto'} eq $rec->{'net_proto'});
|
|
}
|
|
if ($args->{'net_socktype'} && $args->{'net_socktype'} ne '-') {
|
|
return(1) unless ($args->{'net_socktype'} eq $rec->{'net_socktype'});
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
# Translate mode & sdmode for parsing
|
|
sub rewriteModes {
|
|
my $filts = shift;
|
|
|
|
# Mode wrangling - Rewrite for better matches
|
|
for ('mode_req','mode_deny') {
|
|
|
|
if ($filts->{$_} && $filts->{$_} ne "All") {
|
|
|
|
my @mode = ();
|
|
my $tmpMode = undef;
|
|
|
|
@mode = split(//, $filts->{$_});
|
|
|
|
if (@mode > 0) {
|
|
#$tmpMode = join("|", @mode);
|
|
$tmpMode = join("", @mode);
|
|
if ($tmpMode =~ /m/) {
|
|
$tmpMode =~ s/m//g;
|
|
$tmpMode = "m" . $tmpMode;
|
|
}
|
|
} else {
|
|
delete($filts->{$_});
|
|
}
|
|
|
|
if ($tmpMode) {
|
|
$filts->{$_} = $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 getFilterList ($) {
|
|
|
|
my $args = shift;
|
|
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_req'}) { $filts->{'mode_req'} = $args->{'mode_req'}; }
|
|
if ($args->{'mode_deny'}) { $filts->{'mode_deny'} = $args->{'mode_deny'}; }
|
|
if ($args->{'op'}) { $filts->{'op'} = $args->{'op'}; }
|
|
if ($args->{'attr'}) { $filts->{'attr'} = $args->{'attr'}; }
|
|
if ($args->{'name_alt'}) { $filts->{'name_alt'} = $args->{'name_alt'}; }
|
|
if ($args->{'net_family'}) { $filts->{'net_family'} = $args->{'net_family'}; }
|
|
if ($args->{'net_proto'}) { $filts->{'net_proto'} = $args->{'net_proto'}; }
|
|
if ($args->{'net_socktype'}) { $filts->{'net_socktype'} = $args->{'net_socktype'}; }
|
|
|
|
for (sort(keys(%$filts))) {
|
|
if ($filts->{$_} eq '-' || $filts->{$_} eq 'All') {
|
|
delete($filts->{$_});
|
|
}
|
|
}
|
|
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_req'}) {
|
|
$args->{'mode_req'} =~ s/\&//g;
|
|
$args->{'mode_req'} =~ s/Mode\://g;
|
|
$args->{'mode_req'} =~ s/\s//g;
|
|
|
|
if ($args->{'mode_req'} eq "All") {
|
|
delete($args->{'mode_req'});
|
|
}
|
|
}
|
|
########################################
|
|
|
|
$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 = getFilterList($args);
|
|
my $midQuery = getQueryFilters($filts, $start, $end);
|
|
if ($midQuery) { $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();
|
|
|
|
$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 ($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";
|
|
|
|
print LOG "$repName: Log generated by Novell AppArmor, $date\n\n";
|
|
printf LOG "%-21s%-32s%-8s%-51s", "Host", "Date", "Program", "Profile",
|
|
"PID", "Severity", "Mode Deny", "Mode Request","Detail", "Access Type",
|
|
"Operation", "Attribute", "Additional Name", "Parent", "Active Hat",
|
|
"Net Family", "Net Protocol", "Net Socket Type";
|
|
|
|
print LOG "\n";
|
|
|
|
for (sort (@$db)) {
|
|
print LOG "$_->{'host'},$_->{'time'},$_->{'prog'},$_->{'profile'},";
|
|
print LOG "$_->{'pid'},$_->{'severity'},$_->{'mode_deny'},$_->{'mode_req'},";
|
|
print LOG "$_->{'resource'},$_->{'sdmode'},$_->{'op'},$_->{'attr'},";
|
|
print LOG "$_->{'name_alt'},$_->{'parent'},$_->{'active_hat'},";
|
|
print LOG "$_->{'net_family'},$_->{'net_proto'},$_->{'net_socktype'}\n";
|
|
}
|
|
|
|
close LOG;
|
|
}
|
|
|
|
sub exportLog {
|
|
|
|
my ($exportLog, $db, $header) = @_;
|
|
|
|
return unless $db;
|
|
|
|
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) {
|
|
print LOG "$_->{'host'},$_->{'time'},$_->{'prog'},$_->{'profile'},";
|
|
print LOG "$_->{'pid'},$_->{'severity'},$_->{'mode_deny'},$_->{'mode_req'},";
|
|
print LOG "$_->{'resource'},$_->{'sdmode'},$_->{'op'},$_->{'attr'},";
|
|
print LOG "$_->{'name_alt'},$_->{'parent'},$_->{'active_hat'},";
|
|
print LOG "$_->{'net_family'},$_->{'net_proto'},$_->{'net_socktype'}\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>Host</th><th>Date</th><th>Program</th>" .
|
|
"<th>Profile</th><th>PID</th><th>Severity</th><th>Mode Deny</th>" .
|
|
"<th>Mode Request</th><th>Detail</th><th>Access Type</th><th>Operation</th>" .
|
|
"<th>Attribute</th><th>Additional Name</th><th>Parent</th><th>Active Hat</th>" .
|
|
"<th>Net Family</th><th>Net Protocol</th><th>Net Socket Type</th></tr>\n";
|
|
|
|
my $idx = 1;
|
|
|
|
for (@$db) {
|
|
$idx++;
|
|
|
|
my $logLine =
|
|
"<td> $_->{'date'} </td>"
|
|
. "<td> $_->{'prog'} </td>"
|
|
. "<td> $_->{'profile'} </td>"
|
|
. "<td> $_->{'pid'} </td>"
|
|
. "<td> $_->{'severity'} </td>"
|
|
. "<td> $_->{'mode_deny'} </td>"
|
|
. "<td> $_->{'mode_req'} </td>"
|
|
. "<td> $_->{'resource'} </td>"
|
|
. "<td> $_->{'sdmode'} </td>"
|
|
. "<td> $_->{'op'} </td>"
|
|
. "<td> $_->{'attr'} </td>"
|
|
. "<td> $_->{'name_alt'} </td>"
|
|
. "<td> $_->{'parent'} </td>"
|
|
. "<td> $_->{'active_hat'} </td>"
|
|
. "<td> $_->{'net_family'} </td>"
|
|
. "<td> $_->{'net_proto'} </td>"
|
|
. "<td> $_->{'net_socktype'} </td></tr>";
|
|
|
|
my $plainCell = "<tr><td> $_->{'host'} </td>";
|
|
my $shadedCell = "<tr='edefef'><td> $_->{'host'} </td>";
|
|
my $logLinePlain = $plainCell . $logLine;
|
|
my $logLineShaded = $shadedCell . $logLine;
|
|
|
|
if ($idx % 2 == 0) {
|
|
print LOG "$logLinePlain\n";
|
|
} else {
|
|
# Shade every other row
|
|
print LOG "$logLineShaded\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));
|
|
}
|
|
|
|
}
|
|
|
|
# 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\>/) {
|
|
|
|
/\<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";
|
|
|
|
# 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 $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
|
|
for my $filt (%$rep) {
|
|
next unless $filt && $rep->{$filt};
|
|
$rep->{$filt} =~ s/\s+//g; # repname won't be in here, so no spaces
|
|
if ( $rep->{$filt} eq "-" || $rep->{$filt} eq 'All' ||
|
|
$rep->{$filt} eq '*' )
|
|
{
|
|
delete($rep->{$filt});
|
|
}
|
|
}
|
|
|
|
$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
|
|
############################################################
|
|
for my $key(keys(%$filts)) {
|
|
|
|
# Special case for severity
|
|
if ( $key eq 'severity' ) {
|
|
|
|
if ($filts->{$key} eq "-" || $filts->{$key} eq "All") {
|
|
delete($filts->{$key});
|
|
} elsif ($filts->{$key} eq "-1"
|
|
|| $filts->{$key} 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->{$key}\' ";
|
|
} else {
|
|
$query .= "WHERE events.severity >= \'$filts->{$key}\' ";
|
|
}
|
|
$wFlag = 1;
|
|
}
|
|
|
|
# Special case for sdmode
|
|
} elsif ($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;
|
|
|
|
# All other filters
|
|
} elsif ($wFlag == 0) {
|
|
$query .= "WHERE events.$key LIKE \'\%$filts->{$key}\%\' ";
|
|
$wFlag = 1;
|
|
} else {
|
|
$query .= "AND events.$key LIKE \'\%$filts->{$key}\%\' ";
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
if ($midQuery) { $query .= "$midQuery"; }
|
|
}
|
|
|
|
# Finish query
|
|
$query .= "Order by $sortKey LIMIT $limit,$numEvents";
|
|
|
|
return $query;
|
|
}
|
|
|
|
# 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_req'} = $args->{'mode'}; }
|
|
if ($args->{'mode_req'}) { $filts->{'mode_req'} = $args->{'mode_req'}; }
|
|
if ($args->{'mode_deny'}) { $filts->{'mode_deny'} = $args->{'mode_deny'}; }
|
|
|
|
}
|
|
|
|
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_req'}
|
|
&& ($filts->{'mode_req'} eq "-" || $filts->{'mode_req'} eq "All"))
|
|
{
|
|
delete($filts->{'mode_req'});
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
# 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, op, pid, sdmode, type, mode_deny, mode_req,
|
|
# resource, target, profile, prog, name_alt, attr, parent, active_hat,
|
|
# net_family, net_proto, net_socktype, 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->{'op'},$rec->{'pid'},
|
|
$rec->{'sdmode'},$rec->{'type'},$rec->{'mode_deny'},$rec->{'mode_req'},
|
|
$rec->{'resource'},$rec->{'target'},$rec->{'profile'}, $rec->{'prog'},
|
|
$rec->{'name_alt'},$rec->{'attr'},$rec->{'parent'},$rec->{'active_hat'},
|
|
$rec->{'net_family'},$rec->{'net_proto'},$rec->{'net_socktype'},
|
|
$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->{'logFile'}) {
|
|
$eventRep = $args->{'logFile'};
|
|
}
|
|
|
|
if (open(REP, "<$eventRep")) {
|
|
|
|
my $page = 1;
|
|
|
|
if ($args->{'page'}) { $page = $args->{'page'}; }
|
|
|
|
my $id = 1;
|
|
my $slurp = 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/) {
|
|
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_deny'},$db->{'mode_req'},
|
|
$db->{'resource'},$db->{'sdmode'},$db->{'op'},$db->{'attr'},
|
|
$db->{'name_alt'},$db->{'parent'},$db->{'active_hat'},
|
|
$db->{'net_family'},$db->{'net_proto'},$db->{'net_socktype'})
|
|
= 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 $eventRep = "/var/log/apparmor/reports/events.rpt";
|
|
|
|
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'},$_->{'time'},$_->{'prog'},$_->{'profile'},";
|
|
print REP "$_->{'pid'},$_->{'severity'},$_->{'mode_deny'},$_->{'mode_req'},";
|
|
print REP "$_->{'resource'},$_->{'sdmode'},$_->{'op'},$_->{'attr'},";
|
|
print REP "$_->{'name_alt'},$_->{'parent'},$_->{'active_hat'},";
|
|
print REP "$_->{'net_family'},$_->{'net_proto'},$_->{'net_socktype'}\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 here
|
|
|
|
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")) {
|
|
|
|
$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_req'}
|
|
&& ($args->{'mode_req'} =~ /All/ || $args->{'mode_req'} =~ /^\s*-\s*$/))
|
|
{
|
|
delete($args->{'mode_req'});
|
|
}
|
|
if ($args->{'mode_deny'}
|
|
&& ($args->{'mode_deny'} =~ /All/ || $args->{'mode_deny'} =~ /^\s*-\s*$/))
|
|
{
|
|
delete($args->{'mode_deny'});
|
|
}
|
|
|
|
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->{'severity'}
|
|
&& ($args->{'severity'} =~ /All/ || $args->{'severity'} =~ /^\s*-\s*$/))
|
|
{
|
|
delete($args->{'severity'});
|
|
}
|
|
|
|
my $regExp = 'prog|profile|pid|resource|mode|severity|date|op|target|attr|net_|name_alt';
|
|
|
|
# get list of keys
|
|
my @keyList = keys(%$args);
|
|
|
|
# find filters in @keyList
|
|
if ( grep(/$regExp/, @keyList) == 1 ) {
|
|
$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->{'severity'},$rec->{'mode_deny'},$rec->{'mode_req'},
|
|
$rec->{'resource'},$rec->{'sdmode'},$rec->{'op'},$rec->{'attr'},
|
|
$rec->{'name_alt'},$rec->{'parent'},$rec->{'active_hat'},
|
|
$rec->{'net_family'},$rec->{'net_proto'},$rec->{'net_socktype'})
|
|
= split(/\,/, $_);
|
|
|
|
|
|
# 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
|
|
next if matchFailed($args,$rec);
|
|
|
|
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 $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_req'}
|
|
&& ($args->{'mode_req'} =~ /All/ || $args->{'mode_req'} =~ /^\s*-\s*$/))
|
|
{
|
|
delete($args->{'mode_req'});
|
|
}
|
|
|
|
if ($args->{'mode_deny'}
|
|
&& ($args->{'mode_deny'} =~ /All/ || $args->{'mode_deny'} =~ /^\s*-\s*$/))
|
|
{
|
|
delete($args->{'mode_deny'});
|
|
}
|
|
|
|
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->{'severity'}
|
|
&& ($args->{'severity'} =~ /All/ || $args->{'severity'} =~ /^\s*-\s*$/))
|
|
{
|
|
delete($args->{'severity'});
|
|
}
|
|
|
|
$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'}/; }
|
|
|
|
($rec->{'host'}, $rec->{'time'}, $rec->{'prog'}, $rec->{'profile'},
|
|
$rec->{'pid'}, $rec->{'severity'}, $rec->{'mode_req'}, $rec->{'resource'},
|
|
$rec->{'sdmode'}) = split(/\,/, $_);
|
|
|
|
# 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
|
|
next if matchFailed($args,$rec);
|
|
|
|
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?
|
|
}
|
|
}
|
|
|
|
$error = writeEventReport(\@db, $args);
|
|
|
|
} else {
|
|
$error = "Couldn't open $eventRep.";
|
|
}
|
|
|
|
return $error;
|
|
}
|
|
|
|
1;
|
|
|