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