Mercurial > hg > maltfilter
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) { |