#!/usr/bin/perl -w # ---------------------------------------------------------------------- # Copyright (c) 2005 Novell, Inc. All Rights Reserved. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, contact Novell, Inc. # # To contact Novell about this file by physical or electronic mail, # you may find current contact information at www.novell.com. # ---------------------------------------------------------------------- use strict; use Data::Dumper; use DBI; use Fcntl; use File::Temp qw(tempfile); use Getopt::Long; use POSIX 'setsid'; use Time::Local; use File::Tail; use Immunix::Severity; require LibAppArmor; ########################################################################## # locations my $productname = "apparmor"; my $cfgdir = "/etc/$productname"; my $dbdir = "/var/log/$productname"; my $cfgfile = "$cfgdir/notify.cfg"; my $errlog = "$dbdir/event-dispatch.log"; my $logfile = "/var/log/audit/audit.log"; my $syslogfile = "/var/log/messages"; ########################################################################## # options variables my $pidfile = ''; GetOptions('pidfile|p=s' => \$pidfile); my $DEBUG = 0; my $config; my $verbose = { last_notify => 0 }; my $summary = { last_notify => 0 }; my $terse = { last_notify => 0 }; # we don't want to call str2time on every line and also batch up event dbs # a month at a time, so we need to keep track of a few extra things my $timestamp = 0; my $lasttime = ""; my $counter = 0; my $thismonth = 0; my $nextmonth = 0; # pop open a connection to the severity database my $sevdb = new Immunix::Severity("$cfgdir/severity.db", -1); my $REdate = '\w{3}\s+\d+\s+\d{2}:\d{2}:\d{2}'; my $last_inserted_time; my $last_inserted_counter; ########################################################################## # commit how often? my $timeout = 5; # keep track of when we commited last my $last_flush_time = 0; # keep track of some statistics my $max = 0; my $inserts = 0; my $total = 0; my @commit_buffer; my @debug_buffer; my @verbose_buffer; my @summary_buffer; my @terse_buffer; my $date_module = "None"; my %templates = ( "path" => "(time,counter,type,op,profile,sdmode,mode_req,mode_deny,resource,prog,pid,severity) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)", "link" => "(time,counter,type,op,profile,sdmode,resource,target,prog,pid,severity) VALUES(?,?,?,?,?,?,?,?,?,?,?)", "chattr" => "(time,counter,type,op,profile,sdmode,resource,mode_req,mode_deny,prog,pid,severity) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)", "capability" => "(time,counter,type,op,profile,sdmode,resource,prog,pid,severity) VALUES(?,?,?,?,?,?,?,?,?,?)", "capable" => "(time,counter,type,op,prog,pid,profile) VALUES(?,?,?,?,?,?,?)", "unknown_hat" => "(time,counter,type,op,profile,sdmode,resource,pid) VALUES(?,?,?,?,?,?,?,?)", "fork" => "(time,counter,type,op,profile,sdmode,pid,resource) VALUES(?,?,?,?,?,?,?,?)", "changing_profile" => "(time,counter,type,op,profile,sdmode,pid) VALUES(?,?,?,?,?,?,?)", "profile_replacement" => "(time,counter,type,op,profile,sdmode,prog,pid,severity) VALUES(?,?,?,?,?,?,?,?,?)", "net" => "(time,counter,type,op,net_family,net_socktype,net_proto,pid,profile) VALUES(?,?,?,?,?,?,?,?,?)", "removed" => "(time,counter,type,op,severity) VALUES(?,?,?,?,?)", "initialized" => "(time,counter,type,op,resource,severity) VALUES(?,?,?,?,?,?)", "ctrl_var" => "(time,counter,type,op,resource,mode_req,mode_deny,severity) VALUES(?,?,?,?,?,?,?,?)", "profile_load" => "(time,counter,type,op,resource,prog,pid) VALUES(?,?,?,?,?,?,?)", ); ########################################################################## # generic functions sub errlog ($) { my $mesg = shift; my $localtime = localtime(time); print ERRLOG "[$localtime] $mesg\n"; } sub readconfig () { my $cfg = {}; # record when we read the config file $cfg->{load_time} = time; if (open(CFG, $cfgfile)) { # yank in the values we need while () { $cfg->{$1} = $2 if /^(\S+)\s+(.+)\s*$/; } close(CFG); } return $cfg; } sub daemonize { chdir '/' or die "Can't chdir to /: $!"; open STDIN, '/dev/null' or die "Can't read /dev/null: $!"; open STDOUT, '>/dev/null' or die "Can't write to /dev/null: $!"; defined(my $pid = fork) or die "Can't fork: $!"; exit if $pid; setsid or die "Can't start a new session: $!"; open STDERR, '>&STDOUT' or die "Can't dup stdout: $!"; } sub parsedate ($) { my $time = shift; my $timestamp = 0; if ($date_module eq 'TimeDate') { $timestamp = Date::Parse::str2time($time); } elsif ($date_module eq 'DateManip') { $timestamp = Date::Manip::UnixDate(Date::Manip::ParseDateString($time), '%s'); } else { errlog "No date module found, exiing"; kill HUP => -$$; } return $timestamp; } ########################################################################## # database handling functions sub connect_database ($) { my $dbdir = shift; my $dbh = DBI->connect("dbi:SQLite:dbname=$dbdir/events.db", "", "", {RaiseError=>1}); # we'll do the commits ourselves so performance doesn't suck $dbh->{AutoCommit} = 0; # bump up our cache size a little $dbh->do("PRAGMA cache_size = 20000;"); # figure out if the tables already exist or not my %existing_tables; my $sth = $dbh->prepare("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;"); $sth->execute; while (my @row = $sth->fetchrow_array) { $existing_tables{ $row[0] } = 1; } $sth->finish; # create the info table and fill in the appropriate values for this db unless ($existing_tables{info}) { my $host = `hostname -f`; chomp $host; $dbh->do("CREATE TABLE info (name,value)"); $sth = $dbh->prepare("INSERT INTO info(name,value) VALUES(?,?)"); $sth->execute("version", "0.2"); $sth->execute("host", "$host"); } # create the events table unless ($existing_tables{events}) { $dbh->do( "CREATE TABLE events ( id INTEGER PRIMARY KEY AUTOINCREMENT, time INTEGER NOT NULL, counter INTEGER NOT NULL, op, pid, sdmode, type, mode_deny, mode_req, resource, target, profile, prog, name_alt, attr, parent, active_hat, net_family, net_proto, net_socktype, severity INTEGER )" ); # set up the indexes we want #my @indexes = qw(time type sdmode mode resource profile prog severity); my @indexes = qw(time type op sdmode mode_req mode_deny resource profile prog severity); for my $index (@indexes) { $dbh->do("CREATE INDEX " . $index . "_idx ON events($index)"); } } # make sure our changes actually get saved $dbh->commit || errlog "Error commiting changes: $!"; # mark the db as up to date as of now $last_flush_time = time; return $dbh; } ########################################################################## sub verbose_notify_handler { my ($email, $file, $last, $level, $unknown) = @_; $last = localtime($last); my $now = time; my $host = `hostname -f`; chomp $host; my $subj = "Verbose Security Report for $host."; my $mesg = "The following security events occured since $last:\n\n"; my @events; if (open(V, $file)) { while () { chomp; if (/^(\d+) (\d+) (.+)$/) { my ($timestamp, $counter, $logmsg) = ($1, $2, $3); push @events, [ $timestamp, $counter ]; $mesg .= "$logmsg\n"; } } close(V); if (@events) { if ($DEBUG) { my $count = scalar @events; errlog "[$count events] sending verbose notification to $email."; } # actually send out the notification... open(MAIL, "| sendmail -F 'AppArmor Security Notification' $email"); print MAIL "To: $email\n"; print MAIL "Subject: $subj\n\n"; print MAIL "$mesg\n"; print MAIL ".\n"; close(MAIL); } # delete the verbose notification logfile once we've processed it unlink($file); } } sub summary_notify_handler { my ($email, $file, $last, $level, $unknown) = @_; $last = localtime($last); my $now = time; my $host = `hostname -f`; chomp $host; my $subj = "Summary Security Report for $host."; my $mesg = "The following security events occured since $last:\n\n"; my @events; if (open(V, $file)) { while () { chomp; if (/^(\d+) (\d+) (.+)$/) { my ($timestamp, $counter, $logmsg) = ($1, $2, $3); push @events, [ $timestamp, $counter ]; $mesg .= "$logmsg\n"; } } close(V); if (@events) { if ($DEBUG) { my $count = scalar @events; errlog "[$count events] sending summary notification to $email."; } # actually send out the notification... open(MAIL, "| sendmail -F 'AppArmor Security Notification' $email"); print MAIL "To: $email\n"; print MAIL "Subject: $subj\n\n"; print MAIL "$mesg\n"; print MAIL ".\n"; close(MAIL); } # delete the verbose notification logfile once we've processed it unlink($file); } } sub terse_notify_handler { my ($email, $file, $last, $level, $unknown) = @_; $last = localtime($last); my $now = time; my $host = `hostname -f`; chomp $host; my @events; my $count = 0; if (open(V, $file)) { while () { chomp; if (/^(\d+) (\d+) (.+)$/) { my ($timestamp, $counter, $logmsg) = ($1, $2, $3); push @events, [ $timestamp, $counter ]; $count++; } } close(V); if ($count) { if ($DEBUG) { errlog "[$count events] sending terse notification to $email."; } my $subj = "Security Report for $host."; my $mesg = "$host has had $count security events since $last."; # actually send out the notification... open(MAIL, "| sendmail -F 'AppArmor Security Notification' $email"); print MAIL "To: $email\n"; print MAIL "Subject: $subj\n\n"; print MAIL "$mesg\n"; print MAIL ".\n"; close(MAIL); } # delete the terse notification logfile once we've processed it unlink($file); } } sub fork_into_background { my ($name, $func, @args) = @_; my $pid = fork; if (not defined $pid) { # something bad happened, just log it... errlog "couldn't fork for \"$name\": $!" } elsif ($pid == 0) { # we're in the child process now... # set our process name $0 = $name; # call our subroutine my $ret = &$func(@args); exit($ret); } return $pid; } ########################################################################## # Parse event record into key-value pairs sub parseEvent($) { my %ev = (); my $msg = shift; chomp($msg); my $event = LibAppArmor::parse_record($msg); # resource is an alternate term for 'name1' below # mode is an alternate term for 'mode_deny' below $ev{'time'} = LibAppArmor::aa_log_record::swig_epoch_get($event); $ev{'op'} = LibAppArmor::aa_log_record::swig_operation_get($event); $ev{'pid'} = LibAppArmor::aa_log_record::swig_pid_get($event); $ev{'mode_deny'} = LibAppArmor::aa_log_record::swig_denied_mask_get($event); $ev{'mode_req'} = LibAppArmor::aa_log_record::swig_requested_mask_get($event); $ev{'profile'}= LibAppArmor::aa_log_record::swig_profile_get($event); $ev{'prog'} = LibAppArmor::aa_log_record::swig_name_get($event); $ev{'name2'} = LibAppArmor::aa_log_record::swig_name2_get($event); $ev{'attr'} = LibAppArmor::aa_log_record::swig_attribute_get($event); $ev{'parent'} = LibAppArmor::aa_log_record::swig_parent_get($event); $ev{'magic_token'} = LibAppArmor::aa_log_record::swig_magic_token_get($event); $ev{'resource'} = LibAppArmor::aa_log_record::swig_info_get($event); $ev{'active_hat'} = LibAppArmor::aa_log_record::swig_active_hat_get($event); $ev{'sdmode'} = LibAppArmor::aa_log_record::swig_event_get($event); # NetDomain if ( $ev{'op'} && $ev{'op'} =~ /socket/ ) { next if $ev{'op'} =~ /create/; $ev{'net_family'} = LibAppArmor::aa_log_record::swig_net_family_get($event); $ev{'net_proto'} = LibAppArmor::aa_log_record::swig_net_protocol_get($event); $ev{'net_socktype'} = LibAppArmor::aa_log_record::swig_net_sock_type_get($event); } LibAppArmor::free_record($event); if ( ! $ev{'time'} ) { $ev{'time'} = time; } # remove null responses for (keys(%ev)) { if ( ! $ev{$_} || $ev{$_} !~ /\w+/) {delete($ev{$_}); } #errlog "EVENT: $_ is $ev{$_}"; } if ( $ev{'sdmode'} ) { #0 = invalid, 1 = error, 2 = AUDIT, 3 = ALLOW/PERMIT, #4 = DENIED/REJECTED, 5 = HINT, 6 = STATUS/config change if ( $ev{'sdmode'} == 2 ) { $ev{'sdmode'} = "AUDITING"; } elsif ( $ev{'sdmode'} == 3 ) { $ev{'sdmode'} = "PERMITING"; } elsif ( $ev{'sdmode'} == 4 ) { $ev{'sdmode'} = "REJECTING"; } else { delete($ev{'sdmode'}); } } return \%ev; } sub process_event ($$) { my $dbh = shift; my $logmsg = shift; my $sth; my $severity = ""; my @eventList = (); my $type = undef; my $time = undef; return unless $logmsg && $logmsg =~ /APPARMOR/; my $ev = parseEvent($logmsg); # skip logprof hints if ( ! $ev->{'op'} || $ev->{'op'} eq 'clone') { return; } $time = time; # XXX - do we want current time or $ev->{'time'}? if ($time ne $lasttime) { $counter = 0; $timestamp = $time; $lasttime = $time; } $counter++; # some statistics... $max = $counter if $counter > $max; # if we already have events in the db, make sure we don't try to re-enter # duplicates if we start up again and parse the same logfile over again if ($last_inserted_time) { return if $timestamp < $last_inserted_time; if ($timestamp == $last_inserted_time) { return if $counter <= $last_inserted_counter; } $last_inserted_time = undef; } if ( $ev->{'sdmode'} && $ev->{'sdmode'} eq "REJECTING") { $severity = $sevdb->rank($ev->{'prog'}, $ev->{'mode_req'}); if ( ! $severity ) { $severity = "-1"; } # we only do notification for enforce mode events if ($config->{verbose_freq}) { if ( ($severity >= $config->{verbose_level}) || (($severity == -1) && $config->{verbose_unknown})) { push @verbose_buffer, [ $timestamp, $counter, $logmsg ]; } } if ($config->{summary_freq}) { if ( ($severity >= $config->{summary_level}) || (($severity == -1) && $config->{summary_unknown})) { push @summary_buffer, [ $timestamp, $counter, "path", $ev->{'prog'}, $ev->{'mode_req'}, $ev->{'resource'} ]; } } if ($config->{terse_freq}) { if ( ($severity >= $config->{terse_level}) || (($severity == -1) && $config->{terse_unknown})) { push @terse_buffer, [ $timestamp, $counter, "dummy" ]; } } } unless ( $ev->{'op'} ) { my $errmsg = "ERROR: No operation found: "; for my $k (sort keys(%$ev)) { $errmsg .= "$k is $ev->{$k}, "; } errlog("$errmsg\n"); return; } # Format the message to match the db template if ($ev->{'op'} eq 'link' ) { $type = 'link'; push(@eventList, [$time,$counter,$type,$ev->{'profile'},$ev->{'sdmode'}, $ev->{'resource'},$ev->{'target'},$ev->{'prog'},$ev->{'pid'},$severity]); } elsif ($ev->{'op'} eq 'attribute') { $type = 'chattr'; push(@eventList, []); push(@eventList, [$time,$counter,$type,$ev->{'op'},$ev->{'profile'},$ev->{'sdmode'}, $ev->{'resource'},$ev->{'mode_req'},$ev->{'mode_deny'},$ev->{'prog'}, $ev->{'pid'},$severity]); } elsif ($ev->{'op'} eq 'capability') { $type = 'capability'; push(@eventList, [$time,$counter,$type,$ev->{'op'},$ev->{'profile'},$ev->{'sdmode'}, $ev->{'resource'},$ev->{'prog'},$ev->{'pid'},$severity]); } elsif ($ev->{'op'} eq 'capable') { $type = 'capable'; push(@eventList, [$time,$counter,$type,$ev->{'op'},$ev->{'prog'}, $ev->{'profile'},$ev->{'pid'}]); } elsif ($ev->{'op'} =~ /ontrol variable/ ) { $type = 'ctrl_var'; push(@eventList, [$time,$counter,$type,$ev->{'op'},$ev->{'resource'}, $ev->{'mode_req'},$ev->{'mode_deny'},$severity]); } elsif ($ev->{'op'} eq 'unknown_hat') { $type = 'unknown_hat'; push(@eventList, [$time,$counter,$type,$ev->{'op'},$ev->{'profile'},$ev->{'sdmode'}, $ev->{'resource'},$ev->{'pid'},$severity]); } elsif ($ev->{'op'} eq 'fork') { $type = 'fork'; push(@eventList, [$time,$counter,$type,$ev->{'op'},$ev->{'profile'},$ev->{'sdmode'}, $ev->{'pid'},$ev->{'resource'}]); } elsif ($ev->{'op'} eq 'changing_profile') { $type = 'changing_profile'; push(@eventList, [$time,$counter,$type,$ev->{'op'},$ev->{'profile'},$ev->{'sdmode'}, $ev->{'pid'}]); } elsif ($ev->{'op'} eq 'profile_load') { $type = 'profile_load'; push(@eventList, [$time,$counter,$type,$ev->{'op'},$ev->{'resource'}, $ev->{'prog'},$ev->{'pid'}]); } elsif ($ev->{'op'} eq 'profile_replace') { $type = 'profile_replacement'; push(@eventList, [$time,$counter,$type,$ev->{'op'},$ev->{'profile'},$ev->{'sdmode'}, $ev->{'prog'},$ev->{'pid'},$severity]); } elsif ($ev->{'op'} eq 'removed') { $type = 'removed'; push(@eventList, [$time,$counter,$type,$ev->{'op'},$severity]); } elsif ($ev->{'op'} eq 'initialized') { $type = 'initialized'; push(@eventList, [$time,$counter,$type,$ev->{'op'},$ev->{'resource'},$severity]); } elsif ( $ev->{'op'} =~ /socket/) { $type = 'net'; push(@eventList, [$time,$counter,$type,$ev->{'op'},$ev->{'net_family'}, $ev->{'net_sock_type'},$ev->{'net_proto'},$ev->{'pid'},$ev->{'profile'}]); } else { $type = 'path'; if ( ! $ev->{'prog'} ) { $ev->{'prog'} = "NIL"; } push(@eventList, [$time,$counter,$type,$ev->{'op'},$ev->{'profile'}, $ev->{'sdmode'},$ev->{'mode_req'},$ev->{'mode_deny'},$ev->{'resource'}, $ev->{'prog'},$ev->{'pid'},$severity]); } push(@commit_buffer, @eventList); $inserts++; } sub dump_events { my ($which, @events) = @_; if ($DEBUG) { my $count = scalar @events; errlog "dumping $count events to $which db."; } if (open(F, ">>$dbdir/$which.db")) { for my $event (@events) { my @event = @$event; print F "@event\n"; } close(F); } else { errlog "can't write to $dbdir/$which.db: $!"; } } sub check_timers ($) { my $dbh = shift; # what time is it right... NOW my $now = time; # make sure we commit periodically if (($inserts > 10000) || ($now >= ($last_flush_time + $timeout))) { my $last_prepare = ""; my $sth; for my $event (sort { $a->[0] cmp $b->[0] } @commit_buffer) { my @event = @{$event}; #my $type = shift @event; my $type = $event[2]; eval { if ($type ne $last_prepare) { $sth = $dbh->prepare("INSERT INTO events $templates{$type}"); $last_prepare = $type; } $sth->execute(@event); }; if ($@) { print ERRLOG "DBI Execution failed: $DBI::errstr\n"; } #$sth->execute(@event); } $dbh->commit || errlog "Error commiting changes: $!"; # need to get the time again to include how much time it takes to # actually write all this crap to the db $now = time; if ($DEBUG && $inserts) { $total += $inserts; my $delta = $now - $last_flush_time; my $rate = int($inserts / $delta); errlog "$rate/s $inserts in ${delta}s total=$total max=$max"; } $last_flush_time = $now; @commit_buffer = (); $max = 0; $inserts = 0; if (@verbose_buffer) { # if we've got verbose events, dump them dump_events("verbose", @verbose_buffer); # and clear out our buffer @verbose_buffer = (); } if (@terse_buffer) { # if we've got terse events, dump them dump_events("terse", @terse_buffer); # and clear out our buffer @terse_buffer = (); } # bail out if we don't have notification configured return unless -f $cfgfile; # what time did we last read the config file? my $load_time = $config->{load_time}; # check when the config file was last modified... my $mtime = (stat($cfgfile))[9]; # if it's been changed since we last read the config file, we need to # load the new settings if ($load_time < $mtime) { errlog "Reloading changed config file."; $config = readconfig(); } } # bail out if we don't have notification configured return unless -f $cfgfile; if ($config->{terse_freq}) { if (($terse->{last_notify} + $config->{terse_freq}) <= $now) { if (-f "$dbdir/terse.db") { $DEBUG && errlog "doing terse notification..."; # get a temporary filename... my ($fh, $filename) = tempfile("terseXXXXXX", DIR => $dbdir); # overwrite the temp file we just created... rename("$dbdir/terse.db", $filename); if ($DEBUG) { errlog "terse file is $filename"; } # do the actual notification in the background fork_into_background("terse-notification", \&terse_notify_handler, $config->{terse_email}, $filename, $terse->{last_notify}, $config->{terse_level}, $config->{terse_unknown}); # ...keep track of when we last sent out a notify $terse->{last_notify} = $now; } } } if ($config->{summary_freq}) { if (($summary->{last_notify} + $config->{summary_freq}) <= $now) { if (-f "$dbdir/summary.db") { $DEBUG && errlog "doing summary notification..."; # get a temporary filename... my ($fh, $filename) = tempfile("summaryXXXXXX", DIR => $dbdir); # overwrite the temp file we just created... rename("$dbdir/summary.db", $filename); # do the actual notification in the background fork_into_background("summary-notification", \&summary_notify_handler, $config->{summary_email}, $filename, $summary->{last_notify}, $config->{summary_level}, $config->{summary_unknown}); # ...keep track of when we last sent out a notify $summary->{last_notify} = $now; } } } if ($config->{verbose_freq}) { if (($verbose->{last_notify} + $config->{verbose_freq}) <= $now) { if (-f "$dbdir/verbose.db") { $DEBUG && errlog "doing verbose notification..."; # get a temporary filename... my ($fh, $filename) = tempfile("verboseXXXXXX", DIR => $dbdir); # overwrite the temp file we just created... rename("$dbdir/verbose.db", $filename); if ($DEBUG) { errlog "verbose file is $filename"; } # do the actual notification in the background fork_into_background("verbose-notification", \&verbose_notify_handler, $config->{verbose_email}, $filename, $verbose->{last_notify}, $config->{verbose_level}, $config->{verbose_unknown}); # ...keep track of when we last sent out a notify $verbose->{last_notify} = $now; } } } } sub get_last_event { my $dbh = shift; my ($time, $counter); # get the oldest timestamp... my $sth = $dbh->prepare('SELECT MAX(time) FROM events'); $sth->execute; my @row = $sth->fetchrow_array || (0); $time = $row[0]; if ($time) { # get the highest counter for this timestamp... $sth = $dbh->prepare("SELECT MAX(counter) FROM events WHERE time = $time"); $sth->execute; @row = $sth->fetchrow_array || (0); $counter = $row[0]; } return ($time, $counter); } ########################################################################## # start the real magic... my $finished; # make sure we exit if someone sends us the right signal sub sig_handler { my $signame = shift; errlog("Caught signal '$signame'. Exiting..."); $finished = 1; } # set up our error log without buffering open(ERRLOG, ">>$dbdir/event-dispatch.log"); my $oldfd = select(ERRLOG); $| = 1; select($oldfd); $config = readconfig(); # fork off into the background. we need to do this before we connect to # the db, otherwise, we'll get an ugly error about rolling back a # connection that's being destroyed daemonize; # automagically reap child processes $SIG{INT} = \&sig_handler; $SIG{TERM} = \&sig_handler; $SIG{CHLD} = 'IGNORE'; # Sigh, portable dates in perl sucks eval "use Date::Parse"; if (!$@) { $date_module = 'TimeDate'; } else { eval "use Date::Manip"; if (!$@) { $date_module = 'DateManip'; } else { errlog "Unable to load Date module; use either TimeDate or Date::Manip"; $finished = 1; } } # if they want us to write a pid, do it if ($pidfile) { if (open(PIDFILE, ">$pidfile")) { print PIDFILE "$$\n"; close(PIDFILE); } } my $dbh = connect_database($dbdir); ($last_inserted_time, $last_inserted_counter) = get_last_event($dbh); my $auditlog = File::Tail->new( name => $logfile, debug => 1, tail => -1, interval => 1, maxinterval => 5, adjustafter => 20, errmode => "return", ignore_noexistant => 1 ); my $syslog = File::Tail->new( name => $syslogfile, debug => 1, tail => -1, interval => 1, maxinterval => 5, adjustafter => 20, errmode => "return", ignore_noexistant => 1 ); my $line = ''; # process complete lines from the buffer... while (not $finished) { my ($nfound, $timeleft, @pending) = File::Tail::select(undef, undef, undef, $timeout, ($auditlog, $syslog)); foreach (@pending) { process_event($dbh, $_->read); } # see if we should flush pending entries to disk and/or do notification check_timers($dbh); } # make sure we don't exit with any pending events not written to the db $dbh->commit || errlog "Error commiting changes: $!"; $dbh->disconnect || errlog "Error disconnecting from db: $!"; # close our error/debugging log file close(ERRLOG); unlink($pidfile) if $pidfile; exit 0;