###############################################################################
# #
# 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.
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";
&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;
&main();
sub main {
+ # Gather locations which should be exported.
+ my @locations_to_export = &gather_locations_to_export();
+
# Flush all chains.
&flush();
+ # 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);
# Load P2P block rules.
&p2pblock();
+ # Load GeoIP block rules.
+ &geoipblock();
+
# Reload firewall policy.
run("/usr/sbin/firewall-policy");
}
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 = ();
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"));
}
}
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));
}
# 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_intf_options, ("-o", $destination_intf));
+ }
+
# Add time constraint options.
push(@options, @time_options);
# 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");
}
}
- # 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.
- if ($source_intf) {
- push(@source_options, ("-i", $source_intf));
- }
-
- if ($destination_intf) {
- push(@destination_options, ("-o", $destination_intf));
- }
-
push(@options, @source_options);
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) {
# 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");
}
}
}
}
}
+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;
# 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));
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;
+}