#!/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{ $docTitle }; 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*/
/g; my $titleName = $title; if ($callName) { $titleName = $callName; } $titleName =~ s/\s*;\s*/
/g; $titleName = ' ' if ($titleName eq $title); print qq{ $label $id $class $mask $titleName $description
};

		$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{
      
}; } endTable(); } sub startTable { print q{ }; } sub endTable { print q{
Event Name Event ID Event Class Mask
Call Name Reference
Format
}; } # 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/:/:/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 .