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