1# 2# Copyright 2009 Sun Microsystems, Inc. All rights reserved. 3# Use is subject to license terms. 4# 5# CDDL HEADER START 6# 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. 10# 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. 15# 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] 21# 22# CDDL HEADER END 23# 24 25# WARNING -- this package implements a Sun private interface; it may 26# change without notice. 27 28package Sun::Solaris::BSM::_BSMparse; 29require 5.005; 30use strict; 31use Exporter; 32use Sun::Solaris::Utils qw(gettext); 33 34use vars qw($VERSION $failedOpen 35 %EXPORT_TAGS @ISA @EXPORT_OK @EXPORT_FAIL); 36 37$VERSION = '1.01'; 38 39@ISA = qw(Exporter); 40my @constants = qw(); 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); 45 46$failedOpen = gettext("failed to open %s: %s"); 47 48sub new { 49 my $obj = shift; 50 my $debug = shift; # bool 51 my $filters = shift; # options for filtering 52 53 my $dir = '/etc/security'; 54 my $attrDir = '/usr/lib/audit'; 55 my $configDir = $dir; 56 $attrDir = shift if (@_); # override for test 57 $configDir = shift if (@_); # ditto 58 59 my $suffix = ''; 60 $suffix = shift if (@_); # test, again 61 62 $obj = ref($obj) || $obj; 63 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"); 70 71 return (bless { 72 'attrFile' => $recordf, 73 'classFile' => $classf, 74 'classFilter' => $filters->{'classFilter'}, 75 'controlFile' => $controlf, 76 'debug' => $debug, 77 'eventFile' => $eventf, 78 'eventFilter' => $filters->{'eventFilter'}, 79 'idFilter' => $filters->{'idFilter'}, 80 'havePath' => 0, 81 'kernelDefault' => '', 82 'userDefault' => '', 83 'userFile' => $userf}, $obj); 84} 85 86# readAttr 87# read the hand edited attrFile file 88# 89# return a hash reference 90 91sub readAttr { 92 my $obj = shift; 93 94 my $file = $obj->{'attrFile'}; 95 my $fileHandle = do {local *FileHandle; *FileHandle}; 96 open($fileHandle, $file) or die sprintf("$failedOpen\n", $file, $!); 97 98 my $count = 0; 99 my $lastAttr = ''; 100 my $lastMacro = ''; 101 102 my $attrState = -1; 103 my $caseState = 0; 104 my $label; 105 my $callName = ''; 106 my $skip = ''; 107 my $description = 'none'; 108 my $format = 'none'; 109 my $comment = ''; 110 my $title = 'none'; 111 my $note = ''; 112 my $case = ''; 113 my @case = (); 114 my %skipClass; 115 my %attr = (); 116 my %token = (); 117 my $classFilter = $obj->{'classFilter'}; 118 $classFilter = '' unless (defined ($classFilter)); 119 120 my %noteAlias = (); 121 while (<$fileHandle>) { 122 chomp; 123 s/#.*//; # remove comment 124 next if (/^\s*$/); 125 126 if ($attrState < 0) { # initial state: header info 127 # continue assigning lines to multiline macros 128 # type: message 129 if ( $lastMacro ne '' ) { 130 my ($mcr, $attr) = split(/\s*:\s*/, $lastMacro); 131 132 if ($mcr eq "message") { 133 chomp($noteAlias{$attr}); 134 chop($noteAlias{$attr}); 135 136 $_ =~ /^\s*(.*)/i; 137 $noteAlias{$attr} .= $1; 138 139 $lastMacro = chkBslash($lastMacro, \$1); 140 } 141 next; 142 } 143 144 $lastMacro = ''; 145 if (/^\s*skipClass\s*=\s*(.*)/i) { 146 my $class = $1; 147 # don't skip what you're searching for 148 next if (index(lc($classFilter),lc($class)) > -1); 149 $skipClass{$1} = 1; 150 next; 151 } 152 elsif (/^\s*token\s*=\s*(.*)/i) { 153 my ($attr, $value) = split(/\s*:\s*/, $1); 154 $token{$attr} = $value; 155 next; 156 } 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); 161 next; 162 } 163 elsif (/^\s*kernel\s*=\s*(.*)/i) { 164 my ($attr, $value) = split(/\s*:\s*/, $1); 165 $obj->{'kernelDefault'} = $1; 166 next; 167 } 168 elsif (/^\s*user\s*=\s*(.*)/i) { 169 my ($attr, $value) = split(/\s*:\s*/, $1); 170 $obj->{'userDefault'} = $1; 171 next; 172 } 173 } 174 175 # continue assigning lines to multiline attributes 176 # type: case, comment, note, format 177 if ( $lastAttr ne '' ) { 178 my $curAttrVal = ''; 179 180 eval "\$curAttrVal = \$$lastAttr"; 181 chomp($curAttrVal); 182 chop($curAttrVal); 183 184 $_ =~ /^\s*(.*)/i; 185 $curAttrVal .= $1; 186 187 eval "\$$lastAttr = \$curAttrVal"; 188 189 $lastAttr = chkBslash($lastAttr, \$1); 190 next; 191 } 192 193 $lastAttr = ''; 194 if (/^\s*label\s*=\s*(.*)/i) { 195 $attrState = 0 if ($attrState < 0); 196 my $newLabel = $1; 197 198 if ($obj->{'debug'}) { 199 print STDERR qq{ 200$newLabel is duplicated in the attribute file (line $.) 201 } if ($attr{$newLabel}); 202 } 203 # if $attrState not zero, an unwritten record exists 204 if ($attrState) { 205 $callName = $obj->filterCallName($label, 206 $callName); 207 push(@case, [$case, $format, $comment, $note]); 208 209 if ($obj->filterLabel($label)) { 210 $attr{$label} = 211 [$callName, $description, $title, 212 $skip, @case]; 213 $count++; 214 } 215 $format = $description = $title = 'none'; 216 $case = $note = $comment = $skip = $callName 217 = ''; 218 @case = (); 219 $caseState = 0; 220 } 221 $label = $newLabel; 222 $attrState = 1; 223 } 224 elsif (/^\s*skip\s*=\s*(.*)/i) { 225 $skip = $1; 226 } 227 elsif (/^\s*syscall\s*=\s*(.*)/i) { 228 $callName = $1; 229 } 230 elsif (/^\s*program\s*=\s*(.*)/i) { 231 $callName = $1; 232 } 233 elsif (/^\s*title\s*=\s*(.*)/i) { 234 $title = $1; 235 } 236 elsif (/^\s*see\s*=\s*(.*)/i) { 237 $description = $1; 238 } 239 elsif (/^\s*format\s*=\s*(.*)/i) { 240 $format = $1; 241 $lastAttr = chkBslash("format", \$1); 242 } 243 elsif (/^\s*comment\s*=\s*(.*)/i) { 244 $comment .= $1; 245 $lastAttr = chkBslash("comment", \$1); 246 } 247 elsif (/^\s*note\s*=\s*(.*)/i) { 248 $note .= $1; 249 $lastAttr = chkBslash("note", \$1); 250 } 251 elsif (/^\s*case\s*=\s*(.*)/i) { 252 if ($caseState) { 253 push(@case, [$case, $format, $comment, $note]); 254 $format = 'none'; 255 $comment = $note = ''; 256 } 257 $case = $1; 258 $lastAttr = chkBslash("case", \$1); 259 $caseState = 1; 260 } 261 } 262 if ($attrState) { 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, 267 @case]; 268 $count++; 269 } 270 } 271 close $fileHandle; 272 print STDERR "found $count audit attribute entries\n" if ($obj->{'debug'}); 273 274 return ($obj->{'attr'} = \%attr, \%token, \%skipClass, \%noteAlias); 275} 276 277# readEvent 278# read eventFile and extract audit event information, including 279# which classes are associated with each event and what call is 280# related. 281 282sub readEvent { 283 my $obj = shift; 284 285 my %event = (); 286 my $file = $obj->{'eventFile'}; 287 288 my $fileHandle = do {local *FileHandle; *FileHandle}; 289 open($fileHandle, $file) or die sprintf("$failedOpen\n", $file, $!); 290 291 my $count = 0; 292 293 unless (defined $obj->{'class'} && (scalar keys %{$obj->{'class'}} > 1)) { 294 $obj->readClass(); 295 } 296 297 my @classFilterMasks = (); 298 my $classFilter = $obj->{'classFilter'}; 299 if ($classFilter) { 300 foreach (split(',', $classFilter)) { 301 push @classFilterMasks, $obj->{'class'}{$_}; 302 } 303 } 304 # ignore customer-supplied audit events (id > 32767) 305 306 while (<$fileHandle>) { 307 chomp; 308 s/#.*//; # remove comment 309 next if (/^\s*$/); 310 if (/^\s*(\d+):(\w+):([^:]+):(.*)/) { 311 my $id = $1; 312 my $label = $2; 313 my $description = $3; 314 my $class = $4; 315 316 if ($id !~ /\d+/) { 317 print STDERR "$id is not numeric (line $.)\n"; 318 next; 319 } 320 next if ($id > 32767); 321 322 $class =~ s/\s*$//; 323 324 if ($obj->{'debug'}) { 325 print STDERR qq{ 326$label is duplicated in the event file (line $.) 327 } if ($event{$label}); 328 } 329 next unless ($obj->filterLabel($label)); 330 my $mask = 0; 331 if ($classFilter) { 332 foreach (split(/\s*,\s*/, $class)) { 333 $mask |= $obj->{'class'}{$_}; 334 } 335 my $skip = 0; 336 foreach my $filterMask (@classFilterMasks) { 337 unless ($mask & $filterMask) { 338 $skip = 1; 339 last; 340 } 341 } 342 next if $skip; 343 } 344 if ($obj->{'idFilter'}) { 345 next unless ($obj->{'idFilter'} == $id); 346 } 347 $event{$label} = [$id, $class, $description]; 348 349 $count++; 350 } 351 } 352 close $fileHandle; 353 print STDERR "found $count audit events\n" if ($obj->{'debug'}); 354 355 return ($obj->{'event'} = \%event); 356} 357 358# readClass 359# read classFile and extract audit class information 360 361sub readClass { 362 my $obj = shift; 363 364 my %class = (); 365 my $file = $obj->{'classFile'}; 366 367 my $fileHandle = do {local *FileHandle; *FileHandle}; 368 open($fileHandle, $file) or die sprintf("$failedOpen\n", $file, $!); 369 370 my $count = 0; 371 372 while (<$fileHandle>) { 373 chomp; 374 s/#.*//; # remove comment 375 next if (/^\s*$/); 376 my ($mask1, $class) = split(/:/); # third field not used 377 my $mask2 = hex($mask1); # integer 378 $class{$class} = $mask2; 379 $count++; 380 } 381 close $fileHandle; 382 print STDERR "found $count audit classes\n" if ($obj->{'debug'}); 383 384 return ($obj->{'class'} = \%class); 385} 386 387sub filterLabel { 388 my $obj = shift; 389 my $label = shift; 390 391 my $eventFilter = $obj->{'eventFilter'}; 392 my $keepIt = 1; 393 394 $keepIt = 0 if ($eventFilter && ($label !~ /$eventFilter/i)); 395 396 return ($keepIt); 397} 398 399# Normally, the root of the event label is the system call. The 400# attrFile attribute syscall or program overrides this. 401 402sub filterCallName { 403 my $obj = shift; 404 my $label = shift; 405 my $callName = shift; 406 407 return ($callName) if ($callName); 408 409 $label =~ /AUE_(.*)/; 410 411 my $name = $1; 412 413 return (lc ($name)); 414} 415 416# readControl 417# read controlFile and extract flags and naflags information 418# at present, minfree, maxfree and the audit partitions are not 419# checked. 420 421sub readControl { 422 my $obj = shift; 423 my $failMode = shift; 424 425 my $cError = 0; 426 my $errors = ''; 427 my $file = $obj->{'controlFile'}; 428 my $invalidClass = gettext('invalid class, %s, in audit_control: %s'); 429 430 my $fileHandle = do {local *FileHandle; *FileHandle}; 431 unless (open($fileHandle, $file)) { 432 die sprintf("$failedOpen\n", $file, $!) 433 unless ($failMode eq 'ignore'); 434 return (0, ''); 435 } 436 my %class = $obj->{'class'}; 437 my @paths = $obj->{'paths'}; 438 while (<$fileHandle>) { 439 chomp; 440 s/#.*//; # remove comment 441 next if (/^\s*$/); 442 if ((/^\s*flags:/i) || (/^\s*naflags:/i)) { 443 my ($class) = /flags:\s*(.*)/; 444 my @class = split(/\s*,\s*/, $class); 445 446 foreach $class (@class) { 447 $class =~ s/^[-+^]+//; 448 unless (defined ($class{$class})) { 449 $errors .= 450 sprintf("$invalidClass\n", 451 $class, $_); 452 $cError++; 453 } 454 } 455 } 456 elsif (/^\s*dir:\s*(.*)/) { 457 push (@paths, $1); 458 $obj->{'havePath'} = 1; 459 } 460 } 461 close $fileHandle; 462 return ($cError, $errors); 463} 464 465sub getPathList { 466 my $obj = shift; 467 468 $obj->readControl() unless ($obj->{'havePath'}); 469 470 return ($obj->{'paths'}); 471} 472 473# readUser 474# read userFile and extract audit information for validation 475 476sub readUser { 477 my $obj = shift; 478 my $failMode = shift; 479 480 my $cError = 0; 481 my $error = ''; 482 my $file = $obj->{'userFile'}; 483 484 my $fileHandle = do {local *FileHandle; *FileHandle}; 485 unless (open($fileHandle, $file)) { 486 die sprintf("$failedOpen\n", $file, $!) 487 unless ($failMode eq 'ignore'); 488 return (0, ''); 489 } 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'); 497 498 my %class = $obj->{'class'}; 499 while (<$fileHandle>) { 500 chomp; 501 s/#.*//; # remove comment 502 next if (/^\s*$/); 503 my $colonCount = tr/:/:/; 504 505 if ($colonCount != 2) { 506 $error .= sprintf("$syntaxErr1\n", $_); 507 $cError++; 508 } 509 my ($user, $always, $never) = split(/\s*:\s*/); 510 unless (defined($user)) { 511 $error .= sprintf("$syntaxErr2\n", $_); 512 $cError++; 513 next; 514 } 515 $error .= sprintf("$emptyErr\n", $_) unless ($always); 516 517 my ($name) = getpwnam($user); 518 unless (defined($name)) { 519 $error .= sprintf("$undefined\n", $user); 520 $cError++; 521 } 522 unless (defined($always) && defined($never)) { 523 $error .= sprintf("$emptyErr\n", $_); 524 $cError++; 525 next; 526 } 527 my $verify = $always . ',' . $never; 528 my @class = split(/\s*,\s*/, $verify); 529 my $thisClass; 530 531 foreach $thisClass (@class) { 532 $thisClass =~ s/^[-+^]+//; 533 unless (defined $class{$thisClass}) { 534 $error .= sprintf("$invalidErr\n", $thisClass, 535 $_); 536 $cError++; 537 } 538 } 539 } 540 close $fileHandle; 541 return ($cError, $error); 542} 543 544# ckAttrEvent complains if controlFile and attrFile don''t contain the 545# same list of events. 546 547sub ckAttrEvent { 548 my $obj = shift; 549 550 my $cError = 0; 551 my $error = ''; 552 my $cAttr = 0; 553 my $label; 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'); 558 559 my %attr = %{$obj->{'attr'}}; 560 my %event = %{$obj->{'event'}}; 561 foreach $label (keys %attr) { 562 $cAttr++; 563 unless ($event{$label}) { 564 $error .= sprintf("$attrErr\n", $label); 565 $cError++; 566 } 567 } 568 my $cEvent = 0; 569 foreach $label (keys %event) { 570 $cEvent++; 571 unless ($attr{$label}) { 572 $error .= sprintf("$eventErr\n", $label); 573 $cError++; 574 } 575 } 576 # debug only; not I18N'd 577 print STDERR 578 "$cAttr audit_record_attr entries and $cEvent audit_event entries\n" 579 if ($obj->{'debug'}); 580 return ($cError, $error); 581} 582 583# chkBslash (helper) 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 586# string. 587sub chkBslash ($$) { 588 my $retStr = shift; 589 my $strPtr = shift; 590 591 if ( $$strPtr !~ /\\$/ ) { 592 $retStr = ''; 593 } 594 595 return $retStr; 596} 597 5981; 599