xref: /illumos-gate/usr/src/cmd/auditrecord/auditrecord.pl (revision 45ede40b2394db7967e59f19288fae9b62efd4aa)
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
29require 5.8.4;
30use strict;
31use warnings;
32
33our (%opt, $parse, $callFilter, $debug,
34    %attr, %event, %class, %skipClass, %token, %noteAlias,
35    $title, $note, $name, $col1, $col2, $col3, $skip);
36
37use Getopt::Std;
38use locale;
39use POSIX qw(locale_h);
40use Sun::Solaris::Utils qw(gettext textdomain);
41use Sun::Solaris::BSM::_BSMparse;
42
43setlocale(LC_ALL, "");
44textdomain(TEXT_DOMAIN);
45
46if (!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
54unless ($opt{a} || $opt{c} || $opt{e} || $opt{h} || $opt{i} ||
55	$opt{p} || $opt{s}) {
56	usage();
57	exit (1);
58}
59
60my %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
64my $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
69if (defined($callFilter)) {
70	$callFilter = qr/\b$callFilter\b/;
71} else {
72	$callFilter = qr//;
73}
74$parse = new Sun::Solaris::BSM::_BSMparse($debug, \%options);
75
76my ($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
91if ($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
108if (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
120if ($html) {
121	writeHTML();
122} else {
123	writeASCII();
124}
125
126exit (0);
127
128# writeASCII -- collect what's been read from various sources and
129# output the formatted audit records
130
131sub 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
255sub 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
398sub 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
418sub 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
431sub 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
458sub 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
488sub 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
518sub 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
547sub 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
590sub 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
596format nameLine =
597
598@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
599$title
600.
601
602format threeColumns =
603  @<<<<<<<<<< @<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
604$col1, $col2, $col3
605.
606
607format twoColumns =
608      @<<<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
609$col1, $col2
610.
611format col2Wrapped =
612				   ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
613$col2
614.
615
616format space =
617
618.
619
620format wrapped1 =
621    ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
622$note
623.
624
625format wrapped2 =
626^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
627$note
628.
629