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);