package Guardian::Events; use strict; use warnings; use Exporter qw(import); our @EXPORT_OK = qw(Init CheckAction Update); # Hash which stores all supported commands from the queue. my %commands = ( 'count' => \&Counter, 'block' => \&CallBlock, 'unblock' => \&CallUnblock, 'flush' => \&CallFlush, ); # Hash to store addresses and their current count. my %counthash = (); # Hash to store all currentyl blocked addresses and a timestamp # when the block for this address can be released. my %blockhash = (); # Hash to store user-defined IP addresses and/or subnets which should be # ignored in case any events should be repored for them. my %ignorehash = (); # This object will contain the reference to the logger object after calling Init. my $logger; # ## The "Init" (Block) function. # ## This function is responsible to initialize Block as a class based object. ## It has to be called once before any blocking event can be processed. # ## The following arguments must be passed, during initialization: ## "BlockCount" and "BlockTime" which both have to be natural numbers. # sub Init (%) { my ( $class, %args ) = @_; my $self = \%args; # Fail, if some critical arguments are missing. unless ((exists($self->{BlockCount})) && (exists($self->{BlockTime}))) { die "Could not initialize Block: Too less arguments are given.\n"; } # Use bless to make "$self" to an object of class "$class". bless($self, $class); # Assign logger object. $logger = $self->{Logger}; # Log used firewall engine. $logger->Log("debug", "Using firewall engine: $self->{FirewallEngine}"); # Try to load the specified firewall engine or die. my $module_name = "Guardian::" . $self->{FirewallEngine}; eval "use $module_name; 1" or die "Could not load a module for firewall engine: $self->{FirewallEngine}!"; # Check if an IgnoreFile has been configured. if (exists($self->{IgnoreFile})) { # Call function to handle the ignore mechanism. &GenerateIgnoreList($self->{IgnoreFile}); } # Return the class object. return $self; } # ## The main "CheckAction" function. # ## This function is used to handle each recived event from the main event queue of guardian. # ## It will check if the given command is valid and will pass it to the responsible function. # sub CheckAction ($$) { my $self = shift; my @event = split(/ /, $_[0], 4); my ($command, $address, $module, $message) = @event; # Check if we got an invalid command. unless(exists($commands{$command})) { $logger->Log("err", "The CheckAction function has been called with an unsupported command ($command)!"); return; } # Convert and validate the given address. my $bin_address = &Guardian::Base::IPOrNet2Int($address); # Abort if the given address could not be converted because it is not valid. unless ($bin_address) { $logger->Log("err", "Invalid IP address: $address"); return; } # Check if address should be ignored. if(&_IsOnIgnoreList($bin_address)) { # Log message. $logger->Log("info", "Ignoring event for $address, because it is part of the ignore list."); return; } # Call required handler. my $error = $commands{$command}->($self, $address, $module, $message); # If we got any return code, something went wrong and should be logged. if ($error) { $logger->Log("err", "Got error: $error"); return; } } # ## The main "Counter" function. # ## This function is used to handle each count message + address, which has been sent by the main event ## loop of guardian. # ## It stores the address and the current count into the counthash and increase the count each time when ## the same address should be counted again. When the current count reaches the configured BlockCount, ## the responsible function will be called to block the address. # sub Counter ($@) { my $self = shift; my ($address, $module, $message) = @_; # Log event. $logger->Log("debug", "$module reported $message for address: $address"); # Increase existing count or start counting for new source addresses. if (exists($counthash{$address})) { # Skip already blocked addresses. if (exists($blockhash{$address})) { return undef; } # Increase count of the existing entry. $counthash{$address} = $counthash{$address} + 1; # Log altered count of the address. $logger->Log("debug", "Source $address now has count $counthash{$address}/$self->{BlockCount}..."); } else { # Log that counting for the address has been started. $logger->Log("debug", "Start counting for $address..."); # Set count to "1". $counthash{$address} = 1; } # Check if the address has reached the configured count limit. if ($counthash{$address} >= $self->{BlockCount}) { # Write out log message. $logger->Log("info", "Blocking $address for $self->{BlockTime} seconds..."); # Call block subroutine to block the address. my $error = &CallBlock($self, $address, $module, $message); # Return the message if an error occurs. return $error; } # Everything worked well, return nothing. return undef; } # ## The RemoveBlocks function. # ## This function periodly will be called and is responsible for releasing the block if the Blocktime ## on an address has expired. # ## To do this, the code will loop through all entries of the blockhash and check ## if the estimiated BlockTime of each address has reached and so the block can be released. # sub RemoveBlocks () { my $self = shift; # Get the current time. my $time = time(); # Loop through the blockhash. foreach my $address (keys %blockhash) { # Check if the blocktime for the address has expired. if ($blockhash{$address} <= $time) { # Call unblock subroutine. my $error = &CallUnblock($self, $address, "BlockTime", "has expired for $address"); # Log error messages if returned. if ($error) { $logger->Log("err", "$error"); } } } # Everything okay, return nothing. return undef; } # ## The CallBlock function. # ## This function is called, if the BlockCount for an address is reached or a direct ## request for blocking an address has been recieved. # sub CallBlock ($@) { my $self = shift; my ($address, $module, $message) = @_; # Log the call for blocking an address. $logger->Log("info", "$module - $message"); # Check if an entry for this address exists # in the blockhash. If not, the address has # not been blocked yet, call the responisible # function to do this now. unless (exists($blockhash{$address})) { # Obtain the configured FirewallAction. my $action = $self->{FirewallAction}; # Block the given address. my $error = &DoBlock($address, $action); # If we got back an error message something went wrong. if ($error) { # Exit function and return the used FirewallEngine and the error message. return "$self->{FirewallEngine} - $error"; } else { # Address has been successfully blocked, print a log message. $logger->Log("debug", "Address $address successfully has been blocked..."); } } # Generate time when the block will expire. my $expire = time() + $self->{BlockTime}; # Store the blocked address and the expire time # in the blockhash. $blockhash{$address} = $expire; # Return nothing "undef" if everything is okay. return undef; } # ## CallUnblock function. # ## This function is responsible for unblocking and deleting a given ## address from the blockhash. # sub CallUnblock ($) { my $self = shift; my ($address, $module, $message) = @_; # Log the call for unblocking an address. $logger->Log("info", "$module - $message"); # Return an error if no entry for the given address # is present in the blockhash. unless (exists($blockhash{$address})) { return "Address $address was not blocked!"; } # Unblock the address. my $error = &DoUnblock($address); # If an error message is returned, something went wrong. if ($error) { # Exit function and return the error message. return $error; } else { # Address successfully has been unblocked. $logger->Log("debug", "Address $address successfully has been unblocked..."); } # Drop address from blockhash. delete ($blockhash{$address}); # Everything worked well, return nothing. return undef; } # ## GenerateIgnoreList function. # ## This function is responsible for generating/updating the ## IgnoreHash which contains all ignored IP addresses and ## networks. # sub GenerateIgnoreList($) { my $file = shift; # Check if the given IgnoreFile could be opened. unless(-e $file) { $logger->Log("err", "The configured IgnoreFile \($file\) could not be opened. Skipped!"); return; } # Open the given IgnoreFile. open (IGNORE, $file); # Read-in the file line by line. while () { # Skip comments. next if (/\#/); # Skip blank lines. next if (/^\s*$/); # Remove any newlines. chomp; # Check if the line contains a valid single address or network and # convert it into binary format. Store the result/start and # end values in a temporary array. my @values = &Guardian::Base::IPOrNet2Int($_); # If the function returned any values, the line contained a valid # single address or network which successfully has been converted into # binary format. if (@values) { # Assign the array as value to the ignorehash. $ignorehash{$_} = [@values]; } else { # Log invalid entry. $logger->Log("err", "IgnoreFile contains an invalid address/network: $_"); # Skip line. next; } } # Close filehandle for the IgnoreFile. close (IGNORE); } # ## Private function to check if an address is part of the Ignore Hash. # ## This function requires # sub _IsOnIgnoreList ($) { my $bin_address = shift; # Loop through the ignore hash and grab the stored values. foreach my $key ( keys %ignorehash ) { # Dereference values array. my @values = @{$ignorehash{$key}}; # Obtain amount of items for the current value array. my $items = scalar @values; # Whether the amount equals one, the given binary address just # needs to be compared against a single address. if ($items eq "1") { my ($ignored_address) = @values; # Simple check if the stored and the given binary address # are the same. if ($bin_address eq $ignored_address) { # The given address is part of the ignore list. $logger->Log("debug", "Address $key found on the ignore list."); # Return "1" (True). return 1; } } # If the amount equals two, for passed binary address needs to # be checked if it is part of the ignored network range. elsif ($items eq "2") { my ($first_address, $last_address) = @values; # Check if the passed binary address is bigger than # the first address and smaler than the last address # (between) the stored network range. if (($bin_address >= $first_address) && ($bin_address <= $last_address)) { # The address is part of an ignored network. $logger->Log("debug", "Address is found inside the ignored network $key."); # Return "1" (True). return 1; } # If the amount is not eighter one or two, the current entry of the ignorehash seems # to be corrupted. Skip and log it. } else { # Write log message about this corruped item in the ignore hash. $logger->Log("err", "Invalid item in the Ignore Hash: $key - @values"); # Skip this element of the ignore hash. next; } } # If we got here, the given address is not part of the ignore hash. # Return nothing (False). return; } 1;