#!/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; require Guardian::Config; require Guardian::Parser; require Guardian::Socket; # Define version. my $version ="2.0"; # Array to store the monitored logfiles. my @monitored_files = ( "/var/log/snort/alert", ); # 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"}); # 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";; # 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(); print "Got event: $event\n"; # Drop processed event from queue. $queue->dequeue(); } # XXX # Temporary workaround to reduce the load of the main process. sleep(1); } # ## Init function. # ## This function contains code which has to be executed while guardian ## is starting. # sub Init () { # Setup IPC mechanism via Socket in an own thread. threads->create(\&Socket); # 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); } } } # ## 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]; # Get the fileposition. my $fileposition = &Init_fileposition("$file"); # Create inotify watcher. my $watcher = new Linux::Inotify2 or die "Could not use inotify. $!\n"; # Monitor the specified file. $watcher->watch("$file", IN_MODIFY) or die "Could not monitor $file. $!\n"; # Get all notifications. while ($watcher->read) { my @message = (); # Open the file. open (FILE, $file) or die "Could not open $file. $!\n"; # 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); } # Update fileposition. $fileposition = 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("$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); } } } # ## 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(); # 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); # 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 fileposition initialization. # ## This function is used to get the cursor position of the end of file (EOF) of ## a specified file. # ## 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 Init_fileposition ($) { my $file = $_[0]; # Open the file. open(FILE, $file) or die "Could not open $file. $!\n"; # Just seek to the end of the file (EOF). seek(FILE, 0, 2); # Get and store the position. my $position = tell(FILE), # Close the file again. close(FILE); # Return the position. return $position; }