Add ability to reload the ignore list.
[people/stevee/guardian.git] / modules / Events.pm
CommitLineData
df6ec59f
SS
1package Guardian::Events;
2use strict;
3use warnings;
4
5use Exporter qw(import);
6
7c8f0696 7our @EXPORT_OK = qw(Init CheckAction GenerateIgnoreList Update);
df6ec59f
SS
8
9# Hash which stores all supported commands from the queue.
10my %commands = (
11 'count' => \&Counter,
12 'block' => \&CallBlock,
13 'unblock' => \&CallUnblock,
14 'flush' => \&CallFlush,
7121d8cd 15 'reload' => \&main::Reload,
ab7c10eb 16 'reload-ignore-list' => \&main::ReloadIgnoreList,
df6ec59f
SS
17);
18
19# Hash to store addresses and their current count.
20my %counthash = ();
21
22# Hash to store all currentyl blocked addresses and a timestamp
23# when the block for this address can be released.
24my %blockhash = ();
25
66e1ad0a 26# Hash to store user-defined IP addresses and/or subnets which should be
0d218038 27# ignored in case any events should be repored for them.
66e1ad0a
SS
28my %ignorehash = ();
29
0d218038
SS
30# Array to store localhost related IP addresses.
31# They are always white-listed to prevent guardian from blocking
32# any local traffic.
33my @localhost_addresses = ("127.0.0.1", "::1");
34
df6ec59f
SS
35# This object will contain the reference to the logger object after calling Init.
36my $logger;
37
38#
39## The "Init" (Block) function.
40#
41## This function is responsible to initialize Block as a class based object.
42## It has to be called once before any blocking event can be processed.
43#
44## The following arguments must be passed, during initialization:
45## "BlockCount" and "BlockTime" which both have to be natural numbers.
46#
47sub Init (%) {
48 my ( $class, %args ) = @_;
49 my $self = \%args;
50
51 # Fail, if some critical arguments are missing.
52 unless ((exists($self->{BlockCount})) && (exists($self->{BlockTime}))) {
53 die "Could not initialize Block: Too less arguments are given.\n";
54 }
55
56 # Use bless to make "$self" to an object of class "$class".
57 bless($self, $class);
58
59 # Assign logger object.
60 $logger = $self->{Logger};
61
62 # Log used firewall engine.
63 $logger->Log("debug", "Using firewall engine: $self->{FirewallEngine}");
64
65 # Try to load the specified firewall engine or die.
66 my $module_name = "Guardian::" . $self->{FirewallEngine};
67 eval "use $module_name; 1" or die "Could not load a module for firewall engine: $self->{FirewallEngine}!";
68
66e1ad0a
SS
69 # Check if an IgnoreFile has been configured.
70 if (exists($self->{IgnoreFile})) {
71 # Call function to handle the ignore mechanism.
72 &GenerateIgnoreList($self->{IgnoreFile});
0d218038
SS
73 } else {
74 # Whitelist local addresses.
75 %ignorehash = &_whitelist_localhost();
66e1ad0a
SS
76 }
77
df6ec59f
SS
78 # Return the class object.
79 return $self;
80}
81
82#
83## The main "CheckAction" function.
84#
85## This function is used to handle each recived event from the main event queue of guardian.
86#
87## It will check if the given command is valid and will pass it to the responsible function.
88#
89sub CheckAction ($$) {
90 my $self = shift;
91 my @event = split(/ /, $_[0], 4);
92 my ($command, $address, $module, $message) = @event;
93
94 # Check if we got an invalid command.
95 unless(exists($commands{$command})) {
96 $logger->Log("err", "The CheckAction function has been called with an unsupported command ($command)!");
97 return;
98 }
99
e1ff25ce
SS
100 # Check if the given event contains an address.
101 if ($address) {
102 # Convert and validate the given address.
103 my $bin_address = &Guardian::Base::IPOrNet2Int($address);
104
105 # Abort if the given address could not be converted because it is not valid.
106 unless ($bin_address) {
107 $logger->Log("err", "Invalid IP address: $address");
108 return;
109 }
110
111 # Check if address should be ignored.
112 if(&_IsOnIgnoreList($bin_address)) {
113 # Log message.
114 $logger->Log("info", "Ignoring event for $address, because it is part of the ignore list.");
115 return;
116 }
66e1ad0a
SS
117 }
118
df6ec59f
SS
119 # Call required handler.
120 my $error = $commands{$command}->($self, $address, $module, $message);
121
122 # If we got any return code, something went wrong and should be logged.
123 if ($error) {
124 $logger->Log("err", "Got error: $error");
125 return;
126 }
127}
128
129#
130## The main "Counter" function.
131#
132## This function is used to handle each count message + address, which has been sent by the main event
133## loop of guardian.
134#
135## It stores the address and the current count into the counthash and increase the count each time when
136## the same address should be counted again. When the current count reaches the configured BlockCount,
137## the responsible function will be called to block the address.
138#
139sub Counter ($@) {
140 my $self = shift;
141 my ($address, $module, $message) = @_;
142
143 # Log event.
144 $logger->Log("debug", "$module reported $message for address: $address");
145
146 # Increase existing count or start counting for new source addresses.
147 if (exists($counthash{$address})) {
148 # Skip already blocked addresses.
149 if (exists($blockhash{$address})) {
150 return undef;
151 }
152
153 # Increase count of the existing entry.
154 $counthash{$address} = $counthash{$address} + 1;
155
156 # Log altered count of the address.
157 $logger->Log("debug", "Source $address now has count $counthash{$address}/$self->{BlockCount}...");
158 } else {
159 # Log that counting for the address has been started.
160 $logger->Log("debug", "Start counting for $address...");
161
162 # Set count to "1".
163 $counthash{$address} = 1;
164 }
165
166 # Check if the address has reached the configured count limit.
167 if ($counthash{$address} >= $self->{BlockCount}) {
168 # Write out log message.
169 $logger->Log("info", "Blocking $address for $self->{BlockTime} seconds...");
170
171 # Call block subroutine to block the address.
172 my $error = &CallBlock($self, $address, $module, $message);
173
174 # Return the message if an error occurs.
175 return $error;
176 }
177
178 # Everything worked well, return nothing.
179 return undef;
180}
181
182#
183## The RemoveBlocks function.
184#
185## This function periodly will be called and is responsible for releasing the block if the Blocktime
186## on an address has expired.
187#
188## To do this, the code will loop through all entries of the blockhash and check
189## if the estimiated BlockTime of each address has reached and so the block can be released.
190#
191sub RemoveBlocks () {
192 my $self = shift;
193
194 # Get the current time.
195 my $time = time();
196
197 # Loop through the blockhash.
198 foreach my $address (keys %blockhash) {
199 # Check if the blocktime for the address has expired.
200 if ($blockhash{$address} <= $time) {
201 # Call unblock subroutine.
202 my $error = &CallUnblock($self, $address, "BlockTime", "has expired for $address");
203
204 # Log error messages if returned.
205 if ($error) {
206 $logger->Log("err", "$error");
207 }
208 }
209 }
210
211 # Everything okay, return nothing.
212 return undef;
213}
214
215#
216## The CallBlock function.
217#
218## This function is called, if the BlockCount for an address is reached or a direct
219## request for blocking an address has been recieved.
220#
221sub CallBlock ($@) {
222 my $self = shift;
223 my ($address, $module, $message) = @_;
224
225 # Log the call for blocking an address.
226 $logger->Log("info", "$module - $message");
227
228 # Check if an entry for this address exists
229 # in the blockhash. If not, the address has
230 # not been blocked yet, call the responisible
231 # function to do this now.
232 unless (exists($blockhash{$address})) {
e9c558fe
SS
233 # Obtain the configured FirewallAction.
234 my $action = $self->{FirewallAction};
df6ec59f
SS
235
236 # Block the given address.
237 my $error = &DoBlock($address, $action);
238
239 # If we got back an error message something went wrong.
240 if ($error) {
f842727a
SS
241 # Exit function and return the used FirewallEngine and the error message.
242 return "$self->{FirewallEngine} - $error";
df6ec59f
SS
243 } else {
244 # Address has been successfully blocked, print a log message.
245 $logger->Log("debug", "Address $address successfully has been blocked...");
246 }
247 }
248
249 # Generate time when the block will expire.
250 my $expire = time() + $self->{BlockTime};
251
252 # Store the blocked address and the expire time
253 # in the blockhash.
254 $blockhash{$address} = $expire;
255
256 # Return nothing "undef" if everything is okay.
257 return undef;
258}
259
260#
261## CallUnblock function.
262#
263## This function is responsible for unblocking and deleting a given
264## address from the blockhash.
265#
266sub CallUnblock ($) {
267 my $self = shift;
268 my ($address, $module, $message) = @_;
269
270 # Log the call for unblocking an address.
271 $logger->Log("info", "$module - $message");
272
273 # Return an error if no entry for the given address
274 # is present in the blockhash.
275 unless (exists($blockhash{$address})) {
276 return "Address $address was not blocked!";
277 }
278
279 # Unblock the address.
280 my $error = &DoUnblock($address);
281
282 # If an error message is returned, something went wrong.
283 if ($error) {
284 # Exit function and return the error message.
285 return $error;
286 } else {
287 # Address successfully has been unblocked.
288 $logger->Log("debug", "Address $address successfully has been unblocked...");
289 }
290
291 # Drop address from blockhash.
292 delete ($blockhash{$address});
293
294 # Everything worked well, return nothing.
295 return undef;
296}
297
66e1ad0a
SS
298#
299## GenerateIgnoreList function.
300#
301## This function is responsible for generating/updating the
302## IgnoreHash which contains all ignored IP addresses and
303## networks.
304#
305sub GenerateIgnoreList($) {
306 my $file = shift;
900cad7f
SS
307 my @include_files;
308
309 # Reset current ignore hash and add
310 # localhost related IP addresses.
311 %ignorehash = &_whitelist_localhost();
66e1ad0a
SS
312
313 # Check if the given IgnoreFile could be opened.
314 unless(-e $file) {
315 $logger->Log("err", "The configured IgnoreFile \($file\) could not be opened. Skipped!");
316 return;
317 }
318
900cad7f 319 # Open the given IgnoreFile.
66e1ad0a
SS
320 open (IGNORE, $file);
321
322 # Read-in the file line by line.
323 while (<IGNORE>) {
324 # Skip comments.
325 next if (/\#/);
326
327 # Skip blank lines.
328 next if (/^\s*$/);
329
330 # Remove any newlines.
331 chomp;
332
900cad7f
SS
333 # Check for an include instruction.
334 if ($_ =~ /^Include_File = (.*)/) {
335 my $include_file = $1;
66e1ad0a 336
900cad7f
SS
337 # Check if the parsed include file exists and is read-able.
338 if (-e $include_file) {
339 # Add file to the array of files wich will be included.
340 push(@include_files, $include_file);
341
342 # Write out log message.
343 $logger->Log("debug", "Addresses from $include_file will be included...");
344 } else {
345 # Log missing file.
346 $logger->Log("err", "$include_file will not be included. File does not exist!");
347 }
348 } else {
349 # Check if the line contains a valid single address or network and
350 # convert it into binary format. Store the result/start and
351 # end values in a temporary array.
352 my @values = &Guardian::Base::IPOrNet2Int($_);
353
354 # If the function returned any values, the line contained a valid
355 # single address or network which successfully has been converted into
356 # binary format.
357 if (@values) {
358 # Assign the array as value to the ignorehash.
359 $ignorehash{$_} = [@values];
360 } else {
361 # Log invalid entry.
362 $logger->Log("err", "IgnoreFile contains an invalid address/network: $_");
363
364 # Skip line.
365 next;
366 }
66e1ad0a
SS
367 }
368 }
369
370 # Close filehandle for the IgnoreFile.
371 close (IGNORE);
5cd378cd 372
900cad7f
SS
373 # Check if any files should be included.
374 if (@include_files) {
375 # Loop through the array of files which should be included.
376 foreach my $file (@include_files) {
377 # Open the file.
378 open(INCLUDE, $file);
379
380 # Read-in file line by line.
381 while(<INCLUDE>) {
382 # Skip any comments.
383 next if (/\#/);
384
385 # Skip any blank lines.
386 next if (/^\s*$/);
387
388 # Chomp any newlines.
389 chomp;
390
391 # Check if the line contains a valid single address or network and
392 # convert it into binary format. Store the result/start and
393 # end values in a temporary array.
394 my @values = &Guardian::Base::IPOrNet2Int($_);
395
396 # If the function returned any values, the line contained a valid
397 # single address or network which successfully has been converted into
398 # binary format.
399 if (@values) {
400 # Assign the array as value to the ignorehash.
401 $ignorehash{$_} = [@values];
402 } else {
403 # Log invalid entry.
404 $logger->Log("err", "$file contains an invalid address/network: $_");
405
406 # Skip line.
407 next;
408 }
409 }
410
411 # Close filehandle.
412 close(INCLUDE);
413 }
414 }
415
5cd378cd
SS
416 # Get amount of current elements in hash.
417 my $amount = scalar(keys(%ignorehash));
418
419 # Write out log message.
420 $logger->Log("debug", "Ignore list currently contains $amount entries.");
66e1ad0a
SS
421}
422
423#
424## Private function to check if an address is part of the Ignore Hash.
425#
7c8f0696
SS
426## This function checks if a passed IP address in binary format (!),
427## directly or as part of an ignored network is stored in the ignore hash.
66e1ad0a
SS
428#
429sub _IsOnIgnoreList ($) {
430 my $bin_address = shift;
431
432 # Loop through the ignore hash and grab the stored values.
433 foreach my $key ( keys %ignorehash ) {
434 # Dereference values array.
435 my @values = @{$ignorehash{$key}};
436
437 # Obtain amount of items for the current value array.
438 my $items = scalar @values;
439
440 # Whether the amount equals one, the given binary address just
441 # needs to be compared against a single address.
442 if ($items eq "1") {
443 my ($ignored_address) = @values;
444
445 # Simple check if the stored and the given binary address
446 # are the same.
447 if ($bin_address eq $ignored_address) {
448 # The given address is part of the ignore list.
449 $logger->Log("debug", "Address $key found on the ignore list.");
450
451 # Return "1" (True).
452 return 1;
453 }
454 }
455
456 # If the amount equals two, for passed binary address needs to
457 # be checked if it is part of the ignored network range.
458 elsif ($items eq "2") {
459 my ($first_address, $last_address) = @values;
460
461 # Check if the passed binary address is bigger than
462 # the first address and smaler than the last address
463 # (between) the stored network range.
464 if (($bin_address >= $first_address) && ($bin_address <= $last_address)) {
465 # The address is part of an ignored network.
466 $logger->Log("debug", "Address is found inside the ignored network $key.");
467
468 # Return "1" (True).
469 return 1;
470 }
471
472 # If the amount is not eighter one or two, the current entry of the ignorehash seems
473 # to be corrupted. Skip and log it.
474 } else {
475 # Write log message about this corruped item in the ignore hash.
476 $logger->Log("err", "Invalid item in the Ignore Hash: $key - @values");
477
478 # Skip this element of the ignore hash.
479 next;
480 }
481 }
482
483 # If we got here, the given address is not part of the ignore hash.
484 # Return nothing (False).
485 return;
486}
487
0d218038
SS
488#
489## The _whitelist_localhost function.
490#
491## This tiny private function simple generates and returns a hash which contains
492## the clear and binary converted addresses for all array-stored
493## (@localhost_addresses) in an ignorelist compatible format.
494#
495sub _whitelist_localhost () {
496 my %temphash;
497
498 # Loop through the array of localhost related addresses.
499 foreach my $address (@localhost_addresses) {
500 # Validate and convert the addresss into binary format.
501 my @values = &Guardian::Base::IPOrNet2Int($address);
502
503 # Check if any values are returned.
504 if (@values) {
505 # Store the converted binary values in the temporary hash.
506 $temphash{$address} = [@values];
507 }
508 }
509
510 # Return the temporary hash.
511 return %temphash;
512}
513
df6ec59f 5141;