X-Git-Url: http://git.ipfire.org/?p=people%2Fteissler%2Fipfire-2.x.git;a=blobdiff_plain;f=config%2Ffirewall%2Frules.pl;h=a0bc32c9665da8e34d5af3c37ef82b50c3d02ca8;hp=6bb9ea0afcf8a18296e57dcb46aa67983b18b74c;hb=025741919a54ceb2ce96961e74f3afd1ad10706b;hpb=68d1eb101713a381788a2f566156131c91095275 diff --git a/config/firewall/rules.pl b/config/firewall/rules.pl index 6bb9ea0af..a0bc32c96 100755 --- a/config/firewall/rules.pl +++ b/config/firewall/rules.pl @@ -1,4 +1,4 @@ -#!/usr/bin/perl +#!/usr/bin/perl -w ############################################################################### # # # IPFire.org - A linux based firewall # @@ -20,8 +20,6 @@ ############################################################################### use strict; -use Time::Local; -no warnings 'uninitialized'; require '/var/ipfire/general-functions.pl'; require "${General::swroot}/lang.pl"; @@ -30,21 +28,32 @@ require "/usr/lib/firewall/firewall-lib.pl"; # Set to one to enable debugging mode. my $DEBUG = 0; +my $IPTABLES = "iptables --wait"; + +# iptables chains +my $CHAIN_INPUT = "INPUTFW"; +my $CHAIN_FORWARD = "FORWARDFW"; +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"); + +my @VALID_TARGETS = ("ACCEPT", "DROP", "REJECT"); + my %fwdfwsettings=(); my %defaultNetworks=(); -my %configfwdfw=(); -my %color=(); -my %icmptypes=(); -my %ovpnSettings=(); +my %configfwdfw=();; my %customgrp=(); -our %sourcehash=(); -our %targethash=(); -my @timeframe=(); my %configinputfw=(); my %configoutgoingfw=(); my %confignatfw=(); my %aliases=(); -my @DPROT=(); my @p2ps=(); my $configfwdfw = "${General::swroot}/firewall/config"; @@ -53,16 +62,6 @@ my $configoutgoing = "${General::swroot}/firewall/outgoing"; my $p2pfile = "${General::swroot}/firewall/p2protocols"; my $configgrp = "${General::swroot}/fwhosts/customgroups"; my $netsettings = "${General::swroot}/ethernet/settings"; -my $errormessage = ''; -my $orange = ''; -my $green = ''; -my $blue = ''; -my ($TYPE,$PROT,$SPROT,$DPROT,$SPORT,$DPORT,$TIME,$TIMEFROM,$TIMETILL,$SRC_TGT); -my $CHAIN = "FORWARDFW"; -my $conexists = 'off'; -my $command = 'iptables --wait -A'; -my $dnat =''; -my $snat =''; &General::readhash("${General::swroot}/firewall/settings", \%fwdfwsettings); &General::readhash("$netsettings", \%defaultNetworks); @@ -72,51 +71,23 @@ my $snat =''; &General::readhasharray($configgrp, \%customgrp); &General::get_aliases(\%aliases); -#check if we have an internetconnection -open (CONN,"/var/ipfire/red/iface"); -my $con = ; -close(CONN); +my @log_limit_options = &make_log_limit_options(); -if (-f "/var/ipfire/red/active"){ - $conexists='on'; -} +# MAIN +&main(); -open (CONN1,"/var/ipfire/red/local-ipaddress"); -my $redip = ; -close(CONN1); - -################# -# DEBUG/TEST # -################# -my $MODE=!$DEBUG; # 0 - normal operation - # 1 - print configline and rules to console - # -################# -my $param=shift; - -if($param eq 'flush'){ - if ($MODE eq '1'){ - print " Flushing chains...\n"; - } - &flush; -}else{ - if ($MODE eq '1'){ - print " Flushing chains...\n"; - } - &flush; - if ($MODE eq '1'){ - print " Preparing rules...\n"; - } - &preparerules; - if($MODE eq '0'){ - if ($fwdfwsettings{'POLICY'} eq 'MODE1'){ - &p2pblock; - run("/usr/sbin/firewall-policy"); - }elsif($fwdfwsettings{'POLICY'} eq 'MODE2'){ - &p2pblock; - run("/usr/sbin/firewall-policy"); - } - } +sub main { + # Flush all chains. + &flush(); + + # Reload firewall rules. + &preparerules(); + + # Load P2P block rules. + &p2pblock(); + + # Reload firewall policy. + run("/usr/sbin/firewall-policy"); } sub run { @@ -127,15 +98,38 @@ sub run { print "$command\n"; } else { system "$command"; + + if ($?) { + print_error("ERROR: $command"); + } } } +sub print_error { + my $message = shift; + + print STDERR "$message\n"; +} + +sub print_rule { + my $hash = shift; + + print "\nRULE:"; + + my $i = 0; + foreach (@$hash) { + printf(" %2d: %s", $i++, $_); + } + print "\n"; +} + sub flush { - run("iptables --wait -F FORWARDFW"); - run("iptables --wait -F INPUTFW"); - run("iptables --wait -F OUTGOINGFW"); - run("iptables --wait -t nat -F NAT_DESTINATION"); - run("iptables --wait -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 { @@ -151,261 +145,304 @@ sub preparerules { } sub buildrules { - my $hash=shift; - my $STAG; - my $natip; - my $snatport; - my $fireport; - my $nat; - my $fwaccessdport; - my $natchain; - my $icmptype; - foreach my $key (sort {$a <=> $b} keys %$hash){ - next if (($$hash{$key}[6] eq 'RED' || $$hash{$key}[6] eq 'RED1') && $conexists eq 'off' ); - $command="iptables --wait -A"; - if ($$hash{$key}[28] eq 'ON'){ - $command='iptables --wait -t nat -A'; - $natip=&get_nat_ip($$hash{$key}[29],$$hash{$key}[31]); - if($$hash{$key}[31] eq 'dnat'){ - $nat='DNAT'; - if ($$hash{$key}[30] =~ /\|/){ - $$hash{$key}[30]=~ tr/|/,/; - $fireport='-m multiport --dport '.$$hash{$key}[30]; - }else{ - $fireport='--dport '.$$hash{$key}[30] if ($$hash{$key}[30]>0); - } - }else{ - $nat='SNAT'; - } + my $hash = shift; + + foreach my $key (sort {$a <=> $b} keys %$hash) { + # Skip disabled rules. + next unless ($$hash{$key}[2] eq 'ON'); + + if ($DEBUG) { + print_rule($$hash{$key}); } - $STAG=''; - if($$hash{$key}[2] eq 'ON'){ - #get source ip's - if ($$hash{$key}[3] eq 'cust_grp_src'){ - foreach my $grp (sort {$a <=> $b} keys %customgrp){ - if($customgrp{$grp}[0] eq $$hash{$key}[4]){ - &get_address($customgrp{$grp}[3],$customgrp{$grp}[2],"src"); - } - } - }else{ - &get_address($$hash{$key}[3],$$hash{$key}[4],"src"); + + # Check if the target is valid. + my $target = $$hash{$key}[0]; + if (!$target ~~ @VALID_TARGETS) { + print_error("Invalid target '$target' for rule $key"); + next; + } + + # Check if the chain is valid. + my $chain = $$hash{$key}[1]; + if (!$chain ~~ @VALID_CHAINS) { + print_error("Invalid chain '$chain' in rule $key"); + next; + } + + # Collect all sources. + my @sources = &fwlib::get_addresses($hash, $key, "src"); + + # Collect all destinations. + my @destinations = &fwlib::get_addresses($hash, $key, "tgt"); + + # 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'); + + # Check if NAT is enabled and initialize variables, that we use for that. + my $NAT = ($$hash{$key}[28] eq 'ON'); + my $NAT_MODE; + if ($NAT) { + $NAT_MODE = uc($$hash{$key}[31]); + } + + # Set up time constraints. + my @time_options = (); + if ($$hash{$key}[18] eq 'ON') { + push(@time_options, ("-m", "time")); + + # Select all days of the week this match is active. + my @weekdays = (); + if ($$hash{$key}[19] ne '') { + push (@weekdays, "Mon"); } - #get target ip's - if ($$hash{$key}[5] eq 'cust_grp_tgt'){ - foreach my $grp (sort {$a <=> $b} keys %customgrp){ - if($customgrp{$grp}[0] eq $$hash{$key}[6]){ - &get_address($customgrp{$grp}[3],$customgrp{$grp}[2],"tgt"); - } - } - }elsif($$hash{$key}[5] eq 'ipfire' ){ - if($$hash{$key}[6] eq 'GREEN'){ - $targethash{$key}[0]=$defaultNetworks{'GREEN_ADDRESS'}; - } - if($$hash{$key}[6] eq 'BLUE'){ - $targethash{$key}[0]=$defaultNetworks{'BLUE_ADDRESS'}; - } - if($$hash{$key}[6] eq 'ORANGE'){ - $targethash{$key}[0]=$defaultNetworks{'ORANGE_ADDRESS'}; - } - if($$hash{$key}[6] eq 'ALL'){ - $targethash{$key}[0]='0.0.0.0/0'; - } - if($$hash{$key}[6] eq 'RED' || $$hash{$key}[6] eq 'RED1'){ - open(FILE, "/var/ipfire/red/local-ipaddress")or die "Couldn't open local-ipaddress"; - $targethash{$key}[0]= ; - close(FILE); - }else{ - foreach my $alias (sort keys %aliases){ - if ($$hash{$key}[6] eq $alias){ - $targethash{$key}[0]=$aliases{$alias}{'IPT'}; - } - } - } - }else{ - &get_address($$hash{$key}[5],$$hash{$key}[6],"tgt"); + if ($$hash{$key}[20] ne '') { + push (@weekdays, "Tue"); + } + if ($$hash{$key}[21] ne '') { + push (@weekdays, "Wed"); + } + if ($$hash{$key}[22] ne '') { + push (@weekdays, "Thu"); } - ##get source prot and port - $SRC_TGT='SRC'; - $SPORT = &get_port($hash,$key); - $SRC_TGT=''; - - ##get target prot and port - $DPROT=&get_prot($hash,$key); - - if ($DPROT eq ''){$DPROT=' ';} - @DPROT=split(",",$DPROT); - - #get time if defined - if($$hash{$key}[18] eq 'ON'){ - my ($time1,$time2,$daylight); - my $daylight=$$hash{$key}[28]; - $time1=&get_time($$hash{$key}[26],$daylight); - $time2=&get_time($$hash{$key}[27],$daylight); - if($$hash{$key}[19] ne ''){push (@timeframe,"Mon");} - if($$hash{$key}[20] ne ''){push (@timeframe,"Tue");} - if($$hash{$key}[21] ne ''){push (@timeframe,"Wed");} - if($$hash{$key}[22] ne ''){push (@timeframe,"Thu");} - if($$hash{$key}[23] ne ''){push (@timeframe,"Fri");} - if($$hash{$key}[24] ne ''){push (@timeframe,"Sat");} - if($$hash{$key}[25] ne ''){push (@timeframe,"Sun");} - $TIME=join(",",@timeframe); - - $TIMEFROM="--timestart $time1 "; - $TIMETILL="--timestop $time2 "; - $TIME="-m time --weekdays $TIME $TIMEFROM $TIMETILL"; + if ($$hash{$key}[23] ne '') { + push (@weekdays, "Fri"); } - foreach my $DPROT (@DPROT){ - $DPORT = &get_port($hash,$key,$DPROT); - $PROT=$DPROT; - $PROT="-p $PROT" if ($PROT ne '' && $PROT ne ' '); - if ($DPROT ne 'TCP' && $DPROT ne'UDP' && $DPROT ne 'ICMP' ){ - $DPORT=''; + if ($$hash{$key}[24] ne '') { + push (@weekdays, "Sat"); + } + if ($$hash{$key}[25] ne '') { + push (@weekdays, "Sun"); + } + if (@weekdays) { + push(@time_options, ("--weekdays", join(",", @weekdays))); + } + + # Convert start time. + my $time_start = &format_time($$hash{$key}[26]); + if ($time_start) { + push(@time_options, ("--timestart", $time_start)); + } + + # Convert end time. + my $time_stop = &format_time($$hash{$key}[27]); + if ($time_stop) { + push(@time_options, ("--timestop", $time_stop)); + } + } + + # Check which protocols are used in this rule and so that we can + # later group rules by protocols. + my @protocols = &get_protocols($hash, $key); + if (!@protocols) { + print_error("Invalid protocol configuration for rule $key"); + next; + } + + foreach my $protocol (@protocols) { + # Check if the given protocol is supported. + if (($protocol ne "all") && (!$protocol ~~ @PROTOCOLS)) { + print_error("Protocol $protocol is not supported (rule $key)"); + next; + } + + # Prepare protocol options (like ICMP types, ports, etc...). + my @protocol_options = &get_protocol_options($hash, $key, $protocol, 0); + + # Check if this protocol knows ports. + my $protocol_has_ports = ($protocol ~~ @PROTOCOLS_WITH_PORTS); + + foreach my $src (@sources) { + # Skip invalid source. + next unless ($src); + + # Sanitize source. + my $source = $src; + if ($source ~~ @ANY_ADDRESSES) { + $source = ""; } - foreach my $a (sort keys %sourcehash){ - foreach my $b (sort keys %targethash){ - if(! $sourcehash{$a}[0] || ! $targethash{$b}[0] || ($natip eq '-d ' && $$hash{$key}[28] eq 'ON') || (!$natip && $$hash{$key}[28] eq 'ON')){ - #Skip rules when no RED IP is set (DHCP,DSL) - next; - } - next if ($targethash{$b}[0] eq 'none'); - $STAG=''; - if ($sourcehash{$a}[0] ne $targethash{$b}[0] && $targethash{$b}[0] ne 'none' || $sourcehash{$a}[0] eq '0.0.0.0/0.0.0.0'){ - if($DPROT ne ''){ - if(substr($sourcehash{$a}[0], 3, 3) ne 'mac' && $sourcehash{$a}[0] ne ''){ $STAG="-s";} - #Process ICMP RULE - if(substr($DPORT, 2, 4) eq 'icmp'){ - my @icmprule= split(",",substr($DPORT, 12,)); - foreach (@icmprule){ - $icmptype="--icmp-type "; - if ($_ eq "BLANK") { - $icmptype=""; - $_=""; - } - if ($$hash{$key}[17] eq 'ON'){ - run("$command $$hash{$key}[1] $PROT $STAG $sourcehash{$a}[0] $SPORT -d $targethash{$b}[0] $icmptype $_ $TIME -j LOG"); - } - run("$command $$hash{$key}[1] $PROT $STAG $sourcehash{$a}[0] $SPORT -d $targethash{$b}[0] $icmptype $_ $TIME -j $$hash{$key}[0]"); - } - #PROCESS DNAT RULE (Portforward) - }elsif($$hash{$key}[28] eq 'ON' && $$hash{$key}[31] eq 'dnat'){ - $natchain='NAT_DESTINATION'; - if ($$hash{$key}[17] eq 'ON'){ - run("$command $natchain $PROT $STAG $sourcehash{$a}[0] $SPORT $natip $fireport $TIME -j LOG --log-prefix 'DNAT'"); - } - my ($ip,$sub) =split("/",$targethash{$b}[0]); - #Process NAT with servicegroup used - if ($$hash{$key}[28] eq 'ON' && $$hash{$key}[31] eq 'dnat' && $$hash{$key}[14] eq 'cust_srvgrp'){ - run("$command $natchain $PROT $STAG $sourcehash{$a}[0] $SPORT $natip $fireport $TIME -j $nat --to-destination $ip $DPORT"); - $fwaccessdport=$DPORT; - }else{ - run("$command $natchain $PROT $STAG $sourcehash{$a}[0] $SPORT $natip $fireport $TIME -j $nat --to-destination $ip$DPORT"); - $DPORT =~ s/\-/:/g; - if ($DPORT){ - $fwaccessdport="--dport ".substr($DPORT,1,); - }elsif(! $DPORT && $$hash{$key}[30] ne ''){ - if ($$hash{$key}[30]=~m/|/i){ - $$hash{$key}[30] =~ s/\|/,/g; - $fwaccessdport="-m multiport --dport $$hash{$key}[30]"; - }else{ - $fwaccessdport="--dport $$hash{$key}[30]"; - } - } - } - run("iptables --wait -A FORWARDFW $PROT $STAG $sourcehash{$a}[0] -d $ip $fwaccessdport $TIME -j $$hash{$key}[0]"); - next; - #PROCESS SNAT RULE - }elsif($$hash{$key}[28] eq 'ON' && $$hash{$key}[31] eq 'snat'){ - $natchain='NAT_SOURCE'; - if ($$hash{$key}[17] eq 'ON' ){ - run("$command $natchain $PROT $STAG $sourcehash{$a}[0] $SPORT -d $targethash{$b}[0] $DPORT $TIME -j LOG --log-prefix 'SNAT'"); - } - run("$command $natchain $PROT $STAG $sourcehash{$a}[0] $SPORT -d $targethash{$b}[0] $DPORT $TIME -j $nat --to-source $natip"); - } - #PROCESS EVERY OTHER RULE (If NOT ICMP, else the rule would be applied double) - if ($PROT ne '-p ICMP'){ - if ($$hash{$key}[17] eq 'ON' && $$hash{$key}[28] ne 'ON'){ - run("$command $$hash{$key}[1] $PROT $STAG $sourcehash{$a}[0] $SPORT -d $targethash{$b}[0] $DPORT $TIME -j LOG"); - } - run("iptables --wait -A $$hash{$key}[1] $PROT $STAG $sourcehash{$a}[0] $SPORT -d $targethash{$b}[0] $DPORT $TIME -j $$hash{$key}[0]"); + + foreach my $dst (@destinations) { + # Skip invalid rules. + next if (!$dst || ($dst eq "none")); + + # Sanitize destination. + my $destination = $dst; + if ($destination ~~ @ANY_ADDRESSES) { + $destination = ""; + } + + # Array with iptables arguments. + my @options = (); + + # Append protocol. + if ($protocol ne "all") { + push(@options, @protocol_options); + } + + # Prepare source options. + my @source_options = (); + if ($source =~ /mac/) { + push(@source_options, $source); + } elsif ($source) { + push(@source_options, ("-s", $source)); + } + + # 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 = &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). + next unless ($nat_address); + + # Destination NAT + if ($NAT_MODE eq "DNAT") { + # 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 = (); + 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(@nat_options, @time_options); + + my $dnat_port; + if ($protocol_has_ports) { + $dnat_port = &get_dnat_target_port($hash, $key); + } + + my @nat_action_options = (); + + # 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(); } - #PROCESS Prot ICMP and type = All ICMP-Types - if ($PROT eq '-p ICMP' && $$hash{$key}[9] eq 'All ICMP-Types'){ - if ($$hash{$key}[17] eq 'ON' && $$hash{$key}[28] ne 'ON'){ - run("$command $$hash{$key}[1] $PROT $STAG $sourcehash{$a}[0] $SPORT -d $targethash{$b}[0] $DPORT $TIME -j LOG"); + next unless ($destination); + + my ($dnat_address, $dnat_mask) = split("/", $destination); + @destination_options = ("-d", $dnat_address); + + if ($protocol_has_ports) { + my $dnat_port = &get_dnat_target_port($hash, $key); + + if ($dnat_port) { + $dnat_address .= ":$dnat_port"; } - run("iptables --wait -A $$hash{$key}[1] $PROT $STAG $sourcehash{$a}[0] $SPORT -d $targethash{$b}[0] $DPORT $TIME -j $$hash{$key}[0]"); } + + push(@nat_action_options, ("-j", "DNAT", "--to-destination", $dnat_address)); } + + if ($LOG) { + 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 @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 @nat_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"); } } + + 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 @log_limit_options -j LOG --log-prefix '$chain '"); + } + run("$IPTABLES -A $chain @options -j $target"); } } } - %sourcehash=(); - %targethash=(); - undef $TIME; - undef $TIMEFROM; - undef $TIMETILL; - undef $fireport; } } -sub get_nat_ip { - my $val=shift; - my $type=shift; - my $result; - if($val eq 'RED' || $val eq 'GREEN' || $val eq 'ORANGE' || $val eq 'BLUE'){ - $result=$defaultNetworks{$val.'_ADDRESS'}; - }elsif($val eq 'ALL'){ - $result='-i '.$con; - }elsif($val eq 'Default IP' && $type eq 'dnat'){ - $result='-d '.$redip; - }elsif($val eq 'Default IP' && $type eq 'snat'){ - $result=$redip; - }else{ - foreach my $al (sort keys %aliases){ - if($val eq $al && $type eq 'dnat'){ - $result='-d '.$aliases{$al}{'IPT'}; - }elsif($val eq $al && $type eq 'snat'){ - $result=$aliases{$al}{'IPT'}; - } - } +# Formats the given timestamp into the iptables format which is "hh:mm" UTC. +sub format_time { + my $val = shift; + + # Convert the given time into minutes. + my $minutes = &time_convert_to_minutes($val); + + # Move the timestamp into UTC. + $minutes += &time_utc_offset(); + + # Make sure $minutes is between 00:00 and 23:59. + if ($minutes < 0) { + $minutes += 1440; } - return $result; -} -sub get_time { - my $val=shift; - my $val1=shift; - my $time; - my $minutes; - my $ruletime; - $minutes = &utcmin($val); - $ruletime = $minutes + &time_get_utc($val); - if ($ruletime < 0){$ruletime +=1440;} - if ($ruletime > 1440){$ruletime -=1440;} - $time=sprintf "%02d:%02d", $ruletime / 60, $ruletime % 60; - return $time; + if ($minutes > 1440) { + $minutes -= 1440; + } + + # Format as hh:mm. + return sprintf("%02d:%02d", $minutes / 60, $minutes % 60); } -sub time_get_utc { - # Calculates the UTCtime from a given time - my $val=shift; - my @localtime=localtime(time); - my @gmtime=gmtime(time); - my $diff = ($gmtime[2]*60+$gmtime[1]%60)-($localtime[2]*60+$localtime[1]%60); - return $diff; +# Calculates the offsets in minutes from the local timezone to UTC. +sub time_utc_offset { + my @localtime = localtime(time); + my @gmtime = gmtime(time); + + return ($gmtime[2] * 60 + $gmtime[1] % 60) - ($localtime[2] * 60 + $localtime[1] % 60); } -sub utcmin { - my $ruletime=shift; - my ($hrs,$min) = split(":",$ruletime); - my $newtime = $hrs*60+$min; - return $newtime; +# Takes a timestamp like "14:00" and converts it into minutes since midnight. +sub time_convert_to_minutes { + my ($hrs, $min) = split(":", shift); + + return ($hrs * 60) + $min; } sub p2pblock { - my $P2PSTRING; + my $P2PSTRING = ""; my $DO; open( FILE, "< $p2pfile" ) or die "Unable to read $p2pfile"; @p2ps = ; @@ -427,160 +464,215 @@ sub p2pblock { } if($P2PSTRING) { - run("/sbin/iptables --wait -A FORWARDFW $CMD $P2PSTRING -j $DO"); + run("$IPTABLES -A FORWARDFW $CMD $P2PSTRING -j $DO"); } } -sub get_address { - my $base=shift; #source of checking ($configfwdfw{$key}[x] or groupkey - my $base2=shift; - my $type=shift; #src or tgt - my $hash; - if ($type eq 'src'){ - $hash=\%sourcehash; - }else{ - $hash=\%targethash; - } - my $key = &General::findhasharraykey($hash); - if($base eq 'src_addr' || $base eq 'tgt_addr' ){ - if (&General::validmac($base2)){ - $$hash{$key}[0] = "-m mac --mac-source $base2"; - }else{ - $$hash{$key}[0] = $base2; - } - }elsif($base eq 'std_net_src' || $base eq 'std_net_tgt' || $base eq 'Standard Network'){ - $$hash{$key}[0]=&fwlib::get_std_net_ip($base2,$con); - }elsif($base eq 'cust_net_src' || $base eq 'cust_net_tgt' || $base eq 'Custom Network'){ - $$hash{$key}[0]=&fwlib::get_net_ip($base2); - }elsif($base eq 'cust_host_src' || $base eq 'cust_host_tgt' || $base eq 'Custom Host'){ - $$hash{$key}[0]=&fwlib::get_host_ip($base2,$type); - }elsif($base eq 'ovpn_net_src' || $base eq 'ovpn_net_tgt' || $base eq 'OpenVPN static network'){ - $$hash{$key}[0]=&fwlib::get_ovpn_net_ip($base2,1); - }elsif($base eq 'ovpn_host_src' ||$base eq 'ovpn_host_tgt' || $base eq 'OpenVPN static host'){ - $$hash{$key}[0]=&fwlib::get_ovpn_host_ip($base2,33); - }elsif($base eq 'ovpn_n2n_src' ||$base eq 'ovpn_n2n_tgt' || $base eq 'OpenVPN N-2-N'){ - $$hash{$key}[0]=&fwlib::get_ovpn_n2n_ip($base2,11); - }elsif($base eq 'ipsec_net_src' || $base eq 'ipsec_net_tgt' || $base eq 'IpSec Network'){ - $$hash{$key}[0]=&fwlib::get_ipsec_net_ip($base2,11); - }elsif($base eq 'ipfire_src' ){ - if($base2 eq 'GREEN'){ - $$hash{$key}[0]=$defaultNetworks{'GREEN_ADDRESS'}; - } - if($base2 eq 'BLUE'){ - $$hash{$key}[0]=$defaultNetworks{'BLUE_ADDRESS'}; - } - if($base2 eq 'ORANGE'){ - $$hash{$key}[0]=$defaultNetworks{'ORANGE_ADDRESS'}; - } - if($base2 eq 'ALL'){ - $$hash{$key}[0]='0.0.0.0/0'; - } - if($base2 eq 'RED' || $base2 eq 'RED1'){ - open(FILE, "/var/ipfire/red/local-ipaddress"); - $$hash{$key}[0]= ; - close(FILE); - }else{ - foreach my $alias (sort keys %aliases){ - if ($base2 eq $alias){ - $$hash{$key}[0]=$aliases{$alias}{'IPT'}; - } +sub get_protocols { + my $hash = shift; + my $key = shift; + + my $uses_source_ports = ($$hash{$key}[7] eq "ON"); + my $uses_services = ($$hash{$key}[11] eq "ON"); + + my @protocols = (); + + # Rules which don't have source ports or services (like ICMP, ESP, ...). + if (!$uses_source_ports && !$uses_services) { + push(@protocols, $$hash{$key}[8]); + + # Rules which either use ports or services. + } elsif ($uses_source_ports || $uses_services) { + # Check if service group or service + if ($$hash{$key}[14] eq 'cust_srv') { + push(@protocols, &fwlib::get_srv_prot($$hash{$key}[15])); + + } elsif($$hash{$key}[14] eq 'cust_srvgrp'){ + my $protos = &fwlib::get_srvgrp_prot($$hash{$key}[15]); + push(@protocols, split(",", $protos)); + + } else { + # Fetch the protocol for this rule. + my $protocol = lc($$hash{$key}[8]); + + # Fetch source and destination ports for this rule. + my $source_ports = $$hash{$key}[10]; + my $destination_ports = $$hash{$key}[15]; + + # Check if ports are set for protocols which do not support ports. + if (!($protocol ~~ @PROTOCOLS_WITH_PORTS) && ($source_ports || $destination_ports)) { + print_error("$protocol does not support ports"); + return (); } + + push(@protocols, $protocol); } } -} -sub get_prot { - my $hash=shift; - my $key=shift; - #check AH,GRE,ESP or ICMP - if ($$hash{$key}[7] ne 'ON' && $$hash{$key}[11] ne 'ON'){ - return "$$hash{$key}[8]"; + # Remove all empty elements + @protocols = map { $_ ? $_ : () } @protocols; + + # If no protocol has been defined, we assume "all". + if (!@protocols) { + push(@protocols, "all"); } - if ($$hash{$key}[7] eq 'ON' || $$hash{$key}[11] eq 'ON'){ - #check if servicegroup or service - if($$hash{$key}[14] eq 'cust_srv'){ - return &fwlib::get_srv_prot($$hash{$key}[15]); - }elsif($$hash{$key}[14] eq 'cust_srvgrp'){ - return &fwlib::get_srvgrp_prot($$hash{$key}[15]); - }elsif (($$hash{$key}[10] ne '' || $$hash{$key}[15] ne '') && $$hash{$key}[8] eq ''){ #when ports are used and prot set to "all" - return "TCP,UDP"; - }elsif (($$hash{$key}[10] ne '' || $$hash{$key}[15] ne '') && ($$hash{$key}[8] eq 'TCP' || $$hash{$key}[8] eq 'UDP')){ #when ports are used and prot set to "tcp" or "udp" - return "$$hash{$key}[8]"; - }elsif (($$hash{$key}[10] eq '' && $$hash{$key}[15] eq '') && $$hash{$key}[8] ne 'ICMP'){ #when ports are NOT used and prot NOT set to "ICMP" - return "$$hash{$key}[8]"; - }else{ - return "$$hash{$key}[8]"; - } + + # Make all protocol names lowercase. + @protocols = map { lc } @protocols; + + return @protocols; +} + +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)); } - #DNAT - if ($SRC_TGT eq '' && $$hash{$key}[31] eq 'dnat' && $$hash{$key}[11] eq '' && $$hash{$key}[12] ne ''){ - return "$$hash{$key}[8]"; + + # Process source ports. + my $use_src_ports = ($$hash{$key}[7] eq "ON"); + my $src_ports = $$hash{$key}[10]; + + if ($use_src_ports && $src_ports) { + push(@options, &format_ports($src_ports, "src")); } -} -sub get_port { - my $hash=shift; - my $key=shift; - my $prot=shift; - #Get manual defined Ports from SOURCE - if ($$hash{$key}[7] eq 'ON' && $SRC_TGT eq 'SRC'){ - if ($$hash{$key}[10] ne ''){ - $$hash{$key}[10] =~ s/\|/,/g; - if(index($$hash{$key}[10],",") > 0){ - return "-m multiport --sport $$hash{$key}[10] "; - }else{ - if($$hash{$key}[28] ne 'ON' || ($$hash{$key}[28] eq 'ON' && $$hash{$key}[31] eq 'snat') ||($$hash{$key}[28] eq 'ON' && $$hash{$key}[31] eq 'dnat') ){ - return "--sport $$hash{$key}[10] "; - }else{ - return ":$$hash{$key}[10]"; - } - } - } - #Get manual ports from TARGET - }elsif($$hash{$key}[11] eq 'ON' && $SRC_TGT eq ''){ - if($$hash{$key}[14] eq 'TGT_PORT'){ - if ($$hash{$key}[15] ne ''){ - $$hash{$key}[15] =~ s/\|/,/g; - if(index($$hash{$key}[15],",") > 0){ - return "-m multiport --dport $$hash{$key}[15] "; - }else{ - if($$hash{$key}[28] ne 'ON' || ($$hash{$key}[28] eq 'ON' && $$hash{$key}[31] eq 'snat') ){ - return "--dport $$hash{$key}[15] "; - }else{ - $$hash{$key}[15] =~ s/\:/-/g; - return ":$$hash{$key}[15]"; - } - } - } - #Get ports defined in custom Service (firewall-groups) - }elsif($$hash{$key}[14] eq 'cust_srv'){ - if ($prot ne 'ICMP'){ - if($$hash{$key}[31] eq 'dnat' && $$hash{$key}[28] eq 'ON'){ - my $ports =&fwlib::get_srv_port($$hash{$key}[15],1,$prot); - $ports =~ s/\:/-/g; - return ":".$ports - }else{ - return "--dport ".&fwlib::get_srv_port($$hash{$key}[15],1,$prot); - } - }elsif($prot eq 'ICMP' && $$hash{$key}[11] eq 'ON'){ #When PROT is ICMP and "use targetport is checked, this is an icmp-service - return "--icmp-type ".&fwlib::get_srv_port($$hash{$key}[15],3,$prot); - } - #Get ports from services which are used in custom servicegroups (firewall-groups) - }elsif($$hash{$key}[14] eq 'cust_srvgrp'){ - if ($prot ne 'ICMP'){ - return &fwlib::get_srvgrp_port($$hash{$key}[15],$prot); + # Process destination ports. + my $use_dst_ports = ($$hash{$key}[11] eq "ON"); + my $use_dnat = (($$hash{$key}[28] eq "ON") && ($$hash{$key}[31] eq "dnat")); + + if ($use_dst_ports) { + my $dst_ports_mode = $$hash{$key}[14]; + my $dst_ports = $$hash{$key}[15]; + + if (($dst_ports_mode eq "TGT_PORT") && $dst_ports) { + if ($nat_options_wanted && $use_dnat && $$hash{$key}[30]) { + $dst_ports = $$hash{$key}[30]; } - elsif($prot eq 'ICMP'){ - return &fwlib::get_srvgrp_port($$hash{$key}[15],$prot); + push(@options, &format_ports($dst_ports, "dst")); + + } elsif ($dst_ports_mode eq "cust_srv") { + if ($protocol eq "ICMP") { + push(@options, ("--icmp-type", &fwlib::get_srv_port($dst_ports, 3, "ICMP"))); + } else { + $dst_ports = &fwlib::get_srv_port($dst_ports, 1, uc($protocol)); + push(@options, &format_ports($dst_ports, "dst")); } + + } elsif ($dst_ports_mode eq "cust_srvgrp") { + push(@options, &fwlib::get_srvgrp_port($dst_ports, uc($protocol))); } } - #CHECK ICMP - if ($$hash{$key}[7] ne 'ON' && $$hash{$key}[11] ne 'ON' && $SRC_TGT eq ''){ - if($$hash{$key}[9] ne '' && $$hash{$key}[9] ne 'All ICMP-Types'){ - return "--icmp-type $$hash{$key}[9] "; - }elsif($$hash{$key}[9] eq 'All ICMP-Types'){ - return; + + # Check if a single ICMP type is selected. + if (!$use_src_ports && !$use_dst_ports && $protocol eq "icmp") { + my $icmp_type = $$hash{$key}[9]; + + if (($icmp_type ne "All ICMP-Types") && $icmp_type) { + push(@options, ("--icmp-type", $icmp_type)); } } + + return @options; +} + +sub format_ports { + my $ports = shift; + my $type = shift; + + my $arg; + if ($type eq "src") { + $arg = "--sport"; + } elsif ($type eq "dst") { + $arg = "--dport"; + } + + my @options = (); + + if ($ports =~ /\|/) { + $ports =~ s/\|/,/g; + push(@options, ("-m", "multiport")); + } + + if ($ports) { + push(@options, ($arg, $ports)); + } + + return @options; } + +sub get_dnat_target_port { + my $hash = shift; + my $key = shift; + + if ($$hash{$key}[14] eq "TGT_PORT") { + 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; +} +