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