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