]> git.ipfire.org Git - people/stevee/guardian.git/blame - modules/Events.pm
Add logrotate support.
[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,
648ca493 16 'logrotate' => \&main::Logrotate,
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;
307
308 # Check if the given IgnoreFile could be opened.
309 unless(-e $file) {
310 $logger->Log("err", "The configured IgnoreFile \($file\) could not be opened. Skipped!");
311 return;
312 }
313
0d218038
SS
314 # Reset current ignore hash and add
315 # localhost related IP addresses.
316 %ignorehash = &_whitelist_localhost();
317
66e1ad0a
SS
318 # Open the given IgnoreFile.
319 open (IGNORE, $file);
320
321 # Read-in the file line by line.
322 while (<IGNORE>) {
323 # Skip comments.
324 next if (/\#/);
325
326 # Skip blank lines.
327 next if (/^\s*$/);
328
329 # Remove any newlines.
330 chomp;
331
332 # Check if the line contains a valid single address or network and
333 # convert it into binary format. Store the result/start and
334 # end values in a temporary array.
335 my @values = &Guardian::Base::IPOrNet2Int($_);
336
337 # If the function returned any values, the line contained a valid
338 # single address or network which successfully has been converted into
339 # binary format.
340 if (@values) {
341 # Assign the array as value to the ignorehash.
342 $ignorehash{$_} = [@values];
343 } else {
344 # Log invalid entry.
345 $logger->Log("err", "IgnoreFile contains an invalid address/network: $_");
346
347 # Skip line.
348 next;
349 }
350 }
351
352 # Close filehandle for the IgnoreFile.
353 close (IGNORE);
5cd378cd
SS
354
355 # Get amount of current elements in hash.
356 my $amount = scalar(keys(%ignorehash));
357
358 # Write out log message.
359 $logger->Log("debug", "Ignore list currently contains $amount entries.");
66e1ad0a
SS
360}
361
362#
363## Private function to check if an address is part of the Ignore Hash.
364#
7c8f0696
SS
365## This function checks if a passed IP address in binary format (!),
366## directly or as part of an ignored network is stored in the ignore hash.
66e1ad0a
SS
367#
368sub _IsOnIgnoreList ($) {
369 my $bin_address = shift;
370
371 # Loop through the ignore hash and grab the stored values.
372 foreach my $key ( keys %ignorehash ) {
373 # Dereference values array.
374 my @values = @{$ignorehash{$key}};
375
376 # Obtain amount of items for the current value array.
377 my $items = scalar @values;
378
379 # Whether the amount equals one, the given binary address just
380 # needs to be compared against a single address.
381 if ($items eq "1") {
382 my ($ignored_address) = @values;
383
384 # Simple check if the stored and the given binary address
385 # are the same.
386 if ($bin_address eq $ignored_address) {
387 # The given address is part of the ignore list.
388 $logger->Log("debug", "Address $key found on the ignore list.");
389
390 # Return "1" (True).
391 return 1;
392 }
393 }
394
395 # If the amount equals two, for passed binary address needs to
396 # be checked if it is part of the ignored network range.
397 elsif ($items eq "2") {
398 my ($first_address, $last_address) = @values;
399
400 # Check if the passed binary address is bigger than
401 # the first address and smaler than the last address
402 # (between) the stored network range.
403 if (($bin_address >= $first_address) && ($bin_address <= $last_address)) {
404 # The address is part of an ignored network.
405 $logger->Log("debug", "Address is found inside the ignored network $key.");
406
407 # Return "1" (True).
408 return 1;
409 }
410
411 # If the amount is not eighter one or two, the current entry of the ignorehash seems
412 # to be corrupted. Skip and log it.
413 } else {
414 # Write log message about this corruped item in the ignore hash.
415 $logger->Log("err", "Invalid item in the Ignore Hash: $key - @values");
416
417 # Skip this element of the ignore hash.
418 next;
419 }
420 }
421
422 # If we got here, the given address is not part of the ignore hash.
423 # Return nothing (False).
424 return;
425}
426
0d218038
SS
427#
428## The _whitelist_localhost function.
429#
430## This tiny private function simple generates and returns a hash which contains
431## the clear and binary converted addresses for all array-stored
432## (@localhost_addresses) in an ignorelist compatible format.
433#
434sub _whitelist_localhost () {
435 my %temphash;
436
437 # Loop through the array of localhost related addresses.
438 foreach my $address (@localhost_addresses) {
439 # Validate and convert the addresss into binary format.
440 my @values = &Guardian::Base::IPOrNet2Int($address);
441
442 # Check if any values are returned.
443 if (@values) {
444 # Store the converted binary values in the temporary hash.
445 $temphash{$address} = [@values];
446 }
447 }
448
449 # Return the temporary hash.
450 return %temphash;
451}
452
df6ec59f 4531;