comparison maltfilter @ 54:19dace24ad46

Remove default scanfiles; Clean up update_entry() code; Add "SCANFILE_ONCE" setting feature; Add sanity checking of some configuration values; Move more code from main to appropriate functions to handle reinitialization HUPs better.
author Matti Hamalainen <ccr@tnsp.org>
date Sun, 16 Aug 2009 17:11:47 +0300
parents dc072a56f343
children 30a5b56b753e
comparison
equal deleted inserted replaced
53:dc072a56f343 54:19dace24ad46
47 "SYSACCT_MAX_UID" => 100, 47 "SYSACCT_MAX_UID" => 100,
48 48
49 "FULL_TIME" => 1, 49 "FULL_TIME" => 1,
50 50
51 "PASSWD" => "/etc/passwd", 51 "PASSWD" => "/etc/passwd",
52 );
53
54 # Default logfiles to monitor (SCANFILES setting of configuration overrides these)
55 my @scanfiles_def = (
56 "/var/log/auth.log",
57 "/var/log/httpd/error.log",
58 "/var/log/httpd/access.log"
59 ); 52 );
60 53
61 my @noblock_ips_def = ( 54 my @noblock_ips_def = (
62 "127.0.0.1", 55 "127.0.0.1",
63 ); 56 );
127 ############################################################################# 120 #############################################################################
128 ### Global variables 121 ### Global variables
129 ############################################################################# 122 #############################################################################
130 my $reportmode = 0; # Full report mode 123 my $reportmode = 0; # Full report mode
131 my @scanfiles = (); # Files to scan 124 my @scanfiles = (); # Files to scan
125 my @scanfiles_once = (); # Files to scan only once during startup or HUP (e.g. not continuously followed)
132 my @noblock_ips = (); # IPs not to block 126 my @noblock_ips = (); # IPs not to block
133 my %filehandles = (); # Global hash holding opened scanned log filehandles 127 my %filehandles = (); # Global hash holding opened scanned log filehandles
134 my $pid_file = ""; # Name of Maltfilter daemon pid file 128 my $pid_file = ""; # Name of Maltfilter daemon pid file
135 my @configfiles = (); # Array of configuration file names 129 my @configfiles = (); # Array of configuration file names
136 my $LOGFILE; # Maltfilter logfile handle 130 my $LOGFILE; # Maltfilter logfile handle
534 } 528 }
535 } 529 }
536 530
537 ### Get current Netfilter INPUT table entries that match 531 ### Get current Netfilter INPUT table entries that match
538 ### entry types we manage, e.g. blocklist 532 ### entry types we manage, e.g. blocklist
539 sub update_blocklist() 533 sub update_blocklist($)
540 { 534 {
535 # NOTICE: argument not used now
536
541 $ENV{"PATH"} = ""; 537 $ENV{"PATH"} = "";
542 open(STATUS, $settings{"IPTABLES"}." -v -n -L INPUT |") or 538 open(STATUS, $settings{"IPTABLES"}." -v -n -L INPUT |") or
543 mdie("Could not execute ".$settings{"IPTABLES"}."\n"); 539 mdie("Could not execute ".$settings{"IPTABLES"}."\n");
544 my %newlist = (); 540 my %newlist = ();
545 undef(%newlist); 541 undef(%newlist);
626 } 622 }
627 } 623 }
628 } 624 }
629 } 625 }
630 626
631 ### Update one entry of 627 ### Update one entry data
628 sub update_date($$)
629 {
630 if (!defined($_[0]->{"date1"}) || ($_[1] > 0 && $_[0]->{"date1"} < 0)) {
631 $_[0]->{"date1"} = $_[1];
632 }
633 if (!defined($_[0]->{"date2"}) || $_[1] > $_[0]->{"date2"}) {
634 $_[0]->{"date2"} = $_[1];
635 }
636 }
637
632 sub update_entry($$$$$$) 638 sub update_entry($$$$$$)
633 { 639 {
634 my ($struct, $mip, $mdate, $mclass, $mreason, $addhits) = @_; 640 my ($struct, $mip, $mdate, $mclass, $mreason, $addhits) = @_;
635 641
642 $struct->{$mip} = {} unless defined($struct->{$mip});
643 my $entry = $struct->{$mip};
644 $struct->{$mip}{"reason"}{$mclass} = {};
645 my $reason = $struct->{$mip}{"reason"}{$mclass};
646
647 # Add hits only when requested
636 if ($addhits) { 648 if ($addhits) {
637 $struct->{$mip}{"hits"}++; 649 $entry->{"hits"}++;
638 $struct->{$mip}{"reason"}{$mclass}{"hits"}++; 650 $reason->{"hits"}++;
639 } else { 651 } else {
640 $struct->{$mip}{"hits"} = 1 unless defined($struct->{$mip}{"hits"}); 652 $entry->{"hits"} = 1 unless defined($entry->{"hits"});
641 $struct->{$mip}{"reason"}{$mclass}{"hits"} = 1 unless defined($struct->{$mip}{"reason"}{$mclass}{"hits"}); 653 $reason->{"hits"} = 1 unless defined($reason->{"hits"});
642 } 654 }
643 655
656 # Messages is an array in reportmode
644 if ($reportmode) { 657 if ($reportmode) {
645 push(@{$struct->{$mip}{"reason"}{$mclass}{"msg"}}, $mreason); 658 push(@{$reason->{"msg"}}, $mreason);
646 } else { 659 } else {
647 $struct->{$mip}{"reason"}{$mclass}{"msg"} = $mreason; 660 $reason->{"msg"} = $mreason;
648 } 661 }
649 662
650 if (!defined($struct->{$mip}{"date1"}) || ($mdate > 0 && $struct->{$mip}{"date1"} < 0)) { 663 # Update timestamps (generic and reason)
651 $struct->{$mip}{"date1"} = $mdate; 664 update_date($entry, $mdate);
652 } 665 update_date($reason, $mdate);
653 if (!defined($struct->{$mip}{"date2"}) || $mdate > $struct->{$mip}{"date2"}) { 666
654 $struct->{$mip}{"date2"} = $mdate; 667 return $entry->{"hits"};
655 }
656
657 if (!defined($struct->{$mip}{"reason"}{$mclass}{"date2"}) || ($mdate > 0 && $struct->{$mip}{"reason"}{$mclass}{"date2"} < 0)) {
658 $struct->{$mip}{"reason"}{$mclass}{"date2"} = $mdate;
659 }
660 if (!defined($struct->{$mip}{"reason"}{$mclass}{"date2"}) || $mdate > $struct->{$mip}{"reason"}{$mclass}{"date2"}) {
661 $struct->{$mip}{"reason"}{$mclass}{"date2"} = $mdate;
662 }
663
664 return $struct->{$mip}{"hits"};
665 } 668 }
666 669
667 ### Check if given "try count" exceeds treshold and if entry 670 ### Check if given "try count" exceeds treshold and if entry
668 ### is NOT in Netfilter already, then add it if so. 671 ### is NOT in Netfilter already, then add it if so.
669 sub check_add_hit($$$$$) 672 sub check_add_hit($$$$$)
718 } 721 }
719 722
720 ### Like Perl's die(), but also print a logfile entry. 723 ### Like Perl's die(), but also print a logfile entry.
721 sub mdie($) 724 sub mdie($)
722 { 725 {
723 mlog(-1, $_[0]); 726 mlog(-1, $_[0]) if ($LOGFILE);
724 die($_[0]); 727 die($_[0]);
725 } 728 }
726 729
727 ### Initialize 730 ### Initialize
728 sub malt_init 731 sub malt_init
729 { 732 {
730 mlog(0, "Updating initial blocklist from netfilter.\n"); 733 foreach my $filename (@scanfiles_once) {
731 update_blocklist(); 734 mlog(0, "Parsing [once] ".$filename." ...\n");
735 if (open(INFILE, "<", $filename)) {
736 while (<INFILE>) {
737 chomp;
738 check_log_line($_);
739 }
740 } else {
741 mlog(-1, "Could not open '".$filename."', skipping now.\n");
742 }
743 close(INFILE);
744 }
732 745
733 foreach my $filename (@scanfiles) { 746 foreach my $filename (@scanfiles) {
734 local *INFILE; 747 local *INFILE;
735 mlog(0, "Parsing ".$filename." ...\n"); 748 mlog(0, "Parsing ".$filename." ...\n");
736 open(INFILE, "<", $filename) or mdie("Could not open '".$filename."'!\n"); 749 if (open(INFILE, "<", $filename)) {
737 $filehandles{$filename} = *INFILE; 750 $filehandles{$filename} = *INFILE;
738 while (<INFILE>) { 751 while (<INFILE>) {
739 chomp; 752 chomp;
740 check_log_line($_); 753 check_log_line($_);
741 } 754 }
742 } 755 } else {
743 756 mlog(-1, "Could not open '".$filename."', skipping now.\n");
744 mlog(0, "Weeding old entries.\n"); 757 }
745 weed_entries(); 758 }
746 } 759 }
747 760
748 ### Quick cleanup (not complete shutdown) 761 ### Quick cleanup (not complete shutdown)
749 sub malt_cleanup 762 sub malt_cleanup
750 { 763 {
808 if ($counter < 0 || $counter++ >= 30) { 821 if ($counter < 0 || $counter++ >= 30) {
809 # Every once in a while, update known IP list from iptables 822 # Every once in a while, update known IP list from iptables
810 # (in case entries have appeared there from "outside") 823 # (in case entries have appeared there from "outside")
811 # and perform weeding of old entries. 824 # and perform weeding of old entries.
812 $counter = 0; 825 $counter = 0;
813 update_blocklist(); 826 update_blocklist(time());
814 weed_entries(); 827 weed_entries();
815 generate_status($settings{"STATUS_FILE_PLAIN"}, 0); 828 generate_status($settings{"STATUS_FILE_PLAIN"}, 0);
816 generate_status($settings{"STATUS_FILE_HTML"}, 1); 829 generate_status($settings{"STATUS_FILE_HTML"}, 1);
817 } 830 }
818 sleep(5); 831 sleep(5);
846 } 859 }
847 } elsif (/^\s*\"?([a-zA-Z0-9_]+)\"?\s*=>?\s*\"(.*?)\",?\s*$/) { 860 } elsif (/^\s*\"?([a-zA-Z0-9_]+)\"?\s*=>?\s*\"(.*?)\",?\s*$/) {
848 my $key = uc($1); 861 my $key = uc($1);
849 my $value = $2; 862 my $value = $2;
850 if ($key eq "SCANFILE") { 863 if ($key eq "SCANFILE") {
851 push(@scanfiles_def, $value); 864 push(@scanfiles, $value);
865 } elsif ($key eq "SCANFILE_ONCE") {
866 push(@scanfiles_once, $value);
852 } elsif ($key eq "NOBLOCK_IPS") { 867 } elsif ($key eq "NOBLOCK_IPS") {
853 push(@noblock_ips_def, $value); 868 push(@noblock_ips_def, $value);
854 } elsif (defined($settings{$key})) { 869 } elsif (defined($settings{$key})) {
855 $settings{$key} = $value; 870 $settings{$key} = $value;
856 } else { 871 } else {
872 887
873 ### Read all configuration files 888 ### Read all configuration files
874 sub malt_configure 889 sub malt_configure
875 { 890 {
876 # Let user define his/her own logfiles to scan 891 # Let user define his/her own logfiles to scan
877 @scanfiles_def = (); 892 @scanfiles = ();
878 undef(@scanfiles_def); 893 undef(@scanfiles);
894
895 @scanfiles_once = ();
896 undef(@scanfiles_once);
897
879 foreach my $filename (@configfiles) { 898 foreach my $filename (@configfiles) {
880 mdie("Errors in configuration file '$filename', bailing out.\n") 899 mdie("Errors in configuration file '$filename', bailing out.\n")
881 unless (malt_read_config($filename) == 0); 900 unless (malt_read_config($filename) == 0);
882 } 901 }
883 902
903 # Clean up certain arrays duplicate entries
904 my %saw = ();
905 @scanfiles = grep(!$saw{$_}++, @scanfiles);
906
907 %saw = ();
908 @scanfiles_once = grep(!$saw{$_}++, @scanfiles_once);
909
910 %saw = ();
911 @noblock_ips = grep(!$saw{$_}++, @noblock_ips_def);
912 undef(%saw);
913
914 mlog(-1, "Not blocking following IPs: ".join(", ", @noblock_ips)."\n");
915
916 # Check if we have anything to do
917 if ($reportmode) {
918 mdie("Nothing to do, no SCANFILE(s) or SCANFILE_ONCE(s) defined in configuration.\n") unless ($#scanfiles > 0 || $#scanfiles_once > 0);
919 } else {
920 mdie("Nothing to do, no SCANFILE(s) defined in configuration.\n") unless ($#scanfiles > 0);
921 }
922
923 # Test existence of iptables
924 if (! -e $settings{"IPTABLES"} || ! -x $settings{"IPTABLES"}) {
925 mdie("iptables binary does not exist or is not executable: ".$settings{"IPTABLES"}."\n");
926 }
927
928 # Check settings
884 mdie("SYSACCT_MIN_UID must be >= 1.\n") unless ($settings{"SYSACCT_MIN_UID"} >= 1); 929 mdie("SYSACCT_MIN_UID must be >= 1.\n") unless ($settings{"SYSACCT_MIN_UID"} >= 1);
885 mdie("SYSACCT_MAX_UID must be >= SYSACCT_MIN_UID.\n") unless ($settings{"SYSACCT_MAX_UID"} >= $settings{"SYSACCT_MIN_UID"}); 930 mdie("SYSACCT_MAX_UID must be >= SYSACCT_MIN_UID.\n") unless ($settings{"SYSACCT_MAX_UID"} >= $settings{"SYSACCT_MIN_UID"});
886 931
887 open(PASSWD, "<", $settings{"PASSWD"}) or mdie("Could not open '".$settings{"PASSWD"}."' for reading!\n"); 932 open(PASSWD, "<", $settings{"PASSWD"}) or mdie("Could not open '".$settings{"PASSWD"}."' for reading!\n");
888 while (<PASSWD>) { 933 while (<PASSWD>) {
929 while (defined(my $filename = shift)) { 974 while (defined(my $filename = shift)) {
930 push(@configfiles, $filename); 975 push(@configfiles, $filename);
931 } 976 }
932 977
933 malt_configure(); 978 malt_configure();
934
935
936 # Clean up certain arrays duplicate entries
937 my %saw = ();
938 @scanfiles = grep(!$saw{$_}++, @scanfiles_def);
939
940 %saw = ();
941 @noblock_ips = grep(!$saw{$_}++, @noblock_ips_def);
942 undef(%saw);
943 979
944 # Open logfile 980 # Open logfile
945 if ($settings{"DRY_RUN"}) { 981 if ($settings{"DRY_RUN"}) {
946 print $progbanner. 982 print $progbanner.
947 "*********************************************\n". 983 "*********************************************\n".
951 } elsif ($settings{"LOGFILE"} ne "") { 987 } elsif ($settings{"LOGFILE"} ne "") {
952 open($LOGFILE, ">>", $settings{"LOGFILE"}) or die("Could not open logfile '".$settings{"LOGFILE"}."' for writing!\n"); 988 open($LOGFILE, ">>", $settings{"LOGFILE"}) or die("Could not open logfile '".$settings{"LOGFILE"}."' for writing!\n");
953 mlog(-1, "Log started\n"); 989 mlog(-1, "Log started\n");
954 } 990 }
955 991
956 # Test existence of iptables
957 if (! -e $settings{"IPTABLES"} || ! -x $settings{"IPTABLES"}) {
958 mdie("iptables binary does not exist or is not executable: ".$settings{"IPTABLES"}."\n");
959 }
960
961 mlog(-1, "Not blocking following IPs: ".join(", ", @noblock_ips)."\n");
962
963 # Initialize 992 # Initialize
993 mlog(0, "Updating initial blocklist from netfilter.\n");
994 update_blocklist(-1);
964 malt_init(); 995 malt_init();
965 996
966 # Fork to background, unless dry-running 997 # Fork to background, unless dry-running
967 if ($settings{"DRY_RUN"}) { 998 if ($settings{"DRY_RUN"}) {
968 if ($reportmode) { 999 if ($reportmode) {