Home | History | Annotate | Download | only in auditrecord
      1 #!/usr/perl5/bin/perl
      2 #
      3 # CDDL HEADER START
      4 #
      5 # The contents of this file are subject to the terms of the
      6 # Common Development and Distribution License (the "License").
      7 # You may not use this file except in compliance with the License.
      8 #
      9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
     10 # or http://www.opensolaris.org/os/licensing.
     11 # See the License for the specific language governing permissions
     12 # and limitations under the License.
     13 #
     14 # When distributing Covered Code, include this CDDL HEADER in each
     15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     16 # If applicable, add the following below this CDDL HEADER, with the
     17 # fields enclosed by brackets "[]" replaced with your own identifying
     18 # information: Portions Copyright [yyyy] [name of copyright owner]
     19 #
     20 # CDDL HEADER END
     21 #
     22 #
     23 # Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
     24 # Use is subject to license terms.
     25 #
     26 
     27 # auditrecord - display one or more audit records
     28 
     29 require 5.8.4;		
     30 use strict;
     31 use warnings;
     32 
     33 our (%opt, $parse, $callFilter, $debug,
     34     %attr, %event, %class, %skipClass, %token, %noteAlias,
     35     $title, $note, $name, $col1, $col2, $col3, $skip);
     36 
     37 use Getopt::Std;
     38 use locale;
     39 use POSIX qw(locale_h);
     40 use Sun::Solaris::Utils qw(gettext textdomain);
     41 use Sun::Solaris::BSM::_BSMparse;
     42 
     43 setlocale(LC_ALL, "");
     44 textdomain(TEXT_DOMAIN);
     45 
     46 if (!getopts('adhe:c:i:p:s:', \%opt) || @ARGV) {
     47 	my $errString =
     48 	    gettext("$0 takes no arguments other than switches.\n");
     49 	print STDERR $errString if (@ARGV);
     50 	usage();
     51 	exit (1);
     52 }
     53 
     54 unless ($opt{a} || $opt{c} || $opt{e} || $opt{h} || $opt{i} ||
     55 	$opt{p} || $opt{s}) {
     56 	usage();
     57 	exit (1);
     58 }
     59 
     60 my %options;
     61 $options{'classFilter'} = $opt{c};   # filter on this class
     62 $debug			= $opt{d};   # debug mode on
     63 $options{'eventFilter'} = $opt{e};   # filter on this event
     64 my $html		= $opt{h};   # output in html format
     65 $options{'idFilter'}	= $opt{i};   # filter on this id
     66 $callFilter		= $opt{p};   # filter on this program name
     67 $callFilter		= $opt{s} if ($opt{s}); # filter on this system call
     68 
     69 if (defined($callFilter)) {
     70 	$callFilter = qr/\b$callFilter\b/;
     71 } else {
     72 	$callFilter = qr//;
     73 }
     74 $parse = new Sun::Solaris::BSM::_BSMparse($debug, \%options);
     75 
     76 my ($attr, $token, $skipClass, $noteAlias) = $parse->readAttr();
     77 %attr  = %$attr;
     78 %token = %$token;
     79 %noteAlias = %$noteAlias;
     80 %skipClass = %$skipClass;
     81 
     82 %class = %{$parse->readClass()};
     83 %event = %{$parse->readEvent()};
     84 
     85 # the calls to readControl and readUser are for debug; they are not
     86 # needed for generation of record formats.  'ignore' means if there
     87 # is no permission to read the file, don't die, just soldier on.
     88 
     89 # $error is L10N'd by $parse
     90 
     91 if ($debug) {
     92 	my ($cnt, $error);
     93 
     94 	# verify audit_control content
     95 	($cnt, $error) = $parse->readControl('ignore');
     96 	print STDERR $error if ($cnt);
     97 
     98 	# verify audit_user content
     99 	($cnt, $error) = $parse->readUser('ignore');
    100 	print STDERR $error if ($cnt);
    101 
    102 	# check audit_event, audit_display_attr
    103 	($cnt, $error) = $parse->ckAttrEvent();
    104 	print STDERR $error if ($cnt);
    105 }
    106 
    107 # check for invalid class to -c option if supplied
    108 if (defined $options{'classFilter'}) {
    109 	my $invalidClass = gettext('Invalid class %s supplied.');
    110 	my $isInvalidClass = 0;
    111 	foreach (split(/\s*,\s*/, $options{'classFilter'})) {
    112 		unless (exists $class{$_}) {
    113 			printf STDERR "$invalidClass\n", $_;
    114 			$isInvalidClass = 1;
    115 		}
    116 	}
    117 	exit (1) if $isInvalidClass;
    118 }
    119 
    120 if ($html) {
    121 	writeHTML();
    122 } else {
    123 	writeASCII();
    124 }
    125 
    126 exit (0);
    127 
    128 # writeASCII -- collect what's been read from various sources and
    129 # output the formatted audit records
    130 
    131 sub writeASCII {
    132 	my $label;
    133 
    134 	my $errString;
    135 
    136 	foreach $label (sort(keys(%event))) {
    137 		my $description;
    138 		my @case;
    139 
    140 		my ($id, $class, $eventDescription) = @{$event{$label}};
    141 
    142 		our ($title, $note, $name, $col1, $col2, $col3);
    143 
    144 		my ($skipThisClass, $mask) = classToMask($class, $label);
    145 
    146 		next if ($skipThisClass);
    147 
    148 		$mask = sprintf("0x%08X", $mask);
    149 
    150 		($name, $description, $title, $skip, @case) =
    151 			getAttributes($label, $eventDescription);
    152 
    153 		next if ($name eq 'undefined');
    154 
    155 		next unless $description =~ $callFilter;
    156 
    157 		$~ = 'nameLine';
    158 		write;
    159 
    160 		$note = $skip;
    161 		$~ = 'wrapped1';
    162 		while ($note) {
    163 			write;
    164 		}
    165 		next if ($skip);
    166 
    167 		$~ = 'threeColumns';
    168 		($col1, $col2, $col3) = getCallInfo($id, $name, $description);
    169 		my @col1 = split(/\s*;\s*/, $col1);
    170 		my @col2 = split(/\s*;\s*/, $col2);
    171 		my @col3 = split(/\s*;\s*/, $col3);
    172 		my $rows = $#col1;
    173 		$rows = $#col2 if ($#col2 > $rows);
    174 		$rows = $#col3 if ($#col3 > $rows);
    175 		for (my $i = 0; $i <= $rows; $i++) {
    176 			$col1 = defined ($col1[$i]) ? $col1[$i] : '';
    177 			$col2 = defined ($col2[$i]) ? $col2[$i] : '';
    178 			$col3 = defined ($col3[$i]) ? 'See ' . $col3[$i] : '';
    179 			write;
    180 		}
    181 		$col1 = 'event ID';
    182 		$col2 = $id;
    183 		$col3 = $label;
    184 		write;
    185 
    186 		$col1 = 'class';
    187 		$col2 = $class;
    188 		$col3 = "($mask)";
    189 		write;
    190 
    191 		my $haveFormat = 0;
    192 		my $caseElement;
    193 
    194 		foreach $caseElement (@case) {
    195 			# $note1 is the "case" description
    196 			# $note2 is a "note"
    197 			my ($note1, $format, $comment, $note2) = @$caseElement;
    198 
    199 			$note = $note1;
    200 			$~ = 'wrapped1';
    201 			while ($note) {
    202 				write;
    203 			}
    204 			unless (defined($format)) {
    205 				$errString = gettext(
    206 				    "missing format field: %s");
    207 				printf STDERR ("$errString\n", $label);
    208 				next;
    209 			}
    210 			unless ($format eq 'none') {
    211 				$haveFormat = 1;
    212 
    213 				my $list = getFormatList($format, $id);
    214 
    215 				my @format  = split(/\s*:\s*/, $list);
    216 				my @comment = split(/\s*:\s*/, $comment);
    217 
    218 				my $item;
    219 
    220 				foreach $item (@format) {
    221 					$~ = 'twoColumns';
    222 					($col1, $col2) =
    223 					    getFormatLine($item, $label,
    224 					    @comment);
    225 					write;
    226 					$~ = "col2Wrapped";
    227 					while ($col2) {
    228 						write;
    229 					}
    230 				}
    231 			}
    232 			$note2 = $noteAlias{$note2} if ($noteAlias{$note2});
    233 			if ($note2) {
    234 				$note = $note2;
    235 				$~ = 'space';
    236 				write;
    237 				$~ = 'wrapped1';
    238 				while ($note) {
    239 					write;
    240 				}
    241 			}
    242 		}
    243 		unless ($haveFormat) {
    244 			$~ = 'wrapped1';
    245 			$note = gettext('No format information available');
    246 			write;
    247 		}
    248 	}
    249 }
    250 
    251 # writeHTML -- collect what's been read from various sources
    252 # and output the formatted audit records
    253 #
    254 
    255 sub writeHTML {
    256 	my $label;
    257 
    258 	my $description;
    259 	my @case;
    260 
    261 	my $docTitle = gettext("Audit Record Formats");
    262 
    263 	print qq{
    264 <!doctype html PUBLIC "-//IETF//DTD HTML//EN">
    265 <html>
    266 <head>
    267   <title>$docTitle</title>
    268   <META http-equiv="Content-Style-Type" content="text/css">
    269 </head>
    270 
    271 <body TEXT="#000000" BGCOLOR="#F0F0F0">
    272 	};
    273 
    274 	my $tableRows = 0;	# work around Netscape large table bug
    275 	startTable();		# by generating multiple tables
    276 
    277 	foreach $label (sort(keys(%event))) {
    278 		my ($id, $class, $eventDescription) = @{$event{$label}};
    279 
    280 		our ($title, $name, $note, $col1, $col2, $col3);
    281 
    282 		my ($skipThisClass, $mask) = classToMask($class, $label);
    283 
    284 		next if ($skipThisClass);
    285 
    286 		$mask = sprintf("0x%08X", $mask);
    287 
    288 		my $description;
    289 
    290 		($name, $description, $title, $skip, @case) =
    291 			getAttributes($label, $eventDescription);
    292 
    293 		next if ($name eq 'undefined');
    294 
    295 		next unless $description =~ $callFilter;
    296 
    297 		$tableRows++;
    298 		if ($tableRows > 50) {
    299 			endTable();
    300 			startTable();
    301 			$tableRows = 0;
    302 		}
    303 
    304 		my ($callType, $callName);
    305 		($callType, $callName, $description) =
    306 			getCallInfo($id, $name, $description);
    307 		$description =~ s/\s*;\s*/<br>/g;
    308 
    309 		my $titleName = $title;
    310 		if ($callName) {
    311 			$titleName = $callName;
    312 		}
    313 		$titleName =~ s/\s*;\s*/<br>/g;
    314 		$titleName = '&nbsp;' if ($titleName eq $title);
    315 
    316 		print qq{
    317   <tr bgcolor="#C0C0C0">
    318     <td>$label</td>
    319     <td>$id</td>
    320     <td>$class</td>
    321     <td>$mask</td>
    322   </tr>
    323   <tr>
    324     <td colspan=2>$titleName</td>
    325     <td colspan=2>$description</td>
    326   </tr>
    327   <tr>
    328     <td colspan=4>
    329       <pre>
    330 };
    331 
    332 		$note = $skip;
    333 		$~ = 'wrapped2';
    334 		while ($note) {
    335 			write;
    336 		}
    337 		next if ($skip);
    338 
    339 		my $haveFormat = 0;
    340 		my $caseElement;
    341 
    342 		foreach $caseElement (@case) {
    343 			my ($note1, $format, $comment, $note2) = @$caseElement;
    344 
    345 			$note = $note1;
    346 			$~ = 'wrapped2';
    347 			while ($note) {
    348 				write;
    349 			}
    350 			unless (defined($format)) {
    351 				my $errString = gettext(
    352 				    "Missing format field: %s\n");
    353 				printf STDERR ($errString, $label);
    354 				next;
    355 			}
    356 			unless ($format eq 'none') {
    357 				$haveFormat = 1;
    358 
    359 				my $list = getFormatList($format, $id);
    360 
    361 				my @format  = split(/\s*:\s*/, $list);
    362 				my @comment = split(/\s*:\s*/, $comment);
    363 				my $item;
    364 
    365 				$~ = 'twoColumns';
    366 				foreach $item (@format) {
    367 					($col1, $col2) =
    368 					    getFormatLine($item, $label,
    369 					    @comment);
    370 					write;
    371 				}
    372 			}
    373 			if ($note2) {
    374 				$note2 = $noteAlias{$note2} if ($noteAlias{$note2});
    375 				$note = $note2;
    376 				$~ = 'space';
    377 				write;
    378 				$~ = 'wrapped2';
    379 				while ($note) {
    380 					write;
    381 				}
    382 			}
    383 		}
    384 		unless ($haveFormat) {
    385 			$~ = 'wrapped2';
    386 			$note = 'No format information available';
    387 			write;
    388 		}
    389 		print q{
    390       </pre>
    391     </td/>
    392   </tr>
    393 		};
    394 	}
    395 	endTable();
    396 }
    397 
    398 sub startTable {
    399 
    400 	print q{
    401 <table border=1>
    402   <tr bgcolor="#C0C0C0">
    403     <th>Event Name</th>
    404     <th>Event ID</th>
    405     <th>Event Class</th>
    406     <th>Mask</th>
    407   </tr>
    408   <tr>
    409     <th colspan=2>Call Name</th>
    410     <th colspan=2>Reference</th>
    411   <tr>
    412   <tr>
    413     <th colspan=4>Format</th>
    414   </tr>
    415 	};
    416 }
    417 
    418 sub endTable {
    419 
    420 	print q{
    421 </table>
    422 </body>
    423 </html>
    424 	};
    425 }
    426 
    427 # classToMask: One, given a class list, it calculates the mask; Two,
    428 # it checks to see if every item on the class list is marked for
    429 # skipping, and if so, sets a flag.
    430 
    431 sub classToMask {
    432 	my $classList = shift;
    433 	my $label = shift;
    434 	my $mask = 0;
    435 
    436 	my @classes = split(/\s*,\s*/, $classList);
    437 	my $skipThisClass = 0;
    438 
    439 	my $thisClass;
    440 	foreach $thisClass (@classes) {
    441 		unless (defined($class{$thisClass})) {
    442 			my $errString = gettext(
    443 			    "%s not found in audit_class.  Omitting %s\n");
    444 			$errString = sprintf($errString, $thisClass,
    445 			    $label);
    446 			print STDERR $errString if ($debug);
    447 			next;
    448 		}
    449 		$skipThisClass = 1 if ($skipClass{$thisClass});
    450 		$mask |=  $class{$thisClass};
    451 	}
    452 	return ($skipThisClass, $mask);
    453 }
    454 
    455 # getAttributes: Combine fields from %event and %attr; a description
    456 # in the attribute file overrides a description from audit_event
    457 
    458 sub getAttributes {
    459 	my $label = shift;
    460 	my $desc = shift;	# description from audit_event
    461 
    462 	my ($description, $title, $skip, @case);
    463 
    464 	my $errString = gettext("%s not found in attribute file.");
    465 	my $name = gettext("undefined");
    466 
    467 	if (defined($attr{$label})) {
    468 		($name, $description, $title, $skip, @case) = @{$attr{$label}};
    469 		if ($description eq 'none') {
    470 			if ($desc eq 'blank') {
    471 				$description = '';
    472 			} else {
    473 				$description = $desc;
    474 			}
    475 		}
    476 		$name = '' if ($name eq 'none');
    477 		$title = $name if (($title eq 'none') || (!defined($title)));
    478 	} else {
    479 		printf STDERR ("$errString\n", $label) if ($debug);
    480 	}
    481 	return ($name, $description, $title, $skip, @case);
    482 }
    483 
    484 # getCallInfo: the system call or program name for an audit record can
    485 # usually be derived from the event name; %attr provides exceptions to
    486 # this rule
    487 
    488 sub getCallInfo {
    489 	my $id = shift;
    490 	my $name = shift;
    491 	my $desc = shift;
    492 
    493 	my $callType;
    494 	my $callName;
    495 	my $description;
    496 
    497 	if ($name) {
    498 		if ($id < 6000) {
    499 			$callType = 'system call';
    500 		} else {
    501 			$callType = 'program';
    502 		}
    503 		($callName) = split(/\s*:\s*/, $name);
    504 	} else {
    505 		$callType = '';
    506 		$callName = '';
    507 	}
    508 	$description = '';
    509 	$description = "$desc" if ($desc);
    510 
    511 	return ($callType, $callName, $description);
    512 }
    513 
    514 # getFormatList: determine the order and details of kernel vs user
    515 # audit records.  If the first token is "head" then the token list
    516 # is explicit, otherwise the header, subject and return are implied.
    517 
    518 sub getFormatList {
    519 	my $format = shift;
    520 	my $id = shift;
    521 
    522 	my $list;
    523 
    524 	if ($format =~ /^head:/) {
    525 		$list = $format;
    526 	}
    527 	elsif ($format eq 'kernel') {
    528 		$list = $parse->{'kernelDefault'};
    529 		$list =~ s/insert://;
    530 	} elsif ($format eq 'user') {
    531 		$list = $parse->{'userDefault'};
    532 		$list =~ s/insert://;
    533 	} elsif ($id < 6000) {
    534 		$list = $parse->{'kernelDefault'};
    535 		$list =~ s/insert/$format/;
    536 	} else {
    537 		$list = $parse->{'userDefault'};
    538 		$list =~ s/insert/$format/;
    539 	}
    540 	return ($list);
    541 }
    542 
    543 # getFormatLine: the arguments from the attribute 'format' are
    544 # expanded to their printable form and also paired with a comment if
    545 # one exists
    546 
    547 sub getFormatLine {
    548 	my $arg = shift;
    549 	my $label = shift;
    550 	my @comment = @_;
    551 
    552 	my $isOption = 0;
    553 
    554 	my ($token, $comment);
    555 
    556 	my $cmt = -1;
    557 	if ($arg =~ s/(\D*)(\d+)$/$1/) {  # trailing digits select a comment
    558 		$cmt = $2 - 1;
    559 	}
    560 	$isOption = 1 if ($arg =~ s/^\[(.+)\]$/$1/);
    561 
    562 	if (defined($token{$arg})) {	# expand abbreviated name to token
    563 		$token = $token{$arg};
    564 	} else {
    565 		$token = $arg;		# no abbreviation found
    566 	}
    567 	$token = '['.$token.']' if ($isOption);
    568 
    569 	if ($cmt > -1) {
    570 		unless(defined($comment[$cmt])) {
    571 			my $errString = gettext(
    572 			    "missing comment for %s %s token %d\n");
    573 			printf STDERR ($errString, $label, $token,
    574 			    $cmt);
    575 			$comment = gettext('missing comment field');
    576 		} else {
    577 			$comment = $comment[$cmt];
    578 			$comment =~ s/&colon;/:/g;	#':' is a delimiter
    579 		}
    580 	} else {
    581 		$comment = '';
    582 	}
    583 	unless (defined($token) && defined($comment)) {
    584 		my $errString = gettext("attribute format/comment error for %s\n");
    585 		printf STDERR ($errString, $label);
    586 	}
    587 	return ($token, $comment);
    588 }
    589 
    590 sub usage {
    591 	print "$0 [ -d ] [ -h ] {[ -a ] | [ -e event ] |\n";
    592 	print "\t[ -c class ] | [-i id ] | [ -p program ] |\n";
    593 	print "\t[ -s syscall ]}\n";
    594 }
    595 
    596 format nameLine =
    597 
    598 @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    599 $title
    600 .
    601 
    602 format threeColumns =
    603   @<<<<<<<<<< @<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    604 $col1, $col2, $col3
    605 .
    606 
    607 format twoColumns =
    608       @<<<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    609 $col1, $col2
    610 .
    611 format col2Wrapped =
    612 				   ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    613 $col2
    614 .
    615 
    616 format space =
    617 
    618 .
    619 
    620 format wrapped1 =
    621     ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    622 $note
    623 .
    624 
    625 format wrapped2 =
    626 ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    627 $note
    628 .
    629