Mercurial > hg > maltfilter
comparison maltfilter @ 66:42889eed0ce8
Lots of cleanups, etc. Documentation updates.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Tue, 18 Aug 2009 03:21:30 +0300 |
parents | d2e2b82dd2f2 |
children | 8df5d52436a1 |
comparison
equal
deleted
inserted
replaced
65:d2e2b82dd2f2 | 66:42889eed0ce8 |
---|---|
10 use Date::Parse; | 10 use Date::Parse; |
11 use Net::IP; | 11 use Net::IP; |
12 use Net::DNS; | 12 use Net::DNS; |
13 use LWP::UserAgent; | 13 use LWP::UserAgent; |
14 | 14 |
15 my $progversion = "0.15.0"; | 15 my $progversion = "0.16.0"; |
16 my $progbanner = | 16 my $progbanner = |
17 "Malicious Attack Livid Termination Filter daemon (maltfilter) v$progversion\n". | 17 "Malicious Attack Livid Termination Filter daemon (maltfilter) v$progversion\n". |
18 "Programmed by Matti 'ccr' Hamalainen <ccr\@tnsp.org>\n". | 18 "Programmed by Matti 'ccr' Hamalainen <ccr\@tnsp.org>\n". |
19 "(C) Copyright 2009 Tecnic Software productions (TNSP)\n"; | 19 "(C) Copyright 2009 Tecnic Software productions (TNSP)\n"; |
20 | 20 |
21 | |
21 ############################################################################# | 22 ############################################################################# |
22 ### Default settings and configuration | 23 ### Default settings and configuration |
23 ############################################################################# | 24 ############################################################################# |
24 my %settings = ( | 25 my %settings = ( |
25 "VERBOSITY" => 3, | 26 "VERBOSITY" => 3, |
26 "DRY_RUN" => 1, | 27 "DRY_RUN" => 1, |
27 "WEED_FILTER" => 168, # in hours | 28 "LOGFILE" => "", |
28 "WEED_GLOBAL" => 336, # in hours | 29 "STATS_MAX_AGE" => 336, # in hours |
29 "THRESHOLD" => 3, | |
30 "ACTION" => "DROP", | |
31 "LOGFILE" => "", | |
32 "IPTABLES" => "/sbin/iptables", | |
33 | 30 |
34 "PASSWD" => "/etc/passwd", | 31 "PASSWD" => "/etc/passwd", |
35 "SYSACCT_MIN_UID" => 1, | 32 "SYSACCT_MIN_UID" => 1, |
36 "SYSACCT_MAX_UID" => 100, | 33 "SYSACCT_MAX_UID" => 100, |
34 | |
35 "FILTER" => 0, | |
36 "FILTER_THRESHOLD" => 3, | |
37 "FILTER_MAX_AGE" => 168, # in hours | |
38 "FILTER_TARGET" => "DROP", | |
39 "IPTABLES" => "/sbin/iptables", | |
37 | 40 |
38 "FULL_TIME" => 1, | 41 "FULL_TIME" => 1, |
39 "STATUS_FILE_PLAIN" => "", | 42 "STATUS_FILE_PLAIN" => "", |
40 "STATUS_FILE_HTML" => "", | 43 "STATUS_FILE_HTML" => "", |
41 "STATUS_FILE_CSS" => "", | 44 "STATUS_FILE_CSS" => "", |
57 "DRONEBL_MAX_AGE" => 30, # in minutes | 60 "DRONEBL_MAX_AGE" => 30, # in minutes |
58 "DRONEBL_RPC_URI" => "http://dronebl.org/RPC2", | 61 "DRONEBL_RPC_URI" => "http://dronebl.org/RPC2", |
59 "DRONEBL_RPC_KEY" => "", | 62 "DRONEBL_RPC_KEY" => "", |
60 ); | 63 ); |
61 | 64 |
62 my @noblock_ips_def = ( | 65 my @noaction_ips_def = ( |
63 "127.0.0.1", | 66 "127.0.0.1", |
64 ); | 67 ); |
65 | 68 |
66 my %systemacct = (); | 69 my %systemacct = (); |
67 sub check_add_hit($$$$$$); | 70 sub check_add_hit($$$$$$); |
77 my $mdate = $1; | 80 my $mdate = $1; |
78 my $merr = $2; | 81 my $merr = $2; |
79 | 82 |
80 # (1.1) Generic login scan attempts | 83 # (1.1) Generic login scan attempts |
81 if ($merr =~ /^Failed password for invalid user (\S+) from (\d+\.\d+\.\d+\.\d+)/) { | 84 if ($merr =~ /^Failed password for invalid user (\S+) from (\d+\.\d+\.\d+\.\d+)/) { |
82 check_add_hit($2, $mdate, "SSH login scan", "", $settings{"CHK_SSHD"}); | 85 check_add_hit($2, $mdate, "SSH login scan", "", 13, $settings{"CHK_SSHD"}); |
83 } | 86 } |
84 # (1.2) Root account SSH login password bruteforcing attempts. | 87 # (1.2) Root account SSH login password bruteforcing attempts. |
85 elsif (/^Failed password for root from (\d+\.\d+\.\d+\.\d+)/) { | 88 elsif (/^Failed password for root from (\d+\.\d+\.\d+\.\d+)/) { |
86 check_add_hit($1, $mdate, "Root SSH password bruteforce", "", $settings{"CHK_ROOT_SSH_PWD"}); | 89 check_add_hit($1, $mdate, "Root SSH password bruteforce", "", 13, $settings{"CHK_ROOT_SSH_PWD"}); |
87 } | 90 } |
88 # (1.3) System account SSH login password bruteforcing attempts. | 91 # (1.3) System account SSH login password bruteforcing attempts. |
89 if ($merr =~ /^Failed password for (\S+) from (\d+\.\d+\.\d+\.\d+)/) { | 92 if ($merr =~ /^Failed password for (\S+) from (\d+\.\d+\.\d+\.\d+)/) { |
90 my $mip = $2; my $macct = $1; | 93 my $mip = $2; my $macct = $1; |
91 if (defined($systemacct{$macct})) { | 94 if (defined($systemacct{$macct})) { |
92 check_add_hit($mip, $mdate, "SSH system account bruteforce", $macct, $settings{"CHK_SYSACCT_SSH_PWD"}); | 95 check_add_hit($mip, $mdate, "SSH system account bruteforce", $macct, 13, $settings{"CHK_SYSACCT_SSH_PWD"}); |
93 } | 96 } |
94 } | 97 } |
95 } | 98 } |
99 | |
96 # (2) Common/known vulnerable CGI/PHP software scans (like phpMyAdmin) | 100 # (2) Common/known vulnerable CGI/PHP software scans (like phpMyAdmin) |
97 elsif (/^\[(.+?)\]\s+\[error\]\s+\[client\s+(\d+\.\d+\.\d+\.\d+)\]\s+(.+)$/) { | 101 elsif (/^\[(.+?)\]\s+\[error\]\s+\[client\s+(\d+\.\d+\.\d+\.\d+)\]\s+(.+)$/) { |
98 my $mdate = $1; | 102 my $mdate = $1; |
99 my $mip = $2; | 103 my $mip = $2; |
100 my $merr = $3; | 104 my $merr = $3; |
101 if ($merr =~ /^File does not exist: (.+)$/) { | 105 if ($merr =~ /^File does not exist: (.+)$/) { |
102 my $tmp = $1; | 106 my $tmp = $1; |
103 if ($tmp =~ /\/mss2|\/pma|admin|sql|\/roundcube|\/webmail|\/bin|\/mail|xampp|zen|mailto:|appserv|cube|round|_vti_bin|wiki/i) { | 107 if ($tmp =~ /\/mss2|\/pma|admin|sql|\/roundcube|\/webmail|\/bin|\/mail|xampp|zen|mailto:|appserv|cube|round|_vti_bin|wiki/i) { |
104 check_add_hit($mip, $mdate, "CGI vuln scan", $tmp, $settings{"CHK_KNOWN_CGI"}); | 108 check_add_hit($mip, $mdate, "CGI vuln scan", $tmp, 2, $settings{"CHK_KNOWN_CGI"}); |
105 } | 109 } |
106 } | 110 } |
107 } | 111 } |
112 | |
108 # (3) Apache common logging format checks | 113 # (3) Apache common logging format checks |
109 elsif (/(\d+\.\d+\.\d+\.\d+)\s+-\s+-\s+\[(.+?)\]\s+\"GET (\S*?) HTTP\//) { | 114 elsif (/(\d+\.\d+\.\d+\.\d+)\s+-\s+-\s+\[(.+?)\]\s+\"GET (\S*?) HTTP\//) { |
110 my $mdate = $2; | 115 my $mdate = $2; |
111 my $mip = $1; | 116 my $mip = $1; |
112 my $merr = $3; | 117 my $merr = $3; |
113 | 118 |
114 # (3.1) Simple match for generic PHP XSS vulnerability scans | 119 # (3.1) Simple match for generic PHP XSS vulnerability scans |
115 if ($merr =~ /\.php\?\S*?=http:\/\/([^\/]+)/) { | 120 if ($merr =~ /\.php\?\S*?=http:\/\/([^\/]+)/) { |
116 if (!check_hosts($settings{"CHK_GOOD_HOSTS"}, $1)) { | 121 if (!check_hosts($settings{"CHK_GOOD_HOSTS"}, $1)) { |
117 if ($merr =~ /\.php\?\S*?=(http:\/\/[^\&\?]+\??)/) { | 122 if ($merr =~ /\.php\?\S*?=(http:\/\/[^\&\?]+\??)/) { |
118 check_add_evidence($mip, $1, $merr); | 123 evidence_queue($mip, $1, $merr); |
119 } | 124 } |
120 check_add_hit($mip, $mdate, "PHP XSS", $merr, $settings{"CHK_PHP_XSS"}); | 125 check_add_hit($mip, $mdate, "PHP XSS", $merr, 2, $settings{"CHK_PHP_XSS"}); |
121 } | 126 } |
122 } | 127 } |
123 # (3.2) Try to match proxy scanning attempts | 128 # (3.2) Try to match proxy scanning attempts |
124 elsif ($merr =~ /^http:\/\/([^\/]+)/) { | 129 elsif ($merr =~ /^http:\/\/([^\/]+)/) { |
125 if (!check_hosts($settings{"CHK_GOOD_HOSTS"}, $1)) { | 130 if (!check_hosts($settings{"CHK_GOOD_HOSTS"}, $1)) { |
126 check_add_hit($mip, $mdate, "Proxy scan", $merr, $settings{"CHK_PROXY_SCAN"}); | 131 check_add_hit($mip, $mdate, "Proxy scan", $merr, 2, $settings{"CHK_PROXY_SCAN"}); |
127 } | 132 } |
128 } | 133 } |
129 } | 134 } |
130 } | 135 } |
131 | 136 |
134 ### Global variables | 139 ### Global variables |
135 ############################################################################# | 140 ############################################################################# |
136 my $reportmode = 0; # Full report mode | 141 my $reportmode = 0; # Full report mode |
137 my @scanfiles = (); # Files to scan | 142 my @scanfiles = (); # Files to scan |
138 my @scanfiles_once = (); # Files to scan only once during startup or HUP (e.g. not continuously followed) | 143 my @scanfiles_once = (); # Files to scan only once during startup or HUP (e.g. not continuously followed) |
139 my @noblock_ips = (); # IPs not to block | 144 my @noaction_ips = (); # IPs not to block |
140 my %filehandles = (); # Global hash holding opened scanned log filehandles | 145 my %filehandles = (); # Global hash holding opened scanned log filehandles |
141 my $pid_file = ""; # Name of Maltfilter daemon pid file | 146 my $pid_file = ""; # Name of Maltfilter daemon pid file |
142 my @configfiles = (); # Array of configuration file names | 147 my @configfiles = (); # Array of configuration file names |
143 my $LOGFILE; # Maltfilter logfile handle | 148 my $LOGFILE; # Maltfilter logfile handle |
144 my %dronebl = (); | 149 my %dronebl = (); |
145 | 150 |
146 # IPs currently blocked in Netfilter $blocklist{$ip} = date | 151 # IPs currently blocked in Netfilter $filterlist{$ip} = date |
147 my %blocklist = (); | 152 my %filterlist = (); |
148 | 153 |
149 # Gathered information about hosts | 154 # Gathered information about hosts |
150 # $statlist{$ip}-> | 155 # $statlist{$ip}-> |
151 # "date1" = timestamp of first hit | 156 # "date1" = timestamp of first hit |
152 # "date2" = timestamp of latest hit | 157 # "date2" = timestamp of latest hit |
306 | 311 |
307 "Hits | IP-address | First hit | Latest hit | Reason(s)\n" | 312 "Hits | IP-address | First hit | Latest hit | Reason(s)\n" |
308 ); | 313 ); |
309 | 314 |
310 foreach my $mip (sort { $func->($table, $a, $b) } keys %{$keys}) { | 315 foreach my $mip (sort { $func->($table, $a, $b) } keys %{$keys}) { |
311 my $blocked = defined($blocklist{$mip}) ? "blocked" : "unblocked"; | 316 my $blocked = defined($filterlist{$mip}) ? "blocked" : "unblocked"; |
312 printElem($m, $f, " <tr class=\"$blocked\">"); | 317 printElem($m, $f, " <tr class=\"$blocked\">"); |
313 printTD($m, $f, sprintf(bb($m)."%-10d".eb($m), $table->{$mip}{"hits"})); | 318 printTD($m, $f, sprintf(bb($m)."%-10d".eb($m), $table->{$mip}{"hits"})); |
314 printElem(!$m, $f, " | "); | 319 printElem(!$m, $f, " | "); |
315 printTD($m, $f, sprintf("%-15s", get_link($m, $mip))); | 320 printTD($m, $f, sprintf("%-15s", get_link($m, $mip))); |
316 printElem(!$m, $f, " | "); | 321 printElem(!$m, $f, " | "); |
381 | 386 |
382 my @previp = ("0.0.0.0", "0.0.0.0"); | 387 my @previp = ("0.0.0.0", "0.0.0.0"); |
383 my @ncolor = (0, 0); | 388 my @ncolor = (0, 0); |
384 | 389 |
385 my $printEntry = sub { | 390 my $printEntry = sub { |
386 my $blocked = "class=\"".(defined($blocklist{$_[0]}) ? "blocked" : "unblocked")."\""; | 391 my $blocked = "class=\"".(defined($filterlist{$_[0]}) ? "blocked" : "unblocked")."\""; |
387 if (test_ips($previp[$_[1]], $_[0]) < 3) { | 392 if (test_ips($previp[$_[1]], $_[0]) < 3) { |
388 $ncolor[$_[1]]++; | 393 $ncolor[$_[1]]++; |
389 } | 394 } |
390 $previp[$_[1]] = $_[0]; | 395 $previp[$_[1]] = $_[0]; |
391 my $str = "style=\"background: ".$ipcolors[$ncolor[$_[1]] % scalar @ipcolors].";\""; | 396 my $str = "style=\"background: ".$ipcolors[$ncolor[$_[1]] % scalar @ipcolors].";\""; |
475 </head> | 480 </head> |
476 <body> | 481 <body> |
477 "); | 482 "); |
478 | 483 |
479 printH($m, $f, 1, "Maltfilter v$progversion status report"); | 484 printH($m, $f, 1, "Maltfilter v$progversion status report"); |
480 my $period = get_period($settings{"WEED_GLOBAL"}); | 485 my $period = get_period($settings{"STATS_MAX_AGE"}); |
481 | 486 |
482 printP($m, $f, | 487 printP($m, $f, |
483 "Generated ".bb($m).get_time_str(time()).eb($m).". Data computed from ". | 488 "Generated ".bb($m).get_time_str(time()).eb($m).". Data computed from ". |
484 ($reportmode ? "complete logfile scan" : "a period of last $period").".\n"); | 489 ($reportmode ? "complete logfile scan" : "a period of last $period").".\n"); |
485 | 490 |
486 printP($m, $f, "The hit classes marked as 'IPTABLES' are a pseudo-class meaning an\n". | 491 printP($m, $f, "The hit classes marked as 'IPTABLES' are a pseudo-class meaning an\n". |
487 "blocked IP that was in Netfilter before Maltfilter was started.\n"); | 492 "blocked IP that was in Netfilter before Maltfilter was started.\n"); |
488 | 493 |
489 printH($m, $f, 2, "Currently filtered entries"); | 494 printH($m, $f, 2, "Currently filtered entries"); |
490 $period = get_period($settings{"WEED_FILTER"}); | 495 $period = get_period($settings{"FILTER_MAX_AGE"}); |
491 printP($m, $f, "List of IPs that are currently filtered (or would be, if this is\n". | 496 printP($m, $f, "List of IPs that are currently filtered (or would be, if this is\n". |
492 "a report-only mode). Data from period of $period.\n"); | 497 "a report-only mode). Data from period of $period.\n"); |
493 print_table1($m, $f, \%statlist, \%blocklist, \&cmp_hits, "blocked"); | 498 print_table1($m, $f, \%statlist, \%filterlist, \&cmp_hits, "blocked"); |
494 | 499 |
495 printH($m, $f, 2, "Summary of non-ignored entries"); | 500 printH($m, $f, 2, "Summary of non-ignored entries"); |
496 printP($m, $f, "List of 'hits' of suspicious activity noticed by Maltfilter, but not\n". | 501 printP($m, $f, "List of 'hits' of suspicious activity noticed by Maltfilter, but not\n". |
497 "necessarily acted upon. Sorted by descending IP address.\n"); | 502 "necessarily acted upon. Sorted by descending IP address.\n"); |
498 print_table2($m, $f, \%statlist, \%statlist, \&cmp_ips, "global"); | 503 print_table2($m, $f, \%statlist, \%statlist, \&cmp_ips, "global"); |
510 ############################################################################# | 515 ############################################################################# |
511 ### DroneBL submission support | 516 ### DroneBL submission support |
512 ############################################################################# | 517 ############################################################################# |
513 sub dronebl_process | 518 sub dronebl_process |
514 { | 519 { |
515 # return if ($reportmode); | |
516 return unless ($settings{"DRONEBL"} > 0); | 520 return unless ($settings{"DRONEBL"} > 0); |
521 return if ($settings{"DRY_RUN"}); | |
517 | 522 |
518 # Create submission data | 523 # Create submission data |
519 my $xml = "<?xml version=\"1.0\"?>\n<request key=\"".$settings{"DRONEBL_RPC_KEY"}."\">\n"; | 524 my $xml = "<?xml version=\"1.0\"?>\n<request key=\"".$settings{"DRONEBL_RPC_KEY"}."\">\n"; |
520 my $entries = 0; | 525 my $entries = 0; |
521 while (my ($ip, $entry) = each(%dronebl)) { | 526 while (my ($ip, $entry) = each(%dronebl)) { |
522 if ($entry->{"sent"} == 0) { | 527 if ($entry->{"sent"} == 0 && $entry->{"tries"} < 3) { |
523 $xml .= "<add ip=\"".$ip."\" type=\"".$entry->{"type"}."\" />\n"; | 528 $xml .= "<add ip=\"".$ip."\" type=\"1\" />\n"; |
529 # $xml .= "<add ip=\"".$ip."\" type=\"".$entry->{"type"}."\" />\n"; | |
524 $entries++; | 530 $entries++; |
525 } | 531 } |
526 } | 532 } |
527 $xml .= "</request>\n"; | 533 $xml .= "</request>\n"; |
528 | 534 |
529 mlog(1, "Trying to submit $entries entries to DroneBL.\n"); | 535 # Bait out if no entries to submit |
530 print STDERR $xml; | |
531 return; | |
532 | |
533 return unless ($entries > 0); | 536 return unless ($entries > 0); |
537 mlog(1, "[DroneBL] Trying to submit $entries entries.\n"); | |
538 | |
539 return; | |
534 | 540 |
535 # Submit via HTTP XML-RPC | 541 # Submit via HTTP XML-RPC |
536 my $tmp = LWP::UserAgent->new; | 542 my $tmp = LWP::UserAgent->new; |
537 $tmp->agent("Maltfilter/".$progversion); | 543 $tmp->agent("Maltfilter/".$progversion); |
538 $tmp->timeout(10); | 544 $tmp->timeout(10); |
541 $req->content($xml); | 547 $req->content($xml); |
542 $req->user_agent("Maltfilter/".$progversion); | 548 $req->user_agent("Maltfilter/".$progversion); |
543 my $res = $tmp->request($req); | 549 my $res = $tmp->request($req); |
544 | 550 |
545 if ($res->is_success) { | 551 if ($res->is_success) { |
546 while (my ($ip, $entry) = each(%dronebl)) { | 552 mlog(2, "[DroneBL] [".$res->code."] ".$res->message."\n"); |
547 $entry->{"sent"} = 1; | 553 print $res->content."\n"; |
548 } | 554 # while (my ($ip, $entry) = each(%dronebl)) { |
549 } else { | 555 # $entry->{"sent"} = 1; |
550 mlog(-1, "DroneBL submission failed: [".$res->code."] ".$res->message."\n"); | 556 # } |
557 } else { | |
558 mlog(-1, "[DroneBL] Submission failed: [".$res->code."] ".$res->message."\n"); | |
551 } | 559 } |
552 | 560 |
553 # Remove submitted expired entries | 561 # Remove submitted expired entries |
554 while (my ($ip, $entry) = each(%dronebl)) { | 562 while (my ($ip, $entry) = each(%dronebl)) { |
555 print "$ip: ".$entry->{"sent"}."\n" unless check_time3($entry->{"date"}); | 563 if (!check_time3($entry->{"date"})) { |
556 } | 564 mlog(1, "[DroneBL] $ip submission expired.\n") unless ($entry->{"sent"} > 0); |
557 } | 565 delete($dronebl{$ip}); |
558 | 566 } |
567 } | |
568 } | |
569 | |
570 sub dronebl_queue($$$) | |
571 { | |
572 my ($mip, $mdate, $mtype) = @_; | |
573 if (!defined($dronebl{$mip})) { | |
574 mlog(3, "[DroneBL] Queueing $mip \@ $mdate ($mtype)\n"); | |
575 $dronebl{$mip}{"type"} = $mtype; | |
576 $dronebl{$mip}{"date"} = $mdate; | |
577 $dronebl{$mip}{"sent"} = 0; | |
578 $dronebl{$mip}{"tries"} = 0; | |
579 } | |
580 } | |
559 | 581 |
560 ############################################################################# | 582 ############################################################################# |
561 ### Evidence gathering | 583 ### Evidence gathering |
562 ############################################################################# | 584 ############################################################################# |
563 my %evidence = (); | 585 my %evidence = (); |
564 | 586 |
565 sub check_add_evidence($$$) | 587 sub evidence_queue($$$) |
566 { | 588 { |
567 my ($mip, $mdata, $mfull) = @_; | 589 my ($mip, $mdata, $mfull) = @_; |
568 | 590 |
569 return unless ($settings{"EVIDENCE"}); | 591 return unless ($settings{"EVIDENCE"} > 0); |
570 | 592 |
571 my $tmp = $mdata; | 593 my $tmp = $mdata; |
572 $tmp =~ s/http:\/\///; | 594 $tmp =~ s/http:\/\///; |
573 $tmp =~ s/^\.+/_/; | 595 $tmp =~ s/^\.+/_/; |
574 $tmp =~ s/[^A-Za-z0-9:\.]/_/g; | 596 $tmp =~ s/[^A-Za-z0-9:\.]/_/g; |
576 $evidence{$mdata}{"coll"} = $tmp; | 598 $evidence{$mdata}{"coll"} = $tmp; |
577 $evidence{$mdata}{"hosts"}{$mip} = 1; | 599 $evidence{$mdata}{"hosts"}{$mip} = 1; |
578 $evidence{$mdata}{"full"}{$mfull} = 1; | 600 $evidence{$mdata}{"full"}{$mfull} = 1; |
579 } | 601 } |
580 | 602 |
581 sub http_fetch($$) | 603 sub evidence_fetch($$) |
582 { | 604 { |
583 my $tmp = LWP::UserAgent->new; | 605 my $tmp = LWP::UserAgent->new; |
584 $tmp->agent("-"); | 606 $tmp->agent("-"); |
585 $tmp->timeout(10); | 607 $tmp->timeout(10); |
586 $tmp->default_headers->referer($_[1]); | 608 $tmp->default_headers->referer($_[1]); |
587 my $req = HTTP::Request->new(GET => $_[0]); | 609 my $req = HTTP::Request->new(GET => $_[0]); |
588 return $tmp->request($req); | 610 return $tmp->request($req); |
589 } | 611 } |
590 | 612 |
591 sub gather_evidence | 613 sub evidence_gather |
592 { | 614 { |
593 my $dns = Net::DNS::Resolver->new; | 615 my $dns = Net::DNS::Resolver->new; |
594 my $base = $settings{"EVIDENCE_DIR"}; | 616 my $base = $settings{"EVIDENCE_DIR"}; |
595 | 617 |
596 return unless ($settings{"EVIDENCE"}); | 618 return unless ($settings{"EVIDENCE"} > 0); |
597 | 619 |
598 mdie("Evidence directory '$base' has disappeared.\n") unless (-e $base); | 620 mdie("Evidence directory '$base' has disappeared.\n") unless (-e $base); |
599 | 621 |
600 foreach my $url (keys %evidence) { | 622 foreach my $url (keys %evidence) { |
601 my $did_fetch = 0; | 623 my $did_fetch = 0; |
605 | 627 |
606 # Get data contents only once | 628 # Get data contents only once |
607 if (! -e $filename) { | 629 if (! -e $filename) { |
608 $did_fetch = 1; | 630 $did_fetch = 1; |
609 mlog(1, "Fetching evidence for $url\n"); | 631 mlog(1, "Fetching evidence for $url\n"); |
610 my $res = http_fetch($url, ""); | 632 my $res = evidence_fetch($url, ""); |
611 open(FILE, ">:raw", $filename) or mdie("Could not open '$filename' for writing.\n"); | 633 open(FILE, ">:raw", $filename) or mdie("Could not open '$filename' for writing.\n"); |
612 binmode(FILE, ":raw"); | 634 binmode(FILE, ":raw"); |
613 if ($res->is_success && $res->code >= 200 && $res->code <= 201) { | 635 if ($res->is_success && $res->code >= 200 && $res->code <= 201) { |
614 print FILE $res->content; | 636 print FILE $res->content; |
615 } | 637 } |
695 } | 717 } |
696 | 718 |
697 ### Execute iptables | 719 ### Execute iptables |
698 sub exec_iptables(@) | 720 sub exec_iptables(@) |
699 { | 721 { |
722 $ENV{"PATH"} = ""; | |
700 my @args = ($settings{"IPTABLES"}, @_); | 723 my @args = ($settings{"IPTABLES"}, @_); |
701 if ($settings{"DRY_RUN"}) { | 724 if ($settings{"DRY_RUN"}) { |
702 mlog(3, ":: ".join(" ", @args)."\n"); | 725 mlog(3, ":: ".join(" ", @args)."\n"); |
703 } else { | 726 } else { |
704 system(@args) == 0 or print join(" ", @args)." failed: $?\n"; | 727 system(@args) == 0 or print join(" ", @args)." failed: $?\n"; |
705 } | 728 } |
706 } | 729 } |
707 | 730 |
708 ### Get current Netfilter INPUT table entries that match | 731 ### Get current Netfilter INPUT table entries that match |
709 ### entry types we manage, e.g. blocklist | 732 ### entry types we manage, e.g. filterlist |
710 sub update_blocklist($) | 733 sub update_filterlist($) |
711 { | 734 { |
712 # NOTICE: argument not used now | 735 return unless ($settings{"FILTER"} > 0); |
713 my $first = $_[0]; | 736 my $first = $_[0]; |
714 | 737 mlog(0, "Updating initial filterlist from netfilter.\n") unless ($first > 0); |
738 | |
715 $ENV{"PATH"} = ""; | 739 $ENV{"PATH"} = ""; |
716 open(STATUS, $settings{"IPTABLES"}." -v -n -L INPUT |") or | 740 open(STATUS, $settings{"IPTABLES"}." -v -n -L INPUT |") or |
717 mdie("Could not execute ".$settings{"IPTABLES"}."\n"); | 741 mdie("Could not execute ".$settings{"IPTABLES"}."\n"); |
718 my %newlist = (); | 742 my %newlist = (); |
719 undef(%newlist); | 743 undef(%newlist); |
720 while (<STATUS>) { | 744 while (<STATUS>) { |
721 chomp; | 745 chomp; |
722 if (/^\s*(\d+)\s+\d+\s+$settings{"ACTION"}\s+all\s+--\s+\*\s+\*\s+(\d+\.\d+\.\d+\.\d+)\s+0\.0\.0\.0\/0\s*$/) { | 746 if (/^\s*(\d+)\s+\d+\s+$settings{"FILTER_TARGET"}\s+all\s+--\s+\*\s+\*\s+(\d+\.\d+\.\d+\.\d+)\s+0\.0\.0\.0\/0\s*$/) { |
723 my $mip = $2; | 747 my $mip = $2; |
724 my $mdate = time(); | 748 my $mdate = time(); |
725 if (!defined($blocklist{$mip})) { | 749 if (!defined($filterlist{$mip})) { |
726 mlog(2, "* $mip appeared in iptables.\n") if ($first > 0); | 750 mlog(2, "* $mip appeared in iptables.\n") unless ($first < 0); |
727 $blocklist{$2} = $mdate; | 751 $filterlist{$2} = $mdate; |
728 } | 752 } |
729 $newlist{$2} = $mdate; | 753 $newlist{$2} = $mdate; |
730 update_entry(\%statlist, $mip, -1, "IPTABLES", "", 0); | 754 update_entry(\%statlist, $mip, -1, "IPTABLES", "", 0); |
731 } | 755 } |
732 } | 756 } |
733 close(STATUS); | 757 close(STATUS); |
734 | 758 |
735 foreach my $mip (keys %blocklist) { | 759 foreach my $mip (keys %filterlist) { |
736 if (!defined($newlist{$mip})) { | 760 if (!defined($newlist{$mip})) { |
737 mlog(2, "* $mip removed from iptables.\n"); | 761 mlog(2, "* $mip removed from iptables.\n"); |
738 delete($blocklist{$mip}); | 762 delete($filterlist{$mip}); |
739 } | 763 } |
740 } | 764 } |
741 } | 765 } |
742 | 766 |
743 ### Check if given timestamp is _newer_ than weedperiod threshold. | 767 ### Check if given timestamp is _newer_ than weedperiod threshold. |
744 ### Returns false if timestamp is over weed period, e.g. needs weeding. | 768 ### Returns false if timestamp is over weed period, e.g. needs weeding. |
745 sub check_time1($) | 769 sub check_time1($) |
746 { | 770 { |
747 return ($_[0] > time() - ($settings{"WEED_FILTER"} * 60 * 60)); | 771 return ($_[0] > time() - ($settings{"FILTER_MAX_AGE"} * 60 * 60)); |
748 } | 772 } |
749 | 773 |
750 sub check_time2($) | 774 sub check_time2($) |
751 { | 775 { |
752 return ($_[0] > time() - ($settings{"WEED_GLOBAL"} * 60 * 60)); | 776 return ($_[0] > time() - ($settings{"STATS_MAX_AGE"} * 60 * 60)); |
753 } | 777 } |
754 | 778 |
755 sub check_time3($) | 779 sub check_time3($) |
756 { | 780 { |
757 return ($_[0] > time() - ($settings{"DRONEBL_MAX_AGE"} * 60)); | 781 return ($_[0] > time() - ($settings{"DRONEBL_MAX_AGE"} * 60)); |
758 } | 782 } |
759 | 783 |
760 ### Weed out old entries | 784 ### Weed out old entries |
761 sub weed_do($) | 785 sub weed_do($) |
762 { | 786 { |
763 my $mtime = $blocklist{$_[0]}; | 787 my $mtime = $filterlist{$_[0]}; |
764 mlog(2, "* Weeding $_[0] (".get_time_str($mtime).")\n"); | 788 mlog(2, "* Weeding $_[0] (".get_time_str($mtime).")\n"); |
765 exec_iptables("-D", "INPUT", "-s", $_[0], "-d", "0.0.0.0/0", "-j", $settings{"ACTION"}); | 789 exec_iptables("-D", "INPUT", "-s", $_[0], "-d", "0.0.0.0/0", "-j", $settings{"FILTER_TARGET"}); |
766 delete($blocklist{$_[0]}); | 790 delete($filterlist{$_[0]}); |
767 delete($statlist{$_[0]}); | 791 delete($statlist{$_[0]}); |
768 delete($ignorelist{$_[0]}); | 792 delete($ignorelist{$_[0]}); |
769 } | 793 } |
770 | 794 |
771 sub weed_entries() | 795 sub weed_entries() |
772 { | 796 { |
773 # Don't weed in report mode. | 797 # Don't weed in report mode. |
774 return if ($reportmode); | 798 return unless ($settings{"FILTER"} > 0 && $reportmode == 0); |
775 | 799 |
776 # Weed blocked entries. | 800 # Weed blocked entries. |
777 my @mips = keys %blocklist; | 801 my @mips = keys %filterlist; |
778 foreach my $mip (@mips) { | 802 foreach my $mip (@mips) { |
779 if (defined($blocklist{$mip})) { | 803 if (defined($filterlist{$mip})) { |
780 if ($blocklist{$mip} >= 0) { | 804 if ($filterlist{$mip} >= 0) { |
781 weed_do($mip) unless check_time1($blocklist{$mip}); | 805 weed_do($mip) unless check_time1($filterlist{$mip}); |
782 } else { | 806 } else { |
783 weed_do($mip); | 807 weed_do($mip); |
784 } | 808 } |
785 } | 809 } |
786 } | 810 } |
787 | 811 |
788 # Clean up old entries from other lists | 812 # Clean up old entries from other lists |
789 foreach my $mip (keys %statlist) { | 813 foreach my $mip (keys %statlist) { |
790 if (defined($statlist{$mip})) { | 814 if (defined($statlist{$mip})) { |
791 my $mtime = $statlist{$mip}{"date2"}; | 815 my $mtime = $statlist{$mip}{"date2"}; |
792 if (!check_time2($mtime) && !defined($blocklist{$mip})) { | 816 if (!check_time2($mtime) && !defined($filterlist{$mip})) { |
793 mlog(3, "* Deleting stale $mip (".get_time_str($mtime).")\n"); | 817 mlog(3, "* Deleting stale $mip (".get_time_str($mtime).")\n"); |
794 delete($statlist{$mip}); | 818 delete($statlist{$mip}); |
795 } | 819 } |
796 } | 820 } |
797 } | 821 } |
860 my $mreason = $_[3]; | 884 my $mreason = $_[3]; |
861 my $mtype = $_[4]; | 885 my $mtype = $_[4]; |
862 my $mcond = $_[5]; | 886 my $mcond = $_[5]; |
863 my $cnt; | 887 my $cnt; |
864 | 888 |
865 if (check_hosts_array(\@noblock_ips, $mip)) { | 889 if (check_hosts_array(\@noaction_ips, $mip)) { |
866 mlog(3, "Hit to NOBLOCK_IPS($mip): [$mclass] $mreason\n"); | 890 mlog(2, "Hit to NOACTION_IPS($mip): [$mclass] $mreason\n"); |
867 return; | 891 return; |
868 } | 892 } |
869 | 893 |
870 # If condition is true, we add to regular statlist | 894 # If condition is true, we add to regular statlist |
871 if ($mcond) { | 895 if ($mcond) { |
875 update_entry(\%ignorelist, $mip, $mdate, $mclass, $mreason, 1); | 899 update_entry(\%ignorelist, $mip, $mdate, $mclass, $mreason, 1); |
876 return; | 900 return; |
877 } | 901 } |
878 | 902 |
879 # Check if we have exceeded threshold etc. | 903 # Check if we have exceeded threshold etc. |
880 if ($cnt >= $settings{"THRESHOLD"} && check_time1($mdate)) { | 904 if ($settings{"FILTER"} > 0 && $cnt >= $settings{"FILTER_THRESHOLD"} && check_time1($mdate)) { |
881 # Add to blocklist, unless already there. | 905 # Add to filterlist, unless already there. |
882 if (!defined($blocklist{$mip})) { | 906 if (!defined($filterlist{$mip})) { |
883 mlog(1, "* Adding $mip ($mdate): [$mclass] $mreason\n"); | 907 mlog(1, "* Adding $mip \@ ".get_time_str($mdate).": [$mclass] $mreason\n"); |
884 exec_iptables("-I", "INPUT", "1", "-s", $mip, "-j", $settings{"ACTION"}); | 908 exec_iptables("-I", "INPUT", "1", "-s", $mip, "-j", $settings{"FILTER_TARGET"}); |
885 } | 909 } |
886 # Update date of last hit | 910 # Update date of last hit |
887 $blocklist{$mip} = $mdate; | 911 $filterlist{$mip} = $mdate; |
888 } | 912 } |
889 | 913 |
890 # Separate check for DroneBL | 914 # Separate check for DroneBL |
891 if ($settings{"DRONEBL"} > 0 && $mtype > 0 && $cnt >= $settings{"DRONEBL_THRESHOLD"} && check_time3($mdate)) { | 915 if ($settings{"DRONEBL"} > 0 && $mtype > 0 && $cnt >= $settings{"DRONEBL_THRESHOLD"} && check_time3($mdate)) { |
892 $dronebl{$mip}{"type"} = $mtype; | 916 dronebl_queue($mip, $mdate, $mtype); |
893 $dronebl{$mip}{"date"} = $mdate; | |
894 $dronebl{$mip}{"sent"} = 0 unless defined($dronebl{$mip}{"sent"}); | |
895 } | 917 } |
896 } | 918 } |
897 | 919 |
898 | 920 |
899 ############################################################################# | 921 ############################################################################# |
923 { | 945 { |
924 %statlist = (); | 946 %statlist = (); |
925 undef(%statlist); | 947 undef(%statlist); |
926 %ignorelist = (); | 948 %ignorelist = (); |
927 undef(%ignorelist); | 949 undef(%ignorelist); |
928 mlog(0, "Updating initial blocklist from netfilter.\n"); | 950 update_filterlist(-1); |
929 update_blocklist(-1); | |
930 | 951 |
931 foreach my $filename (@scanfiles_once) { | 952 foreach my $filename (@scanfiles_once) { |
932 mlog(0, "Parsing [ONCE] ".$filename." ...\n"); | 953 mlog(0, "Parsing [ONCE] ".$filename." ...\n"); |
933 if (open(INFILE, "<", $filename)) { | 954 if (open(INFILE, "<", $filename)) { |
934 while (<INFILE>) { | 955 while (<INFILE>) { |
997 mlog(-1, "Received HUP, reinitializing.\n"); | 1018 mlog(-1, "Received HUP, reinitializing.\n"); |
998 malt_cleanup(); | 1019 malt_cleanup(); |
999 malt_configure(); | 1020 malt_configure(); |
1000 malt_init(); | 1021 malt_init(); |
1001 mlog(-1, "Reinitialization finished, resuming scanning.\n"); | 1022 mlog(-1, "Reinitialization finished, resuming scanning.\n"); |
1023 } | |
1024 | |
1025 sub malt_maintenance | |
1026 { | |
1027 update_filterlist(time()); | |
1028 weed_entries(); | |
1029 generate_status($settings{"STATUS_FILE_PLAIN"}, 0); | |
1030 generate_status($settings{"STATUS_FILE_HTML"}, 1); | |
1031 evidence_gather(); | |
1032 dronebl_process(); | |
1002 } | 1033 } |
1003 | 1034 |
1004 ### Main scanning function | 1035 ### Main scanning function |
1005 sub malt_scan | 1036 sub malt_scan |
1006 { | 1037 { |
1015 chomp; | 1046 chomp; |
1016 check_log_line($_); | 1047 check_log_line($_); |
1017 } | 1048 } |
1018 } | 1049 } |
1019 if ($counter < 0 || $counter++ >= 30) { | 1050 if ($counter < 0 || $counter++ >= 30) { |
1020 # Every once in a while, update known IP list from iptables | 1051 # Every once in a while, execute maintenance functions |
1021 # (in case entries have appeared there from "outside") | |
1022 # and perform weeding of old entries. | |
1023 $counter = 0; | 1052 $counter = 0; |
1024 update_blocklist(time()); | 1053 malt_maintenance(); |
1025 weed_entries(); | |
1026 generate_status($settings{"STATUS_FILE_PLAIN"}, 0); | |
1027 generate_status($settings{"STATUS_FILE_HTML"}, 1); | |
1028 gather_evidence(); | |
1029 } | 1054 } |
1030 sleep(2); | 1055 sleep(2); |
1031 foreach my $filename (keys %filehandles) { | 1056 foreach my $filename (keys %filehandles) { |
1032 seek($filehandles{$filename}, $filepos{$filename}, 0); | 1057 seek($filehandles{$filename}, $filepos{$filename}, 0); |
1033 } | 1058 } |
1061 my $value = $2; | 1086 my $value = $2; |
1062 if ($key eq "SCANFILE") { | 1087 if ($key eq "SCANFILE") { |
1063 push(@scanfiles, $value); | 1088 push(@scanfiles, $value); |
1064 } elsif ($key eq "SCANFILE_ONCE") { | 1089 } elsif ($key eq "SCANFILE_ONCE") { |
1065 push(@scanfiles_once, $value); | 1090 push(@scanfiles_once, $value); |
1066 } elsif ($key eq "NOBLOCK_IPS") { | 1091 } elsif ($key eq "NOACTION_IPS") { |
1067 push(@noblock_ips_def, $value); | 1092 push(@noaction_ips_def, $value); |
1068 } elsif (defined($settings{$key})) { | 1093 } elsif (defined($settings{$key})) { |
1069 $settings{$key} = $value; | 1094 $settings{$key} = $value; |
1070 } else { | 1095 } else { |
1071 mlog(-1, "[$filename:$line] Unknown setting '$key' = '$value'\n"); | 1096 mlog(-1, "[$filename:$line] Unknown setting '$key' = '$value'\n"); |
1072 $errors = 1; | 1097 $errors = 1; |
1105 | 1130 |
1106 %saw = (); | 1131 %saw = (); |
1107 @scanfiles_once = grep(!$saw{$_}++, @scanfiles_once); | 1132 @scanfiles_once = grep(!$saw{$_}++, @scanfiles_once); |
1108 | 1133 |
1109 %saw = (); | 1134 %saw = (); |
1110 @noblock_ips = grep(!$saw{$_}++, @noblock_ips_def); | 1135 @noaction_ips = grep(!$saw{$_}++, @noaction_ips_def); |
1111 undef(%saw); | 1136 undef(%saw); |
1112 | 1137 |
1113 mlog(-1, "Not blocking following IPs: ".join(", ", @noblock_ips)."\n"); | 1138 mlog(-1, "Not acting on IPs: ".join(", ", @noaction_ips)."\n"); |
1114 | 1139 |
1115 # Check if we have anything to do | 1140 # Check if we have anything to do |
1116 if ($reportmode) { | 1141 if ($reportmode) { |
1117 mdie("Nothing to do, no SCANFILE(s) or SCANFILE_ONCE(s) defined in configuration.\n") unless ($#scanfiles > 0 || $#scanfiles_once > 0); | 1142 mdie("Nothing to do, no SCANFILE(s) or SCANFILE_ONCE(s) defined in configuration.\n") unless ($#scanfiles > 0 || $#scanfiles_once > 0); |
1118 } else { | 1143 } else { |
1119 mdie("Nothing to do, no SCANFILE(s) defined in configuration.\n") unless ($#scanfiles > 0); | 1144 mdie("Nothing to do, no SCANFILE(s) defined in configuration.\n") unless ($#scanfiles > 0); |
1120 } | 1145 } |
1121 | 1146 |
1122 # Test existence of iptables | 1147 # General settings |
1123 if (! -e $settings{"IPTABLES"} || ! -x $settings{"IPTABLES"}) { | 1148 my $val = $settings{"STATS_MAX_AGE"}; |
1124 mdie("iptables binary does not exist or is not executable: ".$settings{"IPTABLES"}."\n"); | 1149 mdie("Invalid STATS_MAX_AGE value $val, must be > 0.\n") unless ($val > 0); |
1150 | |
1151 # Filtering | |
1152 if ($settings{"FILTER"} > 0) { | |
1153 $val = $settings{"FILTER_MAX_AGE"}; | |
1154 mdie("Invalid FILTER_MAX_AGE value $val, must be > 0.\n") unless ($val > 0); | |
1155 | |
1156 $val = $settings{"FILTER_THRESHOLD"}; | |
1157 mdie("Invalid FILTER_THRESHOLD value $val, must be >= 0.\n") unless ($val >= 0); | |
1158 | |
1159 $val = $settings{"IPTABLES"}; | |
1160 mdie("iptables binary does not exist or is not executable: $val\n") unless (-e $val && -x $val); | |
1161 } else { | |
1162 mlog(1, "Netfilter handling disabled.\n"); | |
1125 } | 1163 } |
1126 | 1164 |
1127 # Check evidence settings | 1165 # Check evidence settings |
1128 if ($settings{"EVIDENCE"}) { | 1166 if ($settings{"EVIDENCE"} > 0) { |
1129 my $base = $settings{"EVIDENCE_DIR"}; | 1167 my $base = $settings{"EVIDENCE_DIR"}; |
1130 mdie("Evidence directory (EVIDENCE_DIR) not set in configuration.\n") if ($base eq ""); | 1168 mdie("Evidence directory (EVIDENCE_DIR) not set in configuration.\n") if ($base eq ""); |
1131 mdie("Evidence directory '$base' does not exist.\n") unless (-e $base); | 1169 mdie("Evidence directory '$base' does not exist.\n") unless (-e $base); |
1132 mdie("Path '$base' is not a directory.\n") unless (-d $base); | 1170 mdie("Path '$base' is not a directory.\n") unless (-d $base); |
1133 mdie("Evidence directory '$base' is not writable by euid.\n") unless (-w $base); | 1171 mdie("Evidence directory '$base' is not writable by euid.\n") unless (-w $base); |
1134 } | 1172 } |
1135 | 1173 |
1136 # Check settings | 1174 # Sanitize DroneBL configuration |
1175 if ($settings{"DRONEBL"} > 0) { | |
1176 mdie("DroneBL RPC key not set.\n") unless ($settings{"DRONEBL_RPC_KEY"} ne ""); | |
1177 } | |
1178 | |
1179 # Check system account / passwd settings | |
1137 mdie("SYSACCT_MIN_UID must be >= 1.\n") unless ($settings{"SYSACCT_MIN_UID"} >= 1); | 1180 mdie("SYSACCT_MIN_UID must be >= 1.\n") unless ($settings{"SYSACCT_MIN_UID"} >= 1); |
1138 mdie("SYSACCT_MAX_UID must be >= SYSACCT_MIN_UID.\n") unless ($settings{"SYSACCT_MAX_UID"} >= $settings{"SYSACCT_MIN_UID"}); | 1181 mdie("SYSACCT_MAX_UID must be >= SYSACCT_MIN_UID.\n") unless ($settings{"SYSACCT_MAX_UID"} >= $settings{"SYSACCT_MIN_UID"}); |
1139 | 1182 |
1140 open(PASSWD, "<", $settings{"PASSWD"}) or mdie("Could not open '".$settings{"PASSWD"}."' for reading!\n"); | 1183 open(PASSWD, "<", $settings{"PASSWD"}) or mdie("Could not open '".$settings{"PASSWD"}."' for reading!\n"); |
1141 while (<PASSWD>) { | 1184 while (<PASSWD>) { |
1158 $SIG{'HUP'} = 'malt_hup'; | 1201 $SIG{'HUP'} = 'malt_hup'; |
1159 | 1202 |
1160 # Print banner and help if no arguments | 1203 # Print banner and help if no arguments |
1161 my $argc = $#ARGV + 1; | 1204 my $argc = $#ARGV + 1; |
1162 if ($argc < 1) { | 1205 if ($argc < 1) { |
1163 print $progbanner. | 1206 print STDERR $progbanner. |
1164 "\n". | 1207 "\n". |
1165 "Usage: maltfilter <pid filename> [config filename] [config filename...]\n". | 1208 "Usage: maltfilter <pid filename> [config filename] [config filename...]\n". |
1166 " maltfilter -f [config filename] [config filename...]\n". | 1209 " maltfilter -f [config filename] [config filename...]\n". |
1167 "-f turns on the full report mode.\n"; | 1210 "-f turns on the full report mode.\n"; |
1168 exit; | 1211 exit; |
1170 | 1213 |
1171 # Test pid file existence unless report mode | 1214 # Test pid file existence unless report mode |
1172 $pid_file = shift; | 1215 $pid_file = shift; |
1173 if ($pid_file eq "-f") { | 1216 if ($pid_file eq "-f") { |
1174 $reportmode = 1; | 1217 $reportmode = 1; |
1218 print STDERR $progbanner; | |
1175 } else { | 1219 } else { |
1176 mdie("'$pid_file' already exists, not starting.\n". | 1220 mdie("'$pid_file' already exists, not starting.\n". |
1177 "If the daemon is NOT running, remove the pid-file and re-start.\n") | 1221 "If the daemon is NOT running, remove the pid-file and re-start.\n") |
1178 if (-e $pid_file); | 1222 if (-e $pid_file); |
1179 } | 1223 } |
1185 | 1229 |
1186 malt_configure(); | 1230 malt_configure(); |
1187 | 1231 |
1188 # Open logfile | 1232 # Open logfile |
1189 if ($settings{"DRY_RUN"}) { | 1233 if ($settings{"DRY_RUN"}) { |
1190 print $progbanner. | 1234 print STDERR |
1191 "*********************************************\n". | 1235 "*********************************\n". |
1192 "* NOTICE! DRY-RUN MODE ENABLED! No changes *\n". | 1236 "* NOTICE! DRY-RUN MODE ENABLED! *\n". |
1193 "* will actually get committed to netfilter! *\n". | 1237 "*********************************\n"; |
1194 "*********************************************\n"; | |
1195 } elsif ($settings{"LOGFILE"} ne "") { | 1238 } elsif ($settings{"LOGFILE"} ne "") { |
1196 open($LOGFILE, ">>", $settings{"LOGFILE"}) or die("Could not open logfile '".$settings{"LOGFILE"}."' for writing!\n"); | 1239 open($LOGFILE, ">>", $settings{"LOGFILE"}) or die("Could not open logfile '".$settings{"LOGFILE"}."' for writing!\n"); |
1197 select((select($LOGFILE), $| = 1)[0]); | 1240 select((select($LOGFILE), $| = 1)[0]); |
1198 mlog(-1, "Log started\n"); | 1241 mlog(-1, "Log started\n"); |
1199 } | 1242 } |
1202 malt_init(); | 1245 malt_init(); |
1203 | 1246 |
1204 # Fork to background, unless dry-running | 1247 # Fork to background, unless dry-running |
1205 if ($settings{"DRY_RUN"}) { | 1248 if ($settings{"DRY_RUN"}) { |
1206 if ($reportmode) { | 1249 if ($reportmode) { |
1207 mlog(-1, "Outputting report files.\n"); | 1250 malt_maintenance(); |
1208 generate_status($settings{"STATUS_FILE_PLAIN"}, 0); | |
1209 generate_status($settings{"STATUS_FILE_HTML"}, 1); | |
1210 gather_evidence(); | |
1211 malt_cleanup(); | 1251 malt_cleanup(); |
1212 } else { | 1252 } else { |
1213 malt_scan(); | 1253 malt_scan(); |
1214 malt_cleanup(); | 1254 malt_cleanup(); |
1215 } | 1255 } |