2007-03-15 22:55:22 +01:00
#
2007-03-24 20:19:29 +01:00
# Shorewall-pl 3.9 -- /usr/share/shorewall-pl/Shorewall/Rules.pm
2007-03-15 22:55:22 +01:00
#
# This program is under GPL [http://www.gnu.org/copyleft/gpl.htm]
#
# (c) 2007 - Tom Eastep (teastep@shorewall.net)
#
# 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., 675 Mass Ave, Cambridge, MA 02139, USA
#
#
2007-03-15 04:17:56 +01:00
package Shorewall::Rules ;
require Exporter ;
use Shorewall::Common ;
use Shorewall::Config ;
use Shorewall::Zones ;
2007-03-15 22:55:22 +01:00
use Shorewall::Interfaces ;
2007-03-15 04:17:56 +01:00
use Shorewall::Chains ;
2007-03-15 22:55:22 +01:00
use Shorewall::Hosts ;
2007-03-15 04:17:56 +01:00
use Shorewall::Actions ;
use Shorewall::Macros ;
2007-03-15 04:25:08 +01:00
use Shorewall::Policy ;
2007-03-16 17:26:34 +01:00
use Shorewall::Proc ;
2007-03-15 04:17:56 +01:00
use strict ;
our @ ISA = qw( Exporter ) ;
2007-03-21 21:08:05 +01:00
our @ EXPORT = qw( add_common_rules
setup_mac_lists
process_criticalhosts
process_routestopped
process_rules
generate_matrix
setup_mss
2007-03-21 21:35:40 +01:00
dump_rule_chains
2007-03-21 21:08:05 +01:00
) ;
2007-03-15 04:17:56 +01:00
our @ EXPORT_OK = qw( process_rule process_rule1 ) ;
our @ VERSION = 1.00 ;
2007-03-21 21:08:05 +01:00
#
# Keep track of chains for the /var/lib/shorewall[-lite]/chains file
#
2007-03-21 21:35:40 +01:00
my @ rule_chains ;
2007-03-15 04:17:56 +01:00
#
# Set to one if we find a SECTION
#
my $ sectioned = 0 ;
2007-03-15 22:55:22 +01:00
sub process_tos () {
my $ chain = 'pretos' ;
my $ stdchain = 'PREROUTING' ;
if ( - s "$ENV{TMP_DIR}/tos" ) {
progress_message2 'Setting up TOS...' ;
my $ pretosref = new_chain 'mangle' , 'pretos' ;
my $ outtosref = new_chain 'mangle' , 'outtos' ;
open TOS , "$ENV{TMP_DIR}/tos" or fatal_error "Unable to open stripped tos file: $!" ;
while ( $ line = <TOS> ) {
2007-03-25 21:27:25 +02:00
my ( $ src , $ dst , $ proto , $ sports , $ ports , $ tos ) = split_line 6 , 'tos file' ;
2007-03-15 22:55:22 +01:00
2007-03-25 21:27:25 +02:00
fatal_error "TOS field required: $line" unless $ tos ne '-' ;
my $ chainref ;
my $ restriction = NO_RESTRICT ;
my ( $ srczone , $ source ) = split /:/ , $ src ;
if ( $ srczone eq $ firewall_zone ) {
$ chainref = $ outtosref ;
$ src = $ source || '-' ;
$ restriction = OUTPUT_RESTRICT ;
} else {
$ chainref = $ pretosref ;
2007-03-25 21:52:01 +02:00
$ src =~ s/^all:?// ;
2007-03-25 21:27:25 +02:00
}
2007-03-25 21:43:33 +02:00
2007-03-25 23:04:24 +02:00
$ dst =~ s/^all:?// ;
2007-03-25 21:27:25 +02:00
expand_rule
$ chainref ,
$ restriction ,
do_proto ( $ proto , $ ports , $ sports ) ,
$ src ,
$ dst ,
'' ,
"-j TOS --set-tos $tos" ,
'' ,
'' ,
'' ;
2007-03-15 22:55:22 +01:00
}
close TOS ;
}
}
sub add_rule_pair ( $$$$ ) {
my ( $ chainref , $ predicate , $ target , $ level ) = @ _ ;
log_rule $ level , $ chainref , $ target , , $ predicate , if $ level ;
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 ;
log_rule $ config { RFC1918_LOG_LEVEL } , $ rfc1918ref , 'DROP' , '' ;
add_rule $ rfc1918ref , '-j DROP' ;
if ( $ config { RFC1918_STRICT } ) {
$ chainref = new_standard_chain 'rfc1918d' ;
}
open RFC , "$ENV{TMP_DIR}/rfc1918" or fatal_error "Unable to open stripped rfc1918 file: $!" ;
while ( $ line = <RFC> ) {
2007-03-25 18:38:00 +02:00
my ( $ networks , $ target ) = split_line 2 , 'rfc1918 file' ;
2007-03-15 22:55:22 +01:00
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_LOG_LEVEL } ? 'rfc1918d' : 'RETURN' ;
} else {
fatal_error "Invalid target ($target) for $networks" ;
}
for my $ network ( split /,/ , $ networks ) {
add_rule $ norfc1918ref , match_source_net ( $ network ) . "-j $s_target" ;
add_rule $ chainref , match_orig_dest ( $ network ) . "-j $target" ;
}
}
close RFC ;
add_rule $ norfc1918ref , '-j rfc1918d' if $ config { RFC1918_STRICT } ;
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 } , '-m state --state NEW ' . match_source_net ( $ hostref - > [ 2 ] ) . "${policy}-j norfc1918" ;
}
}
}
sub setup_syn_flood_chains () {
for my $ chainref ( @ policy_chains ) {
my $ limit = $ chainref - > { synparams } ;
if ( $ limit ) {
my $ level = $ chainref - > { loglevel } ;
( $ limit , my $ burst ) = split ':' , $ limit ;
$ burst = $ burst ? "--limit-burst $burst " : '' ;
my $ synchainref = new_chain 'filter' , syn_chain $ chainref - > { name } ;
add_rule $ synchainref , "-m limit --limit $limit ${burst}-j RETURN" ;
log_rule_limit $ level , $ synchainref , $ chainref - > { name } , 'DROP' , '-m limit --limit 5/min --limit-burst 5' , '' , 'add' , '' if $ level ;
add_rule $ synchainref , '-j DROP' ;
}
}
}
sub setup_blacklist () {
2007-03-16 23:19:32 +01:00
my $ hosts = find_hosts_by_option 'blacklist' ;
2007-03-15 22:55:22 +01:00
2007-03-16 23:19:32 +01:00
if ( @$ hosts ) {
2007-03-15 22:55:22 +01:00
2007-03-16 23:19:32 +01:00
my ( $ level , $ disposition ) = @ config { 'BLACKLIST_LOGLEVEL' , 'BLACKLIST_DISPOSITION' } ;
2007-03-15 22:55:22 +01:00
2007-03-16 23:19:32 +01:00
progress_message2 " Setting up Blacklist..." ;
2007-03-15 22:55:22 +01:00
2007-03-16 23:19:32 +01:00
new_standard_chain 'blacklst' ;
2007-03-15 22:55:22 +01:00
2007-03-16 23:19:32 +01:00
my $ target = $ disposition eq 'REJECT' ? 'reject' : $ disposition ;
if ( $ level ) {
my $ chainref = new_standard_chain 'blacklog' ;
2007-03-15 22:55:22 +01:00
2007-03-16 23:19:32 +01:00
log_rule_limit ( $ level , $ chainref , 'blacklst' , $ disposition , "$env{LOGLIMIT}" , '' , 'add' , '' ) ;
2007-03-15 22:55:22 +01:00
2007-03-16 23:19:32 +01:00
add_rule $ chainref , "-j $target" ;
2007-03-15 22:55:22 +01:00
2007-03-16 23:19:32 +01:00
$ target = 'blacklog' ;
}
2007-03-15 22:55:22 +01:00
2007-03-16 23:19:32 +01:00
if ( - s "$ENV{TMP_DIR}/blacklist" ) {
2007-03-15 22:55:22 +01:00
2007-03-16 23:19:32 +01:00
open BL , "$ENV{TMP_DIR}/blacklist" or fatal_error "Unable to open stripped blacklist file: $!" ;
progress_message ( " Processing " . find_file 'blacklist' . '...' ) ;
while ( $ line = <BL> ) {
2007-03-25 18:38:00 +02:00
my ( $ networks , $ protocol , $ ports ) = split_line 3 , 'blacklist file' ;
2007-03-16 23:19:32 +01:00
expand_rule
ensure_filter_chain ( 'blacklst' , 0 ) ,
2007-03-18 19:50:34 +01:00
NO_RESTRICT ,
2007-03-16 23:19:32 +01:00
do_proto ( $ protocol , $ ports , '' ) ,
$ networks ,
'' ,
'' ,
"-j $target" ,
'' ,
$ disposition ,
'' ;
progress_message " \"$line\" added to blacklist" ;
}
2007-03-15 22:55:22 +01:00
}
2007-03-16 23:19:32 +01:00
close BL ;
my $ state = $ config { BLACKLISTNEWONLY } ? '-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 ;
for my $ chain ( @ { first_chains $ interface } ) {
add_rule $ filter_table - > { $ chain } , "${source}${state}${policy}-j blacklst" ;
}
progress_message " Blacklisting enabled on ${interface}:${network}" ;
}
2007-03-15 22:55:22 +01:00
}
}
2007-03-16 00:18:58 +01:00
sub process_criticalhosts () {
my $ fn = find_file 'routestopped' ;
my @ critical ;
@ critical = ( ) ;
open RS , "$ENV{TMP_DIR}/routestopped" or fatal_error "Unable to open stripped routestopped file: $!" ;
while ( $ line = <RS> ) {
my $ routeback = 0 ;
2007-03-25 18:38:00 +02:00
my ( $ interface , $ hosts , $ options ) = split_line 3 , 'routestopped file' ;
2007-03-16 00:18:58 +01:00
$ hosts = ALLIPv4 unless $ hosts && $ hosts ne '-' ;
my @ hosts ;
for my $ host ( split /,/ , $ hosts ) {
push @ hosts , "$interface:$hosts" ;
}
unless ( $ options eq '-' ) {
for my $ option ( split /,/ , $ options ) {
unless ( $ option eq 'routeback' || $ option eq 'source' || $ option eq 'dest' ) {
if ( $ option eq 'critical' ) {
push @ critical , @ hosts ;
} else {
warning_message "Unknown routestopped option ( $option ) ignored in routestopped entry \"$line\"" ;
}
}
}
}
}
close RS ;
\ @ critical ;
}
sub process_routestopped () {
my $ fn = find_file 'routestopped' ;
my ( @ allhosts , % source , % dest ) ;
progress_message2 "$doing $fn..." ;
open RS , "$ENV{TMP_DIR}/routestopped" or fatal_error "Unable to open stripped routestopped file: $!" ;
while ( $ line = <RS> ) {
my $ routeback = 0 ;
2007-03-25 18:38:00 +02:00
my ( $ interface , $ hosts , $ options ) = split_line 3 , 'routestopped file' ;
2007-03-16 00:18:58 +01:00
$ hosts = ALLIPv4 unless $ hosts && $ hosts ne '-' ;
my @ hosts ;
for my $ host ( split /,/ , $ hosts ) {
push @ hosts , "$interface:$hosts" ;
}
unless ( $ options eq '-' ) {
for my $ option ( split /,/ , $ options ) {
if ( $ option eq 'routeback' ) {
if ( $ routeback ) {
warning_message "Duplicate 'routeback' option ignored in routestopped entry \"$line\"" ;
} 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" ;
}
}
} elsif ( $ option eq 'source' ) {
for my $ host ( split /,/ , $ hosts ) {
$ source { "$interface:$host" } = 1 ;
}
} elsif ( $ option eq 'dest' ) {
for my $ host ( split /,/ , $ hosts ) {
$ dest { "$interface:$host" } = 1 ;
}
} else {
2007-03-16 01:58:25 +01:00
warning_message "Unknown routestopped option ( $option ) ignored in routestopped entry \"$line\"" unless $ option eq 'critical' ;
2007-03-16 00:18:58 +01:00
}
}
}
push @ allhosts , @ hosts ;
}
close RS ;
for my $ host ( @ allhosts ) {
2007-03-16 01:58:25 +01:00
my ( $ interface , $ h ) = split /:/ , $ host ;
2007-03-16 00:18:58 +01:00
my $ source = match_source_net $ h ;
my $ dest = match_dest_net $ h ;
emit "\$IPTABLES INPUT -i $interface $source ACCEPT" ;
emit "\$IPTABLES OUTPUT -o $interface $dest ACCEPT" if $ config { ADMINISABSENTMINDED } ;
my $ matched = 0 ;
if ( $ source { $ host } ) {
emit "\$IPTABLES FORWARD -i $interface $source ACCEPT" ;
$ matched = 1 ;
}
if ( $ dest { $ host } ) {
emit "\$IPTABLES FORWARD -o $interface $dest ACCEPT" ;
$ matched = 1 ;
}
unless ( $ matched ) {
for my $ host1 ( @ allhosts ) {
unless ( $ host eq $ host1 ) {
2007-03-16 01:58:25 +01:00
my ( $ interface1 , $ h1 ) = split /:/ , $ host1 ;
2007-03-16 00:18:58 +01:00
my $ dest1 = match_dest_net $ h1 ;
emit "\$IPTABLES -A FORWARD -i $interface -o $interface1 $source $dest1 -j ACCEPT" ;
}
}
}
}
}
2007-03-15 22:55:22 +01:00
sub add_common_rules () {
my $ interface ;
my $ chainref ;
my $ level ;
my $ target ;
my $ rule ;
my $ list ;
my $ chain ;
my $ rejectref = new_standard_chain 'reject' ;
2007-03-16 23:19:32 +01:00
$ level = $ env { BLACKLIST_LOG_LEVEL } || 'info' ;
2007-03-15 22:55:22 +01:00
2007-03-16 23:19:32 +01:00
add_rule_pair new_standard_chain ( 'logdrop' ) , ' ' , 'DROP' , $ level ;
add_rule_pair new_standard_chain ( 'logreject' ) , ' ' , 'REJECT' , $ level ;
2007-03-15 22:55:22 +01:00
2007-03-16 23:19:32 +01:00
new_standard_chain 'dynamic' ;
my $ state = $ config { BLACKLISTNEWONLY } ? '-m state --state NEW,INVALID ' : '' ;
2007-03-15 22:55:22 +01:00
for $ interface ( @ interfaces ) {
for $ chain ( input_chain $ interface , forward_chain $ interface ) {
add_rule new_standard_chain ( $ chain ) , "$state -j dynamic" ;
}
new_standard_chain output_chain ( $ interface ) ;
}
setup_blacklist ;
$ list = find_hosts_by_option 'nosmurfs' ;
if ( $ capabilities { ADDRTYPE } ) {
$ chainref = new_standard_chain 'smurfs' ;
add_rule_pair $ chainref , '-m addrtype --src-type BROADCAST ' , 'DROP' , $ config { SMURF_LOG_LEVEL } ;
add_rule_pair $ chainref , '-m addrtype --src-type MULTICAST ' , 'DROP' , $ config { SMURF_LOG_LEVEL } ;
add_rule $ rejectref , '-m addrtype --src-type BROADCAST -j DROP' ;
add_rule $ rejectref , '-m addrtype --src-type MULTICAST -j DROP' ;
} elsif ( @$ list ) {
fatal_error "The nosmurfs option requires Address Type Match in your kernel and iptables" ;
}
if ( @$ list ) {
progress_message2 ' Adding Anti-smurf Rules' ;
for my $ hostref ( @$ list ) {
$ interface = $ hostref - > [ 0 ] ;
my $ ipsec = $ hostref - > [ 1 ] ;
my $ policy = $ capabilities { POLICY_MATCH } ? "-m policy --pol $ipsec --dir in " : '' ;
for $ chain ( @ { first_chains $ interface } ) {
add_rule $ filter_table - > { $ chain } , '-m state --state NEW,INVALID ' . match_source_net ( $ hostref - > [ 2 ] ) . "${policy}-j smurfs" ;
}
}
}
add_rule $ rejectref , '-p tcp -j REJECT --reject-with tcp-reset' ;
if ( $ capabilities { ENHANCED_REJECT } ) {
add_rule $ rejectref , '-p udp -j REJECT' ;
add_rule $ rejectref , '-p icmp -j REJECT --reject-with icmp-host-unreachable' ;
add_rule $ rejectref , '-j REJECT --reject-with icmp-host-prohibited' ;
} else {
add_rule $ rejectref , '-j REJECT' ;
}
$ list = find_interfaces_by_option 'dhcp' ;
if ( @$ list ) {
progress_message2 ' Adding rules for DHCP' ;
for $ interface ( @$ list ) {
for $ chain ( @ { first_chains $ interface } ) {
add_rule $ filter_table - > { $ chain } , '-p udp --dport 67:68 -j ACCEPT' ;
}
add_rule $ filter_table - > { forward_chain $ interface } , "-p udp -o $interface --dport 67:68 -j ACCEPT" if $ interfaces { $ interface } { options } { routeback } ;
}
}
$ list = find_hosts_by_option 'norfc1918' ;
if ( @$ list ) {
progress_message2 ' Enabling RFC1918 Filtering' ;
setup_rfc1918_filteration $ list ;
}
$ list = find_hosts_by_option 'tcpflags' ;
if ( @$ list ) {
my $ disposition ;
2007-03-26 01:18:20 +02:00
progress_message2 " $doing TCP Flags filtering..." ;
2007-03-15 22:55:22 +01:00
$ chainref = new_standard_chain 'tcpflags' ;
if ( $ config { TCP_FLAGS_LOG_LEVEL } ) {
my $ logflagsref = new_standard_chain 'logflags' ;
my $ savelogparms = $ env { LOGPARMS } ;
$ env { LOGPARMS } = "$env{LOGPARMS} --log-ip-options" unless $ config { TCP_FLAGS_LOG_LEVEL } eq 'ULOG' ;
log_rule $ config { TCP_FLAGS_LOG_LEVEL } , $ logflagsref , $ config { TCP_FLAGS_DISPOSITION } , '' ;
$ env { LOGPARMS } = $ savelogparms ;
if ( $ config { TCP_FLAGS_DISPOSITION } eq 'REJECT' ) {
add_rule $ logflagsref , '-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 ) {
$ interface = $ hostref - > [ 0 ] ;
my $ ipsec = $ hostref - > [ 1 ] ;
my $ policy = $ capabilities { POLICY_MATCH } ? "-m policy --pol $ipsec --dir in " : '' ;
for $ chain ( @ { first_chains $ interface } ) {
add_rule $ filter_table - > { $ chain } , '-p tcp ' . match_source_net ( $ hostref - > [ 2 ] ) . "${policy}-j tcpflags" ;
}
}
}
if ( $ config { DYNAMIC_ZONES } ) {
for $ interface ( @ interfaces ) {
for $ chain ( @ { dynamic_chains $ interface } ) {
new_standard_chain $ chain ;
}
}
( new_chain 'nat' , $ chain = dynamic_in ( $ interface ) ) - > { referenced } = 1 ;
add_rule $ filter_table - > { input_chain $ interface } , "-j $chain" ;
add_rule $ filter_table - > { forward_chain $ interface } , '-j ' . dynamic_fwd $ interface ;
add_rule $ filter_table - > { output_chain $ interface } , '-j ' . dynamic_out $ interface ;
}
$ list = find_interfaces_by_option 'upnp' ;
if ( @$ list ) {
progress_message2 ' $doing UPnP' ;
( new_chain 'nat' , 'UPnP' ) - > { referenced } = 1 ;
for $ interface ( @$ list ) {
add_rule $ nat_table - > { PREROUTING } , "-i $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' ;
2007-03-19 04:57:58 +01:00
for my $ hostref ( @$ maclist_hosts ) {
2007-03-22 15:41:32 +01:00
$ maclist_interfaces { $ hostref - > [ 0 ] } = 1 ;
2007-03-15 22:55:22 +01:00
}
my @ maclist_interfaces = ( sort keys % maclist_interfaces ) ;
progress_message " $doing MAC Verification for @maclist_interfaces -- Phase $phase..." ;
if ( $ phase == 1 ) {
for my $ interface ( @ maclist_interfaces ) {
my $ chainref = new_chain $ table , mac_chain $ interface ;
add_rule $ chainref , '-s 0.0.0.0 -d 255.255.255.255 -p udp --dport 67:68 -j RETURN'
if ( $ table eq 'mangle' ) && $ interfaces { $ interface } { options } { dhcp } ;
if ( $ config { MACLIST_TTL } ) {
my $ chain1ref = new_chain $ table , macrecent_target $ interface ;
my $ chain = $ chainref - > { name } ;
add_rule $ chainref , "-m recent --rcheck --seconds $config{MACLIST_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" ;
}
}
open MAC , "$ENV{TMP_DIR}/maclist" or fatal_error "Unable to open stripped maclist file: $!" ;
while ( $ line = <MAC> ) {
2007-03-25 18:38:00 +02:00
my ( $ disposition , $ interface , $ mac , $ addresses ) = split_line 4 , 'maclist file' ;
2007-03-15 22:55:22 +01:00
if ( $ disposition eq 'COMMENT' ) {
if ( $ capabilities { COMMENTS } ) {
( $ comment = $ line ) =~ s/^\s*COMMENT\s*// ;
$ comment =~ s/\s*$// ;
} else {
warning_message "COMMENT ignored -- requires comment support in iptables/Netfilter" ;
}
} else {
( $ disposition , my $ level ) = split /:/ , $ disposition ;
my $ targetref = $ maclist_targets { $ disposition } ;
fatal_error "Invalid DISPOSITION ( $disposition) in rule \"$line\"" if ( $ table eq 'mangle' ) && ! $ targetref - > { mangle } ;
fatal_error "No hosts on $interface have the maclist option specified: \"$line\"" unless $ maclist_interfaces { $ interface } ;
my $ chainref = $ chain_table { $ table } { ( $ config { MACLIST_TTL } ? macrecent_target $ interface : mac_chain $ interface ) } ;
$ mac = '' unless $ mac && ( $ mac ne '-' ) ;
$ addresses = '' unless $ 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 $ level ;
add_rule $ chainref , "${mac}${source}-j $targetref->{target}" ;
}
} else {
log_rule_limit $ level , $ chainref , mac_chain ( $ interface ) , $ disposition , '' , '' , 'add' , $ mac if $ level ;
add_rule $ chainref , "$mac-j $targetref->{target}" ;
}
progress_message " Maclist entry \"$line\" $done" ;
}
}
close MAC ;
$ 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 $ target = mac_chain $ interface ;
if ( $ table eq 'filter' ) {
for my $ chain ( @ { first_chains $ interface } ) {
add_rule $ filter_table - > { $ chain } , "${source}-m state --statue NEW ${policy}-j $target" ;
}
} else {
add_rule $ mangle_table - > { PREROUTING } , "-i $interface ${source}-m state --state NEW ${policy}-j $target" ;
}
}
} else {
my $ target = $ env { MACLIST_TARGET } ;
my $ level = $ config { MACLIST_LOG_LEVEL } ;
my $ disposition = $ config { MACLIST_DISPOSITION } ;
for my $ interface ( @ maclist_interfaces ) {
my $ chainref = $ chain_table { $ table } { ( $ config { MACLIST_TTL } ? macrecent_target $ interface : mac_chain $ interface ) } ;
2007-03-22 21:24:21 +01:00
my $ chain = $ chainref - > { name } ;
if ( $ config { MACLIST_LOG_LEVEL } || $ config { MACLIST_DISPOSITION } != 'ACCEPT' ) {
add_command $ chainref , "if interface_is_usable $interface; then" ;
add_command $ chainref , " ip -f inet addr show $interface 2> /dev/null | grep 'inet.*brd' | sed 's/inet //; s/brd //; s/scope.*//;' | while read address broadcast; do" ;
add_command $ chainref , ' address=${address%/*}' ;
add_command $ chainref , ' if [ -n "$broadcast" ]; then' ;
add_command $ chainref , ' echo "-A $chain -s $address -d $broadcast -j RETURN" >&3' ;
add_command $ chainref , ' fi' ;
add_command $ chainref , '' ;
add_command $ chainref , ' echo "-A $chain -s $address -d 255.255.255.255 -j RETURN" >&3' ;
add_command $ chainref , ' echo "-A $chain -s $address -d 224.0.0.0/4 -j RETURN" >&3' ;
add_command $ chainref , ' done' ;
add_command $ chainref , 'else' ;
add_command $ chainref , " fatal_error \"Interface $interface must be up before Shorewall can start\"" ;
add_command $ chainref , "fi\n" ;
}
2007-03-15 22:55:22 +01:00
log_rule_limit $ level , $ chainref , $ chain , $ disposition , '' , '' , 'add' , '' ;
add_rule $ chainref , "-j $target" ;
}
}
}
2007-03-15 04:17:56 +01:00
sub process_rule1 ( $ $ $ $ $ $ $ $ $ ) ;
#
# Expand a macro rule from the rules file
#
sub process_macro ( $$$$$$$$$$$ ) {
my ( $ macrofile , $ target , $ param , $ source , $ dest , $ proto , $ ports , $ sports , $ origdest , $ rate , $ user ) = @ _ ;
my $ standard = ( $ macrofile =~ /^($env{SHAREDIR})/ ) ;
progress_message "..Expanding Macro $macrofile..." ;
open M , $ macrofile or fatal_error "Unable to open $macrofile: $!" ;
while ( $ line = <M> ) {
chomp $ line ;
next if $ line =~ /^\s*#/ ;
next if $ line =~ /^\s*$/ ;
$ line =~ s/#.*$// ;
$ line = expand_shell_variables $ line unless $ standard ;
2007-03-25 18:38:00 +02:00
my ( $ mtarget , $ msource , $ mdest , $ mproto , $ mports , $ msports , $ mrate , $ muser ) = split_line 8 , 'macro file' ;
2007-03-15 04:17:56 +01:00
$ mtarget = merge_levels $ target , $ mtarget ;
if ( $ mtarget =~ /^PARAM:?/ ) {
fatal_error 'PARAM requires that a parameter be supplied in macro invocation' unless $ param ;
$ mtarget = substitute_action $ param , $ mtarget ;
}
my $ action = isolate_basic_target $ mtarget ;
my $ actiontype = $ targets { $ action } ;
if ( $ actiontype & ACTION ) {
unless ( $ usedactions { $ action } ) {
createactionchain $ mtarget ;
$ usedactions { $ mtarget } = 1 ;
}
$ mtarget = find_logactionchain $ mtarget ;
} else {
fatal_error "Invalid Action ($mtarget) in rule \"$line\"" unless $ actiontype & STANDARD ;
}
if ( $ msource ) {
if ( ( $ msource eq '-' ) || ( $ msource eq 'SOURCE' ) ) {
$ msource = $ source || '' ;
} elsif ( $ msource eq 'DEST' ) {
$ msource = $ dest || '' ;
} else {
$ msource = merge_macro_source_dest $ msource , $ source ;
}
} else {
$ msource = '' ;
}
$ msource = '' if $ msource eq '-' ;
if ( $ mdest ) {
if ( ( $ mdest eq '-' ) || ( $ mdest eq 'DEST' ) ) {
$ mdest = $ dest || '' ;
} elsif ( $ mdest eq 'SOURCE' ) {
$ mdest = $ source || '' ;
} else {
$ mdest = merge_macro_source_dest $ mdest , $ dest ;
}
} else {
$ mdest = '' ;
}
$ mdest = '' if $ mdest eq '-' ;
$ mproto = merge_macro_column $ mproto , $ proto ;
$ mports = merge_macro_column $ mports , $ ports ;
$ msports = merge_macro_column $ msports , $ sports ;
$ mrate = merge_macro_column $ mrate , $ rate ;
$ muser = merge_macro_column $ muser , $ user ;
process_rule1 $ mtarget , $ msource , $ mdest , $ mproto , $ mports , $ msports , $ origdest , $ rate , $ user ;
progress_message " Rule \"$line\" $done" ; }
close M ;
progress_message '..End Macro'
}
#
# Once a rule has been completely resolved by macro expansion, it is processed by this function.
#
sub process_rule1 ( $$$$$$$$$ ) {
my ( $ target , $ source , $ dest , $ proto , $ ports , $ sports , $ origdest , $ ratelimit , $ user ) = @ _ ;
my ( $ action , $ loglevel ) = split_action $ target ;
my $ rule = '' ;
my $ actionchainref ;
#
# Determine the validity of the action
#
my $ actiontype = $ targets { $ action } || find_macro ( isolate_basic_target $ action ) ;
fatal_error "Unknown action ($action) in rule \"$line\"" unless $ actiontype ;
if ( $ actiontype == MACRO ) {
process_macro
$ macros { isolate_basic_target $ action } , $
target ,
( split '/' , $ action ) [ 1 ] ,
$ source ,
$ dest ,
$ proto ,
$ ports ,
$ sports ,
$ origdest ,
$ ratelimit ,
$ user ;
return ;
}
#
# We can now dispense with the postfix characters
#
$ action =~ s/[\+\-!]$// ;
#
# Mark target as used
#
if ( $ actiontype & ACTION ) {
unless ( $ usedactions { $ target } ) {
$ usedactions { $ target } = 1 ;
createactionchain $ target ;
}
}
#
# Take care of irregular syntax and targets
#
if ( $ actiontype & REDIRECT ) {
if ( $ dest eq '-' ) {
$ dest = "$firewall_zone" ;
} else {
$ dest = "$firewall_zone" . '::' . "$dest" ;
}
} elsif ( $ action eq 'REJECT' ) {
$ action = 'reject' ;
} elsif ( $ action eq 'CONTINUE' ) {
$ action = 'RETURN' ;
}
#
# Isolate and validate source and destination zones
#
my $ sourcezone ;
my $ destzone ;
if ( $ source =~ /^(.+?):(.*)/ ) {
$ sourcezone = $ 1 ;
$ source = $ 2 ;
} else {
$ sourcezone = $ source ;
$ source = ALLIPv4 ;
}
if ( $ dest =~ /^(.+?):(.*)/ ) {
$ destzone = $ 1 ;
$ dest = $ 2 ;
} else {
$ destzone = $ dest ;
$ dest = ALLIPv4 ;
}
fatal_error "Unknown source zone ($sourcezone) in rule \"$line\"" unless $ zones { $ sourcezone } ;
fatal_error "Unknown destination zone ($destzone) in rule \"$line\"" unless $ zones { $ destzone } ;
2007-03-25 21:43:33 +02:00
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 ;
}
2007-03-15 04:17:56 +01:00
#
# Take care of chain
#
my $ chain = "${sourcezone}2${destzone}" ;
my $ chainref = ensure_filter_chain $ chain , 1 ;
#
# Validate Policy
#
my $ policy = $ chainref - > { policy } ;
fatal_error "No policy defined from $sourcezone to zone $destzone" unless $ policy ;
fatal_error "Rules may not override a NONE policy: rule \"$line\"" if $ policy eq 'NONE' ;
#
# Generate Fixed part of the rule
#
$ rule = do_proto $ proto , $ ports , $ sports . do_ratelimit ( $ ratelimit ) . ( do_user $ user ) ;
#
# Generate NAT rule(s), if any
#
if ( $ actiontype & NATRULE ) {
my ( $ server , $ serverport , $ natchain ) ;
fatal_error "$target rules not allowed in the $section SECTION" if $ section ne 'NEW' ;
#
# Isolate server port
#
if ( $ dest =~ /^(.*)(:(\d+))$/ ) {
$ server = $ 1 ;
$ serverport = $ 3 ;
} else {
$ server = $ dest ;
$ serverport = '' ;
}
#
# After DNAT, dest port will be the server port
#
$ ports = $ serverport if $ serverport ;
fatal_error "A server must be specified in the DEST column in $action rules: \"$line\"" unless ( $ actiontype & REDIRECT ) || $ server ;
fatal_error "Invalid server ($server), rule: \"$line\"" if $ server =~ /:/ ;
#
# Generate the target
#
my $ target = '' ;
2007-03-23 23:47:21 +01:00
if ( $ actiontype & REDIRECT ) {
$ target = '-j REDIRECT --to-port ' . ( $ serverport ? $ serverport : $ ports ) ;
} else {
if ( $ action eq 'SAME' ) {
fatal_error 'Port mapping not allowed in SAME rules' if $ serverport ;
$ target = '-j SAME ' ;
for my $ serv ( split /,/ , $ server ) {
$ target . = "--to $serv " ;
}
2007-03-15 04:17:56 +01:00
2007-03-23 23:47:21 +01:00
$ serverport = $ ports ;
} elsif ( $ action eq ' -j DNAT' ) {
$ serverport = ":$serverport" if $ serverport ;
for my $ serv ( split /,/ , $ server ) {
$ target . = "--to ${serv}${serverport} " ;
}
2007-03-15 04:17:56 +01:00
}
2007-03-23 23:47:21 +01:00
unless ( $ origdest && $ origdest ne '-' && $ origdest ne 'detect' ) {
if ( $ config { DETECT_DNAT_IPADDRS } ) {
my $ interfacesref = $ zones { $ sourcezone } { interfaces } ;
my @ interfaces = keys %$ interfacesref ;
$ origdest = @ interfaces ? "detect:@interfaces" : ALLIPv4 ;
} else {
$ origdest = ALLIPv4 ;
}
2007-03-23 02:37:23 +01:00
}
}
2007-03-15 04:17:56 +01:00
#
# And generate the nat table rule(s)
#
expand_rule
2007-03-23 17:12:36 +01:00
ensure_chain ( 'nat' , $ zones { $ sourcezone } { type } eq 'firewall' ? 'OUTPUT' : dnat_chain $ sourcezone ) ,
2007-03-18 19:50:34 +01:00
PREROUTE_RESTRICT ,
2007-03-15 04:17:56 +01:00
$ rule ,
$ source ,
$ origdest ,
'' ,
$ target ,
$ loglevel ,
$ action ,
$ serverport ? do_proto ( $ proto , '' , '' ) : '' ;
#
# After NAT, the destination port will be the server port; Also, we log NAT rules in the nat table rather than in the filter table.
#
unless ( $ actiontype & NATONLY ) {
$ rule = do_proto $ proto , $ ports , $ sports . do_ratelimit ( $ ratelimit ) . do_user $ user ;
$ loglevel = '' ;
}
2007-03-23 02:37:23 +01:00
} else {
if ( $ actiontype & NONAT ) {
#
# NONAT or ACCEPT+ -- May not specify a destination interface
#
fatal_error "Invalid DEST ($dest) in $action rule \"$line\"" if $ dest =~ /:/ ;
2007-03-23 17:12:36 +01:00
$ origdest = '' unless $ origdest and $ origdest ne '-' ;
if ( $ origdest eq 'detect' ) {
my $ interfacesref = $ zones { $ sourcezone } { interfaces } ;
my $ interfaces = "@$interfacesref" ;
$ origdest = $ interfaces ? "detect:$interfaces" : ALLIPv4 ;
}
2007-03-23 02:37:23 +01:00
expand_rule
ensure_chain ( 'nat' , $ zones { $ sourcezone } { type } eq 'firewall' ? 'OUTPUT' : dnat_chain $ sourcezone ) ,
PREROUTE_RESTRICT ,
$ rule ,
$ source ,
$ dest ,
2007-03-23 17:12:36 +01:00
$ origdest ,
2007-03-23 02:37:23 +01:00
'-j RETURN ' ,
$ loglevel ,
$ action ,
'' ;
}
2007-03-15 04:17:56 +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 = '' ;
}
expand_rule
ensure_chain ( 'filter' , $ chain ) ,
2007-03-25 21:43:33 +02:00
$ restriction ,
2007-03-15 04:17:56 +01:00
$ rule ,
$ source ,
$ dest ,
$ origdest ,
"-j $action " ,
$ loglevel ,
$ action ,
'' ;
}
}
#
# Process a Record in the rules file
#
sub process_rule ( $$$$$$$$$ ) {
my ( $ target , $ source , $ dest , $ proto , $ ports , $ sports , $ origdest , $ ratelimit , $ user ) = @ _ ;
my $ intrazone = 0 ;
my $ includesrcfw = 1 ;
my $ includedstfw = 1 ;
my $ optimize = $ config { OPTIMIZE } ;
my $ thisline = $ line ;
#
# 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' ;
$ section = 'NEW' ;
$ 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 ;
}
}
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 ( $ source eq 'all-' ) {
$ dest = 'all' ;
$ includedstfw = 0 ;
}
}
my $ action = isolate_basic_target $ target ;
$ optimize = 0 if $ action =~ /!^/ ;
if ( $ source eq 'all' ) {
for my $ zone ( @ zones ) {
if ( $ includesrcfw || ( $ zones { $ zone } { type } ne 'firewall' ) ) {
if ( $ dest eq 'all' ) {
for my $ zone1 ( @ zones ) {
if ( $ includedstfw || ( $ zones { $ zone1 } { type } ne 'firewall' ) ) {
if ( $ intrazone || ( $ zone ne $ zone1 ) ) {
my $ policychainref = $ filter_table - > { "${zone}2${zone1}" } { policychain } ;
fatal_error "No policy from zone $zone to zone $zone1" unless $ policychainref ;
if ( ( ( my $ policy ) = $ policychainref - > { policy } ) ne 'NONE' ) {
if ( $ optimize > 0 ) {
my $ loglevel = $ policychainref - > { loglevel } ;
if ( $ loglevel ) {
next if $ target eq "${policy}:$loglevel}" ;
} else {
next if $ action eq $ policy ;
}
}
process_rule1 $ target , $ zone , $ zone1 , $ proto , $ ports , $ sports , $ origdest , $ ratelimit , $ user ;
}
}
}
}
} else {
process_rule1 $ target , $ zone , $ dest , $ proto , $ ports , $ sports , $ origdest , $ ratelimit , $ user ;
}
}
}
} elsif ( $ dest eq 'all' ) {
for my $ zone1 ( @ zones ) {
my $ zone = ( split /:/ , $ source ) [ 0 ] ;
if ( ( $ includedstfw || ( $ zones { $ zone1 } { type } ne 'firewall' ) ) && ( ( $ zone ne $ zone1 ) || $ intrazone ) ) {
process_rule1 $ target , $ source , $ zone1 , $ proto , $ ports , $ sports , $ origdest , $ ratelimit , $ user ;
}
}
} else {
process_rule1 $ target , $ source , $ dest , $ proto , $ ports , $ sports , $ origdest , $ ratelimit , $ user ;
}
progress_message " Rule \"$thisline\" $done" ;
}
#
# Process the Rules File
#
sub process_rules () {
open RULES , "$ENV{TMP_DIR}/rules" or fatal_error "Unable to open stripped rules file: $!" ;
while ( $ line = <RULES> ) {
2007-03-25 18:38:00 +02:00
my ( $ target , $ source , $ dest , $ proto , $ ports , $ sports , $ origdest , $ ratelimit , $ user ) = split_line 9 , 'rules file' ;
2007-03-15 04:17:56 +01:00
if ( $ target eq 'COMMENT' ) {
if ( $ capabilities { COMMENTS } ) {
( $ comment = $ line ) =~ s/^\s*COMMENT\s*// ;
$ comment =~ s/\s*$// ;
} else {
warning_message "COMMENT ignored -- requires comment support in iptables/Netfilter" ;
}
} elsif ( $ target eq 'SECTION' ) {
fatal_error "Invalid SECTION $source" unless defined $ sections { $ source } ;
fatal_error "Duplicate or out of order SECTION $source" if $ sections { $ source } ;
2007-03-25 18:38:00 +02:00
fatal_error "Invalid Section $source $dest" if $ dest && $ dest ne '-' ;
2007-03-15 04:17:56 +01:00
$ sectioned = 1 ;
$ sections { $ source } = 1 ;
if ( $ section eq 'RELATED' ) {
$ sections { ESTABLISHED } = 1 ;
finish_section 'ESTABLISHED' ;
} elsif ( $ section eq 'NEW' ) {
@ sections { 'ESTABLISHED' , 'RELATED' } = ( 1 , 1 ) ;
finish_section ( ( $ section eq 'RELATED' ) ? 'RELATED' : 'ESTABLISHED,RELATED' ) ;
}
$ section = $ source ;
} else {
process_rule $ target , $ source , $ dest , $ proto , $ ports , $ sports , $ origdest , $ ratelimit , $ user ;
}
}
close RULES ;
$ comment = '' ;
$ section = 'DONE' ;
}
2007-03-15 04:25:08 +01:00
#
# To quote an old comment, generate_matrix makes a sows 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 functions and the rules that it generates.
#
# The function traverses the full "source-zone X destination-zone" matrix and generates the rules necessary to direct traffic through the right set of rules.
#
sub generate_matrix () {
#
# Helper functions for generate_matrix()
#-----------------------------------------
#
# Return the target for rules from the $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 ;
if ( $ chainref - > { policy } ne 'CONTINUE' ) {
my $ policyref = $ chainref - > { policychain } ;
return $ policyref - > { name } if $ policyref ;
fatal_error "No policy defined for zone $zone to zone $zone1" ;
}
'' ;
}
#
# Add a jump to the passed chain ($chainref) to the dynamic zone chain for the passed zone.
#
sub create_zone_dyn_chain ( $$ ) {
my ( $ zone , $ chainref ) = @ _ ;
my $ name = "${zone}_dyn" ;
new_standard_chain $ name ;
add_rule $ chainref , "-j $name" ;
}
#
# Insert the passed exclusions at the front of the passed chain.
#
sub insert_exclusions ( $$ ) {
my ( $ chainref , $ exclusionsref ) = @ _ ;
my $ num = 1 ;
for my $ host ( @ { $ exclusionsref } ) {
my ( $ interface , $ net ) = split /:/ , $ host ;
insert_rule $ chainref , $ num + + , "-i $interface " . match_source_net ( $ host ) . '-j RETURN' ;
}
}
#
# Add the passed exclusions at the end of the passed chain.
#
sub add_exclusions ( $$ ) {
my ( $ chainref , $ exclusionsref ) = @ _ ;
for my $ host ( @ { $ exclusionsref } ) {
my ( $ interface , $ net ) = split /:/ , $ host ;
add_rule $ chainref , "-i $interface " . match_source_net ( $ host ) . '-j RETURN' ;
}
}
#
# Generate_Matrix() Starts Here
#
my $ prerouting_rule = 1 ;
my $ postrouting_rule = 1 ;
my $ exclusion_seq = 1 ;
my % chain_exclusions ;
my % policy_exclusions ;
for my $ interface ( @ interfaces ) {
addnatjump 'POSTROUTING' , snat_chain ( $ interface ) , "-o $interface " ;
}
if ( $ config { DYNAMIC_ZONES } ) {
for my $ interface ( @ interfaces ) {
addnatjump 'PREROUTING' , dynamic_in ( $ interface ) , "-i $interface " ;
}
}
addnatjump 'PREROUTING' , 'nat_in' , '' ;
addnatjump 'POSTROUTING' , 'nat_out' , '' ;
for my $ interface ( @ interfaces ) {
addnatjump 'PREROUTING' , input_chain ( $ interface ) , "-i $interface " ;
addnatjump 'POSTROUTING' , output_chain ( $ interface ) , "-o $interface " ;
}
for my $ zone ( grep $ zones { $ _ } { options } { complex } , @ zones ) {
my $ frwd_ref = new_standard_chain "${zone}_frwd" ;
my $ zoneref = $ zones { $ zone } ;
my $ exclusions = $ zoneref - > { exclusions } ;
if ( @$ exclusions ) {
my $ num = 1 ;
my $ in_ref = new_standard_chain "${zone}_input" ;
my $ out_ref = new_standard_chain "${zone}_output" ;
add_rule ensure_filter_chain ( "${zone}2${zone}" , 1 ) , '-j ACCEPT' if rules_target $ zone , $ zone eq 'ACCEPT' ;
for my $ host ( @$ exclusions ) {
my ( $ interface , $ net ) = split /:/ , $ host ;
add_rule $ frwd_ref , "-i $interface -s $net -j RETURN" ;
add_rule $ in_ref , "-i $interface -s $net -j RETURN" ;
add_rule $ out_ref , "-i $interface -s $net -j RETURN" ;
}
if ( $ capabilities { POLICY_MATCH } ) {
my $ type = $ zoneref - > { type } ;
my $ source_ref = $ zoneref - > { hosts } { ipsec } || [] ;
create_zone_dyn_chain $ zone , $ frwd_ref && $ config { DYNAMIC_ZONES } && ( @$ source_ref || $ type ne 'ipsec4' ) ;
2007-03-19 05:28:47 +01:00
for my $ interface ( keys %$ source_ref ) {
my $ arrayref = $ source_ref - > { $ interface } ;
2007-03-15 04:25:08 +01:00
for my $ hostref ( @ { $ arrayref } ) {
my $ ipsec_match = match_ipsec_in $ zone , $ hostref ;
for my $ net ( @ { $ hostref - > { hosts } } ) {
add_rule
find_chainref ( 'filter' , forward_chain $ interface ) ,
match_source_net $ net . $ ipsec_match . "-j $frwd_ref->n{name}" ;
}
}
}
}
}
}
#
# Main source-zone matrix-generation loop
#
for my $ zone ( grep ( $ zones { $ _ } { type } ne 'firewall' , @ zones ) ) {
my $ zoneref = $ zones { $ zone } ;
my $ source_hosts_ref = $ zoneref - > { hosts } ;
my $ chain1 = rules_target $ firewall_zone , $ zone ;
my $ chain2 = rules_target $ zone , $ firewall_zone ;
my $ complex = $ zoneref - > { options } { complex } || 0 ;
my $ type = $ zoneref - > { type } ;
my $ exclusions = $ zoneref - > { exclusions } ;
my $ need_broadcast = { } ; ### Fixme ###
my $ frwd_ref = 0 ;
my $ chain = 0 ;
if ( $ complex ) {
$ frwd_ref = $ filter_table - > { "${zone}_frwd" } ;
my $ dnat_ref = ensure_chain 'nat' , dnat_chain ( $ zone ) ;
if ( @$ exclusions ) {
insert_exclusions $ dnat_ref , $ exclusions if $ dnat_ref - > { referenced } ;
}
}
2007-03-21 21:08:05 +01:00
if ( $ config { DYNAMIC_ZONES } ) {
push @ rule_chains , [ $ firewall_zone , $ zone , $ chain1 ] ;
push @ rule_chains , [ $ zone , $ firewall_zone , $ chain2 ] ;
}
2007-03-15 04:25:08 +01:00
#
# Take care of PREROUTING, INPUT and OUTPUT jumps
#
for my $ typeref ( values %$ source_hosts_ref ) {
2007-03-19 05:28:47 +01:00
for my $ interface ( keys %$ typeref ) {
my $ arrayref = $ typeref - > { $ interface } ;
2007-03-15 04:25:08 +01:00
for my $ hostref ( @$ arrayref ) {
my $ ipsec_in_match = match_ipsec_in $ zone , $ hostref ;
my $ ipsec_out_match = match_ipsec_out $ zone , $ hostref ;
for my $ net ( @ { $ hostref - > { hosts } } ) {
my $ source = match_source_net $ net ;
my $ dest = match_dest_net $ net ;
if ( $ chain1 ) {
if ( @$ exclusions ) {
add_rule $ filter_table - > { output_chain $ interface } , $ dest . $ ipsec_out_match . "-j ${zone}_output" ;
add_rule $ filter_table - > { "${zone}_output" } , "-j $chain1" ;
} else {
add_rule $ filter_table - > { output_chain $ interface } , $ dest . $ ipsec_out_match . "-j $chain1" ;
}
}
insertnatjump 'PREROUTING' , dnat_chain $ zone , \ $ prerouting_rule , ( "-i $interface " . $ source . $ ipsec_in_match ) ;
if ( $ chain2 ) {
if ( @$ exclusions ) {
add_rule $ filter_table - > { input_chain $ interface } , $ source . $ ipsec_in_match . "-j ${zone}_input" ;
add_rule $ filter_table - > { "${zone}_input" } , "-j $chain2" ;
} else {
add_rule $ filter_table - > { input_chain $ interface } , $ source . $ ipsec_in_match . "-j $chain2" ;
}
}
add_rule $ filter_table - > { forward_chain $ interface } , $ source . $ ipsec_in_match . "-j $frwd_ref->{name}"
if $ complex && $ hostref - > { ipsec } ne 'ipsec' ;
}
}
}
}
#
# 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 ( grep $ zones { $ _ } { type } ne 'firewall' , @ zones ) {
my $ zone1ref = $ zones { $ 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 ) {
#
# One thing that the Llama fails to mention is that evaluating a hash in a numeric context produces a warning.
#
no warnings ;
2007-03-20 15:06:00 +01:00
next if ( % { $ zoneref - > { interfaces } } < 2 ) && ! ( $ zoneref - > { options } { in_out } { routeback } || @$ exclusions ) ;
2007-03-15 04:25:08 +01:00
}
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 = grep $ zones { $ _ } { type } ne 'firewall' , @ 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 = $ zones { $ zone1 } ;
my $ policy = $ filter_table - > { "${zone}2${zone1}" } - > { policy } ;
next if $ policy eq 'NONE' ;
my $ chain = rules_target $ zone , $ zone1 ;
next unless $ chain ;
2007-03-21 21:08:05 +01:00
push @ rule_chains , [ $ zone , $ zone1 , $ chain ] if $ config { DYNAMIC_ZONES } ;
2007-03-15 04:25:08 +01:00
my $ num_ifaces = 0 ;
if ( $ zone eq $ zone1 ) {
#
# One thing that the Llama fails to mention is that evaluating a hash in a numeric context produces a warning.
#
no warnings ;
2007-03-20 15:06:00 +01:00
next ZONE1 if ( $ num_ifaces = % { $ zoneref - > { interfaces } } ) < 2 && ! ( $ zoneref - > { options } { in_out } { routeback } || @$ exclusions ) ;
2007-03-15 04:25:08 +01:00
}
my $ chainref = $ filter_table - > { $ chain } ;
my $ exclusions1 = $ zone1ref - > { exclusions } ;
my $ dest_hosts_ref = $ zone1ref - > { hosts } ;
if ( @$ exclusions1 ) {
if ( $ chain eq "all2$zone1" ) {
unless ( $ chain_exclusions { $ chain } ) {
$ chain_exclusions { $ chain } = 1 ;
insert_exclusions $ chainref , $ exclusions1 ;
}
} elsif ( $ chain =~ /2all$/ ) {
my $ chain1 = $ policy_exclusions { "${chain}_${zone1}" } ;
unless ( $ chain ) {
$ chain1 = newexclusionchain ;
$ policy_exclusions { "${chain}_${zone1}" } = $ chain1 ;
my $ chain1ref = ensure_filter_chain $ chain1 , 0 ;
add_exclusions $ chain1ref , $ exclusions1 ;
add_rule $ chain1ref , "-j $chain" ;
}
$ chain = $ chain1 ;
} else {
insert_exclusions $ chainref , $ exclusions1 ;
}
}
if ( $ complex ) {
for my $ typeref ( values %$ dest_hosts_ref ) {
2007-03-19 05:28:47 +01:00
for my $ interface ( keys %$ typeref ) {
my $ arrayref = $ typeref - > { $ interface } ;
2007-03-15 04:25:08 +01:00
for my $ hostref ( @$ arrayref ) {
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_rule $ frwd_ref , "-o $interface " . match_dest_net ( $ net ) . $ ipsec_out_match . "-j $chain" ;
}
}
}
}
}
} else {
for my $ typeref ( values %$ source_hosts_ref ) {
2007-03-19 05:28:47 +01:00
for my $ interface ( keys %$ typeref ) {
my $ arrayref = $ typeref - > { $ interface } ;
2007-03-15 04:25:08 +01:00
my $ chain3ref = $ filter_table - > { forward_chain $ interface } ;
for my $ hostref ( @$ arrayref ) {
for my $ net ( @ { $ hostref - > { hosts } } ) {
my $ source_match = match_source_net $ net ;
for my $ type1ref ( values %$ dest_hosts_ref ) {
2007-03-19 05:28:47 +01:00
for my $ interface1 ( keys %$ type1ref ) {
my $ array1ref = $ type1ref - > { $ interface1 } ;
2007-03-15 04:25:08 +01:00
for my $ host1ref ( @$ array1ref ) {
my $ ipsec_out_match = match_ipsec_out $ zone1 , $ host1ref ;
for my $ net1 ( @ { $ host1ref - > { hosts } } ) {
unless ( $ interface eq $ interface1 && $ net eq $ net1 && ! $ host1ref - > { options } { routeback } ) {
add_rule $ chain3ref , "-o $interface1 " . $ source_match . match_dest_net ( $ net1 ) . $ ipsec_out_match . "-j $chain" ;
}
}
}
}
}
}
}
}
}
}
#
# E N D F O R W A R D I N G
#
# Now add (an) unconditional jump(s) to the last unique policy-only chain determined above, if any
#
if ( $ last_chain ) {
if ( $ complex ) {
add_rule $ frwd_ref , "-j $last_chain" ;
} else {
for my $ typeref ( values %$ source_hosts_ref ) {
2007-03-19 05:28:47 +01:00
for my $ interface ( keys %$ typeref ) {
my $ arrayref = $ typeref - > { $ interface } ;
2007-03-15 04:25:08 +01:00
my $ chain2ref = $ filter_table - > { forward_chain $ interface } ;
for my $ hostref ( @$ arrayref ) {
for my $ net ( @ { $ hostref - > { hosts } } ) {
add_rule $ chain2ref , match_source_net ( $ net ) . "-j $last_chain" ;
}
}
}
}
}
}
}
}
#
# Now add the jumps to the interface chains from FORWARD, INPUT, OUTPUT and POSTROUTING
#
for my $ interface ( @ interfaces ) {
add_rule $ filter_table - > { FORWARD } , "-i $interface -j " . forward_chain $ interface ;
add_rule $ filter_table - > { INPUT } , "-i $interface -j " . input_chain $ interface ;
add_rule $ filter_table - > { OUTPUT } , "-o $interface -j " . output_chain $ interface ;
addnatjump 'POSTROUTING' , masq_chain ( $ interface ) , "-o $interface " ;
}
my $ chainref = $ filter_table - > { "${firewall_zone}2${firewall_zone}" } ;
add_rule $ filter_table - > { OUTPUT } , "-o lo -j " . ( $ chainref - > { referenced } ? "$chainref->{name}" : 'ACCEPT' ) ;
add_rule $ filter_table - > { INPUT } , '-i lo -j ACCEPT' ;
complete_standard_chain $ filter_table - > { INPUT } , 'all' , $ firewall_zone ;
complete_standard_chain $ filter_table - > { OUTPUT } , $ firewall_zone , 'all' ;
complete_standard_chain $ filter_table - > { FORWARD } , 'all' , 'all' ;
my % builtins = ( mangle = > [ qw/PREROUTING INPUT FORWARD POSTROUTING/ ] ,
nat = > [ qw/PREROUTING OUTPUT POSTROUTING/ ] ,
filter = > [ qw/INPUT FORWARD OUTPUT/ ] ) ;
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' ;
}
}
}
}
2007-03-17 00:57:43 +01:00
sub setup_mss ( $ ) {
my $ clampmss = $ _ [ 0 ] ;
my $ option = "\Lclampmss" eq 'yes' ? '--clamp-mss-to-pmtu' : '--set-mss $clampmss' ;
add_rule $ filter_table - > { FORWARD } , "-p tcp --tcp-flags SYN,RST SYN -j TCPMSS $option" ;
}
2007-03-21 21:35:40 +01:00
sub dump_rule_chains () {
for my $ arrayref ( @ rule_chains ) {
emit_unindented "@$arrayref" ;
}
}
2007-03-15 04:17:56 +01:00
1 ;