# ------------------------------------------------------------------ # # 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 () { 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 () { 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 () { 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 "\n\n"; print LOG "\n"; # $header comes from reportgen.pl (scheduled reports) if ($header) { print LOG "$header\n\n"; } else { print LOG "

$exportLog


\n

Log generated by Novell AppArmor, $date

\n\n"; } print LOG "

\n"; print LOG "" . "" . "" . "" . "\n"; my $idx = 1; for (@$db) { $idx++; my $logLine = "" . "" . "" . "" . "" . "" . "" . "" . "" . "" . "" . "" . "" . "" . "" . "" . ""; my $plainCell = ""; my $shadedCell = ""; 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 "
HostDateProgramProfilePIDSeverityMode DenyMode RequestDetailAccess TypeOperationAttributeAdditional NameParentActive HatNet FamilyNet ProtocolNet Socket Type
 $_->{'date'}  $_->{'prog'}  $_->{'profile'}  $_->{'pid'}  $_->{'severity'}  $_->{'mode_deny'}  $_->{'mode_req'}  $_->{'resource'}  $_->{'sdmode'}  $_->{'op'}  $_->{'attr'}  $_->{'name_alt'}  $_->{'parent'}  $_->{'active_hat'}  $_->{'net_family'}  $_->{'net_proto'}  $_->{'net_socktype'} 
 $_->{'host'}  $_->{'host'} 
\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 () { chomp; if (/\/) { /\(.+)\<\/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 () { 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 () { 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 () { 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, ); 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 () { 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;