#!/usr/bin/perl ############################################################################### # # # IPFire.org - A linux based firewall # # Copyright (C) 2015 IPFire Development Team # # # # This program is free software: you can redistribute it and/or modify # # it under the terms of the GNU General Public License as published by # # the Free Software Foundation, either version 3 of the License, or # # (at your option) any later version. # # # # 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, see . # # # ############################################################################### 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::Daemon; require Guardian::Events; require Guardian::Logger; require Guardian::Parser; require Guardian::Socket; 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; } # Check if another instance of guardian is allready running. if (&Guardian::Daemon::IsRunning()) { die "Another instance of Guardian is allready running...\n"; } # 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->Init(%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", "@_"); }; # Initialize the event handler. my $events = Guardian::Events->Init(%mainsettings); # Hash to store the currently monitored files and their configured # parsers. my %monitored_files = (); # Shared hash between the main process and all threads. It will store the # monitored files and their current file position. my %file_positions :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. $!";; # Array to store all currently running worker objects. # (Does not include the socket thread) my @running_workers; # Check if guardian should be daemonized or keep in the foreground. unless (defined($cmdargs{"foreground"})) { # Fork into background. &Guardian::Daemon::Daemonize(); } else { # Write PID (process-id). &Guardian::Daemon::WritePID(); } # Call Init function to initzialize guardian. &Init(); # Infinite main loop, which processes all queued events. while(1) { # Get the amount of elements in our queue. # "undef" will be returned if it is empty. my $current_events = $queue->pending(); # If there is at least one element enqued if($current_events > 0) { # Grab the data of the top enqueued event. my $event = $queue->peek(); # Log processed event. $logger->Log("debug", "QUEUE - Processed event: $event"); # Send event data to the events parser to determine # if any action is required. $events->CheckAction($event); # Drop processed event from queue. $queue->dequeue(); } # Call RemoveBlocks routine from the Events module to check # if items from the block list can be dropped. $events->RemoveBlocks(); # Sleep 10ms to reduce the load of the main process. sleep(0.01); } # ## Init function. # ## This function contains code which has to be executed while guardian ## is starting. # sub Init () { # 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(); } # ## Worker function. # ## This function is responsible for monitoring modifications of the given logfile, ## read them and pass them to the message parser. # ## To get file modifications the inotify subsystem of the linux kernel is used. # ## In order to prevent from permanently read and keep files opened, or dealing ## with huge logfiles, at initialization time of the worker process, the file will ## be opened once and the cursor position of the end of file (EOF) get stored. When ## reading any newly added lines from the file, we directly can jump to the last ## known position and get these lines. Afterwards, we store the current curser position ## again, so we can do it in this way over and over again. # ## All read lines get stored in an array, which will be passed to the Parser. # ## If any response (action) from the parser get recieved, it will be put into the ## shared event queue. # sub Worker ($) { my $file = $_[0]; # Obtain the parser name which should be used to parser any # messages of this file. my $parser = $monitored_files{$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. $!"; # Monitor the specified file. $watcher->watch("$file", IN_MODIFY) or die "Could not monitor $file. $!"; # Switch watcher into non-blocking mode. $watcher->blocking(0); # Log successfully spawned worker. $logger->Log("debug", "Spawned worker thread for: $file"); # Infinite loop. while(1) { # Check for any events and perform them, if there # is a least one. if ($watcher->read) { my @message = (); # Obtain fileposition from hash. my $fileposition = $file_positions{$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(%file_positions); # Update fileposition. $file_positions{$file} = tell(FILE); } # Close file. close(FILE); # Send filename and message to the parser, # which will return if an action has to be performed. my @action = &Guardian::Parser::Parser("$parser", @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); } } } # ## 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(); # Log successfull creation of socket. $logger->Log("debug", "Listening to Socket..."); # Accept incomming connections from the socket. while (my $connection = $server->accept()) { # Autoflush the socket after the data # has been recieved. $connection->autoflush(1); # 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); # 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 capturing process signals. # ## This function captures any sent process signals and will call various ## actions, basend on the captured signal. # sub SignalHandler { $SIG{INT} = \&Shutdown; $SIG{TERM} = \&Shutdown; $SIG{QUIT} = \&Shutdown; $SIG{HUP} = \&Reload; } # ## 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 () { # Init/Update hash which contains the cursor position of EOF. %file_positions = &Guardian::Base::FilePositions(\%monitored_files, \%file_positions); # 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..."); # Stop all running workers. &StopWorkers(); # Re-read configuration file. %mainsettings = &Guardian::Config::UseConfig($cmdargs{"config"}); # Update Logger settings. $logger = Guardian::Logger->Init(%mainsettings); # Update logger object in mainsettings hash. $mainsettings{Logger} = $logger; # Update ignore list, if one has been specified. if (exists($mainsettings{IgnoreFile})) { &Guardian::Events::GenerateIgnoreList($mainsettings{IgnoreFile}); } # 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(); # Remove pid file on exit. &Guardian::Daemon::RemovePIDFile(); # Sleep for one second to give perl some # time to proper clean up everything before # exiting. sleep(1); # Log good bye message. $logger->Log("debug", "Good Bye!"); # Exit guardian. exit; }