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