2009-02-22 18:30:14 +01:00
#
2009-03-08 02:40:35 +01:00
# Shorewall-perl 4.4 -- /usr/share/shorewall-perl/Shorewall/Rules.pm
2009-02-22 18:30:14 +01:00
#
# This program is under GPL [http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt]
#
2009-03-08 02:40:35 +01:00
# (c) 2007,2008,2009 - Tom Eastep (teastep@shorewall.net)
2009-02-22 18:30:14 +01:00
#
# Complete documentation is available at http://shorewall.net
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of Version 2 of the GNU General Public License
# as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# This module contains the high-level code for dealing with rules.
#
package Shorewall::Rules ;
require Exporter ;
use Shorewall::Config qw( :DEFAULT :internal ) ;
use Shorewall::IPAddrs ;
use Shorewall::Zones ;
use Shorewall::Chains qw( :DEFAULT :internal ) ;
use Shorewall::Actions ;
use Shorewall::Policy ;
use Shorewall::Proc ;
use strict ;
our @ ISA = qw( Exporter ) ;
our @ EXPORT = qw( process_tos
setup_ecn
add_common_rules
setup_mac_lists
process_rules
generate_matrix
setup_mss
2009-03-28 20:22:15 +01:00
compile_stop_firewall
2009-02-22 18:30:14 +01:00
) ;
our @ EXPORT_OK = qw( process_rule process_rule1 initialize ) ;
2009-03-08 02:40:35 +01:00
our $ VERSION = '4.3_7' ;
2009-02-22 18:30:14 +01:00
#
# Set to one if we find a SECTION
#
our $ sectioned ;
our $ macro_nest_level ;
our $ current_param ;
our @ param_stack ;
our $ family ;
#
# When splitting a line in the rules file, don't pad out the columns with '-' if the first column contains one of these
#
my % rules_commands = ( COMMENT = > 0 ,
SECTION = > 2 ) ;
#
# Initialize globals -- we take this novel approach to globals initialization to allow
# the compiler to run multiple times in the same process. The
# initialize() function does globals initialization for this
# module and is called from an INIT block below. The function is
# also called by Shorewall::Compiler::compiler at the beginning of
# the second and subsequent calls to that function.
#
sub initialize ( $ ) {
$ family = shift ;
$ sectioned = 0 ;
$ macro_nest_level = 0 ;
$ current_param = '' ;
@ param_stack = ( ) ;
}
INIT {
initialize ( F_IPV4 ) ;
}
use constant { MAX_MACRO_NEST_LEVEL = > 5 } ;
sub process_tos () {
my $ chain = $ capabilities { MANGLE_FORWARD } ? 'fortos' : 'pretos' ;
my $ stdchain = $ capabilities { MANGLE_FORWARD } ? 'FORWARD' : 'PREROUTING' ;
my % tosoptions = ( 'minimize-delay' = > 0x10 ,
'maximize-throughput' = > 0x08 ,
'maximize-reliability' = > 0x04 ,
'minimize-cost' = > 0x02 ,
'normal-service' = > 0x00 ) ;
if ( my $ fn = open_file 'tos' ) {
my $ first_entry = 1 ;
my ( $ pretosref , $ outtosref ) ;
first_entry ( sub { progress_message2 "$doing $fn..." ; $ pretosref = ensure_chain 'mangle' , $ chain ; $ outtosref = ensure_chain 'mangle' , 'outtos' ; } ) ;
while ( read_a_line ) {
my ( $ src , $ dst , $ proto , $ sports , $ ports , $ tos , $ mark ) = split_line 6 , 7 , 'tos file entry' ;
$ first_entry = 0 ;
fatal_error 'A value must be supplied in the TOS column' if $ tos eq '-' ;
if ( defined ( my $ tosval = $ tosoptions { "\L$tos" } ) ) {
$ tos = $ tosval ;
} else {
my $ val = numeric_value ( $ tos ) ;
fatal_error "Invalid TOS value ($tos)" unless defined ( $ val ) && $ val < 0x1f ;
}
my $ chainref ;
my $ restriction = NO_RESTRICT ;
my ( $ srczone , $ source , $ remainder ) ;
if ( $ family == F_IPV4 ) {
( $ srczone , $ source , $ remainder ) = split ( /:/ , $ src , 3 ) ;
fatal_error 'Invalid SOURCE' if defined $ remainder ;
} elsif ( $ src =~ /^(.+?):<(.*)>\s*$/ ) {
$ srczone = $ 1 ;
$ source = $ 2 ;
} else {
$ srczone = $ src ;
}
if ( $ srczone eq firewall_zone ) {
$ chainref = $ outtosref ;
$ src = $ source || '-' ;
$ restriction = OUTPUT_RESTRICT ;
} else {
$ chainref = $ pretosref ;
$ src =~ s/^all:?// ;
}
$ dst =~ s/^all:?// ;
expand_rule
$ chainref ,
$ restriction ,
do_proto ( $ proto , $ ports , $ sports ) . do_test ( $ mark , 0xFF ) ,
$ src ,
$ dst ,
'' ,
"-j TOS --set-tos $tos" ,
'' ,
'' ,
'' ;
}
unless ( $ first_entry ) {
add_rule $ mangle_table - > { $ stdchain } , "-j $chain" if $ pretosref - > { referenced } ;
add_rule $ mangle_table - > { OUTPUT } , "-j outtos" if $ outtosref - > { referenced } ;
}
}
}
#
# Setup ECN disabling rules
#
sub setup_ecn ()
{
my % interfaces ;
my @ hosts ;
if ( my $ fn = open_file 'ecn' ) {
first_entry "$doing $fn..." ;
while ( read_a_line ) {
my ( $ interface , $ hosts ) = split_line 1 , 2 , 'ecn file entry' ;
fatal_error "Unknown interface ($interface)" unless known_interface $ interface ;
$ interfaces { $ interface } = 1 ;
$ hosts = ALLIP if $ hosts eq '-' ;
for my $ host ( split_list $ hosts , 'address' ) {
validate_host ( $ host , 1 ) ;
push @ hosts , [ $ interface , $ host ] ;
}
}
if ( @ hosts ) {
my @ interfaces = ( keys % interfaces ) ;
progress_message "$doing ECN control on @interfaces..." ;
for my $ interface ( @ interfaces ) {
my $ chainref = ensure_chain 'mangle' , ecn_chain ( $ interface ) ;
add_jump $ mangle_table - > { POSTROUTING } , $ chainref , 0 , "-p tcp -o $interface " ;
add_jump $ mangle_table - > { OUTPUT } , $ chainref , 0 , "-p tcp -o $interface " ;
}
for my $ host ( @ hosts ) {
add_rule $ mangle_table - > { ecn_chain $ host - > [ 0 ] } , join ( '' , '-p tcp ' , match_dest_net ( $ host - > [ 1 ] ) , ' -j ECN --ecn-tcp-remove' ) ;
}
}
}
}
sub add_rule_pair ( $$$$ ) {
my ( $ chainref , $ predicate , $ target , $ level ) = @ _ ;
log_rule ( $ level , $ chainref , "\U$target" , $ predicate ) if defined $ level && $ level ne '' ;
add_rule $ chainref , "${predicate}-j $target" ;
}
sub setup_rfc1918_filteration ( $ ) {
my $ listref = $ _ [ 0 ] ;
my $ norfc1918ref = new_standard_chain 'norfc1918' ;
my $ rfc1918ref = new_standard_chain 'rfc1918' ;
my $ chainref = $ norfc1918ref ;
warning_message q( The 'norfc1918' option is deprecated ) ;
log_rule $ config { RFC1918_LOG_LEVEL } , $ rfc1918ref , 'DROP' , '' ;
add_rule $ rfc1918ref , '-j DROP' ;
$ chainref = new_standard_chain 'rfc1918d' if $ config { RFC1918_STRICT } ;
my $ fn = open_file 'rfc1918' ;
first_entry "$doing $fn..." ;
while ( read_a_line ) {
require_capability 'CONNTRACK_MATCH' , "The norfc1918 option" , 's' ;
my ( $ networks , $ target ) = split_line 2 , 2 , 'rfc1918 file' ;
my $ s_target ;
if ( $ target eq 'logdrop' ) {
$ target = 'rfc1918' ;
$ s_target = 'rfc1918' ;
} elsif ( $ target eq 'DROP' ) {
$ s_target = 'DROP' ;
} elsif ( $ target eq 'RETURN' ) {
$ s_target = $ config { RFC1918_STRICT } ? 'rfc1918d' : 'RETURN' ;
} else {
fatal_error "Invalid target ($target) for $networks" ;
}
for my $ network ( split_list $ networks , 'network' ) {
add_rule $ norfc1918ref , match_source_net ( $ network ) . "-j $s_target" ;
add_rule $ chainref , match_orig_dest ( $ network ) . "-j $target" ;
}
}
add_rule $ norfc1918ref , '-j rfc1918d' if $ config { RFC1918_STRICT } ;
my $ state = $ globals { UNTRACKED } ? 'NEW,UNTRACKED' : 'NEW' ;
for my $ hostref ( @$ listref ) {
my $ interface = $ hostref - > [ 0 ] ;
my $ ipsec = $ hostref - > [ 1 ] ;
my $ policy = $ capabilities { POLICY_MATCH } ? "-m policy --pol $ipsec --dir in " : '' ;
for my $ chain ( first_chains $ interface ) {
add_rule $ filter_table - > { $ chain } , join ( '' , "-m state --state $state " , match_source_net ( $ hostref - > [ 2 ] ) , "${policy}-j norfc1918" ) ;
}
set_interface_option $ interface , 'use_input_chain' , 1 ;
set_interface_option $ interface , 'use_forward_chain' , 1 ;
}
}
sub setup_blacklist () {
my $ hosts = find_hosts_by_option 'blacklist' ;
my $ chainref ;
my ( $ level , $ disposition ) = @ config { 'BLACKLIST_LOGLEVEL' , 'BLACKLIST_DISPOSITION' } ;
my $ target = $ disposition eq 'REJECT' ? 'reject' : $ disposition ;
if ( @$ hosts ) {
$ chainref = new_standard_chain 'blacklst' ;
if ( defined $ level && $ level ne '' ) {
my $ logchainref = new_standard_chain 'blacklog' ;
log_rule_limit ( $ level , $ logchainref , 'blacklst' , $ disposition , "$globals{LOGLIMIT}" , '' , 'add' , '' ) ;
add_rule $ logchainref , "-j $target" ;
$ target = 'blacklog' ;
}
}
BLACKLIST:
{
if ( my $ fn = open_file 'blacklist' ) {
my $ first_entry = 1 ;
first_entry "$doing $fn..." ;
while ( read_a_line ) {
if ( $ first_entry ) {
unless ( @$ hosts ) {
2009-02-24 00:39:46 +01:00
warning_message qq( The entries in $fn have been ignored because there are no 'blacklist' interfaces ) ;
2009-02-22 18:30:14 +01:00
close_file ;
last BLACKLIST ;
}
$ first_entry = 0 ;
}
my ( $ networks , $ protocol , $ ports ) = split_line 1 , 3 , 'blacklist file' ;
expand_rule (
$ chainref ,
NO_RESTRICT ,
do_proto ( $ protocol , $ ports , '' ) ,
$ networks ,
'' ,
'' ,
"-j $target" ,
'' ,
$ disposition ,
'' ) ;
progress_message " \"$currentline\" added to blacklist" ;
}
}
my $ state = $ config { BLACKLISTNEWONLY } ? $ globals { UNTRACKED } ? '-m state --state NEW,INVALID,UNTRACKED ' : '-m state --state NEW,INVALID ' : '' ;
for my $ hostref ( @$ hosts ) {
my $ interface = $ hostref - > [ 0 ] ;
my $ ipsec = $ hostref - > [ 1 ] ;
my $ policy = $ capabilities { POLICY_MATCH } ? "-m policy --pol $ipsec --dir in " : '' ;
my $ network = $ hostref - > [ 2 ] ;
my $ source = match_source_net $ network ;
my $ target = source_exclusion ( $ hostref - > [ 3 ] , $ chainref ) ;
for my $ chain ( first_chains $ interface ) {
add_jump $ filter_table - > { $ chain } , $ chainref , 0 , "${source}${state}${policy}" ;
}
set_interface_option $ interface , 'use_input_chain' , 1 ;
set_interface_option $ interface , 'use_forward_chain' , 1 ;
progress_message " Blacklisting enabled on ${interface}:${network}" ;
}
}
}
sub process_routestopped () {
my ( @ allhosts , % source , % dest , % notrack , @ rule ) ;
my $ fn = open_file 'routestopped' ;
my $ seq = 0 ;
first_entry "$doing $fn..." ;
while ( read_a_line ) {
my $ routeback = 0 ;
my ( $ interface , $ hosts , $ options , $ proto , $ ports , $ sports ) = split_line 1 , 6 , 'routestopped file' ;
fatal_error "Unknown interface ($interface)" unless known_interface $ interface ;
$ hosts = ALLIP unless $ hosts && $ hosts ne '-' ;
my @ hosts ;
$ seq + + ;
my $ rule = do_proto ( $ proto , $ ports , $ sports ) ;
for my $ host ( split /,/ , $ hosts ) {
validate_host $ host , 1 ;
push @ hosts , "$interface|$host|$seq" ;
push @ rule , $ rule ;
}
unless ( $ options eq '-' ) {
for my $ option ( split /,/ , $ options ) {
if ( $ option eq 'routeback' ) {
if ( $ routeback ) {
warning_message "Duplicate 'routeback' option ignored" ;
} else {
$ routeback = 1 ;
for my $ host ( split /,/ , $ hosts ) {
my $ source = match_source_net $ host ;
my $ dest = match_dest_net $ host ;
emit "run_iptables -A FORWARD -i $interface -o $interface $source $dest -j ACCEPT" ;
clearrule ;
}
}
} elsif ( $ option eq 'source' ) {
for my $ host ( split /,/ , $ hosts ) {
$ source { "$interface|$host|$seq" } = 1 ;
}
} elsif ( $ option eq 'dest' ) {
for my $ host ( split /,/ , $ hosts ) {
$ dest { "$interface|$host|$seq" } = 1 ;
}
} elsif ( $ option eq 'notrack' ) {
for my $ host ( split /,/ , $ hosts ) {
$ notrack { "$interface|$host|$seq" } = 1 ;
}
} else {
warning_message "Unknown routestopped option ( $option ) ignored" unless $ option eq 'critical' ;
2009-03-30 02:49:00 +02:00
warning_message "The 'critical' option is no longer supported (or needed)" ;
2009-02-22 18:30:14 +01:00
}
}
}
push @ allhosts , @ hosts ;
}
for my $ host ( @ allhosts ) {
my ( $ interface , $ h , $ seq ) = split /\|/ , $ host ;
my $ source = match_source_net $ h ;
my $ dest = match_dest_net $ h ;
my $ sourcei = match_source_dev $ interface ;
my $ desti = match_dest_dev $ interface ;
my $ rule = shift @ rule ;
2009-03-30 02:49:00 +02:00
add_rule $ filter_table - > { INPUT } , "$sourcei $source $rule -j ACCEPT" ;
add_rule $ filter_table - > { OUTPUT } , "$desti $dest $rule -j ACCEPT" unless $ config { ADMINISABSENTMINDED } ;
2009-02-22 18:30:14 +01:00
my $ matched = 0 ;
if ( $ source { $ host } ) {
2009-03-30 02:49:00 +02:00
add_rule $ filter_table - > { FORWARD } , "$sourcei $source $rule -j ACCEPT" ;
2009-02-22 18:30:14 +01:00
$ matched = 1 ;
}
if ( $ dest { $ host } ) {
2009-03-30 02:49:00 +02:00
add_rule $ filter_table - > { FORWARD } , "$desti $dest $rule -j ACCEPT" ;
2009-02-22 18:30:14 +01:00
$ matched = 1 ;
}
if ( $ notrack { $ host } ) {
2009-03-30 02:49:00 +02:00
add_rule $ raw_table - > { PREROUTING } , "$sourcei $source $rule -j NOTRACK" ;
add_rule $ raw_table - > { OUTPUT } , "$desti $dest $rule -j NOTRACK" ;
2009-02-22 18:30:14 +01:00
}
unless ( $ matched ) {
for my $ host1 ( @ allhosts ) {
unless ( $ host eq $ host1 ) {
my ( $ interface1 , $ h1 , $ seq1 ) = split /\|/ , $ host1 ;
my $ dest1 = match_dest_net $ h1 ;
my $ desti1 = match_dest_dev $ interface1 ;
2009-03-30 02:49:00 +02:00
add_rule $ filter_table - > { FORWARD } , "$sourcei $desti1 $source $dest1 $rule -j ACCEPT" ;
2009-02-22 18:30:14 +01:00
clearrule ;
}
}
}
}
}
sub setup_mss () ;
sub add_common_rules () {
my $ interface ;
my $ chainref ;
my $ level ;
my $ target ;
my $ rule ;
my $ list ;
my $ chain ;
new_standard_chain 'dynamic' ;
my $ state = $ config { BLACKLISTNEWONLY } ? $ globals { UNTRACKED } ? '-m state --state NEW,INVALID,UNTRACKED ' : '-m state --state NEW,INVALID ' : '' ;
add_rule $ filter_table - > { $ _ } , "$state -j dynamic" for qw( INPUT FORWARD ) ;
setup_mss ;
if ( $ config { FASTACCEPT } ) {
add_rule ( $ filter_table - > { $ _ } , "-m state --state ESTABLISHED,RELATED -j ACCEPT" ) for qw( INPUT FORWARD OUTPUT ) ;
}
my $ rejectref = new_standard_chain 'reject' ;
$ level = $ config { BLACKLIST_LOGLEVEL } ;
add_rule_pair new_standard_chain ( 'logdrop' ) , ' ' , 'DROP' , $ level ;
add_rule_pair new_standard_chain ( 'logreject' ) , ' ' , 'reject' , $ level ;
for $ interface ( all_interfaces ) {
ensure_chain ( 'filter' , $ _ ) for first_chains ( $ interface ) , output_chain ( $ interface ) ;
}
run_user_exit1 'initdone' ;
setup_blacklist ;
$ list = find_hosts_by_option 'nosmurfs' ;
$ chainref = new_standard_chain 'smurfs' ;
if ( $ capabilities { ADDRTYPE } ) {
add_rule $ chainref , '-s 0.0.0.0 -j RETURN' ;
add_rule_pair $ chainref , '-m addrtype --src-type BROADCAST ' , 'DROP' , $ config { SMURF_LOG_LEVEL } ;
} else {
if ( $ family == F_IPV4 ) {
add_command $ chainref , 'for address in $ALL_BCASTS; do' ;
} else {
add_command $ chainref , 'for address in $ALL_ACASTS; do' ;
}
incr_cmd_level $ chainref ;
log_rule ( $ config { SMURF_LOG_LEVEL } , $ chainref , 'DROP' , '-s $address ' ) ;
add_rule $ chainref , '-s $address -j DROP' ;
decr_cmd_level $ chainref ;
add_command $ chainref , 'done' ;
}
if ( $ family == F_IPV4 ) {
add_rule_pair $ chainref , '-s 224.0.0.0/4 ' , 'DROP' , $ config { SMURF_LOG_LEVEL } ;
} else {
add_rule_pair $ chainref , '-s ff00::/10 ' , 'DROP' , $ config { SMURF_LOG_LEVEL } if $ family == F_IPV4 ;
}
if ( $ capabilities { ADDRTYPE } ) {
add_rule $ rejectref , '-m addrtype --src-type BROADCAST -j DROP' ;
} else {
if ( $ family == F_IPV4 ) {
add_command $ rejectref , 'for address in $ALL_BCASTS; do' ;
} else {
add_command $ rejectref , 'for address in $ALL_ACASTS; do' ;
}
incr_cmd_level $ rejectref ;
add_rule $ rejectref , '-d $address -j DROP' ;
decr_cmd_level $ rejectref ;
add_command $ rejectref , 'done' ;
}
if ( $ family == F_IPV4 ) {
add_rule $ rejectref , '-s 224.0.0.0/4 -j DROP' ;
} else {
add_rule $ rejectref , '-s ff00::/10 -j DROP' ;
}
if ( @$ list ) {
progress_message2 'Adding Anti-smurf Rules' ;
my $ state = $ globals { UNTRACKED } ? 'NEW,INVALID,UNTRACKED' : 'NEW,INVALID' ;
for my $ hostref ( @$ list ) {
$ interface = $ hostref - > [ 0 ] ;
my $ ipsec = $ hostref - > [ 1 ] ;
my $ policy = $ capabilities { POLICY_MATCH } ? "-m policy --pol $ipsec --dir in " : '' ;
my $ target = source_exclusion ( $ hostref - > [ 3 ] , $ chainref ) ;
for $ chain ( first_chains $ interface ) {
add_jump $ filter_table - > { $ chain } , $ target , 0 , join ( '' , "-m state --state $state " , match_source_net ( $ hostref - > [ 2 ] ) , $ policy ) ;
}
set_interface_option $ interface , 'use_input_chain' , 1 ;
set_interface_option $ interface , 'use_forward_chain' , 1 ;
}
}
add_rule $ rejectref , '-p 2 -j DROP' ;
add_rule $ rejectref , '-p 6 -j REJECT --reject-with tcp-reset' ;
if ( $ capabilities { ENHANCED_REJECT } ) {
add_rule $ rejectref , '-p 17 -j REJECT' ;
if ( $ family == F_IPV4 ) {
add_rule $ rejectref , '-p 1 -j REJECT --reject-with icmp-host-unreachable' ;
add_rule $ rejectref , '-j REJECT --reject-with icmp-host-prohibited' ;
} else {
add_rule $ rejectref , '-p 58 -j REJECT --reject-with icmp6-addr-unreachable' ;
add_rule $ rejectref , '-j REJECT --reject-with icmp6-adm-prohibited' ;
}
} else {
add_rule $ rejectref , '-j REJECT' ;
}
$ list = find_interfaces_by_option 'dhcp' ;
if ( @$ list ) {
progress_message2 'Adding rules for DHCP' ;
my $ ports = $ family == F_IPV4 ? '67:68' : '546:547' ;
for $ interface ( @$ list ) {
set_interface_option $ interface , 'use_input_chain' , 1 ;
set_interface_option $ interface , 'use_forward_chain' , 1 ;
for $ chain ( input_chain $ interface , output_chain $ interface ) {
add_rule $ filter_table - > { $ chain } , "-p udp --dport $ports -j ACCEPT" ;
}
add_rule $ filter_table - > { forward_chain $ interface } , "-p udp -o $interface --dport $ports -j ACCEPT" if get_interface_option ( $ interface , 'bridge' ) ;
}
}
if ( $ family == F_IPV4 ) {
$ list = find_hosts_by_option 'norfc1918' ;
setup_rfc1918_filteration $ list if @$ list ;
}
$ list = find_hosts_by_option 'tcpflags' ;
if ( @$ list ) {
my $ disposition ;
progress_message2 "$doing TCP Flags filtering..." ;
$ chainref = new_standard_chain 'tcpflags' ;
if ( $ config { TCP_FLAGS_LOG_LEVEL } ne '' ) {
my $ logflagsref = new_standard_chain 'logflags' ;
my $ savelogparms = $ globals { LOGPARMS } ;
$ globals { LOGPARMS } = "$globals{LOGPARMS}--log-ip-options " ;
log_rule $ config { TCP_FLAGS_LOG_LEVEL } , $ logflagsref , $ config { TCP_FLAGS_DISPOSITION } , '' ;
$ globals { LOGPARMS } = $ savelogparms ;
if ( $ config { TCP_FLAGS_DISPOSITION } eq 'REJECT' ) {
add_rule $ logflagsref , '-p 6 -j REJECT --reject-with tcp-reset' ;
} else {
add_rule $ logflagsref , "-j $config{TCP_FLAGS_DISPOSITION}" ;
}
$ disposition = 'logflags' ;
} else {
$ disposition = $ config { TCP_FLAGS_DISPOSITION } ;
}
add_rule $ chainref , "-p tcp --tcp-flags ALL FIN,URG,PSH -j $disposition" ;
add_rule $ chainref , "-p tcp --tcp-flags ALL NONE -j $disposition" ;
add_rule $ chainref , "-p tcp --tcp-flags SYN,RST SYN,RST -j $disposition" ;
add_rule $ chainref , "-p tcp --tcp-flags SYN,FIN SYN,FIN -j $disposition" ;
add_rule $ chainref , "-p tcp --syn --sport 0 -j $disposition" ;
for my $ hostref ( @$ list ) {
my $ interface = $ hostref - > [ 0 ] ;
my $ target = source_exclusion ( $ hostref - > [ 3 ] , $ chainref ) ;
my $ policy = $ capabilities { POLICY_MATCH } ? "-m policy --pol $hostref->[1] --dir in " : '' ;
for $ chain ( first_chains $ interface ) {
add_jump $ filter_table - > { $ chain } , $ target , 0 , join ( '' , '-p tcp ' , match_source_net ( $ hostref - > [ 2 ] ) , $ policy ) ;
}
set_interface_option $ interface , 'use_input_chain' , 1 ;
set_interface_option $ interface , 'use_forward_chain' , 1 ;
}
}
if ( $ family == F_IPV4 ) {
$ list = find_interfaces_by_option 'upnp' ;
if ( @$ list ) {
progress_message2 "$doing UPnP" ;
new_nat_chain ( 'UPnP' ) ;
for $ interface ( @$ list ) {
add_rule $ nat_table - > { PREROUTING } , match_source_dev ( $ interface ) . '-j UPnP' ;
}
}
}
setup_syn_flood_chains ;
}
my % maclist_targets = ( ACCEPT = > { target = > 'RETURN' , mangle = > 1 } ,
REJECT = > { target = > 'reject' , mangle = > 0 } ,
DROP = > { target = > 'DROP' , mangle = > 1 } ) ;
sub setup_mac_lists ( $ ) {
my $ phase = $ _ [ 0 ] ;
my % maclist_interfaces ;
my $ table = $ config { MACLIST_TABLE } ;
my $ maclist_hosts = find_hosts_by_option 'maclist' ;
my $ target = $ globals { MACLIST_TARGET } ;
my $ level = $ config { MACLIST_LOG_LEVEL } ;
my $ disposition = $ config { MACLIST_DISPOSITION } ;
my $ ttl = $ config { MACLIST_TTL } ;
progress_message2 "$doing MAC Filtration -- Phase $phase..." ;
for my $ hostref ( @$ maclist_hosts ) {
$ maclist_interfaces { $ hostref - > [ 0 ] } = 1 ;
}
my @ maclist_interfaces = ( sort keys % maclist_interfaces ) ;
if ( $ phase == 1 ) {
for my $ interface ( @ maclist_interfaces ) {
my $ chainref = new_chain $ table , mac_chain $ interface ;
if ( $ family == F_IPV4 ) {
add_rule $ chainref , '-s 0.0.0.0 -d 255.255.255.255 -p udp --dport 67:68 -j RETURN'
if $ table eq 'mangle' && get_interface_option ( $ interface , 'dhcp' ) ;
} else {
#
# Accept any packet with a link-level source or destination address
#
add_rule $ chainref , '-s ff80::/10 -j RETURN' ;
add_rule $ chainref , '-d ff80::/10 -j RETURN' ;
#
# Accept Multicast
#
add_rule $ chainref , '-d ff00::/10 -j RETURN' ;
}
if ( $ ttl ) {
my $ chain1ref = new_chain $ table , macrecent_target $ interface ;
my $ chain = $ chainref - > { name } ;
add_rule $ chainref , "-m recent --rcheck --seconds $ttl --name $chain -j RETURN" ;
add_rule $ chainref , "-j $chain1ref->{name}" ;
add_rule $ chainref , "-m recent --update --name $chain -j RETURN" ;
add_rule $ chainref , "-m recent --set --name $chain" ;
}
}
my $ fn = open_file 'maclist' ;
first_entry "$doing $fn..." ;
while ( read_a_line ) {
my ( $ original_disposition , $ interface , $ mac , $ addresses ) = split_line1 3 , 4 , 'maclist file' ;
if ( $ original_disposition eq 'COMMENT' ) {
process_comment ;
} else {
my ( $ disposition , $ level , $ remainder ) = split ( /:/ , $ original_disposition , 3 ) ;
fatal_error "Invalid DISPOSITION ($original_disposition)" if defined $ remainder || ! $ disposition ;
my $ targetref = $ maclist_targets { $ disposition } ;
fatal_error "Invalid DISPOSITION ($original_disposition)" if ! $ targetref || ( ( $ table eq 'mangle' ) && ! $ targetref - > { mangle } ) ;
fatal_error "Unknown Interface ($interface)" unless known_interface ( $ interface ) ;
fatal_error "No hosts on $interface have the maclist option specified" unless $ maclist_interfaces { $ interface } ;
my $ chainref = $ chain_table { $ table } { ( $ ttl ? macrecent_target $ interface : mac_chain $ interface ) } ;
$ mac = '' unless $ mac && ( $ mac ne '-' ) ;
$ addresses = '' unless defined $ addresses && ( $ addresses ne '-' ) ;
fatal_error "You must specify a MAC address or an IP address" unless $ mac || $ addresses ;
$ mac = mac_match $ mac if $ mac ;
if ( $ addresses ) {
for my $ address ( split ',' , $ addresses ) {
my $ source = match_source_net $ address ;
log_rule_limit $ level , $ chainref , mac_chain ( $ interface ) , $ disposition , '' , '' , 'add' , "${mac}${source}"
if defined $ level && $ level ne '' ;
add_rule $ chainref , "${mac}${source}-j $targetref->{target}" ;
}
} else {
log_rule_limit $ level , $ chainref , mac_chain ( $ interface ) , $ disposition , '' , '' , 'add' , $ mac
if defined $ level && $ level ne '' ;
add_rule $ chainref , "$mac-j $targetref->{target}" ;
}
progress_message " Maclist entry \"$currentline\" $done" ;
}
}
clear_comment ;
#
# Generate jumps from the input and forward chains
#
for my $ hostref ( @$ maclist_hosts ) {
my $ interface = $ hostref - > [ 0 ] ;
my $ ipsec = $ hostref - > [ 1 ] ;
my $ policy = $ capabilities { POLICY_MATCH } ? "-m policy --pol $ipsec --dir in " : '' ;
my $ source = match_source_net $ hostref - > [ 2 ] ;
my $ state = $ globals { UNTRACKED } ? 'NEW,UNTRACKED' : 'NEW' ;
if ( $ table eq 'filter' ) {
my $ chainref = source_exclusion ( $ hostref - > [ 3 ] , $ filter_table - > { mac_chain $ interface } ) ;
for my $ chain ( first_chains $ interface ) {
add_jump $ filter_table - > { $ chain } , $ chainref , 0 , "${source}-m state --state ${state} ${policy}" ;
}
set_interface_option $ interface , 'use_input_chain' , 1 ;
set_interface_option $ interface , 'use_forward_chain' , 1 ;
} else {
my $ chainref = source_exclusion ( $ hostref - > [ 3 ] , $ mangle_table - > { mac_chain $ interface } ) ;
add_jump $ mangle_table - > { PREROUTING } , $ chainref , 0 , match_source_dev ( $ interface ) . "${source}-m state --state ${state} ${policy}" ;
}
}
} else {
for my $ interface ( @ maclist_interfaces ) {
my $ chainref = $ chain_table { $ table } { ( $ ttl ? macrecent_target $ interface : mac_chain $ interface ) } ;
my $ chain = $ chainref - > { name } ;
if ( $ family == F_IPV4 ) {
if ( $ level ne '' || $ disposition ne 'ACCEPT' ) {
my $ variable = get_interface_addresses source_port_to_bridge ( $ interface ) ;
if ( $ capabilities { ADDRTYPE } ) {
add_commands ( $ chainref ,
"for address in $variable; do" ,
" echo \"-A $chainref->{name} -s \$address -m addrtype --dst-type BROADCAST -j RETURN\" >&3" ,
" echo \"-A $chainref->{name} -s \$address -d 224.0.0.0/4 -j RETURN\" >&3" ,
'done' ) ;
} else {
my $ bridge = source_port_to_bridge ( $ interface ) ;
my $ bridgeref = find_interface ( $ bridge ) ;
add_commands ( $ chainref ,
"for address in $variable; do" ) ;
if ( $ bridgeref - > { broadcasts } ) {
for my $ address ( @ { $ bridgeref - > { broadcasts } } , '255.255.255.255' ) {
add_commands ( $ chainref ,
" echo \"-A $chainref->{name} -s \$address -d $address -j RETURN\" >&3" ) ;
}
} else {
my $ variable1 = get_interface_bcasts $ bridge ;
add_commands ( $ chainref ,
" for address1 in $variable1; do" ,
" echo \"-A $chainref->{name} -s \$address -d \$address1 -j RETURN\" >&3" ,
" done" ) ;
}
add_commands ( $ chainref , " echo \"-A $chainref->{name} -s \$address -d 224.0.0.0/4 -j RETURN\" >&3" ) ;
add_command ( $ chainref , 'done' ) ;
}
}
}
run_user_exit2 ( 'maclog' , $ chainref ) ;
log_rule_limit $ level , $ chainref , $ chain , $ disposition , '' , '' , 'add' , '' if $ level ne '' ;
add_rule $ chainref , "-j $target" ;
}
}
}
sub process_rule1 ( $ $ $ $ $ $ $ $ $ $ $ $ $ ) ;
#
# Expand a macro rule from the rules file
#
sub process_macro ( $$$$$$$$$$$$$$$ ) {
my ( $ macro , $ target , $ param , $ source , $ dest , $ proto , $ ports , $ sports , $ origdest , $ rate , $ user , $ mark , $ connlimit , $ time , $ wildcard ) = @ _ ;
my $ nocomment = no_comment ;
my $ format = 1 ;
macro_comment $ macro ;
my $ macrofile = $ macros { $ macro } ;
progress_message "..Expanding Macro $macrofile..." ;
push_open $ macrofile ;
while ( read_a_line ) {
my ( $ mtarget , $ msource , $ mdest , $ mproto , $ mports , $ msports , $ morigdest , $ mrate , $ muser ) ;
if ( $ format == 1 ) {
( $ mtarget , $ msource , $ mdest , $ mproto , $ mports , $ msports , $ mrate , $ muser , $ morigdest ) = split_line1 1 , 9 , 'macro file' , $ macro_commands ;
} else {
( $ mtarget , $ msource , $ mdest , $ mproto , $ mports , $ msports , $ morigdest , $ mrate , $ muser ) = split_line1 1 , 9 , 'macro file' , $ macro_commands ;
}
if ( $ mtarget eq 'COMMENT' ) {
process_comment unless $ nocomment ;
next ;
}
if ( $ mtarget eq 'FORMAT' ) {
fatal_error "Invalid FORMAT ($msource)" unless $ msource =~ /^[12]$/ ;
$ format = $ msource ;
next ;
}
fatal_error "Invalid macro file entry (too many columns)" if $ morigdest ne '-' && $ format == 1 ;
$ mtarget = merge_levels $ target , $ mtarget ;
if ( $ mtarget =~ /^PARAM(:.*)?$/ ) {
fatal_error 'PARAM requires a parameter to be supplied in macro invocation' unless $ param ne '' ;
$ mtarget = substitute_param $ param , $ mtarget ;
}
my $ action = isolate_basic_target $ mtarget ;
fatal_error "Invalid or missing ACTION ($mtarget)" unless defined $ action ;
my $ actiontype = $ targets { $ action } || find_macro ( $ action ) ;
fatal_error "Invalid Action ($mtarget) in macro" unless $ actiontype & ( ACTION + STANDARD + NATRULE + MACRO ) ;
if ( $ msource ) {
if ( $ msource eq '-' ) {
$ msource = $ source || '' ;
} elsif ( $ msource =~ s/^DEST:?// ) {
$ msource = merge_macro_source_dest $ msource , $ dest ;
} else {
$ msource =~ s/^SOURCE:?// ;
$ msource = merge_macro_source_dest $ msource , $ source ;
}
} else {
$ msource = '' ;
}
if ( $ mdest ) {
if ( $ mdest eq '-' ) {
$ mdest = $ dest || '' ;
} elsif ( $ mdest =~ s/^SOURCE:?// ) {
$ mdest = merge_macro_source_dest $ mdest , $ source ;
} else {
$ mdest =~ s/DEST:?// ;
$ mdest = merge_macro_source_dest $ mdest , $ dest ;
}
} else {
$ mdest = '' ;
}
process_rule1 (
$ mtarget ,
$ msource ,
$ mdest ,
merge_macro_column ( $ mproto , $ proto ) ,
merge_macro_column ( $ mports , $ ports ) ,
merge_macro_column ( $ msports , $ sports ) ,
merge_macro_column ( $ morigdest , $ origdest ) ,
merge_macro_column ( $ mrate , $ rate ) ,
merge_macro_column ( $ muser , $ user ) ,
$ mark ,
$ connlimit ,
$ time ,
$ wildcard
) ;
progress_message " Rule \"$currentline\" $done" ;
}
pop_open ;
progress_message "..End Macro $macrofile" ;
clear_comment unless $ nocomment ;
}
#
# Once a rule has been expanded via wildcards (source and/or dest zone == 'all'), it is processed by this function. If
# the target is a macro, the macro is expanded and this function is called recursively for each rule in the expansion.
#
sub process_rule1 ( $$$$$$$$$$$$$ ) {
my ( $ target , $ source , $ dest , $ proto , $ ports , $ sports , $ origdest , $ ratelimit , $ user , $ mark , $ connlimit , $ time , $ wildcard ) = @ _ ;
my ( $ action , $ loglevel ) = split_action $ target ;
my ( $ basictarget , $ param ) = get_target_param $ action ;
my $ rule = '' ;
my $ actionchainref ;
my $ optimize = $ wildcard ? ( $ basictarget =~ /!$/ ? 0 : $ config { OPTIMIZE } ) : 0 ;
unless ( defined $ param ) {
( $ basictarget , $ param ) = ( $ 1 , $ 2 ) if $ action =~ /^(\w+)[(](.*)[)]$/ ;
}
$ param = '' unless defined $ param ;
#
# Determine the validity of the action
#
my $ actiontype = $ targets { $ basictarget } || find_macro ( $ basictarget ) ;
fatal_error "Unknown action ($action)" unless $ actiontype ;
if ( $ actiontype == MACRO ) {
#
# process_macro() will call process_rule1() recursively for each rule in the macro body
#
fatal_error "Macro invocations nested too deeply" if + + $ macro_nest_level > MAX_MACRO_NEST_LEVEL ;
if ( $ param ne '' ) {
push @ param_stack , $ current_param ;
$ current_param = $ param ;
}
process_macro ( $ basictarget ,
$ target ,
$ current_param ,
$ source ,
$ dest ,
$ proto ,
$ ports ,
$ sports ,
$ origdest ,
$ ratelimit ,
$ user ,
$ mark ,
$ connlimit ,
$ time ,
$ wildcard ) ;
$ macro_nest_level - - ;
$ current_param = pop @ param_stack if $ param ne '' ;
return ;
} elsif ( $ actiontype & NFQ ) {
require_capability ( 'NFQUEUE_TARGET' , 'NFQUEUE Rules' , '' ) ;
my $ paramval = $ param eq '' ? 0 : numeric_value ( $ param ) ;
fatal_error "Invalid value ($param) for NFQUEUE queue number" unless defined ( $ paramval ) && $ paramval <= 65535 ;
$ action = "NFQUEUE --queue-num $paramval" ;
} else {
fatal_error "The $basictarget TARGET does not accept a parameter" unless $ param eq '' ;
}
#
# We can now dispense with the postfix character
#
$ action =~ s/[\+\-!]$// ;
#
# Mark target as used
#
if ( $ actiontype & ACTION ) {
unless ( $ usedactions { $ target } ) {
$ usedactions { $ target } = 1 ;
createactionchain $ target ;
}
}
#
# Take care of irregular syntax and targets
#
2009-04-09 20:45:21 +02:00
my $ log_action = $ action ;
2009-02-22 18:30:14 +01:00
if ( $ actiontype & REDIRECT ) {
my $ z = $ actiontype & NATONLY ? '' : firewall_zone ;
if ( $ dest eq '-' ) {
$ dest = join ( '' , $ z , '::' , $ ports =~ /[:,]/ ? '' : $ ports ) ;
} else {
$ dest = join ( '' , $ z , '::' , $ dest ) unless $ dest =~ /:/ ;
}
} elsif ( $ action eq 'REJECT' ) {
$ action = 'reject' ;
} elsif ( $ action eq 'CONTINUE' ) {
$ action = 'RETURN' ;
} elsif ( $ action eq 'COUNT' ) {
$ action = '' ;
} elsif ( $ actiontype & LOGRULE ) {
fatal_error 'LOG requires a log level' unless defined $ loglevel and $ loglevel ne '' ;
}
#
# Isolate and validate source and destination zones
#
my $ sourcezone ;
my $ destzone ;
my $ sourceref ;
my $ destref ;
my $ origdstports ;
if ( $ source =~ /^(.+?):(.*)/ ) {
fatal_error "Missing SOURCE Qualifier ($source)" if $ 2 eq '' ;
$ sourcezone = $ 1 ;
$ source = $ 2 ;
} else {
$ sourcezone = $ source ;
$ source = ALLIP ;
}
if ( $ dest =~ /^(.*?):(.*)/ ) {
fatal_error "Missing DEST Qualifier ($dest)" if $ 2 eq '' ;
$ destzone = $ 1 ;
$ dest = $ 2 ;
} elsif ( $ dest =~ /.*\..*\./ ) {
#
# Appears to be an address
#
$ destzone = '-' ;
} else {
$ destzone = $ dest ;
$ dest = ALLIP ;
}
fatal_error "Missing source zone" if $ sourcezone eq '-' || $ sourcezone =~ /^:/ ;
fatal_error "Unknown source zone ($sourcezone)" unless $ sourceref = defined_zone ( $ sourcezone ) ;
if ( $ actiontype & NATONLY ) {
2009-03-05 05:03:05 +01:00
unless ( $ destzone eq '-' || $ destzone eq '' ) {
2009-03-05 17:18:58 +01:00
$ destref = defined_zone ( $ destzone ) ;
if ( $ destref ) {
warning_message "Destination zone ($destzone) ignored" ;
} else {
$ dest = join ':' , $ destzone , $ dest ;
$ destzone = '' ;
}
2009-03-05 05:03:05 +01:00
}
2009-02-22 18:30:14 +01:00
} else {
fatal_error "Missing destination zone" if $ destzone eq '-' || $ destzone eq '' ;
fatal_error "Unknown destination zone ($destzone)" unless $ destref = defined_zone ( $ destzone ) ;
}
my $ restriction = NO_RESTRICT ;
if ( $ sourcezone eq firewall_zone ) {
$ restriction = $ destzone eq firewall_zone ? ALL_RESTRICT : OUTPUT_RESTRICT ;
} else {
$ restriction = INPUT_RESTRICT if $ destzone eq firewall_zone ;
}
my ( $ chain , $ chainref , $ policy ) ;
#
# For compatibility with older Shorewall versions
#
$ origdest = ALLIP if $ origdest eq 'all' ;
#
# Take care of chain
#
unless ( $ actiontype & NATONLY ) {
#
# Check for illegal bridge port rule
#
2009-03-13 23:59:49 +01:00
if ( $ destref - > { type } == BPORT ) {
2009-02-22 18:30:14 +01:00
unless ( $ sourceref - > { bridge } eq $ destref - > { bridge } || single_interface ( $ sourcezone ) eq $ destref - > { bridge } ) {
return 1 if $ wildcard ;
fatal_error "Rules with a DESTINATION Bridge Port zone must have a SOURCE zone on the same bridge" ;
}
}
$ chain = "${sourcezone}2${destzone}" ;
$ chainref = ensure_chain 'filter' , $ chain ;
$ policy = $ chainref - > { policy } ;
if ( $ policy eq 'NONE' ) {
return 1 if $ wildcard ;
fatal_error "Rules may not override a NONE policy" ;
}
#
# Handle Optimization
#
if ( $ optimize > 0 ) {
my $ loglevel = $ filter_table - > { $ chainref - > { policychain } } { loglevel } ;
if ( $ loglevel ne '' ) {
return 1 if $ target eq "${policy}:$loglevel}" ;
} else {
return 1 if $ basictarget eq $ policy ;
}
}
#
# Mark the chain as referenced and add appropriate rules from earlier sections.
#
$ chainref = ensure_filter_chain $ chain , 1 ;
}
#
# Generate Fixed part of the rule
#
$ rule = join ( '' , do_proto ( $ proto , $ ports , $ sports ) , do_ratelimit ( $ ratelimit , $ basictarget ) , do_user ( $ user ) , do_test ( $ mark , 0xFF ) , do_connlimit ( $ connlimit ) , do_time ( $ time ) ) ;
unless ( $ section eq 'NEW' ) {
fatal_error "Entries in the $section SECTION of the rules file not permitted with FASTACCEPT=Yes" if $ config { FASTACCEPT } ;
fatal_error "$basictarget rules are not allowed in the $section SECTION" if $ actiontype & ( NATRULE | NONAT ) ;
$ rule . = "-m state --state $section "
}
#
# Generate NAT rule(s), if any
#
if ( $ actiontype & NATRULE ) {
my ( $ server , $ serverport ) ;
my $ randomize = $ dest =~ s/:random$// ? '--random ' : '' ;
require_capability ( 'NAT_ENABLED' , "$basictarget rules" , '' ) ;
#
# Isolate server port
#
if ( $ dest =~ /^(.*)(:(.+))$/ ) {
#
# Server IP and Port
#
$ server = $ 1 ; # May be empty
$ serverport = $ 3 ; # Not Empty due to RE
$ origdstports = $ ports ;
if ( $ origdstports && $ origdstports ne '-' && port_count ( $ origdstports ) == 1 ) {
$ origdstports = validate_port ( $ proto , $ origdstports ) ;
} else {
$ origdstports = '' ;
}
if ( $ serverport =~ /^(\d+)-(\d+)$/ ) {
#
# Server Port Range
#
fatal_error "Invalid port range ($serverport)" unless $ 1 < $ 2 ;
my @ ports = ( $ 1 , $ 2 ) ;
$ _ = validate_port ( proto_name ( $ proto ) , $ _ ) for ( @ ports ) ;
( $ ports = $ serverport ) =~ tr /-/ : / ;
} else {
$ serverport = $ ports = validate_port ( proto_name ( $ proto ) , $ serverport ) ;
}
} elsif ( $ dest eq ':' ) {
#
# Rule with no server IP or port ( zone:: )
#
$ server = $ serverport = '' ;
} else {
#
# Simple server IP address (may be empty or "-")
#
$ server = $ dest ;
$ serverport = '' ;
}
#
# Generate the target
#
my $ target = '' ;
if ( $ actiontype & REDIRECT ) {
fatal_error "A server IP address may not be specified in a REDIRECT rule" if $ server ;
$ target = '-j REDIRECT ' ;
$ target . = "--to-port $serverport " if $ serverport ;
if ( $ origdest eq '' || $ origdest eq '-' ) {
$ origdest = ALLIP ;
} elsif ( $ origdest eq 'detect' ) {
if ( $ config { DETECT_DNAT_IPADDRS } && $ sourcezone ne firewall_zone ) {
my $ interfacesref = $ sourceref - > { interfaces } ;
my @ interfaces = keys %$ interfacesref ;
$ origdest = @ interfaces ? "detect:@interfaces" : ALLIP ;
} else {
$ origdest = ALLIP ;
}
}
} else {
fatal_error "A server must be specified in the DEST column in $action rules" if $ server eq '' ;
if ( $ server =~ /^(.+)-(.+)$/ ) {
validate_range ( $ 1 , $ 2 ) ;
} else {
2009-03-05 20:56:24 +01:00
my @ servers = validate_address $ server , 1 ;
$ server = join ',' , @ servers ;
2009-02-22 18:30:14 +01:00
}
2009-04-01 03:31:04 +02:00
if ( $ action eq 'DNAT' ) {
2009-02-22 18:30:14 +01:00
$ target = '-j DNAT ' ;
$ serverport = ":$serverport" if $ serverport ;
for my $ serv ( split /,/ , $ server ) {
$ target . = "--to-destination ${serv}${serverport} " ;
}
}
unless ( $ origdest && $ origdest ne '-' && $ origdest ne 'detect' ) {
if ( $ config { DETECT_DNAT_IPADDRS } && $ sourcezone ne firewall_zone ) {
my $ interfacesref = $ sourceref - > { interfaces } ;
my @ interfaces = keys %$ interfacesref ;
$ origdest = @ interfaces ? "detect:@interfaces" : ALLIP ;
} else {
$ origdest = ALLIP ;
}
}
}
$ target . = $ randomize ;
#
# And generate the nat table rule(s)
#
2009-03-13 23:59:49 +01:00
expand_rule ( ensure_chain ( 'nat' , $ sourceref - > { type } == FIREWALL ? 'OUTPUT' : dnat_chain $ sourcezone ) ,
2009-02-22 18:30:14 +01:00
PREROUTE_RESTRICT ,
$ rule ,
$ source ,
$ origdest ,
'' ,
$ target ,
$ loglevel ,
2009-04-09 20:45:21 +02:00
$ log_action ,
2009-02-22 18:30:14 +01:00
$ serverport ? do_proto ( $ proto , '' , '' ) : '' ) ;
#
# After NAT:
# - the destination port will be the server port ($ports) -- we did that above
# - the destination IP will be the server IP ($dest)
# - there will be no log level (we log NAT rules in the nat table rather than in the filter table).
# - the target will be ACCEPT.
#
unless ( $ actiontype & NATONLY ) {
$ rule = join ( '' , do_proto ( $ proto , $ ports , $ sports ) , do_ratelimit ( $ ratelimit , 'ACCEPT' ) , do_user $ user , do_test ( $ mark , 0xFF ) ) ;
$ loglevel = '' ;
$ dest = $ server ;
$ action = 'ACCEPT' ;
}
} elsif ( $ actiontype & NONAT ) {
#
# NONAT or ACCEPT+ -- May not specify a destination interface
#
fatal_error "Invalid DEST ($dest) in $action rule" if $ dest =~ /:/ ;
$ origdest = '' unless $ origdest and $ origdest ne '-' ;
if ( $ origdest eq 'detect' ) {
my $ interfacesref = $ sourceref - > { interfaces } ;
2009-04-01 02:04:04 +02:00
my $ interfaces = [ ( keys %$ interfacesref ) ] ;
$ origdest = $ interfaces ? "detect:@$interfaces" : ALLIP ;
2009-02-22 18:30:14 +01:00
}
2009-04-01 00:42:49 +02:00
expand_rule ( ensure_chain ( 'nat' , $ sourceref - > { type } == FIREWALL ? 'OUTPUT' : dnat_chain $ sourcezone ) ,
2009-02-22 18:30:14 +01:00
PREROUTE_RESTRICT ,
$ rule ,
$ source ,
$ dest ,
$ origdest ,
'-j RETURN ' ,
$ loglevel ,
2009-04-09 20:45:21 +02:00
$ log_action ,
2009-02-22 18:30:14 +01:00
'' ) ;
}
#
# Add filter table rule, unless this is a NATONLY rule type
#
unless ( $ actiontype & NATONLY ) {
if ( $ actiontype & ACTION ) {
$ action = ( find_logactionchain $ target ) - > { name } ;
$ loglevel = '' ;
}
unless ( $ origdest eq '-' ) {
require_capability ( 'CONNTRACK_MATCH' , 'ORIGINAL DEST in a non-NAT rule' , 's' ) unless $ actiontype & NATRULE ;
} else {
$ origdest = '' ;
}
2009-04-10 00:21:48 +02:00
$ rule . = "-m conntrack --ctorigdstport $origdstports " if $ capabilities { NEW_CONNTRACK_MATCH } && $ origdstports ;
2009-02-22 18:30:14 +01:00
expand_rule ( ensure_chain ( 'filter' , $ chain ) ,
$ restriction ,
$ rule ,
$ source ,
$ dest ,
$ origdest ,
$ action ? "-j $action " : '' ,
$ loglevel ,
2009-04-09 20:45:21 +02:00
$ log_action ,
2009-02-22 18:30:14 +01:00
'' ) ;
}
}
#
# Process a Record in the rules file
#
# Deals with the ugliness of wildcard zones ('all' in SOURCE and/or DEST column).
#
sub process_rule ( $$$$$$$$$$$$ ) {
my ( $ target , $ source , $ dest , $ proto , $ ports , $ sports , $ origdest , $ ratelimit , $ user , $ mark , $ connlimit , $ time ) = @ _ ;
my $ intrazone = 0 ;
my $ includesrcfw = 1 ;
my $ includedstfw = 1 ;
my $ thisline = $ currentline ;
#
# Section Names are optional so once we get to an actual rule, we need to be sure that
# we close off any missing sections.
#
unless ( $ sectioned ) {
finish_section 'ESTABLISHED,RELATED' ;
$ sections { $ section = 'NEW' } = 1 ;
$ sectioned = 1 ;
}
#
# Handle Wildcards
#
if ( $ source =~ /^all[-+]/ ) {
if ( $ source eq 'all+' ) {
$ source = 'all' ;
$ intrazone = 1 ;
} elsif ( ( $ source eq 'all+-' ) || ( $ source eq 'all-+' ) ) {
$ source = 'all' ;
$ intrazone = 1 ;
$ includesrcfw = 0 ;
} elsif ( $ source eq 'all-' ) {
$ source = 'all' ;
$ includesrcfw = 0 ;
} else {
fatal_error "Invalid SOURCE ($source)" ;
}
}
if ( $ dest =~ /^all[-+]/ ) {
if ( $ dest eq 'all+' ) {
$ dest = 'all' ;
$ intrazone = 1 ;
} elsif ( ( $ dest eq 'all+-' ) || ( $ dest eq 'all-+' ) ) {
$ dest = 'all' ;
$ intrazone = 1 ;
$ includedstfw = 0 ;
} elsif ( $ dest eq 'all-' ) {
$ dest = 'all' ;
$ includedstfw = 0 ;
} else {
fatal_error "Invalid DEST ($dest)" ;
}
}
my $ action = isolate_basic_target $ target ;
fatal_error "Invalid or missing ACTION ($target)" unless defined $ action ;
if ( $ source eq 'all' ) {
for my $ zone ( all_zones ) {
2009-03-13 23:59:49 +01:00
if ( $ includesrcfw || ( zone_type ( $ zone ) != FIREWALL ) ) {
2009-02-22 18:30:14 +01:00
if ( $ dest eq 'all' ) {
for my $ zone1 ( all_zones ) {
2009-03-13 23:59:49 +01:00
if ( $ includedstfw || ( zone_type ( $ zone1 ) != FIREWALL ) ) {
2009-02-22 18:30:14 +01:00
if ( $ intrazone || ( $ zone ne $ zone1 ) ) {
process_rule1 $ target , $ zone , $ zone1 , $ proto , $ ports , $ sports , $ origdest , $ ratelimit , $ user , $ mark , $ connlimit , $ time , 1 ;
}
}
}
} else {
my $ destzone = ( split ( /:/ , $ dest , 2 ) ) [ 0 ] ;
2009-05-02 22:23:29 +02:00
$ destzone = $ action =~ /^REDIRECT/ ? firewall_zone : '' unless defined_zone $ destzone ;
2009-02-22 18:30:14 +01:00
if ( $ intrazone || ( $ zone ne $ destzone ) ) {
process_rule1 $ target , $ zone , $ dest , $ proto , $ ports , $ sports , $ origdest , $ ratelimit , $ user , $ mark , $ connlimit , $ time , 1 ;
}
}
}
}
} elsif ( $ dest eq 'all' ) {
for my $ zone ( all_zones ) {
my $ sourcezone = ( split ( /:/ , $ source , 2 ) ) [ 0 ] ;
2009-03-13 23:59:49 +01:00
if ( ( $ includedstfw || ( zone_type ( $ zone ) != FIREWALL ) ) && ( ( $ sourcezone ne $ zone ) || $ intrazone ) ) {
2009-02-22 18:30:14 +01:00
process_rule1 $ target , $ source , $ zone , $ proto , $ ports , $ sports , $ origdest , $ ratelimit , $ user , $ mark , $ connlimit , $ time , 1 ;
}
}
} else {
process_rule1 $ target , $ source , $ dest , $ proto , $ ports , $ sports , $ origdest , $ ratelimit , $ user , $ mark , $ connlimit , $ time , 0 ;
}
progress_message " Rule \"$thisline\" $done" ;
}
#
# Process the Rules File
#
sub process_rules () {
my $ fn = open_file 'rules' ;
first_entry "$doing $fn..." ;
while ( read_a_line ) {
my ( $ target , $ source , $ dest , $ proto , $ ports , $ sports , $ origdest , $ ratelimit , $ user , $ mark , $ connlimit , $ time ) = split_line1 1 , 12 , 'rules file' , \ % rules_commands ;
if ( $ target eq 'COMMENT' ) {
process_comment ;
} elsif ( $ target eq 'SECTION' ) {
#
# read_a_line has already verified that there are exactly two tokens on the line
#
fatal_error "Invalid SECTION ($source)" unless defined $ sections { $ source } ;
fatal_error "Duplicate or out of order SECTION $source" if $ sections { $ source } ;
$ sectioned = 1 ;
$ sections { $ source } = 1 ;
if ( $ source eq 'RELATED' ) {
$ sections { ESTABLISHED } = 1 ;
finish_section 'ESTABLISHED' ;
} elsif ( $ source eq 'NEW' ) {
@ sections { 'ESTABLISHED' , 'RELATED' } = ( 1 , 1 ) ;
finish_section ( ( $ section eq 'RELATED' ) ? 'RELATED' : 'ESTABLISHED,RELATED' ) ;
}
$ section = $ source ;
} else {
if ( "\L$source" =~ /^none(:.*)?$/ || "\L$dest" =~ /^none(:.*)?$/ ) {
progress_message "Rule \"$currentline\" ignored."
} else {
process_rule $ target , $ source , $ dest , $ proto , $ ports , $ sports , $ origdest , $ ratelimit , $ user , $ mark , $ connlimit , $ time ;
}
}
}
clear_comment ;
$ section = 'DONE' ;
}
#
# Add jumps from the builtin chains to the interface-chains that are used by this configuration
#
sub add_interface_jumps {
#
# Add Nat jumps
#
for my $ interface ( @ _ ) {
addnatjump 'POSTROUTING' , snat_chain ( $ interface ) , match_dest_dev ( $ interface ) ;
}
addnatjump 'PREROUTING' , 'nat_in' , '' ;
addnatjump 'POSTROUTING' , 'nat_out' , '' ;
addnatjump 'PREROUTING' , 'dnat' , '' ;
for my $ interface ( @ _ ) {
addnatjump 'PREROUTING' , input_chain ( $ interface ) , match_source_dev ( $ interface ) ;
addnatjump 'POSTROUTING' , output_chain ( $ interface ) , match_dest_dev ( $ interface ) ;
addnatjump 'POSTROUTING' , masq_chain ( $ interface ) , match_dest_dev ( $ interface ) ;
}
#
# Add the jumps to the interface chains from filter FORWARD, INPUT, OUTPUT
#
for my $ interface ( @ _ ) {
add_jump ( $ filter_table - > { FORWARD } , forward_chain $ interface , 0 , match_source_dev ( $ interface ) ) if use_forward_chain $ interface ;
add_jump ( $ filter_table - > { INPUT } , input_chain $ interface , 0 , match_source_dev ( $ interface ) ) if use_input_chain $ interface ;
if ( use_output_chain $ interface ) {
add_jump $ filter_table - > { OUTPUT } , output_chain $ interface , 0 , match_dest_dev ( $ interface ) unless get_interface_option ( $ interface , 'port' ) ;
}
}
#
# Loopback
#
my $ fw = firewall_zone ;
my $ chainref = $ filter_table - > { "${fw}2${fw}" } ;
add_rule $ filter_table - > { OUTPUT } , "-o lo -j " . ( $ chainref - > { referenced } ? "$chainref->{name}" : 'ACCEPT' ) ;
add_rule $ filter_table - > { INPUT } , '-i lo -j ACCEPT' ;
}
# Generate the rules matrix.
#
# Stealing a comment from the Burroughs B6700 MCP Operating System source, generate_matrix makes a sow's ear out of a silk purse.
#
# The biggest disadvantage of the zone-policy-rule model used by Shorewall is that it doesn't scale well as the number of zones increases (Order N**2 where N = number of zones).
# A major goal of the rewrite of the compiler in Perl was to restrict those scaling effects to this function and the rules that it generates.
#
# The function traverses the full "source-zone by destination-zone" matrix and generates the rules necessary to direct traffic through the right set of filter-table rules.
#
sub generate_matrix () {
#
# Helper functions for generate_matrix()
#-----------------------------------------
#
# Return the target for rules from $zone to $zone1.
#
sub rules_target ( $$ ) {
my ( $ zone , $ zone1 ) = @ _ ;
my $ chain = "${zone}2${zone1}" ;
my $ chainref = $ filter_table - > { $ chain } ;
return $ chain if $ chainref && $ chainref - > { referenced } ;
return 'ACCEPT' if $ zone eq $ zone1 ;
2009-03-09 21:56:53 +01:00
assert ( $ chainref ) ;
2009-02-22 18:30:14 +01:00
if ( $ chainref - > { policy } ne 'CONTINUE' ) {
my $ policyref = $ filter_table - > { $ chainref - > { policychain } } ;
return $ policyref - > { name } if $ policyref ;
fatal_error "No policy defined for zone $zone to zone $zone1" ;
}
'' ; # CONTINUE policy
}
#
# Set a breakpoint in this function if you want to step through generate_matrix().
#
sub start_matrix () {
progress_message2 'Generating Rule Matrix...' ;
}
#
# G e n e r a t e _ M a t r i x ( ) S t a r t s H e r e
#
start_matrix ;
my @ interfaces = ( all_interfaces ) ;
my $ preroutingref = ensure_chain 'nat' , 'dnat' ;
my $ fw = firewall_zone ;
my $ notrackref = $ raw_table - > { notrack_chain $ fw } ;
my @ zones = non_firewall_zones ;
my $ interface_jumps_added = 0 ;
#
# Special processing for complex configurations
#
for my $ zone ( @ zones ) {
my $ zoneref = find_zone ( $ zone ) ;
next if @ zones <= 2 && ! $ zoneref - > { options } { complex } ;
my $ frwd_ref = new_standard_chain zone_forward_chain ( $ zone ) ;
if ( $ capabilities { POLICY_MATCH } ) {
my $ type = $ zoneref - > { type } ;
my $ source_ref = ( $ zoneref - > { hosts } { ipsec } ) || { } ;
for my $ interface ( sort { interface_number ( $ a ) <=> interface_number ( $ b ) } keys %$ source_ref ) {
my $ sourcechainref ;
my $ interfacematch = '' ;
if ( use_forward_chain ( $ interface ) ) {
$ sourcechainref = $ filter_table - > { forward_chain $ interface } ;
} else {
$ sourcechainref = $ filter_table - > { FORWARD } ;
$ interfacematch = match_source_dev $ interface ;
move_rules ( $ filter_table - > { forward_chain $ interface } , $ frwd_ref ) ;
}
my $ arrayref = $ source_ref - > { $ interface } ;
for my $ hostref ( @ { $ arrayref } ) {
my $ ipsec_match = match_ipsec_in $ zone , $ hostref ;
for my $ net ( @ { $ hostref - > { hosts } } ) {
add_jump (
$ sourcechainref ,
source_exclusion ( $ hostref - > { exclusions } , $ frwd_ref ) ,
1 ,
join ( '' , $ interfacematch , match_source_net ( $ net ) , $ ipsec_match )
) ;
}
}
}
}
}
#
# NOTRACK from firewall
#
add_rule $ raw_table - > { OUTPUT } , "-j $notrackref->{name}" if $ notrackref - > { referenced } ;
#
# Main source-zone matrix-generation loop
#
for my $ zone ( @ zones ) {
my $ zoneref = find_zone ( $ zone ) ;
my $ source_hosts_ref = $ zoneref - > { hosts } ;
my $ chain1 = rules_target firewall_zone , $ zone ;
my $ chain2 = rules_target $ zone , firewall_zone ;
my $ chain3 = rules_target $ zone , $ zone ;
my $ complex = $ zoneref - > { options } { complex } || 0 ;
my $ type = $ zoneref - > { type } ;
my $ frwd_ref = $ filter_table - > { zone_forward_chain $ zone } ;
my $ chain = 0 ;
my $ dnatref = ensure_chain 'nat' , dnat_chain ( $ zone ) ;
my $ notrackref = ensure_chain 'raw' , notrack_chain ( $ zone ) ;
my $ nested = $ zoneref - > { options } { nested } ;
my $ parenthasnat = 0 ;
my $ parenthasnotrack = 0 ;
if ( $ nested ) {
#
# This is a sub-zone. We need to determine if
#
# a) A parent zone defines DNAT/REDIRECT or notrack rules; and
# b) The current zone has a CONTINUE policy to some other zone.
#
# If a) but not b), then we must avoid sending packets from this
# zone through the DNAT/REDIRECT or notrack chain for the parent.
#
for my $ parent ( @ { $ zoneref - > { parents } } ) {
my $ ref1 = $ nat_table - > { dnat_chain $ parent } || { } ;
my $ ref2 = $ raw_table - > { notrack_chain $ parent } || { } ;
$ parenthasnat = 1 if $ ref1 - > { referenced } ;
$ parenthasnotrack = 1 if $ ref2 - > { referenced } ;
last if $ parenthasnat && $ parenthasnotrack ;
}
if ( $ parenthasnat || $ parenthasnotrack ) {
for my $ zone1 ( all_zones ) {
if ( $ filter_table - > { "${zone}2${zone1}" } - > { policy } eq 'CONTINUE' ) {
#
# This zone has a continue policy to another zone. We must
# send packets from this zone through the parent's DNAT/REDIRECT/NOTRACK chain.
#
$ nested = 0 ;
last ;
}
}
} else {
#
# No parent has DNAT or notrack so there is nothing to worry about. Don't bother to generate needless RETURN rules in the 'dnat' or 'notrack' chain.
#
$ nested = 0 ;
}
}
#
# Take care of PREROUTING, INPUT and OUTPUT jumps
#
for my $ typeref ( values %$ source_hosts_ref ) {
for my $ interface ( sort { interface_number ( $ a ) <=> interface_number ( $ b ) } keys %$ typeref ) {
my $ arrayref = $ typeref - > { $ interface } ;
if ( $ interface eq '+' ) {
#
# Insert the interface-specific jumps before this one which is not interface-specific
#
add_interface_jumps ( @ interfaces ) unless $ interface_jumps_added + + ;
}
for my $ hostref ( @$ arrayref ) {
my $ ipsec_in_match = match_ipsec_in $ zone , $ hostref ;
my $ ipsec_out_match = match_ipsec_out $ zone , $ hostref ;
my $ exclusions = $ hostref - > { exclusions } ;
for my $ net ( @ { $ hostref - > { hosts } } ) {
my $ dest = match_dest_net $ net ;
if ( $ chain1 ) {
my $ nextchain = dest_exclusion ( $ exclusions , $ chain1 ) ;
my $ outputref ;
my $ interfacematch = '' ;
if ( use_output_chain $ interface ) {
$ outputref = $ filter_table - > { output_chain $ interface } ;
} else {
$ outputref = $ filter_table - > { OUTPUT } ;
$ interfacematch = match_dest_dev $ interface ;
}
add_jump $ outputref , $ nextchain , 0 , join ( '' , $ interfacematch , $ dest , $ ipsec_out_match ) ;
add_jump ( $ outputref , $ nextchain , 0 , join ( '' , $ interfacematch , '-d 255.255.255.255 ' , $ ipsec_out_match ) )
if $ hostref - > { options } { broadcast } ;
move_rules ( $ filter_table - > { output_chain $ interface } , $ filter_table - > { $ chain1 } ) unless use_output_chain $ interface ;
}
clearrule ;
next if $ hostref - > { options } { destonly } ;
my $ source = match_source_net $ net ;
if ( $ dnatref - > { referenced } ) {
#
# There are DNAT/REDIRECT rules with this zone as the source.
# Add a jump from this source network to this zone's DNAT/REDIRECT chain
#
add_jump $ preroutingref , source_exclusion ( $ exclusions , $ dnatref ) , 0 , join ( '' , match_source_dev ( $ interface ) , $ source , $ ipsec_in_match ) ;
}
if ( $ notrackref - > { referenced } ) {
#
# There are notrack rules with this zone as the source.
# Add a jump from this source network to this zone's notrack chain
#
add_jump $ raw_table - > { PREROUTING } , source_exclusion ( $ exclusions , $ notrackref ) , 0 , join ( '' , match_source_dev ( $ interface ) , $ source , $ ipsec_in_match ) ;
}
#
# If this zone has parents with DNAT/REDIRECT or notrack rules and there are no CONTINUE polcies with this zone as the source
# then add a RETURN jump for this source network.
#
if ( $ nested ) {
add_rule $ preroutingref , join ( '' , match_source_dev ( $ interface ) , $ source , $ ipsec_in_match , '-j RETURN' ) if $ parenthasnat ;
add_rule $ raw_table - > { PREROUTING } , join ( '' , match_source_dev ( $ interface ) , $ source , $ ipsec_in_match , '-j RETURN' ) if $ parenthasnotrack ;
}
my $ inputchainref ;
my $ interfacematch = '' ;
if ( use_input_chain $ interface ) {
$ inputchainref = $ filter_table - > { input_chain $ interface } ;
} else {
$ inputchainref = $ filter_table - > { INPUT } ;
$ interfacematch = match_source_dev $ interface ;
}
if ( $ chain2 ) {
add_jump $ inputchainref , source_exclusion ( $ exclusions , $ chain2 ) , 0 , join ( '' , $ interfacematch , $ source , $ ipsec_in_match ) ;
move_rules ( $ filter_table - > { input_chain $ interface } , $ filter_table - > { $ chain2 } ) unless use_input_chain $ interface ;
}
if ( $ frwd_ref && $ hostref - > { ipsec } ne 'ipsec' ) {
my $ ref = source_exclusion ( $ exclusions , $ frwd_ref ) ;
if ( use_forward_chain $ interface ) {
add_jump $ filter_table - > { forward_chain $ interface } , $ ref , 0 , join ( '' , $ source , $ ipsec_in_match ) ;
} else {
add_jump $ filter_table - > { FORWARD } , $ ref , 0 , join ( '' , match_source_dev ( $ interface ) , $ source , $ ipsec_in_match ) ;
move_rules ( $ filter_table - > { forward_chain $ interface } , $ frwd_ref ) ;
}
}
}
}
}
}
#
# F O R W A R D I N G
#
my @ dest_zones ;
my $ last_chain = '' ;
if ( $ config { OPTIMIZE } > 0 ) {
my @ temp_zones ;
ZONE1:
for my $ zone1 ( @ zones ) {
my $ zone1ref = find_zone ( $ zone1 ) ;
my $ policy = $ filter_table - > { "${zone}2${zone1}" } - > { policy } ;
next if $ policy eq 'NONE' ;
my $ chain = rules_target $ zone , $ zone1 ;
next unless $ chain ;
if ( $ zone eq $ zone1 ) {
next if ( scalar ( keys ( % { $ zoneref - > { interfaces } } ) ) < 2 ) && ! $ zoneref - > { options } { in_out } { routeback } ;
}
2009-03-13 23:59:49 +01:00
if ( $ zone1ref - > { type } == BPORT ) {
2009-02-22 18:30:14 +01:00
next unless $ zoneref - > { bridge } eq $ zone1ref - > { bridge } ;
}
if ( $ chain =~ /2all$/ ) {
if ( $ chain ne $ last_chain ) {
$ last_chain = $ chain ;
push @ dest_zones , @ temp_zones ;
@ temp_zones = ( $ zone1 ) ;
} elsif ( $ policy eq 'ACCEPT' ) {
push @ temp_zones , $ zone1 ;
} else {
$ last_chain = $ chain ;
@ temp_zones = ( $ zone1 ) ;
}
} else {
push @ dest_zones , @ temp_zones , $ zone1 ;
@ temp_zones = ( ) ;
$ last_chain = '' ;
}
}
if ( $ last_chain && @ temp_zones == 1 ) {
push @ dest_zones , @ temp_zones ;
$ last_chain = '' ;
}
} else {
@ dest_zones = @ zones ;
}
#
# Here it is -- THE BIG UGLY!!!!!!!!!!!!
#
# We now loop through the destination zones creating jumps to the rules chain for each source/dest combination.
# @dest_zones is the list of destination zones that we need to handle from this source zone
#
ZONE1:
for my $ zone1 ( @ dest_zones ) {
my $ zone1ref = find_zone ( $ zone1 ) ;
my $ policy = $ filter_table - > { "${zone}2${zone1}" } - > { policy } ;
next if $ policy eq 'NONE' ;
my $ chain = rules_target $ zone , $ zone1 ;
next unless $ chain ; # CONTINUE policy with no rules
my $ num_ifaces = 0 ;
if ( $ zone eq $ zone1 ) {
next ZONE1 if ( $ num_ifaces = scalar ( keys ( % { $ zoneref - > { interfaces } } ) ) ) < 2 && ! $ zoneref - > { options } { in_out } { routeback } ;
}
2009-03-13 23:59:49 +01:00
if ( $ zone1ref - > { type } == BPORT ) {
2009-02-22 18:30:14 +01:00
next ZONE1 unless $ zoneref - > { bridge } eq $ zone1ref - > { bridge } ;
}
my $ chainref = $ filter_table - > { $ chain } ;
my $ dest_hosts_ref = $ zone1ref - > { hosts } ;
if ( $ frwd_ref ) {
for my $ typeref ( values %$ dest_hosts_ref ) {
for my $ interface ( sort { interface_number ( $ a ) <=> interface_number ( $ b ) } keys %$ typeref ) {
my $ arrayref = $ typeref - > { $ interface } ;
for my $ hostref ( @$ arrayref ) {
next if $ hostref - > { options } { sourceonly } ;
if ( $ zone ne $ zone1 || $ num_ifaces > 1 || $ hostref - > { options } { routeback } ) {
my $ ipsec_out_match = match_ipsec_out $ zone1 , $ hostref ;
for my $ net ( @ { $ hostref - > { hosts } } ) {
add_jump $ frwd_ref , dest_exclusion ( $ hostref - > { exclusions } , $ chain ) , 0 , join ( '' , match_dest_dev ( $ interface ) , match_dest_net ( $ net ) , $ ipsec_out_match ) ;
}
}
}
}
}
} else {
for my $ typeref ( values %$ source_hosts_ref ) {
for my $ interface ( keys %$ typeref ) {
my $ arrayref = $ typeref - > { $ interface } ;
my $ chain3ref ;
my $ match_source_dev = '' ;
if ( use_forward_chain $ interface ) {
$ chain3ref = $ filter_table - > { forward_chain $ interface } ;
} else {
$ chain3ref = $ filter_table - > { FORWARD } ;
$ match_source_dev = match_source_dev $ interface ;
2009-02-28 18:40:39 +01:00
move_rules $ filter_table - > { forward_chain $ interface } , $ chainref ;
2009-02-22 18:30:14 +01:00
}
for my $ hostref ( @$ arrayref ) {
next if $ hostref - > { options } { destonly } ;
my $ excl3ref = source_exclusion ( $ hostref - > { exclusions } , $ chain3ref ) ;
for my $ net ( @ { $ hostref - > { hosts } } ) {
for my $ type1ref ( values %$ dest_hosts_ref ) {
for my $ interface1 ( keys %$ type1ref ) {
my $ array1ref = $ type1ref - > { $ interface1 } ;
for my $ host1ref ( @$ array1ref ) {
next if $ host1ref - > { options } { sourceonly } ;
my $ ipsec_out_match = match_ipsec_out $ zone1 , $ host1ref ;
for my $ net1 ( @ { $ host1ref - > { hosts } } ) {
unless ( $ interface eq $ interface1 && $ net eq $ net1 && ! $ host1ref - > { options } { routeback } ) {
#
# We defer evaluation of the source net match to accomodate systems without $capabilities{KLUDEFREE};
#
add_jump (
$ excl3ref ,
dest_exclusion ( $ host1ref - > { exclusions } , $ chain ) ,
0 ,
join ( '' ,
$ match_source_dev ,
match_dest_dev ( $ interface1 ) ,
match_source_net ( $ net ) ,
match_dest_net ( $ net1 ) ,
$ ipsec_out_match )
) ;
}
}
}
}
}
}
}
}
}
}
#
# E N D F O R W A R D I N G
#
# Now add an unconditional jump to the last unique policy-only chain determined above, if any
#
add_jump $ frwd_ref , $ last_chain , 1 if $ last_chain ;
}
}
add_interface_jumps @ interfaces unless $ interface_jumps_added ;
my % builtins = ( mangle = > [ qw/PREROUTING INPUT FORWARD POSTROUTING/ ] ,
nat = > [ qw/PREROUTING OUTPUT POSTROUTING/ ] ,
filter = > [ qw/INPUT FORWARD OUTPUT/ ] ) ;
complete_standard_chain $ filter_table - > { INPUT } , 'all' , firewall_zone , 'DROP' ;
complete_standard_chain $ filter_table - > { OUTPUT } , firewall_zone , 'all' , 'REJECT' ;
complete_standard_chain $ filter_table - > { FORWARD } , 'all' , 'all' , 'REJECT' ;
if ( $ config { LOGALLNEW } ) {
for my $ table qw/mangle nat filter/ {
for my $ chain ( @ { $ builtins { $ table } } ) {
log_rule_limit
$ config { LOGALLNEW } ,
$ chain_table { $ table } { $ chain } ,
$ table ,
$ chain ,
'' ,
'' ,
'insert' ,
'-m state --state NEW ' ;
}
}
}
}
sub setup_mss ( ) {
my $ clampmss = $ config { CLAMPMSS } ;
my $ option ;
my $ match = '' ;
my $ chainref = $ filter_table - > { FORWARD } ;
if ( $ clampmss ) {
if ( "\L$clampmss" eq 'yes' ) {
$ option = '--clamp-mss-to-pmtu' ;
} else {
$ match = "-m tcpmss --mss $clampmss: " if $ capabilities { TCPMSS_MATCH } ;
$ option = "--set-mss $clampmss" ;
}
$ match . = '-m policy --pol none --dir out ' if $ capabilities { POLICY_MATCH } ;
}
my $ interfaces = find_interfaces_by_option ( 'mss' ) ;
if ( @$ interfaces ) {
#
# Since we will need multiple rules, we create a separate chain
#
$ chainref = new_chain 'filter' , 'settcpmss' ;
#
# Send all forwarded SYN packets to the 'settcpmss' chain
#
add_rule $ filter_table - > { FORWARD } , "-p tcp --tcp-flags SYN,RST SYN -j settcpmss" ;
my $ in_match = '' ;
my $ out_match = '' ;
if ( $ capabilities { POLICY_MATCH } ) {
$ in_match = '-m policy --pol none --dir in ' ;
$ out_match = '-m policy --pol none --dir out ' ;
}
for ( @$ interfaces ) {
my $ mss = get_interface_option ( $ _ , 'mss' ) ;
my $ mssmatch = $ capabilities { TCPMSS_MATCH } ? "-m tcpmss --mss $mss: " : '' ;
add_rule $ chainref , "-o $_ -p tcp --tcp-flags SYN,RST SYN ${mssmatch}${out_match}-j TCPMSS --set-mss $mss" ;
add_rule $ chainref , "-o $_ -j RETURN" if $ clampmss ;
add_rule $ chainref , "-i $_ -p tcp --tcp-flags SYN,RST SYN ${mssmatch}${in_match}-j TCPMSS --set-mss $mss" ;
add_rule $ chainref , "-i $_ -j RETURN" if $ clampmss ;
}
}
add_rule $ chainref , "-p tcp --tcp-flags SYN,RST SYN ${match}-j TCPMSS $option" if $ clampmss ;
}
2009-03-28 20:22:15 +01:00
#
# Compile the stop_firewall() function
#
2009-03-30 02:49:00 +02:00
sub compile_stop_firewall ( $ ) {
my $ test = shift ;
2009-03-28 20:22:15 +01:00
2009-04-01 00:42:37 +02:00
my $ input = $ filter_table - > { INPUT } ;
my $ output = $ filter_table - > { OUTPUT } ;
my $ forward = $ filter_table - > { FORWARD } ;
2009-03-28 20:22:15 +01:00
emit << 'EOF' ;
#
# Stop/restore the firewall after an error or because of a 'stop' or 'clear' command
#
stop_firewall ( ) {
EOF
2009-04-01 00:42:37 +02:00
$ output - > { policy } = 'ACCEPT' if $ config { ADMINISABSENTMINDED } ;
2009-03-30 02:49:00 +02:00
2009-03-28 20:22:15 +01:00
if ( $ family == F_IPV4 ) {
emit ( ' deletechain() {' ,
' qt $IPTABLES -L $1 -n && qt $IPTABLES -F $1 && qt $IPTABLES -X $1' ) ;
} else {
emit ( ' deletechain() {' ,
' qt $IP6TABLES -L $1 -n && qt $IP6TABLES -F $1 && qt $IP6TABLES -X $1' ) ;
}
emit << 'EOF' ;
}
case $ COMMAND in
stop | clear | restore )
; ;
* )
set + x
case $ COMMAND in
start )
logger - p kern . err "ERROR:$PRODUCT start failed"
; ;
restart )
logger - p kern . err "ERROR:$PRODUCT restart failed"
; ;
restore )
logger - p kern . err "ERROR:$PRODUCT restore failed"
; ;
esac
if [ "$RESTOREFILE" = NONE ] ; then
COMMAND = clear
clear_firewall
echo "$PRODUCT Cleared"
kill $$
exit 2
else
RESTOREPATH = $ { VARDIR } / $ RESTOREFILE
if [ - x $ RESTOREPATH ] ; then
echo Restoring $ { PRODUCT: = Shorewall } ...
if $ RESTOREPATH restore ; then
echo "$PRODUCT restored from $RESTOREPATH"
set_state "Started"
else
set_state "Unknown"
fi
kill $$
exit 2
fi
fi
; ;
esac
set_state "Stopping"
STOPPING = "Yes"
TERMINATOR =
deletechain shorewall
run_stop_exit
EOF
if ( $ capabilities { NAT_ENABLED } ) {
2009-03-30 02:49:00 +02:00
emit << 'EOF' ;
if [ - f $ { VARDIR } / nat ] ; then
while read external interface ; do
del_ip_addr $ external $ interface
done < $ { VARDIR } / nat
rm - f $ { VARDIR } / nat
fi
2009-03-28 20:22:15 +01:00
EOF
}
if ( $ family == F_IPV4 ) {
emit << 'EOF' ;
if [ - f $ { VARDIR } / proxyarp ] ; then
while read address interface external haveroute ; do
qt arp - i $ external - d $ address pub
2009-04-18 18:28:25 +02:00
[ - z "${haveroute}${NOROUTES}" ] && qt $ IP - 4 route del $ address dev $ interface
2009-03-28 20:22:15 +01:00
f = /proc/s ys /net/i pv4 /conf/ $ interface / proxy_arp
[ - f $ f ] && echo 0 > $ f
done < $ { VARDIR } / proxyarp
2009-03-30 02:49:00 +02:00
rm - f $ { VARDIR } / proxyarp
2009-03-28 20:22:15 +01:00
fi
EOF
}
push_indent ;
emit 'delete_tc1' if $ config { CLEAR_TC } ;
emit ( 'undo_routing' ,
'restore_default_route'
) ;
2009-03-30 02:49:00 +02:00
my @ chains = $ config { ADMINISABSENTMINDED } ? qw/INPUT FORWARD/ : qw/INPUT OUTPUT FORWARD/ ;
add_rule $ filter_table - > { $ _ } , '-m state --state ESTABLISHED,RELATED -j ACCEPT' for @ chains ;
2009-03-28 20:22:15 +01:00
if ( $ family == F_IPV6 ) {
2009-04-01 00:42:37 +02:00
add_rule $ input , '-s ff80::/10 -j ACCEPT' ;
add_rule $ input , '-d ff80::/10 -j ACCEPT' ;
add_rule $ input , '-d ff00::/10 -j ACCEPT' ;
2009-03-28 20:22:15 +01:00
2009-03-30 02:49:00 +02:00
unless ( $ config { ADMINISABSENTMINDED } ) {
2009-04-01 00:42:37 +02:00
add_rule $ output , '-d ff80::/10 -j ACCEPT' ;
add_rule $ output , '-d ff00::/10 -j ACCEPT' ;
2009-03-30 02:49:00 +02:00
}
2009-03-28 20:22:15 +01:00
}
process_routestopped ;
2009-04-01 00:42:37 +02:00
add_rule $ input , '-i lo -j ACCEPT' ;
add_rule $ input , '-i lo -j ACCEPT' ;
2009-03-28 20:22:15 +01:00
2009-04-01 00:42:37 +02:00
add_rule $ output , '-o lo -j ACCEPT' unless $ config { ADMINISABSENTMINDED } ;
2009-03-28 20:22:15 +01:00
my $ interfaces = find_interfaces_by_option 'dhcp' ;
if ( @$ interfaces ) {
my $ ports = $ family == F_IPV4 ? '67:68' : '546:547' ;
for my $ interface ( @$ interfaces ) {
2009-04-01 00:42:37 +02:00
add_rule $ input , "-p udp -i $interface --dport $ports -j ACCEPT" ;
add_rule $ output , "-p udp -o $interface --dport $ports -j ACCEPT" unless $ config { ADMINISABSENTMINDED } ;
2009-03-28 20:22:15 +01:00
#
# This might be a bridge
#
2009-04-01 00:42:37 +02:00
add_rule $ forward , "-p udp -i $interface -o $interface --dport $ports -j ACCEPT" ;
2009-03-28 20:22:15 +01:00
}
}
emit '' ;
2009-03-30 02:49:00 +02:00
create_stop_load $ test ;
2009-03-28 20:22:15 +01:00
if ( $ family == F_IPV4 ) {
if ( $ config { IP_FORWARDING } eq 'on' ) {
emit ( 'echo 1 > /proc/sys/net/ipv4/ip_forward' ,
'progress_message2 IPv4 Forwarding Enabled' ) ;
} elsif ( $ config { IP_FORWARDING } eq 'off' ) {
emit ( 'echo 0 > /proc/sys/net/ipv4/ip_forward' ,
'progress_message2 IPv4 Forwarding Disabled!'
) ;
}
} else {
for my $ interface ( all_bridges ) {
emit "do_iptables -A FORWARD -p 58 -i $interface -o $interface -j ACCEPT" ;
}
if ( $ config { IP_FORWARDING } eq 'on' ) {
emit ( 'echo 1 > /proc/sys/net/ipv6/conf/all/forwarding' ,
'progress_message2 IPv6 Forwarding Enabled' ) ;
} elsif ( $ config { IP_FORWARDING } eq 'off' ) {
emit ( 'echo 0 > /proc/sys/net/ipv6/conf/all/forwarding' ,
'progress_message2 IPv6 Forwarding Disabled!'
) ;
}
}
pop_indent ;
emit '
run_stopped_exit ' ;
my @ ipsets = all_ipsets ;
if ( @ ipsets ) {
2009-03-30 02:49:00 +02:00
emit << 'EOF' ;
2009-03-28 20:22:15 +01:00
if [ - n "$(mywhich ipset)" ] ; then
2009-04-18 18:28:25 +02:00
if $ IPSET - S > $ { VARDIR } / ipsets . tmp ; then
2009-03-28 20:22:15 +01:00
#
# Don't save an 'empty' file
#
grep - q '^-N' $ { VARDIR } /ipsets.tmp && mv -f ${VARDIR}/i psets . tmp $ { VARDIR } / ipsets . save
fi
2009-03-30 20:00:23 +02:00
fi
2009-03-28 20:22:15 +01:00
EOF
}
emit '
set_state "Stopped"
logger - p kern . info "$PRODUCT Stopped"
case $ COMMAND in
stop | clear )
; ;
* )
#
# The firewall is being stopped when we were trying to do something
# else. Kill the shell in case we\'re running in a subshell
#
kill $$
; ;
esac
}
' ;
}
2009-02-22 18:30:14 +01:00
1 ;