]> git.ipfire.org Git - people/pmueller/ipfire-2.x.git/blobdiff - config/firewall/rules.pl
Merge branch 'switch-to-libloc' into next-switch-to-libloc
[people/pmueller/ipfire-2.x.git] / config / firewall / rules.pl
old mode 100755 (executable)
new mode 100644 (file)
index 75a9357..40a2632
@@ -2,7 +2,7 @@
 ###############################################################################
 #                                                                             #
 # IPFire.org - A linux based firewall                                         #
-# Copyright (C) 2013 Alexander Marx <amarx@ipfire.org>                        #
+# Copyright (C) 2007-2019  IPFire Team  <info@ipfire.org>                     #
 #                                                                             #
 # This program is free software: you can redistribute it and/or modify        #
 # it under the terms of the GNU General Public License as published by        #
 ###############################################################################
 
 use strict;
+use experimental 'smartmatch';
 
 require '/var/ipfire/general-functions.pl';
 require "${General::swroot}/lang.pl";
+require "${General::swroot}/geoip-functions.pl";
 require "/usr/lib/firewall/firewall-lib.pl";
 
 # Set to one to enable debugging mode.
@@ -54,12 +56,17 @@ my %customgrp=();
 my %configinputfw=();
 my %configoutgoingfw=();
 my %confignatfw=();
+my %geoipsettings = (
+       "GEOIPBLOCK_ENABLED" => "off"
+);
+
 my @p2ps=();
 
 my $configfwdfw                = "${General::swroot}/firewall/config";
 my $configinput            = "${General::swroot}/firewall/input";
 my $configoutgoing  = "${General::swroot}/firewall/outgoing";
 my $p2pfile                    = "${General::swroot}/firewall/p2protocols";
+my $geoipfile          = "${General::swroot}/firewall/geoipblock";
 my $configgrp          = "${General::swroot}/fwhosts/customgroups";
 my $netsettings                = "${General::swroot}/ethernet/settings";
 
@@ -71,6 +78,15 @@ my $netsettings              = "${General::swroot}/ethernet/settings";
 &General::readhasharray($configoutgoing, \%configoutgoingfw);
 &General::readhasharray($configgrp, \%customgrp);
 
+# Check if the geoip settings file exists
+if (-e "$geoipfile") {
+       # Read settings file
+       &General::readhash("$geoipfile", \%geoipsettings);
+}
+
+# Get all GeoIP locations.
+my @locations = &fwlib::get_geoip_locations();
+
 my @log_limit_options = &make_log_limit_options();
 
 my $POLICY_INPUT_ALLOWED   = 0;
@@ -85,17 +101,45 @@ my $POLICY_OUTPUT_ACTION   = $fwoptions{"FWPOLICY1"};
 &main();
 
 sub main {
+       # Gather locations which should be exported.
+       my @locations_to_export = &gather_locations_to_export();
+
        # Flush all chains.
        &flush();
 
-       # Reload firewall rules.
-       &preparerules();
+       # Flush exported locations.
+       &GeoIP::flush_exported_locations();
+
+       # Check if there are any locations to export.
+       if (@locations_to_export) {
+               # Export required locations.
+               &GeoIP::export_locations(\@locations_to_export);
+       }
+
+       # Prepare firewall rules.
+       if (! -z  "${General::swroot}/firewall/input"){
+               &buildrules(\%configinputfw);
+       }
+       if (! -z  "${General::swroot}/firewall/outgoing"){
+               &buildrules(\%configoutgoingfw);
+       }
+       if (! -z  "${General::swroot}/firewall/config"){
+               &buildrules(\%configfwdfw);
+       }
 
        # Load P2P block rules.
        &p2pblock();
 
+       # Load GeoIP block rules.
+       &geoipblock();
+
        # Reload firewall policy.
        run("/usr/sbin/firewall-policy");
+
+       #Reload firewall.local if present
+       if ( -f '/etc/sysconfig/firewall.local'){
+               run("/etc/sysconfig/firewall.local reload");
+       }
 }
 
 sub run {
@@ -146,18 +190,6 @@ sub flush {
        run("$IPTABLES -t mangle -F $CHAIN_MANGLE_NAT_DESTINATION_FIX");
 }
 
-sub preparerules {
-       if (! -z  "${General::swroot}/firewall/input"){
-               &buildrules(\%configinputfw);
-       }
-       if (! -z  "${General::swroot}/firewall/outgoing"){
-               &buildrules(\%configoutgoingfw);
-       }
-       if (! -z  "${General::swroot}/firewall/config"){
-               &buildrules(\%configfwdfw);
-       }
-}
-
 sub buildrules {
        my $hash = shift;
 
@@ -170,9 +202,9 @@ sub buildrules {
        }
 
        if ($POLICY_INPUT_ACTION eq "DROP") {
-               push(@special_input_targets, "REJECT");
+               push(@special_input_targets, ("ACCEPT", "REJECT"));
        } elsif ($POLICY_INPUT_ACTION eq "REJECT") {
-               push(@special_input_targets, "DROP");
+               push(@special_input_targets, ("ACCEPT", "DROP"));
        }
 
        my @special_output_targets = ();
@@ -182,9 +214,9 @@ sub buildrules {
                push(@special_output_targets, "ACCEPT");
 
                if ($POLICY_OUTPUT_ACTION eq "DROP") {
-                       push(@special_output_targets, "REJECT");
+                       push(@special_output_targets, ("ACCEPT", "REJECT"));
                } elsif ($POLICY_OUTPUT_ACTION eq "REJECT") {
-                       push(@special_output_targets, "DROP");
+                       push(@special_output_targets, ("ACCEPT", "DROP"));
                }
        }
 
@@ -280,7 +312,7 @@ sub buildrules {
                # Concurrent connection limit
                my @ratelimit_options = ();
 
-               if (($elements gt 34) && ($$hash{$key}[32] eq 'ON')) {
+               if (($elements ge 34) && ($$hash{$key}[32] eq 'ON')) {
                        my $conn_limit = $$hash{$key}[33];
 
                        if ($conn_limit ge 1) {
@@ -296,13 +328,13 @@ sub buildrules {
                }
 
                # Ratelimit
-               if (($elements gt 37) && ($$hash{$key}[34] eq 'ON')) {
+               if (($elements ge 37) && ($$hash{$key}[34] eq 'ON')) {
                        my $rate_limit = "$$hash{$key}[35]/$$hash{$key}[36]";
 
-                               if ($rate_limit) {
-                                       push(@ratelimit_options, ("-m", "limit"));
-                                       push(@ratelimit_options, ("--limit", $rate_limit));
-                               }
+                       if ($rate_limit) {
+                               push(@ratelimit_options, ("-m", "limit"));
+                               push(@ratelimit_options, ("--limit", $rate_limit));
+                       }
                }
 
                # Check which protocols are used in this rule and so that we can
@@ -364,22 +396,31 @@ sub buildrules {
                                        my @source_options = ();
                                        if ($source =~ /mac/) {
                                                push(@source_options, $source);
-                                       } elsif ($source) {
+                                       } elsif ($source =~ /-m geoip/) {
+                                               push(@source_options, $source);
+                                       } elsif($source) {
                                                push(@source_options, ("-s", $source));
                                        }
 
-                                       if ($source_intf) {
-                                               push(@source_options, ("-i", $source_intf));
-                                       }
-
                                        # Prepare destination options.
                                        my @destination_options = ();
-                                       if ($destination) {
+                                       if ($destination =~ /-m geoip/) {
+                                               push(@destination_options,  $destination);
+                                       } elsif ($destination) {
                                                push(@destination_options, ("-d", $destination));
                                        }
 
+                                       # Add source and destination interface to the filter rules.
+                                       # These are supposed to help filtering forged packets that originate
+                                       # from BLUE with an IP address from GREEN for instance.
+                                       my @source_intf_options = ();
+                                       if ($source_intf) {
+                                               push(@source_intf_options, ("-i", $source_intf));
+                                       }
+
+                                       my @destination_intf_options = ();
                                        if ($destination_intf) {
-                                               push(@destination_options, ("-o", $destination_intf));
+                                               push(@destination_intf_options, ("-o", $destination_intf));
                                        }
 
                                        # Add time constraint options.
@@ -464,15 +505,31 @@ sub buildrules {
 
                                                # Source NAT
                                                } elsif ($NAT_MODE eq "SNAT") {
+                                                       my @snat_options = ( "-m", "policy", "--dir", "out", "--pol", "none" );
                                                        my @nat_options = @options;
 
+                                                       # Get addresses for the configured firewall interfaces.
+                                                       my @local_addresses = &fwlib::get_internal_firewall_ip_addresses(1);
+
+                                                       # Check if the nat_address is one of the local addresses.
+                                                       foreach my $local_address (@local_addresses) {
+                                                               if ($nat_address eq $local_address) {
+                                                                       # Clear SNAT options.
+                                                                       @snat_options = ();
+
+                                                                       # Finish loop.
+                                                                       last;
+                                                               }
+                                                       }
+
+                                                       push(@nat_options, @destination_intf_options);
                                                        push(@nat_options, @source_options);
                                                        push(@nat_options, @destination_options);
 
                                                        if ($LOG) {
-                                                               run("$IPTABLES -t nat -A $CHAIN_NAT_SOURCE @nat_options @log_limit_options -j LOG --log-prefix 'SNAT '");
+                                                               run("$IPTABLES -t nat -A $CHAIN_NAT_SOURCE @nat_options @snat_options @log_limit_options -j LOG --log-prefix 'SNAT '");
                                                        }
-                                                       run("$IPTABLES -t nat -A $CHAIN_NAT_SOURCE @nat_options -j SNAT --to-source $nat_address");
+                                                       run("$IPTABLES -t nat -A $CHAIN_NAT_SOURCE @nat_options @snat_options -j SNAT --to-source $nat_address");
                                                }
                                        }
 
@@ -480,10 +537,10 @@ sub buildrules {
                                        push(@options, @destination_options);
 
                                        # Insert firewall rule.
-                                       if ($LOG && !$NAT) {
-                                               run("$IPTABLES -A $chain @options @log_limit_options -j LOG --log-prefix '$chain '");
+                                       if ($LOG) {
+                                               run("$IPTABLES -A $chain @options @source_intf_options @destination_intf_options @log_limit_options -j LOG --log-prefix '$chain '");
                                        }
-                                       run("$IPTABLES -A $chain @options -j $target");
+                                       run("$IPTABLES -A $chain @options @source_intf_options @destination_intf_options -j $target");
 
                                        # Handle forwarding rules and add corresponding rules for firewall access.
                                        if ($chain eq $CHAIN_FORWARD) {
@@ -491,28 +548,24 @@ sub buildrules {
                                                # is granted/forbidden for any network that the firewall itself is part of, we grant/forbid access
                                                # for the firewall, too.
                                                if ($firewall_is_in_destination_subnet && ($target ~~ @special_input_targets)) {
-                                                       if ($LOG && !$NAT) {
-                                                               run("$IPTABLES -A $CHAIN_INPUT @options @log_limit_options -j LOG --log-prefix '$CHAIN_INPUT '");
+                                                       if ($LOG) {
+                                                               run("$IPTABLES -A $CHAIN_INPUT @options @source_intf_options @log_limit_options -j LOG --log-prefix '$CHAIN_INPUT '");
                                                        }
-                                                       run("$IPTABLES -A $CHAIN_INPUT @options -j $target");
+                                                       run("$IPTABLES -A $CHAIN_INPUT @options @source_intf_options -j $target");
                                                }
 
                                                # Likewise.
                                                if ($firewall_is_in_source_subnet && ($target ~~ @special_output_targets)) {
-                                                       if ($LOG && !$NAT) {
-                                                               run("$IPTABLES -A $CHAIN_OUTPUT @options @log_limit_options -j LOG --log-prefix '$CHAIN_OUTPUT '");
+                                                       if ($LOG) {
+                                                               run("$IPTABLES -A $CHAIN_OUTPUT @options @destination_intf_options @log_limit_options -j LOG --log-prefix '$CHAIN_OUTPUT '");
                                                        }
-                                                       run("$IPTABLES -A $CHAIN_OUTPUT @options -j $target");
+                                                       run("$IPTABLES -A $CHAIN_OUTPUT @options @destination_intf_options -j $target");
                                                }
                                        }
                                }
                        }
                }
        }
-       #Reload firewall.local if present
-       if ( -f '/etc/sysconfig/firewall.local'){
-               run("/etc/sysconfig/firewall.local reload");
-       }
 }
 
 # Formats the given timestamp into the iptables format which is "hh:mm" UTC.
@@ -570,6 +623,26 @@ sub p2pblock {
        }
 }
 
+sub geoipblock {
+       # Flush iptables chain.
+       run("$IPTABLES -F GEOIPBLOCK");
+
+       # If geoip blocking is not enabled, we are finished here.
+       if ($geoipsettings{'GEOIPBLOCK_ENABLED'} ne "on") {
+               # Exit submodule. Process remaining script.
+               return;
+       }
+
+       # Loop through all supported geoip locations and
+       # create iptables rules, if blocking this country
+       # is enabled.
+       foreach my $location (@locations) {
+               if(exists $geoipsettings{$location} && $geoipsettings{$location} eq "on") {
+                       run("$IPTABLES -A GEOIPBLOCK -m geoip --src-cc $location -j DROP");
+               }
+       }
+}
+
 sub get_protocols {
        my $hash = shift;
        my $key = shift;
@@ -760,8 +833,8 @@ sub make_log_limit_options {
        # Maybe we should get this from the configuration.
        my $limit = 10;
 
-       # We limit log messages to $limit messages per minute.
-       push(@options, ("--limit", "$limit/min"));
+       # We limit log messages to $limit messages per second.
+       push(@options, ("--limit", "$limit/second"));
 
        # And we allow bursts of 2x $limit.
        push(@options, ("--limit-burst", $limit * 2));
@@ -782,3 +855,142 @@ sub firewall_is_in_subnet {
 
        return 0;
 }
+
+#
+# Function to gather which locations needs to be exported.
+#
+sub gather_locations_to_export () {
+       my %geoipblock_exports = ();
+
+       # Array to store the final list of locations.
+       my @export_locations;
+
+       # Array to temporary store all used GeoIP groups.
+       my @used_GeoIP_groups;
+
+       # Check if GeoIP-block is enabled.
+       if($geoipsettings{"GEOIPBLOCK_ENABLED"} eq "on") {
+               # Loop through the array of supported locations.
+               foreach my $location (@locations) {
+                       if ($geoipsettings{$location} eq "on") {
+                               $geoipblock_exports{$location} = "1";
+                       }
+               }
+       }
+
+       # Get the firewall locations of the input, forward and output
+       # firewall settings hashhes.
+       my %input_exports = &_grab_geoip_locations_from_fw_settings_hash(\%configinputfw);
+       my %forward_exports = &_grab_geoip_locations_from_fw_settings_hash(\%configfwdfw);
+       my %output_exports = &_grab_geoip_locations_from_fw_settings_hash(\%configoutgoingfw);
+
+       # Merge the hashes.
+       #
+       # If a location is part of multiple hashes, it results in only one entry in the final hash.
+       my %export_locations = ( %geoipblock_exports, %input_exports, %forward_exports, %output_exports );
+
+       # Loop through the hash of exported locations.
+       foreach my $location (keys %export_locations) {
+               # Convert location into upper-case format.
+               my $location_uc = uc($location);
+
+               # Add the location to the array.
+               push(@export_locations, $location_uc);
+       }
+
+       # Return the array.
+       return @export_locations;
+}
+
+#
+# Function to gather the GeoIP locations from a given hash
+# containing the firewall settings.
+#
+sub _grab_geoip_locations_from_fw_settings_hash (\%) {
+       my $hash = shift;
+       my %exports;
+
+       # Loop through the given firewall config hash.
+       foreach my $rule ( keys %$hash ) {
+               # Skip if the rule is disabled.
+               next unless($$hash{$rule}[2] eq "ON");
+
+               # Process rules with GeoIP as source.
+               if($$hash{$rule}[3] eq "cust_geoip_src") {
+                       my $source = $$hash{$rule}[4];
+
+                       # Check if the source is a group.
+                       if($source =~ m/group/) {
+                              my($group, $groupname) = split(":", $source);
+
+                               # Get locations which are part of the group.
+                               my @group_locations = &_grab_geoip_locations_from_group($groupname);
+
+                               # Loop through the array.
+                               foreach my $location (@group_locations) {
+                                       # Add location to the exports hash.
+                                       $exports{$location} = "1";
+                               }
+                       } else {
+                               # Add location to the exports hash.
+                               $exports{$source} = "1";
+                       }
+
+                       # Jump the next rule.
+                       next;
+               }
+
+               # Process rules with GeoIP as target.
+               if($$hash{$rule}[5] eq "cust_geoip_tgt") {
+                       my $destination = $$hash{$rule}[6];
+
+                       # Check if the destination is a group.
+                       if($destination =~ m/group/) {
+                               my($group, $groupname) = split(":", $destination);
+
+                               # Get locations which are part of the group.
+                               my @group_locations = &_grab_geoip_locations_from_group($groupname);
+
+                               # Loop through the array.
+                               foreach my $location (@group_locations) {
+                                       # Add location to the exports hash.
+                                       $exports{$location} = "1";
+                               }
+                       } else {
+                               # Add location to the exports hash.
+                               $exports{$destination} = "1";
+                       }
+
+                       # Jump to next rule.
+                       next;
+               }
+       }
+
+       # Return the array.
+       return %exports;
+}
+
+#
+# Function to gather the GeoIP locations from a given group name.
+#
+sub _grab_geoip_locations_from_group($) {
+       my ($groupname) = @_;
+
+       my %geoipgroups = ();
+       my @group_locations;
+
+       # Get all configured GeoIP related groups.
+       &General::readhasharray("${General::swroot}/fwhosts/customgeoipgrp", \%geoipgroups);
+
+       # Loop through the hash of GeoIP groups.
+       foreach my $key (keys %geoipgroups) {
+               # Seach for members of the given group.
+               if($geoipgroups{$key}[0] eq "$groupname") {
+                       # Add the location to the group_locations array.
+                       push(@group_locations, $geoipgroups{$key}[2]);
+               }
+       }
+
+       # Return the array.
+       return @group_locations;
+}