Hello,
Here is version 1.0.4 of the script previously posted. a. Added regular expression (perl) to select messages to display e.g avctree --re="context=~/java/" will show any avc message that has 'java' in scontext *or* tcontext. e.g avctree --re="*=~/initrc/" will show any avc messages that has 'su' anywhere.
b. Added message selection based on age of message e.g avctree --age 3h will show avc messages not older than 3 hours from when you run the script.
c. Added 'unique' format of print e.g avctree --uniq will show avc messages that are unique once, i.e. scontext, tcontext, comm, name, dev, ino, key all match up (except time tag, audit tag, pid ... so, use with this in mind)
Try this: avctree --uniq --age 1d
/ks
p/s: This post may have duplicates resulting in problem in posting this update. Sorry if so. This will also be 'it' for the time being, having got this script to give me the productivity I need to work with selinux.
------------------------------------------------------ cut --------------------------------------------- #!/usr/bin/perl -w sub lmsg { print <<LMSG;; # Copyright (C) 2007, LEE, "Kok Seng" (kokseng at ieee dot org) # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 2 of # the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307 USA # LMSG } # History # 1.0.0 Format avc messages # 1.0.1 added --re option to select messages to print # 1.0.2 --re option allow context to mean scontext or tcontext, all to mean any key # 1.0.3 added --age option to select based on age of message # 1.0.4 added --uniq option to show messages that are unique my $version='1.0.4+'; use strict; use warnings; my ($thisScript) = ($0 =~ /.*?/*(\w+)$/); # ------------------------------------------------------------------------ ---------------------- use Getopt::Long; sub usage { lmsg; print "Usage:\n"; print <<USAGETXT; $thisScript version $version Utility to format avc messages for readability ----------------------------------------------------------------------- --------------------- $thisScript [[options] ...]
Options: --log=[ all | file,...] : List log files to parse, delimited by comma. no argument or all => /var/log/ messages, /var/log/kernel --tags : Show time and audit tags --key=key,... : List messages indexed-sorted by specified key no argument or all => all keys Not specified => scontext,tcontext,action,comm,name --trim=yes|no|1|0 : Trim context string. Default: yes --re="expr" : Filter using rsupplied regular expression special: a. To match either scontext or tcontext, specify --re="context=~/^(dhcp|su)/" which will match scontext or tcontext if either starts wuth dhcp or su. b. To match any key's value, specify --re="*=~/dhcp|su/" which will print all messages that has dhcp or su in any key's value --age[=time-spec] : Show messages that are not older than specified age. time spec := numeric[unit] numeric := integer or float default: 10 unit := s | m | h | d | w default: m --uniq : Show in unique format. --help ----------------------------------------------------------------------- --------------------- Examples: a. $thisScript --key=scontext Print avc messages indexed-sorted by source context. b. $thisScript --key=tcontext Print avc messages indexed-sorted by target context. c. $thisScript --key=comm Print avc messages indexed-sorted by command executed. d. $thisScript --key=name Print avc messages indexed-sorted by target object's name. e. $thisScript --key=all or $thisScript --key Print avc messages indexed-sorted by all keys. f. $thisScript Print avc messages indexed-sorted by scontext, tcontext, comm, name (default) g. $thisScript --trim=no Print avc messages without trimming context string. h. $thisScript --log=/var/log/messages,/var/log/messages.1,/var/ log/messages.2 Print avc messages from log files listed (delimited by comma). i. $thisScript --tags Print avc messages, including in each message the log time tag and audit tag. j. $thisScript --re="name=~/dhcp/ && comm=~/dhcp/" Print avc messages which has dhcp in name or comm USAGETXT exit -1; } # my $logARG; # Log files to parse my $tagsARG; # Show time and audit tags my $catARG; # Categories to print my $helpARG; # Help my $trimARG; # Trim context string for readability my $ageARG; # Age specification my $reARG; # Regular Expression my $uniqARG; # Unique format usage(), exit unless GetOptions( 'log:s' => $logARG, 'tags!' => $tagsARG, 'key:s' => $catARG, 'trim:s' => $trimARG, 're:s' => $reARG, 'age:s' => $ageARG, 'uniq!' => $uniqARG, 'help!' => $helpARG ); usage() if (defined($helpARG)); ## ------------------------------------------------------------------------ ---------------------- ## Option: skip tags my $skiptags = defined($tagsARG)?0:1; ## Option: log files my @logOPT = grep -e $_, split /,|\n|\r/, $logARG if (defined ($logARG)); @logOPT = ('/var/log/messages','/var/log/kernel','/var/log/debug','/ var//log/audit') if (defined($logARG) && ((!scalar @logOPT) || grep /all/, @logOPT)); @logOPT = ('/var/log/audit') if (!scalar @logOPT && -e '/var/log/ audit'); @logOPT = ('/var/log/kernel') if (!scalar @logOPT && -e '/var/log/ kernel'); @logOPT = ('/var/log/messages') if (!scalar @logOPT); ## Option: Category my @catOPT = split /,|\n|\r/, $catARG if (defined($catARG)); my @catDEF = ('scontext','tcontext','comm','name'); ## Option: Trim my $trimOPT = defined($trimARG) ? ($trimARG =~ /no|0|/i ? 0 : 1 ) : 1; ## Option: regular expression my @reOPT = split /,|\n|\r/, $reARG if (defined($reARG)); ## Option: age my @ageOPT = split /,|\n|\r/, $ageARG if (defined($ageARG)); @ageOPT = ('10m') if (defined($ageARG) && !scalar @ageOPT); my ($age, $tu) = ($ageOPT[0] =~ /\s*([\d.]+)\s*([smhdw]).*/); undef $ageARG if (!defined($age)); $age *= defined($tu)?($tu eq 'm'?60:($tu eq 'h'?3600:($tu eq 'd'? 86400:($tu eq 'w'?604800:1)))):1 if (defined($ageARG)); ## ------------------------------------------------------------------------ ---------------------- # ## Regular expression for parsing avc's 'denied' messages my $avcRE = qr/^(\w{3}\s+\d{2}\s+\d{2}:\d{2}:\d{2})[\s\w]+:\s*audit\ (([\d.:]+))\s*:\s*avc\s*:\s*denied\s+{\s+([\w\s]+)\s+}\s+for\s+(.*)/; ## Holds indexed avc message records my %avc; my $epoch = time(); ## ------------------------------------------------------------------------ ---------------------- ## contextFMT # Format context string for readability sub contextFMT { my $ctxt = shift; my ($u,$r,$t,$l) = split /:/, $ctxt; $u =~ s/(.*)_./$1/; $r =~ s/(.*)_./$1/; $t =~ s/(.*)_./$1/; return $u . '-' . $r . '-' . $t; } ## ------------------------------------------------------------------------ ---------------------- ## prepRE reference-avc reference-re-list # Prepare regular expression from user supplied string sub prepRE { my $avc = shift; my $reOPT = shift; my @re = @$reOPT; if (grep /*\s*=~/, @re) { my @restr; push @restr, /\s**\s*=~\s*/([^/]+)/\s*/g foreach (@re); my $str = "{my $ret=0; ($ret |= ("; s/(.*)/$this{$_}=~/$_// foreach (@restr); $str .= join ' || ', @restr; $str .= ")) foreach (keys %this); $ret;}"; #print "\nprepRE=$str"; return $str; } else { # tbd : improve on regex / ... / parsing s/\s*context\s*(=~\s*/[^/]+/)\s*/(scontext$1||tcontext$1)/g foreach (@re); s/\s*(\w+)\s*=~/$this{'$1'} =~/g foreach (@re); my $str = '(' . join(' ) || ( ' , @re) . ')'; $str =~ s/||\s*(\s*)//g; return $str; } }
## ------------------------------------------------------------------------ ---------------------- ## readLOG log-file-name # Reads the specified log file sub readLOG { my $avc = shift; my $logfile = shift; my $reopt = shift; my $logsn = ($logfile =~ /.*/(.*)$/)[0]; my $tmax = defined($avc->{'_tcontext_max_'})?$avc-> {'_tcontext_max_'}:0; my $smax = defined($avc->{'_scontext_max_'})?$avc-> {'_scontext_max_'}:0; my $rex = undef; open LOGF, '<' . $logfile || die "Cannot open input file: $logfile";
while (<LOGF>) { s/\r|\n//g; next if (!$_); next if (!/\s+avc:\s+/); my ($timetag, $audit, $action, $detail) = ($_ =~ /$avcRE/); next if (!defined($action)||!defined($detail)||!defined($timetag)||! defined($audit));
# okay, we have a avc 'denied' message my %this; # this hash will keep the message's key=value my @fields = split /\s|\r|\n/, $detail; foreach (@fields) { next if (!$_); my ($key,$val) = split/=/; next if (!$key||!$val); $val =~ s/["']*([^"']*)["']*/$1/; $this{"$key"} = $val; }
# check age of message if (defined($ageARG) && defined($audit)) { my ($tm) = ($audit =~ /([\d+.]+).*/); next if (defined($tm) && $epoch - $tm > $age); } # Filter, if specified $rex = prepRE($avc, $reopt) if (!defined($rex) && defined($reopt)); next if (defined($rex) && ! eval $rex );
$this{'action'} = $action; $this{'timetag'} = $timetag; $this{'audit'} = $audit; $this{'file'}= $logsn; if ($trimOPT) { $this{'scontext'} = contextFMT($this{'scontext'}); $this{'tcontext'} = contextFMT($this{'tcontext'}); } $smax = length($this{'scontext'}) if ($smax < length($this {'scontext'})); $tmax = length($this{'tcontext'}) if ($tmax < length($this {'tcontext'}));
# Check if this message is unique my $uniq = 1; if (defined($uniqARG)&&defined($avc{'scontext'})&&defined($avc {'scontext'}->{$this{'scontext'}})) { foreach (@{$avc{'scontext'}->{$this{'scontext'}}}) { if ($_->{'tcontext'} eq $this{'tcontext'} && ($_->{'comm'} eq $this{'comm'})&& ($_->{'name'} eq $this{'name'}) && ($_->{'tclass'} eq $this{'tclass'}) && ($_->{'action'} eq $this{'action'}) && (!defined($_->{'dev'}) || $_->{'dev'} eq $this{'dev'}) && (!defined($_->{'ino'}) || $_->{'ino'} eq $this{'ino'}) && (!defined($_->{'key'}) || $_->{'key'} eq $this{'key'}) ) { $_->{'_same_'} = [()] if (!defined($_->{'_same_'})); push @{$_->{'_same_'}}, %this; $uniq = 0; last; } } } next if (!$uniq);
# Okay, let's index the records with various keys foreach (keys %this) { next if (/audit|timetag|file|_same_/); $avc->{$_} = {} if (!defined($avc->{$_})); $avc->{$_}->{$this{$_}} = [()] if (!defined($avc->{$_}->{$this {$_}})); push @{$avc->{$_}->{$this{$_}}}, %this; }
} close LOGF; $avc->{'_scontext_max_'} = $smax; $avc->{'_tcontext_max_'} = $tmax; } ## ------------------------------------------------------------------------ ---------------------- ## # keyTREE key # Show selected key in a tree view sub keyTREE { my $avc = shift; my $kcat = shift; my $showfile = shift;
my $hcat = $avc->{$kcat}; my $lvl = 1; my $isSctx = ($kcat =~ /scontext/); my $isTctx = ($kcat =~ /tcontext/);
my $smax = $isSctx ? 0 : $avc->{'_scontext_max_'}; my $tmax = $isTctx ? 0 : $avc->{'_tcontext_max_'};
return if (/_scontext_max_|_tcontext_max_/); print "\n# "; for ($_=0; $_ < 80; $_++) {print "-";} print "[", $kcat, "]\n";
foreach my $kmsg (sort keys %$hcat) { printf "|\n+-[%-*s]\n", $smax, $hcat->{$kmsg}[0]->{$kcat}; $lvl++; my $buf; my $i; my $cnt = scalar @{$hcat->{$kmsg}}; foreach my $hmsg (@{$hcat->{$kmsg}}) { $buf .= sprintf "%s %-*s %s %-*s %s%s(%s) : %s : %s %s%s%s\n", $isTctx? '+<-' : '+->', $smax, $isSctx?'':$hmsg->{'scontext'}, $isTctx||$isSctx ? '' : '-+->', $tmax, $isTctx?'':$hmsg->{'tcontext'}, defined($showfile)? $hmsg->{'file'}.'> ':'', $hmsg->{'comm'}, $hmsg->{'pid'}, $hmsg->{'tclass'}, $hmsg->{'action'}, defined($hmsg->{'name'})?': '.$hmsg->{'name'}:'', defined($hmsg->{'key'})?' : '.$hmsg->{'key'}:'', defined($hmsg->{'dev'})?' : '.$hmsg->{'dev'} . (defined($hmsg-> {'ino'})?' : '.$hmsg->{'ino'}:'') : '' ; $i = $lvl; $buf = '| ' . $buf while (--$i); print $buf; $buf = "";
foreach my $kmsg (sort keys %$hmsg) { next if ($kmsg =~ /file|scontext|tcontext|comm|pid|tclass|action| name|dev|ino|key|_same_|$kcat/); next if ($skiptags && $kmsg =~ /timetag|audit/); $buf .= sprintf "%s=%s ", $kmsg, $hmsg->{$kmsg}; } $buf .= sprintf "+-[%u] similar message%s", scalar @{$hmsg-> {'_same_'}}, scalar @{$hmsg->{'_same_'}} > 1 ? 's':'' if (defined($uniqARG) && defined($hmsg->{'_same_'})); if ($buf) { $buf = sprintf "%*s%s\n", ($cnt==1)?$smax+$tmax+10+2:$smax+$tmax +10, ,"", $buf; $i = (--$cnt)? $lvl:$lvl-1; $buf = '| ' . $buf while ($i--); print $buf; $buf = ""; } } $lvl--; } $lvl--; } ## ------------------------------------------------------------------------ ---------------------- # Parse log files my @logLIST=@logOPT; readLOG(%avc, $_, scalar @reOPT?@reOPT:undef) foreach (@logLIST); # Decide which category to print @catOPT = (sort keys %avc) if (defined($catARG) && (! scalar @catOPT) || grep /all/,@catOPT ) ; @catOPT = @catDEF if (!defined($catARG)); print "\n> $thisScript version $version, Copyright (C) 2007, LEE, "Kok Seng" (kokseng at ieee dot org)"; print "\n> Notice: get help and condition of usage inforamtion regarding this script: $thisScript --help"; print "\n> File(s) parsed: ", join ', ', @logOPT, " Key(s) : ", join ', ', @catOPT; print "\n> Regular expression = ", join ' or ', @reOPT if (scalar @reOPT); print "\n> Age not more than ", $ageARG, " (", $age, " seconds)" if (defined($ageARG)); print "\n> Unique mode is ON" if (defined($uniqARG)); print "\n"; keyTREE(%avc, $_,scalar @logOPT > 1?1:undef) foreach (@catOPT); ## ------------------------------------------------------------------------ ---------------------- # vim :ts=4:sw=4: 1;