Mercurial > hg > maltfilter
comparison maltfilter @ 11:26c2cc5077aa
Added reporting functionality.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Fri, 14 Aug 2009 01:19:58 +0300 |
parents | 29ddb6b9b521 |
children | fc053b001027 |
comparison
equal
deleted
inserted
replaced
10:a05ada86fbe0 | 11:26c2cc5077aa |
---|---|
8 ############################################################################# | 8 ############################################################################# |
9 use strict; | 9 use strict; |
10 use Date::Parse; | 10 use Date::Parse; |
11 use Net::IP; | 11 use Net::IP; |
12 | 12 |
13 my $progversion = "0.8"; | |
13 my $progbanner = | 14 my $progbanner = |
14 "Malicious Attack Livid Termination Filter daemon (maltfilter) v0.7\n". | 15 "Malicious Attack Livid Termination Filter daemon (maltfilter) v$progversion\n". |
15 "Programmed by Matti 'ccr' Hamalainen <ccr\@tnsp.org>\n". | 16 "Programmed by Matti 'ccr' Hamalainen <ccr\@tnsp.org>\n". |
16 "(C) Copyright 2009 Tecnic Software productions (TNSP)\n"; | 17 "(C) Copyright 2009 Tecnic Software productions (TNSP)\n"; |
17 | 18 |
18 ############################################################################# | 19 ############################################################################# |
19 ### Settings / configuration | 20 ### Settings / configuration |
20 ############################################################################# | 21 ############################################################################# |
21 my %settings = ( | 22 my %settings = ( |
22 "VERBOSITY" => 4, | 23 "VERBOSITY" => 3, |
23 "DRY_RUN" => 1, | 24 "DRY_RUN" => 1, |
24 "WEEDPERIOD" => 72, | 25 "WEEDPERIOD" => 150, |
25 "TRESHOLD" => 3, | 26 "TRESHOLD" => 3, |
26 "ACTION" => "DROP", | 27 "ACTION" => "DROP", |
27 "LOGFILE" => "/var/log/maltfilter", | 28 "LOGFILE" => "", |
28 "IPTABLES" => "/sbin/iptables", | 29 "IPTABLES" => "/sbin/iptables", |
30 | |
31 "STATUS_FILE_PLAIN" => "", | |
32 "STATUS_FILE_HTML" => "", | |
33 "STATUS_FILE_CSS" => "", | |
29 | 34 |
30 "CHK_SSHD" => 1, | 35 "CHK_SSHD" => 1, |
31 "CHK_KNOWN_CGI" => 1, | 36 "CHK_KNOWN_CGI" => 1, |
32 "CHK_PHP_XSS" => 1, | 37 "CHK_PHP_XSS" => 1, |
33 "CHK_PROXY_SCAN" => 1, | 38 "CHK_PROXY_SCAN" => 1, |
52 my @scanfiles = (); | 57 my @scanfiles = (); |
53 my @noblock_ips = (); | 58 my @noblock_ips = (); |
54 my %filehandles = (); | 59 my %filehandles = (); |
55 my %hitcount = (); | 60 my %hitcount = (); |
56 my %iplist = (); | 61 my %iplist = (); |
62 my %reason = (); | |
63 my %reason_n = (); | |
64 my %ignored = (); | |
65 my %ignored_d = (); | |
57 my $pid_file = ""; | 66 my $pid_file = ""; |
58 my $LOGFILE; | 67 my $LOGFILE; |
59 | 68 |
60 ### Check given logfile line for matches | 69 ### Check given logfile line for matches |
61 sub check_log_line($) | 70 sub check_log_line($) |
65 my $mdate = $1; | 74 my $mdate = $1; |
66 my $merr = $2; | 75 my $merr = $2; |
67 | 76 |
68 # (1.1) Generic login scan attempts | 77 # (1.1) Generic login scan attempts |
69 if ($merr =~ /^Failed password for invalid user \S+ from (\d+\.\d+\.\d+\.\d+)/) { | 78 if ($merr =~ /^Failed password for invalid user \S+ from (\d+\.\d+\.\d+\.\d+)/) { |
70 check_add_entry($1, $mdate, "SSHD", $settings{"CHK_SSHD"}); | 79 check_add_entry($1, $mdate, "SSH login scan", "", $settings{"CHK_SSHD"}); |
71 } | 80 } |
72 # (1.2) Root SSH login password bruteforcing attempts | 81 # (1.2) Root SSH login password bruteforcing attempts |
73 # NOTICE! Do not enable this setting, if you allow SSH root logins via | 82 # NOTICE! Do not enable this setting, if you allow SSH root logins via |
74 # password authentication! Mistyping password may get you blocked then. :) | 83 # password authentication! Mistyping password may get you blocked then. :) |
75 elsif (/^Failed password for root from (\d+\.\d+\.\d+\.\d+)/) { | 84 elsif (/^Failed password for root from (\d+\.\d+\.\d+\.\d+)/) { |
76 check_add_entry($1, $mdate, "Root SSH password bruteforce", $settings{"CHK_ROOT_SSH_PWD"}); | 85 check_add_entry($1, $mdate, "Root SSH password bruteforce", "", $settings{"CHK_ROOT_SSH_PWD"}); |
77 } | 86 } |
78 } | 87 } |
79 # (2) Common/known exploitable CGI/PHP software scans (like phpMyAdmin) | 88 # (2) Common/known exploitable CGI/PHP software scans (like phpMyAdmin) |
80 # NOTICE! This matches ERRORLOG, thus it only works if you DO NOT have | 89 # NOTICE! This matches ERRORLOG, thus it only works if you DO NOT have |
81 # any or some of these installed. Preferably none, or use uncommon | 90 # any or some of these installed. Preferably none, or use uncommon |
85 my $mip = $2; | 94 my $mip = $2; |
86 my $merr = $3; | 95 my $merr = $3; |
87 if ($merr =~ /^File does not exist: (.+)$/) { | 96 if ($merr =~ /^File does not exist: (.+)$/) { |
88 my $tmp = $1; | 97 my $tmp = $1; |
89 if ($tmp =~ /\/mss2|\/pma|admin|sql|\/roundcube|\/webmail|\/bin|\/mail|xampp|zen|mailto:|appserv|cube|round|_vti_bin|wiki/i) { | 98 if ($tmp =~ /\/mss2|\/pma|admin|sql|\/roundcube|\/webmail|\/bin|\/mail|xampp|zen|mailto:|appserv|cube|round|_vti_bin|wiki/i) { |
90 check_add_entry($mip, $mdate, "CGI: $tmp", $settings{"CHK_KNOWN_CGI"}); | 99 check_add_entry($mip, $mdate, "CGI vuln scan", $tmp, $settings{"CHK_KNOWN_CGI"}); |
91 } | 100 } |
92 } | 101 } |
93 } | 102 } |
94 # (3) Match Apache common logging format GET requests here | 103 # (3) Match Apache common logging format GET requests here |
95 elsif (/(\d+\.\d+\.\d+\.\d+)\s+-\s+-\s+\[(.+?)\]\s+\"GET (\S*?) HTTP\//) { | 104 elsif (/(\d+\.\d+\.\d+\.\d+)\s+-\s+-\s+\[(.+?)\]\s+\"GET (\S*?) HTTP\//) { |
101 # NOTICE! If your site genuinely uses (checked) PHP parameters with | 110 # NOTICE! If your site genuinely uses (checked) PHP parameters with |
102 # URIs, you should set CHK_GOOD_HOSTS to match your hostname(s)/IP(s) | 111 # URIs, you should set CHK_GOOD_HOSTS to match your hostname(s)/IP(s) |
103 # used in the URIs. | 112 # used in the URIs. |
104 if ($merr =~ /\.php\?\S*?=http:\/\/([^\/]+)/) { | 113 if ($merr =~ /\.php\?\S*?=http:\/\/([^\/]+)/) { |
105 if (!check_hosts($settings{"CHK_GOOD_HOSTS"}, $1)) { | 114 if (!check_hosts($settings{"CHK_GOOD_HOSTS"}, $1)) { |
106 check_add_entry($mip, $mdate, "PHP XSS: $merr", $settings{"CHK_PHP_XSS"}); | 115 check_add_entry($mip, $mdate, "PHP XSS", $merr, $settings{"CHK_PHP_XSS"}); |
107 } | 116 } |
108 } | 117 } |
109 # (3.2) Try to match proxy scanning attempts | 118 # (3.2) Try to match proxy scanning attempts |
110 elsif ($merr =~ /^http:\/\/([^\/]+)/) { | 119 elsif ($merr =~ /^http:\/\/([^\/]+)/) { |
111 if (!check_hosts($settings{"CHK_GOOD_HOSTS"}, $1)) { | 120 if (!check_hosts($settings{"CHK_GOOD_HOSTS"}, $1)) { |
112 check_add_entry($mip, $mdate, "Proxy scan: $merr", $settings{"CHK_PROXY_SCAN"}); | 121 check_add_entry($mip, $mdate, "Proxy scan", $merr, $settings{"CHK_PROXY_SCAN"}); |
113 } | 122 } |
114 } | 123 } |
115 } | 124 } |
116 } | 125 } |
117 | 126 |
123 { | 132 { |
124 my $level = shift; | 133 my $level = shift; |
125 my $msg = shift; | 134 my $msg = shift; |
126 if (defined($LOGFILE)) { | 135 if (defined($LOGFILE)) { |
127 print $LOGFILE "[".scalar localtime()."] ".$msg if ($settings{"VERBOSITY"} > $level); | 136 print $LOGFILE "[".scalar localtime()."] ".$msg if ($settings{"VERBOSITY"} > $level); |
128 } else { | 137 } elsif ($settings{"DRY_RUN"}) { |
129 print $msg if ($settings{"VERBOSITY"} > $level); | 138 print $msg if ($settings{"VERBOSITY"} > $level); |
130 } | 139 } |
131 } | 140 } |
132 | 141 |
133 | 142 ### Host and IP matching functions |
134 sub check_hosts_array($$) | 143 sub check_hosts_array($$) |
135 { | 144 { |
136 my $chk_host = $_[1]; | 145 my $chk_host = $_[1]; |
137 my $chk_ip = new Net::IP($chk_host); | 146 my $chk_ip = new Net::IP($chk_host); |
138 foreach my $host (@{$_[0]}) { | 147 foreach my $host (@{$_[0]}) { |
194 sub weed_do($) | 203 sub weed_do($) |
195 { | 204 { |
196 if (defined($iplist{$_[0]})) { | 205 if (defined($iplist{$_[0]})) { |
197 mlog(2, "* Weeding $_[0] ($iplist{$_[0]})\n"); | 206 mlog(2, "* Weeding $_[0] ($iplist{$_[0]})\n"); |
198 exec_iptables("-D", "INPUT", "-s", $_[0], "-d", "0.0.0.0/0", "-j", $settings{"ACTION"}); | 207 exec_iptables("-D", "INPUT", "-s", $_[0], "-d", "0.0.0.0/0", "-j", $settings{"ACTION"}); |
208 undef($reason{$_[0]}); | |
209 undef($reason_n{$_[0]}); | |
210 undef($ignored{$_[0]}); | |
211 undef($ignored_d{$_[0]}); | |
199 undef($iplist{$_[0]}); | 212 undef($iplist{$_[0]}); |
200 } | 213 } |
201 } | 214 } |
202 | 215 |
203 sub weed_entries() | 216 sub weed_entries() |
211 } | 224 } |
212 } | 225 } |
213 } | 226 } |
214 } | 227 } |
215 | 228 |
229 ### Output status file | |
230 sub cmp_ips($$) | |
231 { | |
232 my @ipa = split(/\./, $_[0]); | |
233 my @ipb = split(/\./, $_[1]); | |
234 for (my $i = 0; $i < 4; $i++) { | |
235 return -1 if ($ipa[$i] > $ipb[$i]); | |
236 return 1 if ($ipa[$i] < $ipb[$i]); | |
237 } | |
238 return 0; | |
239 } | |
240 | |
241 sub cmp_ip_hits($$$$) | |
242 { | |
243 return -1 if ($_[2] > $_[3]); | |
244 return 1 if ($_[2] < $_[3]); | |
245 return cmp_ips($_[0], $_[1]); | |
246 } | |
247 | |
248 sub printH($$$$) | |
249 { | |
250 my $fh = $_[1]; | |
251 if ($_[0]) { | |
252 print $fh "<h".$_[2].">".$_[3]."</h".$_[2].">\n"; | |
253 } else { | |
254 my $c = ($_[2] <= 1) ? "=" : "-"; | |
255 print $fh $_[3]."\n". $c x length($_[3]) ."\n"; | |
256 } | |
257 } | |
258 | |
259 sub printTD($$$) | |
260 { | |
261 my $fh = $_[1]; | |
262 if ($_[0]) { | |
263 print $fh "<td>".$_[2]."</td>"; | |
264 } else { | |
265 print $fh $_[2]; | |
266 } | |
267 } | |
268 | |
269 sub printP($$$) | |
270 { | |
271 my $fh = $_[1]; | |
272 if ($_[0]) { | |
273 print $fh "<p>\n".$_[2]."</p>\n"; | |
274 } else { | |
275 print $fh $_[2]."\n"; | |
276 } | |
277 } | |
278 | |
279 sub printElem | |
280 { | |
281 my $fh = $_[1]; | |
282 if ($_[0]) { | |
283 print $fh $_[2]; | |
284 } elsif (defined($_[3])) { | |
285 print $fh $_[3]; | |
286 } | |
287 } | |
288 | |
289 sub bb($) | |
290 { | |
291 return $_[0] ? "<b>" : ""; | |
292 } | |
293 | |
294 | |
295 sub eb($) | |
296 { | |
297 return $_[0] ? "</b>" : ""; | |
298 } | |
299 | |
300 sub generate_status($$) | |
301 { | |
302 my $filename = shift; | |
303 my $m = shift; | |
304 | |
305 return unless ($filename ne ""); | |
306 | |
307 mlog(-1, "dumping status '$filename'\n"); | |
308 open(STATUS, ">", $filename) or die("Could not open '".$filename."'!\n"); | |
309 my $f = \*STATUS; | |
310 my $mtime = scalar localtime(); | |
311 | |
312 printElem($m, $f, " | |
313 <html> | |
314 <head> | |
315 <title>Maltfilter status report</title> | |
316 "); | |
317 | |
318 printElem($m, $f, "<link href=\"".$settings{"STATUS_FILE_CSS"}."\" rel=\"stylesheet\" type=\"text/css\" />") | |
319 if ($settings{"STATUS_FILE_CSS"}); | |
320 | |
321 printElem($m, $f, " | |
322 </head> | |
323 <body> | |
324 "); | |
325 | |
326 | |
327 printH($m, $f, 1, "Maltfilter v$progversion status report"); | |
328 printP($m, $f, "Generated: ".$mtime."\n"); | |
329 | |
330 printH($m, $f, 2, "Blocked"); | |
331 | |
332 printElem($m, $f, "<table>\n<tr>". "<th>Hits</th><th>IP-address</th><th>Date of last hit</th><th>Reason(s)</th>"."</tr>\n"); | |
333 my $nexcluded = 0; | |
334 my $ntotal = 0; | |
335 foreach my $mip (sort { $hitcount{$b} <=> $hitcount{$a} } keys %iplist) { | |
336 $nexcluded++ if check_hosts_array(\@noblock_ips, $mip); | |
337 | |
338 printElem($m, $f, " <tr>"); | |
339 printTD($m, $f, sprintf("%-10d", $hitcount{$mip})); | |
340 printTD($m, $f, sprintf("%-15s", $mip)); | |
341 printElem(!$m, $f, " : "); | |
342 printTD($m, $f, scalar localtime($iplist{$mip})); | |
343 my @s = (); | |
344 foreach my $cond (sort keys %{$reason{$mip}}) { | |
345 push(@s, bb($m).$cond.eb($m)." [".$reason_n{$mip}{$cond}." hits] (".$reason{$mip}{$cond}.")"); | |
346 } | |
347 printTD($m, $f, join(", ".($m ? "<br />" : ""), @s)); | |
348 printElem($m, $f, "</tr>\n", "\n"); | |
349 $ntotal++; | |
350 } | |
351 printElem($m, $f, "</table>\n"); | |
352 printP($m, $f, bb($m).$ntotal.eb($m)." entries listed, ". | |
353 bb($m).($ntotal - $nexcluded).eb($m)." blocked, ".bb($m).$nexcluded.eb($m). | |
354 " excluded (defined in NOBLOCK_IPS).\n"); | |
355 | |
356 | |
357 printH($m, $f, 2, "All recorded hits in general"); | |
358 printP($m, $f, "List of 'hits' of suspicious activity noticed by Maltfilter, but not necessarily acted upon.\n"); | |
359 | |
360 printElem($m, $f, "<table>\n<tr>". "<th>IP-address</th><th># of hits</th>" x 2 ."</tr>\n"); | |
361 my $hits = 0; | |
362 my @keys = sort { cmp_ip_hits($a, $b, $hitcount{$a}, $hitcount{$b}) } keys %hitcount; | |
363 my $nkeys = scalar @keys; | |
364 | |
365 my $printEntry = sub { | |
366 printTD($m, $f, sprintf("%-15s", $_[0])); | |
367 printElem(!$m, $f, " : "); | |
368 printTD($m, $f, sprintf("%-8d ", $_[1])); | |
369 }; | |
370 | |
371 my $kmax = $nkeys / 2; | |
372 for (my $i = 0; $i <= $kmax; $i++) { | |
373 printElem($m, $f, " <tr>"); | |
374 if ($i < $kmax) { | |
375 my $mip = $keys[$i]; | |
376 &$printEntry($mip, $hitcount{$mip}); | |
377 $hits += $hitcount{$mip}; | |
378 } | |
379 printElem(!$m, $f, " | "); | |
380 if ($i + $kmax + 1 < $nkeys) { | |
381 my $mip = $keys[$i + $kmax + 1]; | |
382 &$printEntry($mip, $hitcount{$mip}); | |
383 $hits += $hitcount{$mip}; | |
384 } | |
385 printElem($m, $f, "</tr>\n", "\n"); | |
386 } | |
387 | |
388 printElem($m, $f, "</table>\n"); | |
389 | |
390 printP($m, $f, bb($m).(scalar keys %hitcount).eb($m)." IPs total, ".bb($m).$hits.eb($m)." hits total.\n"); | |
391 | |
392 | |
393 printH($m, $f, 2, "Ignored hit types"); | |
394 printP($m, $f, "List of hits that were ignored (not acted upon), because the test was disabled.\n"); | |
395 | |
396 printElem($m, $f, "<table>\n<tr><th>IP-address</th><th>Type (hits, last time of note)</th></tr>\n"); | |
397 foreach my $mip (sort { cmp_ips($a, $b) } keys %ignored) { | |
398 printElem($m, $f, "<tr>"); | |
399 printTD($m, $f, sprintf("%-15s", $mip)); | |
400 printElem($m, $f, "<td>", " : "); | |
401 foreach my $mcond (sort keys %{$ignored{$mip}}) { | |
402 my $s = $mcond." (".$hitcount{$mip}." hits, last ".scalar localtime($ignored_d{$mip}{$mcond}).")"; | |
403 unless ($ignored{$mip}{$mcond} eq "") { $s .= " for '".$ignored{$mip}{$mcond}."'"; } | |
404 print $f $s; | |
405 } | |
406 printElem($m, $f, "</td></tr>", "\n"); | |
407 } | |
408 printElem($m, $f, "</table>\n"); | |
409 | |
410 printElem($m, $f, "</body>\n</html>\n"); | |
411 close(STATUS); | |
412 } | |
413 | |
216 ### Check if given "try count" exceeds treshold and if entry | 414 ### Check if given "try count" exceeds treshold and if entry |
217 ### is NOT in Netfilter already, then add it if so. | 415 ### is NOT in Netfilter already, then add it if so. |
218 sub check_add_entry($$$$) | 416 sub check_add_entry($$$$$) |
219 { | 417 { |
220 my $mip = $_[0]; | 418 my $mip = $_[0]; |
221 my $mdate = str2time($_[1]); | 419 my $mdate = str2time($_[1]); |
222 my $mreason = $_[2]; | 420 my $mclass = $_[2]; |
223 my $mcond = $_[3]; | 421 my $mreason = $_[3]; |
422 my $mcond = $_[4]; | |
224 | 423 |
225 my $cnt = $hitcount{$mip}++; | 424 my $cnt = $hitcount{$mip}++; |
226 if ($cnt >= $settings{"TRESHOLD"} && check_time($mdate)) { | 425 if ($cnt >= $settings{"TRESHOLD"} && check_time($mdate)) { |
227 my $pat; | 426 my $pat; |
228 if (!$mcond) { | 427 if (!$mcond) { |
229 mlog(2, "* Ignoring $mip: $mreason\n"); | 428 $ignored{$mip}{$mclass} = $mreason; |
429 $ignored_d{$mip}{$mclass} = $mdate; | |
230 return; | 430 return; |
231 } | 431 } |
232 if (!defined($iplist{$mip})) { | 432 if (!defined($iplist{$mip})) { |
233 if (!check_hosts_array(@noblock_ips, $mip)) { | 433 if (!check_hosts_array(\@noblock_ips, $mip)) { |
234 # Add entry that has >= treshold hits and is not added yet | 434 # Add entry that has >= treshold hits and is not added yet |
235 mlog(1, "* Adding $mip ($mdate): $mreason\n"); | 435 mlog(1, "* Adding $mip ($mdate): $mreason\n"); |
236 exec_iptables("-I", "INPUT", "1", "-s", $mip, "-j", $settings{"ACTION"}); | 436 exec_iptables("-I", "INPUT", "1", "-s", $mip, "-j", $settings{"ACTION"}); |
237 } | 437 } |
238 $iplist{$mip} = $mdate; | 438 $iplist{$mip} = $mdate; |
439 $reason{$mip}{$mclass} = $mreason; | |
440 $reason_n{$mip}{$mclass}++; | |
239 } else { | 441 } else { |
240 # Over treshold, but is added, check if we can update the timedate | 442 # Over treshold, but is added, check if we can update the timedate |
241 if ($iplist{$mip} >= 0) { | 443 if ($iplist{$mip} < 0) { |
242 if ($mdate > $iplist{$mip}) { | 444 $reason{$mip}{$mclass} = $mreason; |
243 $iplist{$mip} = $mdate; | 445 $reason_n{$mip}{$mclass}++; |
244 } | 446 } |
245 } else { | 447 $iplist{$mip} = $mdate if ($mdate > $iplist{$mip}); |
246 # Empty date, set it now. | |
247 $iplist{$mip} = $mdate; | |
248 } | |
249 } | 448 } |
250 } | 449 } |
251 } | 450 } |
252 | 451 |
253 ### | 452 ### |
254 ### Utility functions | 453 ### Main helper functions |
255 ### | 454 ### |
256 | |
257 sub malt_init { | 455 sub malt_init { |
456 mlog(0, "Updating initial blocklist from netfilter.\n"); | |
457 update_iplist(-1); | |
458 | |
258 foreach my $filename (@scanfiles) { | 459 foreach my $filename (@scanfiles) { |
259 local *INFILE; | 460 local *INFILE; |
260 mlog(0, "- Parsing ".$filename." ...\n"); | 461 mlog(0, "Parsing ".$filename." ...\n"); |
261 open(INFILE, "<", $filename) or die("Could not open '".$filename."'!\n"); | 462 open(INFILE, "<", $filename) or die("Could not open '".$filename."'!\n"); |
262 $filehandles{$filename} = *INFILE; | 463 $filehandles{$filename} = *INFILE; |
263 while (<INFILE>) { | 464 while (<INFILE>) { |
264 chomp; | 465 chomp; |
265 check_log_line($_); | 466 check_log_line($_); |
266 } | 467 } |
267 } | 468 } |
268 | 469 |
269 mlog(0, "- Weeding out old entries.\n"); | 470 mlog(0, "Weeding old entries.\n"); |
270 weed_entries(); | 471 weed_entries(); |
271 } | 472 } |
272 | 473 |
273 sub malt_cleanup { | 474 sub malt_cleanup { |
274 mlog(0, "- Closing open filehandles.\n"); | 475 # Close open filehandles |
275 foreach my $filename (keys %filehandles) { | 476 foreach my $filename (keys %filehandles) { |
276 close($filehandles{$filename}); | 477 close($filehandles{$filename}); |
277 } | 478 } |
278 } | 479 } |
279 | 480 |
280 sub malt_scan { | 481 sub malt_scan { |
281 ### Keep on reading | 482 ### Keep on reading |
282 mlog(1, "- Entering main scanning loop.\n"); | 483 mlog(1, "Entering main scanning loop.\n"); |
283 my $counter = 0; | 484 my $counter = -1; |
284 while (1) { | 485 while (1) { |
285 my %filepos = (); | 486 my %filepos = (); |
286 foreach my $filename (keys %filehandles) { | 487 foreach my $filename (keys %filehandles) { |
287 for ($filepos{$filename} = tell($filehandles{$filename}); $_ = <$filehandles{$filename}>; $filepos{$filename} = tell($filehandles{$filename})) { | 488 for ($filepos{$filename} = tell($filehandles{$filename}); $_ = <$filehandles{$filename}>; $filepos{$filename} = tell($filehandles{$filename})) { |
288 chomp; | 489 chomp; |
289 check_log_line($_); | 490 check_log_line($_); |
290 } | 491 } |
291 } | 492 } |
292 sleep(5); | 493 if ($counter < 0 || $counter++ >= 30) { |
293 if ($counter++ >= 5) { | |
294 # Every once in a while, update known IP list from iptables | 494 # Every once in a while, update known IP list from iptables |
295 # (in case entries have appeared there from "outside") | 495 # (in case entries have appeared there from "outside") |
296 # and perform weeding of old entries. | 496 # and perform weeding of old entries. |
297 $counter = 0; | 497 $counter = 0; |
298 update_iplist(time()); | 498 update_iplist(time()); |
299 weed_entries(); | 499 weed_entries(); |
300 } | 500 generate_status($settings{"STATUS_FILE_PLAIN"}, 0); |
501 generate_status($settings{"STATUS_FILE_HTML"}, 1); | |
502 } | |
503 sleep(5); | |
301 foreach my $filename (keys %filehandles) { | 504 foreach my $filename (keys %filehandles) { |
302 seek($filehandles{$filename}, $filepos{$filename}, 0); | 505 seek($filehandles{$filename}, $filepos{$filename}, 0); |
303 } | 506 } |
304 } | 507 } |
305 } | 508 } |
320 malt_finish(); | 523 malt_finish(); |
321 exit(1); | 524 exit(1); |
322 } | 525 } |
323 | 526 |
324 sub malt_term { | 527 sub malt_term { |
325 mlog(-1, "Receinved TERM, quitting.\n"); | 528 mlog(-1, "Received TERM, quitting.\n"); |
326 malt_cleanup(); | 529 malt_cleanup(); |
327 malt_finish(); | 530 malt_finish(); |
328 exit(1); | 531 exit(1); |
329 } | 532 } |
330 | 533 |
346 | 549 |
347 # Banner | 550 # Banner |
348 my $argc = $#ARGV + 1; | 551 my $argc = $#ARGV + 1; |
349 if ($argc < 1) { | 552 if ($argc < 1) { |
350 print $progbanner. | 553 print $progbanner. |
351 "\nUsage: maltfilter <pid filename> [config filename]\n"; | 554 "\n". |
555 "Usage: maltfilter <pid filename> [config filename]\n". | |
556 " maltfilter -f [config filename]\n". | |
557 "-f turns on the full report mode.\n"; | |
352 exit; | 558 exit; |
353 } | 559 } |
354 | 560 |
355 # Test pid file existence | 561 # Test pid file existence |
562 my $report; | |
356 $pid_file = shift; | 563 $pid_file = shift; |
357 die("'$pid_file' already exists, not starting.\nIf the daemon is NOT running, remove the pid-file and re-start.\n") if (-e $pid_file); | 564 if ($pid_file eq "-f") { |
565 $report = 1; | |
566 } else { | |
567 die("'$pid_file' already exists, not starting.\n". | |
568 "If the daemon is NOT running, remove the pid-file and re-start.\n") | |
569 if (-e $pid_file); | |
570 } | |
358 | 571 |
359 # Read configuration file | 572 # Read configuration file |
360 if (defined(my $config_file = shift)) { | 573 if (defined(my $config_file = shift)) { |
361 my $errors = 0; | 574 my $errors = 0; |
362 | 575 |
397 } | 610 } |
398 close(CONFFILE); | 611 close(CONFFILE); |
399 die("Errors in configuration file '$config_file', bailing out.\n") unless ($errors == 0); | 612 die("Errors in configuration file '$config_file', bailing out.\n") unless ($errors == 0); |
400 } | 613 } |
401 | 614 |
615 # Force dry run mode if we are reporting only | |
616 if ($report) { | |
617 $settings{"DRY_RUN"} = 1; | |
618 } | |
619 | |
402 # Clean up certain arrays duplicate entries | 620 # Clean up certain arrays duplicate entries |
403 my %saw = (); | 621 my %saw = (); |
404 @scanfiles = grep(!$saw{$_}++, @scanfiles_def); | 622 @scanfiles = grep(!$saw{$_}++, @scanfiles_def); |
405 | 623 |
406 undef(%saw); | 624 undef(%saw); |
426 } | 644 } |
427 | 645 |
428 mlog(-1, "Not blocking following IPs: ".join(", ", @noblock_ips)."\n"); | 646 mlog(-1, "Not blocking following IPs: ".join(", ", @noblock_ips)."\n"); |
429 | 647 |
430 # Initialize | 648 # Initialize |
431 update_iplist(-1); | |
432 malt_init(); | 649 malt_init(); |
433 | 650 |
434 # Fork to background, unless dry-running | 651 # Fork to background, unless dry-running |
435 if ($settings{"DRY_RUN"}) { | 652 if ($settings{"DRY_RUN"}) { |
436 malt_scan(); | 653 if ($report) { |
437 malt_cleanup(); | 654 mlog(-1, "Outputting report files.\n"); |
655 generate_status($settings{"STATUS_FILE_PLAIN"}, 0); | |
656 generate_status($settings{"STATUS_FILE_HTML"}, 1); | |
657 malt_cleanup(); | |
658 } else { | |
659 malt_scan(); | |
660 malt_cleanup(); | |
661 } | |
438 } else { | 662 } else { |
439 if (my $pid = fork) { | 663 if (my $pid = fork) { |
440 open(PIDFILE, ">", $pid_file) or die("Could not open pid file '".$pid_file."' for writing!\n"); | 664 open(PIDFILE, ">", $pid_file) or die("Could not open pid file '".$pid_file."' for writing!\n"); |
441 print PIDFILE "$pid\n"; | 665 print PIDFILE "$pid\n"; |
442 close(PIDFILE); | 666 close(PIDFILE); |