X-Git-Url: http://git.ipfire.org/?p=people%2Fstevee%2Fguardian.git;a=blobdiff_plain;f=guardian;h=9245f9004827548fda1f18cecb747c1c11e85bdd;hp=62cb9cf8f99158ef801b0ca2923eea2770bf3ead;hb=fc555263db7fbb79ab3fe1f15476f72bdb546947;hpb=88d9af2ca2c96f98ee03de01fc0fcd0761f5b39d diff --git a/guardian b/guardian index 62cb9cf..9245f90 100644 --- a/guardian +++ b/guardian @@ -22,19 +22,75 @@ use strict; use threads; use threads::shared; +use Getopt::Long; use Thread::Queue; use Linux::Inotify2; +use Time::HiRes qw[ time sleep ]; +require Guardian::Base; +require Guardian::Config; +require Guardian::Logger; require Guardian::Parser; +require Guardian::Socket; -# Array to store the monitored logfiles. -my @monitored_files = ( - "/var/log/snort/alert", +use warnings; + +# Disable warnings of unjoinded threads when stopping guardian. +no warnings 'threads'; + +# Define version. +my $version ="2.0"; + +# Get and store the given command line arguments in a hash. +my %cmdargs = (); + +&GetOptions (\%cmdargs, + 'foreground|f', + 'config|c=s', + 'help|h', + 'version|v', ); +# Show help / version information. +if (defined($cmdargs{"help"})) { + print "Guardian $version \n"; + print "Usage: guardian \n"; + print " -c, --config\t\tspecifiy a configuration file other than the default (/etc/guardian/guardian.conf)\n"; + print " -f, --foreground\trun in the foreground (doesn't fork, any output goes to STDOUT)\n"; + print " -h, --help\t\tshows this help\n"; + print " -v, --version\t\tdisplay programm version and exit.\n"; + exit; +} elsif (defined($cmdargs{"version"})) { + print "Guardian $version \n"; + exit; +} + +# Read-in the configuration file and store the settings. +# Push the may be given config file argument. +my %mainsettings = &Guardian::Config::UseConfig($cmdargs{"config"}); + +# Initialize Logger. +my $logger = Guardian::Logger->New(%mainsettings); +$logger->Log("debug", "Logger successfully initialized..."); + +# Add the logger object to the mainsettings for passing +# it to the modules. +$mainsettings{Logger} = $logger; + +# Redirect perls "die" messages to the logger before exiting. +$SIG{__DIE__} = sub { $logger->Log("err", "@_"); }; + +# Shared hash between the main process and all threads. It will store all +# monitored files and their current file position. +my %monitored_files :shared = (); + # Create the main queue. It is used to store and process all events which are # reported and enqueued by the worker threads. -my $queue :shared = new Thread::Queue or die "Could not create new, empty queue. $!\n";; +my $queue :shared = new Thread::Queue or die "Could not create new, empty queue. $!";; + +# Array to store all currently running worker objects. +# (Does not include the socket thread) +my @running_workers; # Call Init function to initzialize guardian. &Init(); @@ -50,15 +106,15 @@ while(1) { # Grab the data of the top enqueued event. my $event = $queue->peek(); - print "Got event: $event\n"; + # Log processed event. + $logger->Log("debug", "QUEUE - Processed event: $event"); # Drop processed event from queue. $queue->dequeue(); } - # XXX - # Temporary workaround to reduce the load of the main process. - sleep(1); + # Sleep 10ms to reduce the load of the main process. + sleep(0.01); } # @@ -68,15 +124,17 @@ while(1) { ## is starting. # sub Init () { - # Loop through the array of which files should be monitored and - # create a worker thread for each single one. - foreach my $monitored_file (@monitored_files) { - # Check if the file exists and is readable. - if (-r "$monitored_file") { - # Create worker thread for the file. - threads->create(\&Worker,$monitored_file); - } - } + # Setup signal handler. + &SignalHandler(); + + # Setup IPC mechanism via Socket in an own thread. + threads->create(\&Socket); + + # Generate hash of monitored files. + %monitored_files = &Guardian::Base::GenerateMonitoredFiles(\%mainsettings, \%monitored_files); + + # Start worker threads. + &StartWorkers(); } # @@ -100,83 +158,228 @@ sub Init () { ## shared event queue. # sub Worker ($) { - my $file = @_[0]; + my $file = $_[0]; - # Get the fileposition. - my $fileposition = &Init_fileposition("$file"); + # Signal handler to kill worker. + $SIG{'KILL'} = sub { threads->exit(); }; # Create inotify watcher. - my $watcher = new Linux::Inotify2 or die "Could not use inotify. $!\n"; + my $watcher = new Linux::Inotify2 or die "Could not use inotify. $!"; # Monitor the specified file. - $watcher->watch("$file", IN_MODIFY) or die "Could not monitor $file. $!\n"; + $watcher->watch("$file", IN_MODIFY) or die "Could not monitor $file. $!"; - # Get all notifications. - while ($watcher->read) { - my @message = (); + # Switch watcher into non-blocking mode. + $watcher->blocking(0); - # Open the file. - open (FILE, $file) or die "Could not open $file. $!\n"; + # Log successfully spawned worker. + $logger->Log("debug", "Spawned worker thread for: $file"); - # Seek to the last known position. - seek (FILE, $fileposition, 0); + # Infinite loop. + while(1) { + # Check for any events and perform them, if there + # is a least one. + if ($watcher->read) { + my @message = (); - # Get the log message. - while (my $line = ) { - # Remove any newlines. - chomp $line; + # Obtain fileposition from hash. + my $fileposition = $monitored_files{$file}; + + # Open the file. + open (FILE, $file) or die "Could not open $file. $!"; + + # Seek to the last known position. + seek (FILE, $fileposition, 0); + + # Get the log message. + while (my $line = ) { + # Remove any newlines. + chomp $line; + + # Add all lines to the message array. + push (@message, $line); + } + + { + # Lock shared hash. + lock(%monitored_files); + + # Update fileposition. + $monitored_files{$file} = tell(FILE); + } + + # Close file. + close(FILE); - # Add all lines to the message array. - push (@message, $line); + # Send filename and message to the parser, + # which will return if an action has to be performed. + my @action = &Guardian::Parser::Parser("$file", @message); + + # Send the action to the main process and put it into + # the queue. + if (@action) { + # Lock the queue. + lock($queue); + + # Put the required action into the queue. + $queue->enqueue(@action); + } + } else { + # Sleep for 10ms until the next round of the loop will start. + sleep(0.01); } + } +} - # Update fileposition. - $fileposition = tell(FILE); +# +## Socket function. +# +## This function uses the Socket module to create and listen to an UNIX socket. +## It automatically accepts all incomming connections and pass the recieved +## data messages to the the Message_Parser function which is also part of the +## socket module. +# +## If a valid command has been sent through the socket, the corresponding event +## will be enqueued into the shared event queue. +# +sub Socket () { + # Create the Server socket by calling the responsible function. + my $server = &Guardian::Socket::Server(); - # Close file. - close(FILE); + # Log successfull creation of socket. + $logger->Log("debug", "Listening to Socket..."); - # Send filename and message to the parser, - # which will return if an action has to be performed. - my @action = &Guardian::Parser::Parser("$file", @message); + # Accept incomming connections from the socket. + while (my $connection = $server->accept()) { + # Autoflush the socket after the data + # has been recieved. + $connection->autoflush(1); - # Send the action to the main process and put it into - # the queue. - if (@action) { - # Lock the queue. - lock($queue); + # Gather all data from the connection. + while (my $message = <$connection>) { + # Remove any newlines. + chomp($message); + + # Log recieved socket command. + $logger->Log("debug", "Socket - Recieved message: $message"); + + # Send the recieved data message to the + # socket parser. + my $action = &Guardian::Socket::Message_Parser($message); - # Put the required action into the queue. - $queue->enqueue(@action); + # If the parser returns to perform an action, + # add it to the main event queue. + if ($action) { + # Lock the queue. + lock($queue); + + # Enqueue the returned action. + $queue->enqueue($action); + } } } } # -## Function for fileposition initialization. +## Function for capturing process signals. # -## This function is used to get the cursor position of the end of file (EOF) of -## a specified file. +## This function captures any sent process signals and will call various +## actions, basend on the captured signal. # -## In order to prevent from permanently read and keep files opened, or dealing -## with huge logfiles, at initialization time of the worker processes, the file will -## be opened once and the cursor position of the end of file (EOF) get stored. +sub SignalHandler { + $SIG{INT} = \&Shutdown; + $SIG{TERM} = \&Shutdown; + $SIG{QUIT} = \&Shutdown; + $SIG{HUP} = \&Reload; +} + # -sub Init_fileposition ($) { - my $file = $_[0]; +## Function to start the workers (threads) for all monitored files. +# +## This function will loop through the hash of monitored files and will +## spawn an own thread based worker for each file. Every created worker will +## be added to the array of running workers. +# +sub StartWorkers () { + # Loop through the hash which contains the monitored files and start + # a worker thread for each single one. + foreach my $file (keys %monitored_files) { + $logger->Log("debug", "Starting worker thread for $file"); + # Create worker thread for the file. + push @running_workers, threads->create(\&Worker,$file); + } +} + +# +## Function to stop all running workers. +# +## This function is used to stop all currently running workers and will be +## called when reloading or shutting down guardian. +# +sub StopWorkers () { + # Loop through all running workers. + foreach my $worker (@running_workers) { + # Send the worker the "KILL" signal and detach the + # thread so perl can do an automatically clean-up. + $worker->kill('KILL'); + } + $logger->Log("debug", "All workers are stopped now..."); +} + +# +## Reload function. +# +## This function will get called if the signal handler recieves a "SIGHUP" signal, +## or the reload command will be sent via socket connection. It is responsible for +## reloading all configure options and stopping/starting the worker threads. +# +sub Reload () { + # Log reload. + $logger->Log("info", "Reload configuration..."); - # Open the file. - open(FILE, $file) or die "Could not open $file. $!\n"; + # Stop all running workers. + &StopWorkers(); - # Just seek to the end of the file (EOF). - seek(FILE, 0, 2); + # Re-read configuration file. + %mainsettings = &Guardian::Config::UseConfig($cmdargs{"config"}); - # Get and store the position. - my $position = tell(FILE), + # Update Logger settings. + $logger = Guardian::Logger->Update(%mainsettings); + + # Update logger object in mainsettings hash. + $mainsettings{Logger} = $logger; + + # Re-generate hash of monitored files. + %monitored_files = &Guardian::Base::GenerateMonitoredFiles(\%mainsettings, \%monitored_files); + + # Restart the worker threads. + &StartWorkers(); +} + +# +## Shutdown function. +# +## This function is used to do a clean shutdown of guardian. It will be called +## by the signal handler when recieving INT (2), QUIT (3) and TERM (15) signals. +# +sub Shutdown () { + # Log shutdown. + $logger->Log("info", "Shutting down..."); + + # Stop all workers. + &StopWorkers(); + + # Remove socket file on exit. + &Guardian::Socket::RemoveSocketFile(); + + # Sleep for one second to give perl some + # time to proper clean up everything before + # exiting. + sleep(1); - # Close the file again. - close(FILE); + # Log good bye message. + $logger->Log("debug", "Good Bye!"); - # Return the position. - return $position; + # Exit guardian. + exit; }