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 = ' ' 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/:/:/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