Hello,

While diagnosing avc messages, I found the log message too spread-out to form a mental picture, 
since the lack of some rules often result in several domain domains barking. This reminds me of
unforgiving Ada compilers spilling out loads of messages.   I did not want to use audit2allow 
too quickly until I understood what the machine is not happy with. So, I needed a message 
format that let me to that.

Here is a simple perl script to parse log files for avc denial messages, index, sort them, and print them 
in tree (single depth) view, which I hastily put together last night.  I hope it will help others as it did 
for me. Please feel free to modify it for your own use.

You can index the message by any key, for example scontext, tcontext, action, name and etc. You can
also specify the log files to parse.  By default, the script trim the context string of the _u, _r and _t, which
are good for rule readability in source files, but clutter diagnostic print out. However, if this bothers you,
disable trimming by --trim=no option.   To get help and condition of usage,  avctree --help. It is 
best to pipe the output to less so that you can navigate.

A typical partial print out is as follows (this one indexed by tcontext): 

# --------------------------------------------------------------------------------[tcontext]
|
+-[root-object-default      ]
| +<- system-system-initrc_su      su(1753) : dir : search : home : dm-0 : 49182
|
+-[root-object-selinux_config]
| +<- root-system-semanage         semodule(3584) : dir : rename : active : sdb1 : 49833
|
+-[root-object-user_home    ]
| +<- root-system-semanage         semodule(10359) : lnk_file : read : targeted : sdb1 : 98758
| +<- root-system-semanage         semodule(11006) : lnk_file : read : policy : sdb1 : 98764
| +<- root-system-semanage         semodule(3584) : lnk_file : read : targeted : sdb1 : 98758
|
+-[system-object-default    ]
| +<- system-system-initrc_su      su(1753) : dir : search : / : dm-0 : 2
| +<- system-system-hald           hald(1958) : dir : getattr : / : dm-0 : 2
|


No PP module for this script yet.  This script use basic Perl features, so, as along you have base Perl package installed, it should work.
Be happy to hear any comments or suggestion for improving this.

/ks

#------------------------------------------- cut here -----------------------------------------
#!/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
}
my $version='1.0.0';
use strict;
use warnings;
my $thisScript = $0;
$thisScript =~ s#([^/]+[/]|)(\w+)#$2#;
# ----------------------------------------------------------------------------------------------
use Getopt::Long;
sub usage
{
lmsg;
print "Usage:\n";
print <<USAGETXT;
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
--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.
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

usage(), exit unless GetOptions(
'log:s' => \$logARG,
'tags!' => \$tagsARG,
'key:s' => \$catARG,
'trim:s' =>  \$trimARG,
'help!' => \$helpARG
);
usage() if (defined($helpARG));
## ----------------------------------------------------------------------------------------------
## Option: skip tags
my $skiptags = defined($tagsARG)?0:1;
## Option: log files
my @logOPT = grep -d $_, split /,|\n|\r/, $logARG if (defined($logARG));
@logOPT = ('/var/log/messages','/var/log/kernel','/var/log/debug')
if (defined($logARG) && ((!scalar @logOPT) || grep /all/, @logOPT));
@logOPT = ('/var/log/kernel') if (!scalar @logOPT && -d '/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;
## ----------------------------------------------------------------------------------------------
#
## 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+for\s+(.*)/;
## Holds indexed avc message records
my %avc;
## ----------------------------------------------------------------------------------------------
## 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;
}
## ----------------------------------------------------------------------------------------------
## readLOG  log-file-name
# Reads the specified log file
sub readLOG
{
my $avc = shift;
my $logfile = 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;

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;
}
next if (!defined($this{'scontext'}) || !defined($this{'tcontext'}));

$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'}));

# Okay, let's index the records with various keys
foreach (keys %this) {
next if (/audit|timetag|file/);
$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|$kcat/);
next if ($skiptags && $kmsg =~ /timetag|audit/);
$buf .= sprintf "%s=%s ", $kmsg, $hmsg->{$kmsg};
}
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
readLOG(\%avc, $_) foreach (@logOPT);
# Decide which category to print
@catOPT = (sort keys %avc)  if (defined($catARG) && (! scalar @catOPT) ||  grep /all/,@catOPT ) ;
@catOPT = @catDEF if (!defined($catARG));
print "\n> 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\n";
keyTREE(\%avc, $_,scalar @logOPT > 1?1:undef) foreach (@catOPT);
## ----------------------------------------------------------------------------------------------
# vim :ts=4:sw=4:
1;