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