#!/usr/perl5/bin/perl
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright 2009 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
# auditrecord - display one or more audit records
require 5.8.4;
use strict;
use warnings;
our (%opt, $parse, $callFilter, $debug,
%attr, %event, %class, %skipClass, %token, %noteAlias,
$title, $note, $name, $col1, $col2, $col3, $skip);
use Getopt::Std;
use locale;
use POSIX qw(locale_h);
use Sun::Solaris::Utils qw(gettext textdomain);
use Sun::Solaris::BSM::_BSMparse;
setlocale(LC_ALL, "");
textdomain(TEXT_DOMAIN);
if (!getopts('adhe:c:i:p:s:', \%opt) || @ARGV) {
my $errString =
gettext("$0 takes no arguments other than switches.\n");
print STDERR $errString if (@ARGV);
usage();
exit (1);
}
unless ($opt{a} || $opt{c} || $opt{e} || $opt{h} || $opt{i} ||
$opt{p} || $opt{s}) {
usage();
exit (1);
}
my %options;
$options{'classFilter'} = $opt{c}; # filter on this class
$debug = $opt{d}; # debug mode on
$options{'eventFilter'} = $opt{e}; # filter on this event
my $html = $opt{h}; # output in html format
$options{'idFilter'} = $opt{i}; # filter on this id
$callFilter = $opt{p}; # filter on this program name
$callFilter = $opt{s} if ($opt{s}); # filter on this system call
if (defined($callFilter)) {
$callFilter = qr/\b$callFilter\b/;
} else {
$callFilter = qr//;
}
$parse = new Sun::Solaris::BSM::_BSMparse($debug, \%options);
my ($attr, $token, $skipClass, $noteAlias) = $parse->readAttr();
%attr = %$attr;
%token = %$token;
%noteAlias = %$noteAlias;
%skipClass = %$skipClass;
%class = %{$parse->readClass()};
%event = %{$parse->readEvent()};
# the calls to readControl and readUser are for debug; they are not
# needed for generation of record formats. 'ignore' means if there
# is no permission to read the file, don't die, just soldier on.
# $error is L10N'd by $parse
if ($debug) {
my ($cnt, $error);
# verify audit_control content
($cnt, $error) = $parse->readControl('ignore');
print STDERR $error if ($cnt);
# verify audit_user content
($cnt, $error) = $parse->readUser('ignore');
print STDERR $error if ($cnt);
# check audit_event, audit_display_attr
($cnt, $error) = $parse->ckAttrEvent();
print STDERR $error if ($cnt);
}
# check for invalid class to -c option if supplied
if (defined $options{'classFilter'}) {
my $invalidClass = gettext('Invalid class %s supplied.');
my $isInvalidClass = 0;
foreach (split(/\s*,\s*/, $options{'classFilter'})) {
unless (exists $class{$_}) {
printf STDERR "$invalidClass\n", $_;
$isInvalidClass = 1;
}
}
exit (1) if $isInvalidClass;
}
if ($html) {
writeHTML();
} else {
writeASCII();
}
exit (0);
# writeASCII -- collect what's been read from various sources and
# output the formatted audit records
sub writeASCII {
my $label;
my $errString;
foreach $label (sort(keys(%event))) {
my $description;
my @case;
my ($id, $class, $eventDescription) = @{$event{$label}};
our ($title, $note, $name, $col1, $col2, $col3);
my ($skipThisClass, $mask) = classToMask($class, $label);
next if ($skipThisClass);
$mask = sprintf("0x%08X", $mask);
($name, $description, $title, $skip, @case) =
getAttributes($label, $eventDescription);
next if ($name eq 'undefined');
next unless $description =~ $callFilter;
$~ = 'nameLine';
write;
$note = $skip;
$~ = 'wrapped1';
while ($note) {
write;
}
next if ($skip);
$~ = 'threeColumns';
($col1, $col2, $col3) = getCallInfo($id, $name, $description);
my @col1 = split(/\s*;\s*/, $col1);
my @col2 = split(/\s*;\s*/, $col2);
my @col3 = split(/\s*;\s*/, $col3);
my $rows = $#col1;
$rows = $#col2 if ($#col2 > $rows);
$rows = $#col3 if ($#col3 > $rows);
for (my $i = 0; $i <= $rows; $i++) {
$col1 = defined ($col1[$i]) ? $col1[$i] : '';
$col2 = defined ($col2[$i]) ? $col2[$i] : '';
$col3 = defined ($col3[$i]) ? 'See ' . $col3[$i] : '';
write;
}
$col1 = 'event ID';
$col2 = $id;
$col3 = $label;
write;
$col1 = 'class';
$col2 = $class;
$col3 = "($mask)";
write;
my $haveFormat = 0;
my $caseElement;
foreach $caseElement (@case) {
# $note1 is the "case" description
# $note2 is a "note"
my ($note1, $format, $comment, $note2) = @$caseElement;
$note = $note1;
$~ = 'wrapped1';
while ($note) {
write;
}
unless (defined($format)) {
$errString = gettext(
"missing format field: %s");
printf STDERR ("$errString\n", $label);
next;
}
unless ($format eq 'none') {
$haveFormat = 1;
my $list = getFormatList($format, $id);
my @format = split(/\s*:\s*/, $list);
my @comment = split(/\s*:\s*/, $comment);
my $item;
foreach $item (@format) {
$~ = 'twoColumns';
($col1, $col2) =
getFormatLine($item, $label,
@comment);
write;
$~ = "col2Wrapped";
while ($col2) {
write;
}
}
}
$note2 = $noteAlias{$note2} if ($noteAlias{$note2});
if ($note2) {
$note = $note2;
$~ = 'space';
write;
$~ = 'wrapped1';
while ($note) {
write;
}
}
}
unless ($haveFormat) {
$~ = 'wrapped1';
$note = gettext('No format information available');
write;
}
}
}
# writeHTML -- collect what's been read from various sources
# and output the formatted audit records
#
sub writeHTML {
my $label;
my $description;
my @case;
my $docTitle = gettext("Audit Record Formats");
print qq{
<!doctype html PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<title>$docTitle</title>
<META http-equiv="Content-Style-Type" content="text/css">
</head>
<body TEXT="#000000" BGCOLOR="#F0F0F0">
};
my $tableRows = 0; # work around Netscape large table bug
startTable(); # by generating multiple tables
foreach $label (sort(keys(%event))) {
my ($id, $class, $eventDescription) = @{$event{$label}};
our ($title, $name, $note, $col1, $col2, $col3);
my ($skipThisClass, $mask) = classToMask($class, $label);
next if ($skipThisClass);
$mask = sprintf("0x%08X", $mask);
my $description;
($name, $description, $title, $skip, @case) =
getAttributes($label, $eventDescription);
next if ($name eq 'undefined');
next unless $description =~ $callFilter;
$tableRows++;
if ($tableRows > 50) {
endTable();
startTable();
$tableRows = 0;
}
my ($callType, $callName);
($callType, $callName, $description) =
getCallInfo($id, $name, $description);
$description =~ s/\s*;\s*/<br>/g;
my $titleName = $title;
if ($callName) {
$titleName = $callName;
}
$titleName =~ s/\s*;\s*/<br>/g;
$titleName = '&nbsp;' if ($titleName eq $title);
print qq{
<tr bgcolor="#C0C0C0">
<td>$label</td>
<td>$id</td>
<td>$class</td>
<td>$mask</td>
</tr>
<tr>
<td colspan=2>$titleName</td>
<td colspan=2>$description</td>
</tr>
<tr>
<td colspan=4>
<pre>
};
$note = $skip;
$~ = 'wrapped2';
while ($note) {
write;
}
next if ($skip);
my $haveFormat = 0;
my $caseElement;
foreach $caseElement (@case) {
my ($note1, $format, $comment, $note2) = @$caseElement;
$note = $note1;
$~ = 'wrapped2';
while ($note) {
write;
}
unless (defined($format)) {
my $errString = gettext(
"Missing format field: %s\n");
printf STDERR ($errString, $label);
next;
}
unless ($format eq 'none') {
$haveFormat = 1;
my $list = getFormatList($format, $id);
my @format = split(/\s*:\s*/, $list);
my @comment = split(/\s*:\s*/, $comment);
my $item;
$~ = 'twoColumns';
foreach $item (@format) {
($col1, $col2) =
getFormatLine($item, $label,
@comment);
write;
}
}
if ($note2) {
$note2 = $noteAlias{$note2} if ($noteAlias{$note2});
$note = $note2;
$~ = 'space';
write;
$~ = 'wrapped2';
while ($note) {
write;
}
}
}
unless ($haveFormat) {
$~ = 'wrapped2';
$note = 'No format information available';
write;
}
print q{
</pre>
</td/>
</tr>
};
}
endTable();
}
sub startTable {
print q{
<table border=1>
<tr bgcolor="#C0C0C0">
<th>Event Name</th>
<th>Event ID</th>
<th>Event Class</th>
<th>Mask</th>
</tr>
<tr>
<th colspan=2>Call Name</th>
<th colspan=2>Reference</th>
<tr>
<tr>
<th colspan=4>Format</th>
</tr>
};
}
sub endTable {
print q{
</table>
</body>
</html>
};
}
# classToMask: One, given a class list, it calculates the mask; Two,
# it checks to see if every item on the class list is marked for
# skipping, and if so, sets a flag.
sub classToMask {
my $classList = shift;
my $label = shift;
my $mask = 0;
my @classes = split(/\s*,\s*/, $classList);
my $skipThisClass = 0;
my $thisClass;
foreach $thisClass (@classes) {
unless (defined($class{$thisClass})) {
my $errString = gettext(
"%s not found in audit_class. Omitting %s\n");
$errString = sprintf($errString, $thisClass,
$label);
print STDERR $errString if ($debug);
next;
}
$skipThisClass = 1 if ($skipClass{$thisClass});
$mask |= $class{$thisClass};
}
return ($skipThisClass, $mask);
}
# getAttributes: Combine fields from %event and %attr; a description
# in the attribute file overrides a description from audit_event
sub getAttributes {
my $label = shift;
my $desc = shift; # description from audit_event
my ($description, $title, $skip, @case);
my $errString = gettext("%s not found in attribute file.");
my $name = gettext("undefined");
if (defined($attr{$label})) {
($name, $description, $title, $skip, @case) = @{$attr{$label}};
if ($description eq 'none') {
if ($desc eq 'blank') {
$description = '';
} else {
$description = $desc;
}
}
$name = '' if ($name eq 'none');
$title = $name if (($title eq 'none') || (!defined($title)));
} else {
printf STDERR ("$errString\n", $label) if ($debug);
}
return ($name, $description, $title, $skip, @case);
}
# getCallInfo: the system call or program name for an audit record can
# usually be derived from the event name; %attr provides exceptions to
# this rule
sub getCallInfo {
my $id = shift;
my $name = shift;
my $desc = shift;
my $callType;
my $callName;
my $description;
if ($name) {
if ($id < 6000) {
$callType = 'system call';
} else {
$callType = 'program';
}
($callName) = split(/\s*:\s*/, $name);
} else {
$callType = '';
$callName = '';
}
$description = '';
$description = "$desc" if ($desc);
return ($callType, $callName, $description);
}
# getFormatList: determine the order and details of kernel vs user
# audit records. If the first token is "head" then the token list
# is explicit, otherwise the header, subject and return are implied.
sub getFormatList {
my $format = shift;
my $id = shift;
my $list;
if ($format =~ /^head:/) {
$list = $format;
}
elsif ($format eq 'kernel') {
$list = $parse->{'kernelDefault'};
$list =~ s/insert://;
} elsif ($format eq 'user') {
$list = $parse->{'userDefault'};
$list =~ s/insert://;
} elsif ($id < 6000) {
$list = $parse->{'kernelDefault'};
$list =~ s/insert/$format/;
} else {
$list = $parse->{'userDefault'};
$list =~ s/insert/$format/;
}
return ($list);
}
# getFormatLine: the arguments from the attribute 'format' are
# expanded to their printable form and also paired with a comment if
# one exists
sub getFormatLine {
my $arg = shift;
my $label = shift;
my @comment = @_;
my $isOption = 0;
my ($token, $comment);
my $cmt = -1;
if ($arg =~ s/(\D*)(\d+)$/$1/) { # trailing digits select a comment
$cmt = $2 - 1;
}
$isOption = 1 if ($arg =~ s/^\[(.+)\]$/$1/);
if (defined($token{$arg})) { # expand abbreviated name to token
$token = $token{$arg};
} else {
$token = $arg; # no abbreviation found
}
$token = '['.$token.']' if ($isOption);
if ($cmt > -1) {
unless(defined($comment[$cmt])) {
my $errString = gettext(
"missing comment for %s %s token %d\n");
printf STDERR ($errString, $label, $token,
$cmt);
$comment = gettext('missing comment field');
} else {
$comment = $comment[$cmt];
$comment =~ s/&colon;/:/g; #':' is a delimiter
}
} else {
$comment = '';
}
unless (defined($token) && defined($comment)) {
my $errString = gettext("attribute format/comment error for %s\n");
printf STDERR ($errString, $label);
}
return ($token, $comment);
}
sub usage {
print "$0 [ -d ] [ -h ] {[ -a ] | [ -e event ] |\n";
print "\t[ -c class ] | [-i id ] | [ -p program ] |\n";
print "\t[ -s syscall ]}\n";
}
format nameLine =
@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$title
.
format threeColumns =
@<<<<<<<<<< @<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$col1, $col2, $col3
.
format twoColumns =
@<<<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$col1, $col2
.
format col2Wrapped =
^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$col2
.
format space =
.
format wrapped1 =
^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$note
.
format wrapped2 =
^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$note
.