X-Git-Url: http://git.ipfire.org/?p=people%2Fteissler%2Fipfire-2.x.git;a=blobdiff_plain;f=config%2Ffirewall%2Frules.pl;h=a131c54a8ed8fe6835eee0525a1c74d36d8d705b;hp=d2991b897c6f64a4447266991358ee9ff616bb66;hb=f98bb538e59279bb0768a15fc7d95267f4d61c74;hpb=8f4f4634df1057e99b35a127ccb22a343d1f2274 diff --git a/config/firewall/rules.pl b/config/firewall/rules.pl index d2991b897..a131c54a8 100755 --- a/config/firewall/rules.pl +++ b/config/firewall/rules.pl @@ -33,11 +33,13 @@ my $IPTABLES = "iptables --wait"; # iptables chains my $CHAIN_INPUT = "INPUTFW"; my $CHAIN_FORWARD = "FORWARDFW"; -my $CHAIN_OUTPUT = "OUTPUTFW"; +my $CHAIN_OUTPUT = "OUTGOINGFW"; my $CHAIN = $CHAIN_FORWARD; my $CHAIN_NAT_SOURCE = "NAT_SOURCE"; my $CHAIN_NAT_DESTINATION = "NAT_DESTINATION"; +my $CHAIN_MANGLE_NAT_DESTINATION_FIX = "NAT_DESTINATION"; my @VALID_CHAINS = ($CHAIN_INPUT, $CHAIN_FORWARD, $CHAIN_OUTPUT); +my @ANY_ADDRESSES = ("0.0.0.0/0.0.0.0", "0.0.0.0/0", "0/0"); my @PROTOCOLS = ("tcp", "udp", "icmp", "igmp", "ah", "esp", "gre", "ipv6", "ipip"); my @PROTOCOLS_WITH_PORTS = ("tcp", "udp"); @@ -69,6 +71,8 @@ my $netsettings = "${General::swroot}/ethernet/settings"; &General::readhasharray($configgrp, \%customgrp); &General::get_aliases(\%aliases); +my @log_limit_options = &make_log_limit_options(); + # MAIN &main(); @@ -94,6 +98,10 @@ sub run { print "$command\n"; } else { system "$command"; + + if ($?) { + print_error("ERROR: $command"); + } } } @@ -116,11 +124,12 @@ sub print_rule { } sub flush { - run("$IPTABLES -F FORWARDFW"); - run("$IPTABLES -F INPUTFW"); - run("$IPTABLES -F OUTGOINGFW"); - run("$IPTABLES -t nat -F NAT_DESTINATION"); - run("$IPTABLES -t nat -F NAT_SOURCE"); + run("$IPTABLES -F $CHAIN_INPUT"); + run("$IPTABLES -F $CHAIN_FORWARD"); + run("$IPTABLES -F $CHAIN_OUTPUT"); + run("$IPTABLES -t nat -F $CHAIN_NAT_SOURCE"); + run("$IPTABLES -t nat -F $CHAIN_NAT_DESTINATION"); + run("$IPTABLES -t mangle -F $CHAIN_MANGLE_NAT_DESTINATION_FIX"); } sub preparerules { @@ -161,12 +170,13 @@ sub buildrules { } # Collect all sources. - my @sources = &get_addresses($hash, $key, "src"); + my @sources = &fwlib::get_addresses($hash, $key, "src"); # Collect all destinations. - my @destinations = &get_addresses($hash, $key, "tgt"); + my @destinations = &fwlib::get_addresses($hash, $key, "tgt"); - my $time_constraints = ""; + # True if the destination is the firewall itself. + my $destination_is_firewall = ($$hash{$key}[5] eq "ipfire"); # Check if logging should be enabled. my $LOG = ($$hash{$key}[17] eq 'ON'); @@ -239,7 +249,7 @@ sub buildrules { } # Prepare protocol options (like ICMP types, ports, etc...). - my @protocol_options = &get_protocol_options($hash, $key, $protocol); + my @protocol_options = &get_protocol_options($hash, $key, $protocol, 0); # Check if this protocol knows ports. my $protocol_has_ports = ($protocol ~~ @PROTOCOLS_WITH_PORTS); @@ -249,33 +259,49 @@ sub buildrules { # Skip invalid rules. next if (!$source || !$destination || ($destination eq "none")); + # Sanitize source. + if ($source ~~ @ANY_ADDRESSES) { + $source = ""; + } + + # Sanitize destination. + if ($destination ~~ @ANY_ADDRESSES) { + $destination = ""; + } + # Array with iptables arguments. my @options = (); # Append protocol. if ($protocol ne "all") { - push(@options, ("-p", $protocol)); push(@options, @protocol_options); } - # Append source. + # Prepare source options. + my @source_options = (); if ($source =~ /mac/) { - push(@options, $source); - } else { - push(@options, ("-s", $source)); + push(@source_options, $source); + } elsif ($source) { + push(@source_options, ("-s", $source)); } - # Append destination for non-DNAT rules. - unless ($NAT && ($NAT_MODE eq "DNAT")) { - push(@options, ("-d", $destination)); + # Prepare destination options. + my @destination_options = (); + if ($destination) { + push(@destination_options, ("-d", $destination)); } # Add time constraint options. push(@options, @time_options); + my $firewall_is_in_source_subnet = 0; + if ($source) { + $firewall_is_in_source_subnet = &firewall_is_in_subnet($source); + } + # Process NAT rules. if ($NAT) { - my $nat_address = &get_nat_address($$hash{$key}[29]); + my $nat_address = &fwlib::get_nat_address($$hash{$key}[29], $source); # Skip NAT rules if the NAT address is unknown # (i.e. no internet connection has been established, yet). @@ -283,53 +309,86 @@ sub buildrules { # Destination NAT if ($NAT_MODE eq "DNAT") { - my ($dnat_address, $dnat_mask) = split("/", $destination); + # Make port-forwardings useable from the internal networks. + my @internal_addresses = &fwlib::get_internal_firewall_ip_addresses(1); + unless ($nat_address ~~ @internal_addresses) { + &add_dnat_mangle_rules($nat_address, @options); + } - my @nat_options = @options; + my @nat_options = (); + if ($protocol ne "all") { + my @nat_protocol_options = &get_protocol_options($hash, $key, $protocol, 1); + push(@nat_options, @nat_protocol_options); + } + push(@nat_options, @source_options); push(@nat_options, ("-d", $nat_address)); - push(@options, ("-d", $dnat_address)); + my $dnat_port; if ($protocol_has_ports) { - my $dnat_port = &get_dnat_target_port($hash, $key); + $dnat_port = &get_dnat_target_port($hash, $key); + } + + my @nat_action_options = (); - if ($dnat_port) { - $dnat_address .= ":$dnat_port"; + # Use iptables REDIRECT + my $use_redirect = ($destination_is_firewall && !$destination && $protocol_has_ports && $dnat_port); + if ($use_redirect) { + push(@nat_action_options, ("-j", "REDIRECT", "--to-ports", $dnat_port)); + + # Use iptables DNAT + } else { + if ($destination_is_firewall && !$destination) { + $destination = &fwlib::get_external_address(); + } + next unless ($destination); - # Replace --dport with the translated one. - my @new_options = (); - my $skip_count = 0; - foreach my $option (@options) { - next if ($skip_count-- > 0); + my ($dnat_address, $dnat_mask) = split("/", $destination); + @destination_options = ("-d", $dnat_address); - if ($option eq "--dport") { - push(@new_options, ("--dport", $dnat_port)); - $skip_count = 1; - next; - } + if ($protocol_has_ports) { + my $dnat_port = &get_dnat_target_port($hash, $key); - push(@new_options, $option); + if ($dnat_port) { + $dnat_address .= ":$dnat_port"; } - @options = @new_options; } + + push(@nat_action_options, ("-j", "DNAT", "--to-destination", $dnat_address)); } if ($LOG) { - run("$IPTABLES -t nat -A $CHAIN_NAT_DESTINATION @nat_options -j LOG --log-prefix 'DNAT'"); + run("$IPTABLES -t nat -A $CHAIN_NAT_DESTINATION @nat_options @log_limit_options -j LOG --log-prefix 'DNAT '"); } - run("$IPTABLES -t nat -A $CHAIN_NAT_DESTINATION @nat_options -j DNAT --to-destination $dnat_address"); + run("$IPTABLES -t nat -A $CHAIN_NAT_DESTINATION @nat_options @nat_action_options"); # Source NAT } elsif ($NAT_MODE eq "SNAT") { + my @nat_options = @options; + + push(@nat_options, @source_options); + push(@nat_options, @destination_options); + if ($LOG) { - run("$IPTABLES -t nat -A $CHAIN_NAT_SOURCE @options -j LOG --log-prefix 'SNAT'"); + 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 @options -j SNAT --to-source $nat_address"); + run("$IPTABLES -t nat -A $CHAIN_NAT_SOURCE @nat_options -j SNAT --to-source $nat_address"); + } + } + + push(@options, @source_options); + + if ($firewall_is_in_source_subnet && ($fwdfwsettings{"POLICY"} eq "MODE1") && ($chain eq $CHAIN_FORWARD)) { + if ($LOG && !$NAT) { + run("$IPTABLES -A $CHAIN_INPUT @options @log_limit_options -j LOG --log-prefix '$CHAIN_INPUT '"); } + run("$IPTABLES -A $CHAIN_INPUT @options -j $target"); } + push(@options, @destination_options); + # Insert firewall rule. if ($LOG && !$NAT) { - run("$IPTABLES -A $chain @options -j LOG"); + run("$IPTABLES -A $chain @options @log_limit_options -j LOG --log-prefix '$chain '"); } run("$IPTABLES -A $chain @options -j $target"); } @@ -338,49 +397,6 @@ sub buildrules { } } -sub get_external_interface() { - open(IFACE, "/var/ipfire/red/iface") or return ""; - my $iface = ; - close(IFACE); - - return $iface; -} - -sub get_external_address() { - open(ADDR, "/var/ipfire/red/local-ipaddress") or return ""; - my $address = ; - close(ADDR); - - return $address; -} - -sub get_alias { - my $id = shift; - - foreach my $alias (sort keys %aliases) { - if ($id eq $alias) { - return $aliases{$alias}; - } - } -} - -sub get_nat_address { - my $zone = shift; - - # Any static address of any zone. - if ($zone eq "RED" || $zone eq "GREEN" || $zone eq "ORANGE" || $zone eq "BLUE") { - return $defaultNetworks{$zone . "_ADDRESS"}; - - } elsif ($zone eq "Default IP") { - return &get_external_address(); - - } else { - return &get_alias($zone); - } - - print_error("Could not find NAT address"); -} - # Formats the given timestamp into the iptables format which is "hh:mm" UTC. sub format_time { my $val = shift; @@ -446,155 +462,6 @@ sub p2pblock { } } -sub get_addresses { - my $hash = shift; - my $key = shift; - my $type = shift; - - my @addresses = (); - my $addr_type; - my $value; - my $group_name; - - if ($type eq "src") { - $addr_type = $$hash{$key}[3]; - $value = $$hash{$key}[4]; - - } elsif ($type eq "tgt") { - $addr_type = $$hash{$key}[5]; - $value = $$hash{$key}[6]; - } - - if ($addr_type ~~ ["cust_grp_src", "cust_grp_tgt"]) { - foreach my $grp (sort {$a <=> $b} keys %customgrp) { - if ($customgrp{$grp}[0] eq $value) { - my @address = &get_address($customgrp{$grp}[3], $customgrp{$grp}[2], $type); - - if (@address) { - push(@addresses, @address); - } - } - } - } else { - my @address = &get_address($addr_type, $value, $type); - - if (@address) { - push(@addresses, @address); - } - } - - return @addresses; -} - -sub get_address { - my $key = shift; - my $value = shift; - my $type = shift; - - my @ret = (); - - # If the user manually typed an address, we just check if it is a MAC - # address. Otherwise, we assume that it is an IP address. - if ($key ~~ ["src_addr", "tgt_addr"]) { - if (&General::validmac($value)) { - push(@ret, "-m mac --mac-source $value"); - } else { - push(@ret, $value); - } - - # If a default network interface (GREEN, BLUE, etc.) is selected, we - # try to get the corresponding address of the network. - } elsif ($key ~~ ["std_net_src", "std_net_tgt", "Standard Network"]) { - my $external_interface = &get_external_interface(); - - my $network_address = &fwlib::get_std_net_ip($value, $external_interface); - if ($network_address) { - push(@ret, $network_address); - } - - # Custom networks. - } elsif ($key ~~ ["cust_net_src", "cust_net_tgt", "Custom Network"]) { - my $network_address = &fwlib::get_net_ip($value); - if ($network_address) { - push(@ret, $network_address); - } - - # Custom hosts. - } elsif ($key ~~ ["cust_host_src", "cust_host_tgt", "Custom Host"]) { - my $host_address = &fwlib::get_host_ip($value, $type); - if ($host_address) { - push(@ret, $host_address); - } - - # OpenVPN networks. - } elsif ($key ~~ ["ovpn_net_src", "ovpn_net_tgt", "OpenVPN static network"]) { - my $network_address = &fwlib::get_ovpn_net_ip($value, 1); - if ($network_address) { - push(@ret, $network_address); - } - - # OpenVPN hosts. - } elsif ($key ~~ ["ovpn_host_src", "ovpn_host_tgt", "OpenVPN static host"]) { - my $host_address = &fwlib::get_ovpn_host_ip($value, 33); - if ($host_address) { - push(@ret, $host_address); - } - - # OpenVPN N2N. - } elsif ($key ~~ ["ovpn_n2n_src", "ovpn_n2n_tgt", "OpenVPN N-2-N"]) { - my $network_address = &fwlib::get_ovpn_n2n_ip($value, 11); - if ($network_address) { - push(@ret, $network_address); - } - - # IPsec networks. - } elsif ($key ~~ ["ipsec_net_src", "ipsec_net_tgt", "IpSec Network"]) { - my $network_address = &fwlib::get_ipsec_net_ip($value, 11); - if ($network_address) { - push(@ret, $network_address); - } - - # The firewall's own IP addresses. - } elsif ($key ~~ ["ipfire", "ipfire_src"]) { - # ALL - if ($value eq "ALL") { - push(@ret, "0/0"); - - # GREEN - } elsif ($value eq "GREEN") { - push(@ret, $defaultNetworks{"GREEN_ADDRESS"}); - - # BLUE - } elsif ($value eq "BLUE") { - push(@ret, $defaultNetworks{"BLUE_ADDRESS"}); - - # ORANGE - } elsif ($value eq "ORANGE") { - push(@ret, $defaultNetworks{"ORANGE_ADDRESS"}); - - # RED - } elsif ($value ~~ ["RED", "RED1"]) { - my $address = &get_external_address(); - if ($address) { - push(@ret, $address); - } - - # Aliases - } else { - my %alias = &get_alias($value); - if (%alias) { - push(@ret, $alias{"IPT"}); - } - } - - # If nothing was selected, we assume "any". - } else { - push(@ret, "0/0"); - } - - return @ret; -} - sub get_protocols { my $hash = shift; my $key = shift; @@ -654,8 +521,16 @@ sub get_protocol_options { my $hash = shift; my $key = shift; my $protocol = shift; + my $nat_options_wanted = shift; my @options = (); + # Nothing to do if no protocol is specified. + if ($protocol eq "all") { + return @options; + } else { + push(@options, ("-p", $protocol)); + } + # Process source ports. my $use_src_ports = ($$hash{$key}[7] eq "ON"); my $src_ports = $$hash{$key}[10]; @@ -671,11 +546,11 @@ sub get_protocol_options { if ($use_dst_ports) { my $dst_ports_mode = $$hash{$key}[14]; my $dst_ports = $$hash{$key}[15]; - if ($use_dnat && $$hash{$key}[30]) { - $dst_ports = $$hash{$key}[30]; - } if (($dst_ports_mode eq "TGT_PORT") && $dst_ports) { + if ($nat_options_wanted && $use_dnat && $$hash{$key}[30]) { + $dst_ports = $$hash{$key}[30]; + } push(@options, &format_ports($dst_ports, "dst")); } elsif ($dst_ports_mode eq "cust_srv") { @@ -721,7 +596,9 @@ sub format_ports { push(@options, ("-m", "multiport")); } - push(@options, ($arg, $ports)); + if ($ports) { + push(@options, ($arg, $ports)); + } return @options; } @@ -731,6 +608,65 @@ sub get_dnat_target_port { my $key = shift; if ($$hash{$key}[14] eq "TGT_PORT") { - return $$hash{$key}[15]; + my $port = $$hash{$key}[15]; + my $external_port = $$hash{$key}[30]; + + if ($external_port && ($port ne $external_port)) { + return $$hash{$key}[15]; + } } } + +sub add_dnat_mangle_rules { + my $nat_address = shift; + my @options = @_; + + my $mark = 0; + foreach my $zone ("GREEN", "BLUE", "ORANGE") { + $mark++; + + # Skip rule if not all required information exists. + next unless (exists $defaultNetworks{$zone . "_NETADDRESS"}); + next unless (exists $defaultNetworks{$zone . "_NETMASK"}); + + my @mangle_options = @options; + + my $netaddress = $defaultNetworks{$zone . "_NETADDRESS"}; + $netaddress .= "/" . $defaultNetworks{$zone . "_NETMASK"}; + + push(@mangle_options, ("-s", $netaddress, "-d", $nat_address)); + push(@mangle_options, ("-j", "MARK", "--set-mark", $mark)); + + run("$IPTABLES -t mangle -A $CHAIN_MANGLE_NAT_DESTINATION_FIX @mangle_options"); + } +} + +sub make_log_limit_options { + my @options = ("-m", "limit"); + + # 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")); + + # And we allow bursts of 2x $limit. + push(@options, ("--limit-burst", $limit * 2)); + + return @options; +} + +sub firewall_is_in_subnet { + my $subnet = shift; + + # ORANGE is missing here, because nothing may ever access + # the firewall from this network. + my $address = &fwlib::get_internal_firewall_ip_address($subnet, 0); + + if ($address) { + return 1; + } + + return 0; +} +