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