2 # Copyright 2009 Sun Microsystems, Inc. All rights reserved.
3 # Use is subject to license terms.
7 # The contents of this file are subject to the terms of the
8 # Common Development and Distribution License (the "License").
9 # You may not use this file except in compliance with the License.
11 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
12 # or http://www.opensolaris.org/os/licensing.
13 # See the License for the specific language governing permissions
14 # and limitations under the License.
16 # When distributing Covered Code, include this CDDL HEADER in each
17 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
18 # If applicable, add the following below this CDDL HEADER, with the
19 # fields enclosed by brackets "[]" replaced with your own identifying
20 # information: Portions Copyright [yyyy] [name of copyright owner]
25 # WARNING -- this package implements a Sun private interface; it may
26 # change without notice.
28 package Sun
::Solaris
::BSM
::_BSMparse
;
32 use Sun
::Solaris
::Utils
qw(gettext);
34 use vars
qw($VERSION $failedOpen
35 %EXPORT_TAGS @ISA @EXPORT_OK @EXPORT_FAIL);
41 @EXPORT_OK = qw(readAttr readEvent readClass filterLabel filterCallName
42 readControl getPathList readUser ckAttrEvent);
43 @EXPORT_FAIL = qw($failedOpen);
44 %EXPORT_TAGS = (ALL => \@EXPORT_OK);
46 $failedOpen = gettext("failed to open %s: %s");
50 my $debug = shift; # bool
51 my $filters = shift; # options for filtering
53 my $dir = '/etc/security';
54 my $attrDir = '/usr/lib/audit';
56 $attrDir = shift if (@_); # override for test
57 $configDir = shift if (@_); # ditto
60 $suffix = shift if (@_); # test, again
62 $obj = ref($obj) || $obj;
64 my ($recordf, $classf, $controlf, $eventf, $userf) =
65 ("$attrDir/audit_record_attr$suffix",
66 "$configDir/audit_class$suffix",
67 "$configDir/audit_control$suffix",
68 "$configDir/audit_event$suffix",
69 "$configDir/audit_user$suffix");
72 'attrFile' => $recordf,
73 'classFile' => $classf,
74 'classFilter' => $filters->{'classFilter'},
75 'controlFile' => $controlf,
77 'eventFile' => $eventf,
78 'eventFilter' => $filters->{'eventFilter'},
79 'idFilter' => $filters->{'idFilter'},
81 'kernelDefault' => '',
83 'userFile' => $userf}, $obj);
87 # read the hand edited attrFile file
89 # return a hash reference
94 my $file = $obj->{'attrFile'};
95 my $fileHandle = do {local *FileHandle; *FileHandle};
96 open($fileHandle, $file) or die sprintf("$failedOpen\n", $file, $!);
107 my $description = 'none';
117 my $classFilter = $obj->{'classFilter'};
118 $classFilter = '' unless (defined ($classFilter));
121 while (<$fileHandle>) {
123 s/#.*//; # remove comment
126 if ($attrState < 0) { # initial state: header info
127 # continue assigning lines to multiline macros
129 if ( $lastMacro ne '' ) {
130 my ($mcr, $attr) = split(/\s*:\s*/, $lastMacro);
132 if ($mcr eq "message") {
133 chomp($noteAlias{$attr});
134 chop($noteAlias{$attr});
137 $noteAlias{$attr} .= $1;
139 $lastMacro = chkBslash($lastMacro, \$1);
145 if (/^\s*skipClass\s*=\s*(.*)/i) {
147 # don't skip what you're searching for
148 next if (index(lc($classFilter),lc($class)) > -1);
152 elsif (/^\s*token\s*=\s*(.*)/i) {
153 my ($attr, $value) = split(/\s*:\s*/, $1);
154 $token{$attr} = $value;
157 elsif (/^\s*message\s*=\s*(.*)/i) {
158 my ($attr, $value) = split(/\s*:\s*/, $1);
159 $noteAlias{$attr} = $value;
160 $lastMacro = chkBslash("message:$attr", \$1);
163 elsif (/^\s*kernel\s*=\s*(.*)/i) {
164 my ($attr, $value) = split(/\s*:\s*/, $1);
165 $obj->{'kernelDefault'} = $1;
168 elsif (/^\s*user\s*=\s*(.*)/i) {
169 my ($attr, $value) = split(/\s*:\s*/, $1);
170 $obj->{'userDefault'} = $1;
175 # continue assigning lines to multiline attributes
176 # type: case, comment, note, format
177 if ( $lastAttr ne '' ) {
180 eval "\$curAttrVal = \$$lastAttr";
187 eval "\$$lastAttr = \$curAttrVal";
189 $lastAttr = chkBslash($lastAttr, \$1);
194 if (/^\s*label\s*=\s*(.*)/i) {
195 $attrState = 0 if ($attrState < 0);
198 if ($obj->{'debug'}) {
200 $newLabel is duplicated in the attribute file (line $.)
201 } if ($attr{$newLabel});
203 # if $attrState not zero, an unwritten record exists
205 $callName = $obj->filterCallName($label,
207 push(@case, [$case, $format, $comment, $note]);
209 if ($obj->filterLabel($label)) {
211 [$callName, $description, $title,
215 $format = $description = $title = 'none';
216 $case = $note = $comment = $skip = $callName
224 elsif (/^\s*skip\s*=\s*(.*)/i) {
227 elsif (/^\s*syscall\s*=\s*(.*)/i) {
230 elsif (/^\s*program\s*=\s*(.*)/i) {
233 elsif (/^\s*title\s*=\s*(.*)/i) {
236 elsif (/^\s*see\s*=\s*(.*)/i) {
239 elsif (/^\s*format\s*=\s*(.*)/i) {
241 $lastAttr = chkBslash("format", \$1);
243 elsif (/^\s*comment\s*=\s*(.*)/i) {
245 $lastAttr = chkBslash("comment", \$1);
247 elsif (/^\s*note\s*=\s*(.*)/i) {
249 $lastAttr = chkBslash("note", \$1);
251 elsif (/^\s*case\s*=\s*(.*)/i) {
253 push(@case, [$case, $format, $comment, $note]);
255 $comment = $note = '';
258 $lastAttr = chkBslash("case", \$1);
263 $callName = $obj->filterCallName($label, $callName);
264 push(@case, [$case, $format, $comment, $note]);
265 if ($obj->filterLabel($label)) {
266 $attr{$label} = [$callName, $description, $title, $skip,
272 print STDERR "found $count audit attribute entries\n" if ($obj->{'debug'});
274 return ($obj->{'attr'} = \%attr, \%token, \%skipClass, \%noteAlias);
278 # read eventFile and extract audit event information, including
279 # which classes are associated with each event and what call is
286 my $file = $obj->{'eventFile'};
288 my $fileHandle = do {local *FileHandle; *FileHandle};
289 open($fileHandle, $file) or die sprintf("$failedOpen\n", $file, $!);
293 unless (defined $obj->{'class'} && (scalar keys %{$obj->{'class'}} > 1)) {
297 my @classFilterMasks = ();
298 my $classFilter = $obj->{'classFilter'};
300 foreach (split(',', $classFilter)) {
301 push @classFilterMasks, $obj->{'class'}{$_};
304 # ignore customer-supplied audit events (id > 32767)
306 while (<$fileHandle>) {
308 s/#.*//; # remove comment
310 if (/^\s*(\d+):(\w+):([^:]+):(.*)/) {
313 my $description = $3;
317 print STDERR "$id is not numeric (line $.)\n";
320 next if ($id > 32767);
324 if ($obj->{'debug'}) {
326 $label is duplicated in the event file (line $.)
327 } if ($event{$label});
329 next unless ($obj->filterLabel($label));
332 foreach (split(/\s*,\s*/, $class)) {
333 $mask |= $obj->{'class'}{$_};
336 foreach my $filterMask (@classFilterMasks) {
337 unless ($mask & $filterMask) {
344 if ($obj->{'idFilter'}) {
345 next unless ($obj->{'idFilter'} == $id);
347 $event{$label} = [$id, $class, $description];
353 print STDERR "found $count audit events\n" if ($obj->{'debug'});
355 return ($obj->{'event'} = \%event);
359 # read classFile and extract audit class information
365 my $file = $obj->{'classFile'};
367 my $fileHandle = do {local *FileHandle; *FileHandle};
368 open($fileHandle, $file) or die sprintf("$failedOpen\n", $file, $!);
372 while (<$fileHandle>) {
374 s/#.*//; # remove comment
376 my ($mask1, $class) = split(/:/); # third field not used
377 my $mask2 = hex($mask1); # integer
378 $class{$class} = $mask2;
382 print STDERR "found $count audit classes\n" if ($obj->{'debug'});
384 return ($obj->{'class'} = \%class);
391 my $eventFilter = $obj->{'eventFilter'};
394 $keepIt = 0 if ($eventFilter && ($label !~ /$eventFilter/i));
399 # Normally, the root of the event label is the system call. The
400 # attrFile attribute syscall or program overrides this.
405 my $callName = shift;
407 return ($callName) if ($callName);
409 $label =~ /AUE_(.*)/;
417 # read controlFile and extract flags and naflags information
418 # at present, minfree, maxfree and the audit partitions are not
423 my $failMode = shift;
427 my $file = $obj->{'controlFile'};
428 my $invalidClass = gettext('invalid class, %s, in audit_control: %s');
430 my $fileHandle = do {local *FileHandle; *FileHandle};
431 unless (open($fileHandle, $file)) {
432 die sprintf("$failedOpen\n", $file, $!)
433 unless ($failMode eq 'ignore');
436 my %class = $obj->{'class'};
437 my @paths = $obj->{'paths'};
438 while (<$fileHandle>) {
440 s/#.*//; # remove comment
442 if ((/^\s*flags:/i) || (/^\s*naflags:/i)) {
443 my ($class) = /flags:\s*(.*)/;
444 my @class = split(/\s*,\s*/, $class);
446 foreach $class (@class) {
447 $class =~ s/^[-+^]+//;
448 unless (defined ($class{$class})) {
450 sprintf("$invalidClass\n",
456 elsif (/^\s*dir:\s*(.*)/) {
458 $obj->{'havePath'} = 1;
462 return ($cError, $errors);
468 $obj->readControl() unless ($obj->{'havePath'});
470 return ($obj->{'paths'});
474 # read userFile and extract audit information for validation
478 my $failMode = shift;
482 my $file = $obj->{'userFile'};
484 my $fileHandle = do {local *FileHandle; *FileHandle};
485 unless (open($fileHandle, $file)) {
486 die sprintf("$failedOpen\n", $file, $!)
487 unless ($failMode eq 'ignore');
490 # these strings are defined here mostly to avoid indentation problems
491 my $emptyErr = gettext('empty audit mask in audit_user: %s');
492 my $syntaxErr1 = gettext(
493 'incorrect syntax (exactly two colons req\'d) in audit_user: %s');
494 my $syntaxErr2 = gettext('incorrect syntax in audit_user: %s');
495 my $invalidErr = gettext('invalid class, %s, in audit_user: %s');
496 my $undefined = gettext('undefined user name in audit_user: %s');
498 my %class = $obj->{'class'};
499 while (<$fileHandle>) {
501 s/#.*//; # remove comment
503 my $colonCount = tr/:/:/;
505 if ($colonCount != 2) {
506 $error .= sprintf("$syntaxErr1\n", $_);
509 my ($user, $always, $never) = split(/\s*:\s*/);
510 unless (defined($user)) {
511 $error .= sprintf("$syntaxErr2\n", $_);
515 $error .= sprintf("$emptyErr\n", $_) unless ($always);
517 my ($name) = getpwnam($user);
518 unless (defined($name)) {
519 $error .= sprintf("$undefined\n", $user);
522 unless (defined($always) && defined($never)) {
523 $error .= sprintf("$emptyErr\n", $_);
527 my $verify = $always . ',' . $never;
528 my @class = split(/\s*,\s*/, $verify);
531 foreach $thisClass (@class) {
532 $thisClass =~ s/^[-+^]+//;
533 unless (defined $class{$thisClass}) {
534 $error .= sprintf("$invalidErr\n", $thisClass,
541 return ($cError, $error);
544 # ckAttrEvent complains if controlFile and attrFile don''t contain the
545 # same list of events.
554 my $attrErr = gettext(
555 '%s entry in attribute file but not in event file');
556 my $eventErr = gettext(
557 '%s entry in event file but not in attribute file');
559 my %attr = %{$obj->{'attr'}};
560 my %event = %{$obj->{'event'}};
561 foreach $label (keys %attr) {
563 unless ($event{$label}) {
564 $error .= sprintf("$attrErr\n", $label);
569 foreach $label (keys %event) {
571 unless ($attr{$label}) {
572 $error .= sprintf("$eventErr\n", $label);
576 # debug only; not I18N'd
578 "$cAttr audit_record_attr entries and $cEvent audit_event entries\n"
579 if ($obj->{'debug'});
580 return ($cError, $error);
584 # check the given string for backslash character at the end; if found
585 # return the string sent as a first argument, otherwise return empty
591 if ( $$strPtr !~ /\\$/ ) {