2008-12-08 19:45:58 +01:00
#
# Shorewall-perl 4.2 -- /usr/share/shorewall-perl/Shorewall/Chains.pm
#
# This program is under GPL [http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt]
#
# (c) 2007,2008 - 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# This is the low-level iptables module. It provides the basic services
# of chain and rule creation. It is used by the higher level modules such
# as Rules to create iptables-restore input.
#
package Shorewall::Chains ;
require Exporter ;
use Scalar::Util 'reftype' ;
use Shorewall::Config qw( :DEFAULT :internal ) ;
use Shorewall::Zones ;
use Shorewall::IPAddrs ;
use strict ;
our @ ISA = qw( Exporter ) ;
2008-12-08 21:48:02 +01:00
our @ EXPORT = qw(
2008-12-08 19:45:58 +01:00
add_rule
add_jump
insert_rule
new_chain
new_manual_chain
ensure_manual_chain
log_rule_limit
% chain_table
$ nat_table
$ mangle_table
$ filter_table
) ;
our % EXPORT_TAGS = (
internal = > [ qw( STANDARD
NATRULE
BUILTIN
NONAT
NATONLY
REDIRECT
ACTION
MACRO
LOGRULE
NFQ
CHAIN
NO_RESTRICT
PREROUTE_RESTRICT
INPUT_RESTRICT
OUTPUT_RESTRICT
POSTROUTE_RESTRICT
ALL_RESTRICT
2008-12-08 21:48:02 +01:00
chain_family
2008-12-08 19:45:58 +01:00
add_command
add_commands
move_rules
process_comment
no_comment
macro_comment
clear_comment
incr_cmd_level
decr_cmd_level
chain_base
forward_chain
zone_forward_chain
use_forward_chain
input_chain
zone_input_chain
use_input_chain
output_chain
zone_output_chain
use_output_chain
masq_chain
syn_flood_chain
mac_chain
macrecent_target
dnat_chain
snat_chain
ecn_chain
first_chains
ensure_chain
ensure_accounting_chain
ensure_mangle_chain
ensure_nat_chain
new_standard_chain
new_builtin_chain
new_nat_chain
ensure_filter_chain
initialize_chain_table
finish_section
setup_zone_mss
newexclusionchain
clearrule
do_proto
mac_match
verify_mark
verify_small_mark
validate_mark
do_test
do_ratelimit
do_connlimit
do_time
do_user
do_length
do_tos
do_connbytes
do_helper
match_source_dev
match_dest_dev
iprange_match
match_source_net
match_dest_net
match_orig_dest
match_ipsec_in
match_ipsec_out
log_rule
expand_rule
addnatjump
get_interface_address
get_interface_addresses
get_interface_bcasts
2008-12-12 22:31:31 +01:00
get_interface_acasts
2008-12-08 19:45:58 +01:00
get_interface_gateway
get_interface_mac
set_global_variables
create_netfilter_load
create_chainlist_reload
$ section
% sections
% targets
) ] ,
) ;
Exporter:: export_ok_tags ( 'internal' ) ;
2008-12-08 22:49:50 +01:00
our $ VERSION = 4.3 .0 ;
2008-12-08 19:45:58 +01:00
#
# Chain Table
#
# %chain_table { <table> => { <chain1> => { name => <chain name>
# table => <table name>
# is_policy => undef|1 -- if 1, this is a policy chain
# is_optional => undef|1 -- See below.
# referenced => undef|1 -- If 1, will be written to the iptables-restore-input.
# builtin => undef|1 -- If 1, one of Netfilter's built-in chains.
# manual => undef|1 -- If 1, a manual chain.
# accounting => undef|1 -- If 1, an accounting chain
# log => <logging rule number for use when LOGRULENUMBERS>
# policy => <policy>
# policychain => <name of policy chain> -- self-reference if this is a policy chain
# policypair => [ <policy source>, <policy dest> ] -- Used for reporting duplicated policies
# loglevel => <level>
# synparams => <burst/limit + connlimit>
# synchain => <name of synparam chain>
# default => <default action>
# cmdlevel => <number of open loops or blocks in runtime commands>
# rules => [ <rule1>
# <rule2>
# ...
# ]
# } ,
# <chain2> => ...
# }
# }
#
# 'is_optional' only applies to policy chains; when true, indicates that this is a provisional policy chain which might be
# replaced. Policy chains created under the IMPLICIT_CONTINUE=Yes option are marked with is_optional == 1.
#
# Only 'referenced' chains get written to the iptables-restore input.
#
# 'loglevel', 'synparams', 'synchain' and 'default' only apply to policy chains.
#
our % chain_table ;
our $ nat_table ;
our $ mangle_table ;
our $ filter_table ;
#
# It is a layer violation to keep information about the rules file sections in this module but in Shorewall, the rules file
# and the filter table are very closely tied. By keeping the information here, we avoid making several other modules dependent
# on Shorewall::Rules.
#
our % sections ;
our $ section ;
our $ comment ;
use constant { STANDARD = > 1 , #defined by Netfilter
NATRULE = > 2 , #Involves NAT
BUILTIN = > 4 , #A built-in action
NONAT = > 8 , #'NONAT' or 'ACCEPT+'
NATONLY = > 16 , #'DNAT-' or 'REDIRECT-'
REDIRECT = > 32 , #'REDIRECT'
ACTION = > 64 , #An action (may be built-in)
MACRO = > 128 , #A Macro
LOGRULE = > 256 , #'LOG'
NFQ = > 512 , #'NFQUEUE'
CHAIN = > 1024 , #Manual Chain
} ;
our % targets ;
#
# expand_rule() restrictions
#
use constant { NO_RESTRICT = > 0 , # FORWARD chain rule - Both -i and -o may be used in the rule
PREROUTE_RESTRICT = > 1 , # PREROUTING chain rule - -o converted to -d <address list> using main routing table
INPUT_RESTRICT = > 4 , # INPUT chain rule - -o not allowed
OUTPUT_RESTRICT = > 8 , # OUTPUT chain rule - -i not allowed
POSTROUTE_RESTRICT = > 16 , # POSTROUTING chain rule - -i converted to -s <address list> using main routing table
ALL_RESTRICT = > 12 # fw->fw rule - neither -i nor -o allowed
} ;
our $ exclseq ;
our $ iprangematch ;
our $ chainseq ;
our % interfaceaddr ;
our % interfaceaddrs ;
our % interfacenets ;
our % interfacemacs ;
our % interfacebcasts ;
2008-12-12 22:31:31 +01:00
our % interfaceacasts ;
2008-12-08 19:45:58 +01:00
our % interfacegateways ;
our @ builtins = qw( PREROUTING INPUT FORWARD OUTPUT POSTROUTING ) ;
#
# Mode of the generator.
#
use constant { NULL_MODE = > 0 , # Generating neither shell commands nor iptables-restore input
CAT_MODE = > 1 , # Generating iptables-restore input
CMD_MODE = > 2 } ; # Generating shell commands.
our $ mode ;
2008-12-08 21:48:02 +01:00
our $ family ;
2008-12-08 19:45:58 +01:00
#
# 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.
#
2008-12-08 21:48:02 +01:00
sub initialize ( $ ) {
2008-12-08 19:45:58 +01:00
% chain_table = ( raw = > { } ,
mangle = > { } ,
nat = > { } ,
filter = > { } ) ;
$ nat_table = $ chain_table { nat } ;
$ mangle_table = $ chain_table { mangle } ;
$ filter_table = $ chain_table { filter } ;
#
# These get set to 1 as sections are encountered.
#
% sections = ( ESTABLISHED = > 0 ,
RELATED = > 0 ,
NEW = > 0
) ;
#
# Current rules file section.
#
$ section = 'ESTABLISHED' ;
#
# Contents of last COMMENT line.
#
$ comment = '' ;
#
# Used to sequence 'exclusion' chains with names 'excl0', 'excl1', ...
#
$ exclseq = 0 ;
#
# Used to suppress duplicate match specifications.
#
$ iprangematch = 0 ;
#
# Sequence for naming temporary chains
#
$ chainseq = undef ;
#
# Keep track of which interfaces have active 'address', 'addresses', 'networks', etc. variables
#
% interfaceaddr = ( ) ;
% interfaceaddrs = ( ) ;
% interfacenets = ( ) ;
% interfacemacs = ( ) ;
% interfacebcasts = ( ) ;
2008-12-12 22:31:31 +01:00
% interfaceacasts = ( ) ;
2008-12-08 19:45:58 +01:00
% interfacegateways = ( ) ;
2008-12-08 21:48:02 +01:00
$ family = shift ;
2008-12-08 19:45:58 +01:00
}
INIT {
2008-12-08 21:48:02 +01:00
initialize ( F_IPV4 ) ;
2008-12-08 19:45:58 +01:00
}
#
# Add a run-time command to a chain. Arguments are:
#
# Chain reference , Command
#
#
# Process a COMMENT line (in $currentline)
#
sub process_comment () {
if ( $ capabilities { COMMENTS } ) {
( $ comment = $ currentline ) =~ s/^\s*COMMENT\s*// ;
$ comment =~ s/\s*$// ;
} else {
warning_message "COMMENT ignored -- requires comment support in iptables/Netfilter" ;
}
}
#
# Returns True if there is a current COMMENT or if COMMENTS are not available.
#
sub no_comment () {
$ comment ? 1 : $ capabilities { COMMENTS } ? 0 : 1 ;
}
#
# Clear the $comment variable
#
sub clear_comment () {
$ comment = '' ;
}
#
# Set $comment to the passed unless there is a current comment
#
sub macro_comment ( $ ) {
my $ macro = $ _ [ 0 ] ;
$ comment = $ macro unless $ comment || ! ( $ capabilities { COMMENTS } && $ config { AUTO_COMMENT } ) ;
}
#
# Functions to manipulate cmdlevel
#
sub incr_cmd_level ( $ ) {
$ _ [ 0 ] - > { cmdlevel } + + ;
}
sub decr_cmd_level ( $ ) {
fatal_error "Internal error in decr_cmd_level()" if - - $ _ [ 0 ] - > { cmdlevel } < 0 ;
}
sub add_command ($$)
{
my ( $ chainref , $ command ) = @ _ ;
push @ { $ chainref - > { rules } } , join ( '' , ' ' x $ chainref - > { cmdlevel } , $ command ) ;
$ chainref - > { referenced } = 1 ;
}
sub add_commands {
my $ chainref = shift @ _ ;
for my $ command ( @ _ ) {
push @ { $ chainref - > { rules } } , join ( '' , ' ' x $ chainref - > { cmdlevel } , $ command ) ;
}
$ chainref - > { referenced } = 1 ;
}
sub push_rule ( $$ ) {
my ( $ chainref , $ rule ) = @ _ ;
$ rule . = qq( -m comment --comment "$comment" ) if $ comment ;
if ( $ chainref - > { cmdlevel } ) {
$ rule =~ s/"/\\"/g ; #Must preserve quotes in the rule
add_command $ chainref , qq( echo "-A $chainref->{name} $rule" >&3 ) ;
} else {
#
# We omit the chain name for now -- this makes it easier to move rules from one
# chain to another
#
push @ { $ chainref - > { rules } } , join ( ' ' , '-A' , $ rule ) ;
$ chainref - > { referenced } = 1 ;
}
}
#
# Add a rule to a chain. Arguments are:
#
# Chain reference , Rule [, Expand-long-dest-port-lists ]
#
sub add_rule ( $ $ ; $ )
{
my ( $ chainref , $ rule , $ expandports ) = @ _ ;
fatal_error 'Internal Error in add_rule()' if reftype $ rule ;
$ iprangematch = 0 ;
#
# Pre-processing the port lists as was done in Shorewall-shell results in port-list
# processing driving the rest of rule generation.
#
# By post-processing each rule generated by expand_rule(), we avoid all of that
# messiness and replace it with the following localized messiness.
#
# Because source ports are seldom specified and source port lists are rarer still,
# we only worry about the destination ports.
#
if ( $ expandports && $ rule =~ /^(.* --dports\s+)([^ ]+)(.*)$/ ) {
#
# Rule has a --dports specification
#
my ( $ first , $ ports , $ rest ) = ( $ 1 , $ 2 , $ 3 ) ;
if ( ( $ ports =~ tr /:,/ : , / ) > 14 ) {
#
# More than 15 ports specified
#
my @ ports = split '([,:])' , $ ports ;
while ( @ ports ) {
my $ count = 0 ;
my $ newports = '' ;
while ( @ ports && $ count < 15 ) {
my ( $ port , $ separator ) = ( shift @ ports , shift @ ports ) ;
$ separator || = '' ;
if ( + + $ count == 15 ) {
if ( $ separator eq ':' ) {
unshift @ ports , $ port , ':' ;
chop $ newports ;
last ;
} else {
$ newports . = $ port ;
}
} else {
$ newports . = "${port}${separator}" ;
}
}
push_rule ( $ chainref , join ( '' , $ first , $ newports , $ rest ) ) ;
}
} else {
push_rule ( $ chainref , $ rule ) ;
}
} else {
push_rule ( $ chainref , $ rule ) ;
}
}
#
# Add a jump from the chain represented by the reference in the first argument to
# the target in the second argument. The optional third argument specifies any
# matches to be included in the rule and must end with a space character if it is non-null.
#
sub add_jump ( $$$;$ ) {
my ( $ fromref , $ to , $ goto_ok , $ predicate ) = @ _ ;
$ predicate |= '' ;
my $ toref ;
#
# The second argument may be a scalar (chain name or builtin target) or a chain reference
#
if ( reftype $ to ) {
$ toref = $ to ;
$ to = $ toref - > { name } ;
} else {
#
# Ensure that we have the chain unless it is a builtin like 'ACCEPT'
#
$ toref = ensure_chain ( $ fromref - > { table } , $ to ) unless ( $ targets { $ to } || 0 ) & STANDARD ;
}
#
# If the destination is a chain, mark it referenced
#
$ toref - > { referenced } = 1 if $ toref ;
my $ param = $ goto_ok && $ toref && $ capabilities { GOTO_TARGET } ? 'g' : 'j' ;
add_rule ( $ fromref , join ( '' , $ predicate , "-$param $to" ) ) ;
}
#
# Insert a rule into a chain. Arguments are:
#
# Chain reference , Rule Number, Rule
#
sub insert_rule ($$$)
{
my ( $ chainref , $ number , $ rule ) = @ _ ;
fatal_error 'Internal Error in insert_rule()' if $ chainref - > { cmdlevel } ;
$ rule . = "-m comment --comment \"$comment\"" if $ comment ;
splice ( @ { $ chainref - > { rules } } , $ number - 1 , 0 , join ( ' ' , '-A' , $ rule ) ) ;
$ iprangematch = 0 ;
$ chainref - > { referenced } = 1 ;
}
#
# Move the rules from one chain to another
#
# The rules generated by interface options are added to the interfaces's input chain and
# forward chain. Shorewall::Rules::generate_matrix() may decide to move those rules to
# a zone-oriented chain, hence this function.
#
# The source chain must not have any run-time code included in its rules.
#
sub move_rules ( $$ ) {
my ( $ chain1 , $ chain2 ) = @ _ ;
if ( $ chain1 - > { referenced } ) {
my @ rules = @ { $ chain1 - > { rules } } ;
2008-12-15 00:58:01 +01:00
for ( @ rules ) {
fatal_error "Internal Error in move_rules()" unless /^-A/ ;
2008-12-08 19:45:58 +01:00
}
2008-12-15 00:58:01 +01:00
splice @ { $ chain2 - > { rules } } , 0 , 0 , @ rules ;
2008-12-08 19:45:58 +01:00
2008-12-15 00:58:01 +01:00
$ chain2 - > { referenced } = 1 ;
$ chain1 - > { referenced } = 0 ;
$ chain1 - > { rules } = [] ;
2008-12-08 19:45:58 +01:00
}
}
#
# Change the passed interface name so it is a legal shell variable name.
#
sub chain_base ($) {
my $ chain = $ _ [ 0 ] ;
$ chain =~ s/^@/at_/ ;
$ chain =~ tr /[.\-%@]/ _ / ;
$ chain =~ s/\+$// ;
$ chain ;
}
#
# Forward Chain for an interface
#
sub forward_chain ($)
{
$ _ [ 0 ] . '_fwd' ;
}
#
# Forward Chain for a zone
#
sub zone_forward_chain ($) {
$ _ [ 0 ] . '_frwd' ;
}
#
# Returns true if we're to use the interface's forward chain
#
sub use_forward_chain ($) {
my $ interface = $ _ [ 0 ] ;
my $ interfaceref = find_interface ( $ interface ) ;
#
# We must use the interfaces's chain if the interface is associated with multiple zone nets
#
2008-12-15 00:58:01 +01:00
return 1 if $ interfaceref - > { nets } > 1 ;
my $ zone = $ interfaceref - > { zone } ;
#
# Interface associated with a single zone -- Must use the interface chain if
# the zone has multiple interfaces
#
2008-12-15 17:42:54 +01:00
$ interfaceref - > { options } { use_forward_chain } && keys % { zone_interfaces ( $ zone ) } > 1 ;
2008-12-08 19:45:58 +01:00
}
#
# Input Chain for an interface
#
sub input_chain ($)
{
$ _ [ 0 ] . '_in' ;
}
#
# Input Chain for a zone
#
sub zone_input_chain ($) {
$ _ [ 0 ] . '_input' ;
}
#
# Returns true if we're to use the interface's input chain
#
sub use_input_chain ($) {
my $ interface = $ _ [ 0 ] ;
my $ interfaceref = find_interface ( $ interface ) ;
my $ nets = $ interfaceref - > { nets } ;
#
# We must use the interfaces's chain if the interface is associated with multiple zone nets
#
return 1 if $ nets > 1 ;
#
# Don't need it if it isn't associated with any zone
#
return 0 unless $ nets ;
2008-12-15 00:58:01 +01:00
my $ zone = $ interfaceref - > { zone } ;
#
# Interface associated with a single zone -- Must use the interface chain if
# the zone has multiple interfaces
2008-12-15 17:42:54 +01:00
# and this chain has option rules
return 1 if $ interfaceref - > { options } { use_input_chain } && keys % { zone_interfaces ( $ zone ) } > 1 ;
2008-12-08 19:45:58 +01:00
#
# Interface associated with a single zone -- use the zone's input chain if it has one
#
2008-12-15 00:58:01 +01:00
my $ chainref = $ filter_table - > { zone_input_chain $ zone } ;
2008-12-08 19:45:58 +01:00
return 0 if $ chainref ;
#
# Use the '<zone>2fw' chain if it is referenced.
#
2008-12-15 01:37:09 +01:00
$ chainref = $ filter_table - > { join ( '' , $ zone , '2' , firewall_zone ) } ;
2008-12-08 19:45:58 +01:00
! ( $ chainref - > { referenced } || $ chainref - > { is_policy } )
}
#
# Output Chain for an interface
#
sub output_chain ($)
{
$ _ [ 0 ] . '_out' ;
}
#
# Output Chain for a zone
#
sub zone_output_chain ($) {
$ _ [ 0 ] . '_output' ;
}
#
# Returns true if we're to use the interface's output chain
#
sub use_output_chain ($) {
my $ interface = $ _ [ 0 ] ;
my $ interfaceref = find_interface ( $ interface ) ;
my $ nets = $ interfaceref - > { nets } ;
#
# We must use the interfaces's chain if the interface is associated with multiple zone nets
#
return 1 if $ nets > 1 ;
#
# Don't need it if it isn't associated with any zone
#
return 0 unless $ nets ;
#
# Interface associated with a single zone -- use the zone's output chain if it has one
#
2008-12-08 22:49:50 +01:00
my $ chainref = $ filter_table - > { zone_output_chain $ interfaceref - > { zone } } ;
2008-12-08 19:45:58 +01:00
return 0 if $ chainref ;
#
# Use the 'fw2<zone>' chain if it is referenced.
#
2008-12-08 22:49:50 +01:00
$ chainref = $ filter_table - > { join ( '' , firewall_zone , '2' , $ interfaceref - > { zone } ) } ;
2008-12-08 19:45:58 +01:00
! ( $ chainref - > { referenced } || $ chainref - > { is_policy } )
}
#
# Masquerade Chain for an interface
#
sub masq_chain ($)
{
$ _ [ 0 ] . '_masq' ;
}
#
# Syn_flood_chain -- differs from the other _chain functions in that the argument is a chain table reference
#
sub syn_flood_chain ( $ ) {
'@' . $ _ [ 0 ] - > { synchain } ;
}
#
# MAC Verification Chain for an interface
#
sub mac_chain ( $ )
{
$ _ [ 0 ] . '_mac' ;
}
sub macrecent_target ($)
{
$ config { MACLIST_TTL } ? $ _ [ 0 ] . '_rec' : 'RETURN' ;
}
#
# DNAT Chain from a zone
#
sub dnat_chain ( $ )
{
$ _ [ 0 ] . '_dnat' ;
}
#
# SNAT Chain to an interface
#
sub snat_chain ( $ )
{
$ _ [ 0 ] . '_snat' ;
}
#
# ECN Chain to an interface
#
sub ecn_chain ( $ )
{
$ _ [ 0 ] . '_ecn' ;
}
#
# First chains for an interface
#
sub first_chains ( $ ) # $ 1 = interface
{
my $ c = $ _ [ 0 ] ;
( $ c . '_fwd' , $ c . '_in' ) ;
}
#
# Create a new chain and return a reference to it.
#
sub new_chain ($$)
{
my ( $ table , $ chain ) = @ _ ;
fatal_error "Internal error in new_chain()" if $ chain_table { $ table } { $ chain } ;
$ chain_table { $ table } { $ chain } = { name = > $ chain ,
rules = > [] ,
table = > $ table ,
loglevel = > '' ,
log = > 1 ,
cmdlevel = > 0 } ;
}
#
# Create a chain if it doesn't exist already
#
sub ensure_chain ($$)
{
my ( $ table , $ chain ) = @ _ ;
fatal_error 'Internal Error in ensure_chain' unless $ table && $ chain ;
my $ ref = $ chain_table { $ table } { $ chain } ;
return $ ref if $ ref ;
new_chain $ table , $ chain ;
}
sub finish_chain_section ( $ $ ) ;
#
# Create a filter chain if necessary. Optionally populate it with the appropriate ESTABLISHED,RELATED rule(s) and perform SYN rate limiting.
#
sub ensure_filter_chain ( $ $ )
{
my ( $ chain , $ populate ) = @ _ ;
my $ chainref = $ filter_table - > { $ chain } ;
$ chainref = new_chain 'filter' , $ chain unless $ chainref ;
if ( $ populate and ! $ chainref - > { referenced } ) {
if ( $ section eq 'NEW' or $ section eq 'DONE' ) {
finish_chain_section $ chainref , 'ESTABLISHED,RELATED' ;
} elsif ( $ section eq 'RELATED' ) {
finish_chain_section $ chainref , 'ESTABLISHED' ;
}
}
$ chainref - > { referenced } = 1 ;
$ chainref ;
}
#
# Create an accounting chain if necessary.
#
sub ensure_accounting_chain ( $ )
{
my ( $ chain ) = @ _ ;
my $ chainref = $ filter_table - > { $ chain } ;
if ( $ chainref ) {
fatal_error "Non-accounting chain ($chain) used in accounting rule" if ! $ chainref - > { accounting } ;
} else {
$ chainref = new_chain 'filter' , $ chain unless $ chainref ;
$ chainref - > { accounting } = 1 ;
$ chainref - > { referenced } = 1 ;
}
$ chainref ;
}
sub ensure_mangle_chain ($) {
my $ chain = $ _ [ 0 ] ;
my $ chainref = ensure_chain 'mangle' , $ chain ;
$ chainref - > { referenced } = 1 ;
$ chainref ;
}
sub ensure_nat_chain ($) {
my $ chain = $ _ [ 0 ] ;
my $ chainref = ensure_chain 'nat' , $ chain ;
$ chainref - > { referenced } = 1 ;
$ chainref ;
}
#
# Add a builtin chain
#
sub new_builtin_chain ($$$)
{
my ( $ table , $ chain , $ policy ) = @ _ ;
my $ chainref = new_chain $ table , $ chain ;
$ chainref - > { referenced } = 1 ;
$ chainref - > { policy } = $ policy ;
$ chainref - > { builtin } = 1 ;
}
sub new_standard_chain ($) {
my $ chainref = new_chain 'filter' , $ _ [ 0 ] ;
$ chainref - > { referenced } = 1 ;
$ chainref ;
}
sub new_nat_chain ($) {
my $ chainref = new_chain 'nat' , $ _ [ 0 ] ;
$ chainref - > { referenced } = 1 ;
$ chainref ;
}
sub new_manual_chain ($) {
my $ chain = $ _ [ 0 ] ;
fatal_error "Duplicate Chain Name ($chain)" if $ targets { $ chain } || $ filter_table - > { $ chain } ;
$ targets { $ chain } = CHAIN ;
( my $ chainref = ensure_filter_chain ( $ chain , 0 ) ) - > { manual } = 1 ;
$ chainref - > { referenced } = 1 ;
$ chainref ;
}
sub ensure_manual_chain ($) {
my $ chain = $ _ [ 0 ] ;
my $ chainref = $ filter_table - > { $ chain } || new_manual_chain ( $ chain ) ;
fatal_error "$chain exists and is not a manual chain" unless $ chainref - > { manual } ;
$ chainref ;
}
#
# Add all builtin chains to the chain table
#
#
sub initialize_chain_table ()
{
2008-12-08 21:48:02 +01:00
if ( $ family == F_IPV4 ) {
#
# As new targets (Actions, Macros and Manual Chains) are discovered, they are added to the table
#
% targets = ( 'ACCEPT' = > STANDARD ,
'ACCEPT+' = > STANDARD + NONAT ,
'ACCEPT!' = > STANDARD ,
'NONAT' = > STANDARD + NONAT + NATONLY ,
'DROP' = > STANDARD ,
'DROP!' = > STANDARD ,
'REJECT' = > STANDARD ,
'REJECT!' = > STANDARD ,
'DNAT' = > NATRULE ,
'DNAT-' = > NATRULE + NATONLY ,
'REDIRECT' = > NATRULE + REDIRECT ,
'REDIRECT-' = > NATRULE + REDIRECT + NATONLY ,
'LOG' = > STANDARD + LOGRULE ,
'CONTINUE' = > STANDARD ,
'CONTINUE!' = > STANDARD ,
'QUEUE' = > STANDARD ,
'QUEUE!' = > STANDARD ,
'NFQUEUE' = > STANDARD + NFQ ,
'NFQUEUE!' = > STANDARD + NFQ ,
'SAME' = > NATRULE ,
'SAME-' = > NATRULE + NATONLY ,
'dropBcast' = > BUILTIN + ACTION ,
'allowBcast' = > BUILTIN + ACTION ,
'dropNotSyn' = > BUILTIN + ACTION ,
'rejNotSyn' = > BUILTIN + ACTION ,
'dropInvalid' = > BUILTIN + ACTION ,
'allowInvalid' = > BUILTIN + ACTION ,
'allowinUPnP' = > BUILTIN + ACTION ,
'forwardUPnP' = > BUILTIN + ACTION ,
'Limit' = > BUILTIN + ACTION ,
) ;
2008-12-08 19:45:58 +01:00
2008-12-08 21:48:02 +01:00
for my $ chain qw( OUTPUT PREROUTING ) {
new_builtin_chain 'raw' , $ chain , 'ACCEPT' ;
}
2008-12-08 19:45:58 +01:00
2008-12-08 21:48:02 +01:00
for my $ chain qw( INPUT OUTPUT FORWARD ) {
new_builtin_chain 'filter' , $ chain , 'DROP' ;
}
for my $ chain qw( PREROUTING POSTROUTING OUTPUT ) {
new_builtin_chain 'nat' , $ chain , 'ACCEPT' ;
}
2008-12-08 19:45:58 +01:00
2008-12-08 21:48:02 +01:00
for my $ chain qw( PREROUTING INPUT OUTPUT ) {
new_builtin_chain 'mangle' , $ chain , 'ACCEPT' ;
}
if ( $ capabilities { MANGLE_FORWARD } ) {
for my $ chain qw( FORWARD POSTROUTING ) {
new_builtin_chain 'mangle' , $ chain , 'ACCEPT' ;
}
}
} else {
#
# As new targets (Actions, Macros and Manual Chains) are discovered, they are added to the table
#
% targets = ( 'ACCEPT' = > STANDARD ,
'ACCEPT!' = > STANDARD ,
'DROP' = > STANDARD ,
'DROP!' = > STANDARD ,
'REJECT' = > STANDARD ,
'REJECT!' = > STANDARD ,
'LOG' = > STANDARD + LOGRULE ,
'CONTINUE' = > STANDARD ,
'CONTINUE!' = > STANDARD ,
'QUEUE' = > STANDARD ,
'QUEUE!' = > STANDARD ,
'NFQUEUE' = > STANDARD + NFQ ,
'NFQUEUE!' = > STANDARD + NFQ ,
2008-12-12 22:31:31 +01:00
'dropBcast' = > BUILTIN + ACTION ,
'allowBcast' = > BUILTIN + ACTION ,
2008-12-08 21:48:02 +01:00
'dropNotSyn' = > BUILTIN + ACTION ,
'rejNotSyn' = > BUILTIN + ACTION ,
'dropInvalid' = > BUILTIN + ACTION ,
'allowInvalid' = > BUILTIN + ACTION ,
) ;
2008-12-08 19:45:58 +01:00
2008-12-08 21:48:02 +01:00
for my $ chain qw( OUTPUT PREROUTING ) {
new_builtin_chain 'raw' , $ chain , 'ACCEPT' ;
}
for my $ chain qw( INPUT OUTPUT FORWARD ) {
new_builtin_chain 'filter' , $ chain , 'DROP' ;
}
for my $ chain qw( PREROUTING POSTROUTING OUTPUT ) {
new_builtin_chain 'nat' , $ chain , 'ACCEPT' ;
}
for my $ chain qw( PREROUTING INPUT OUTPUT FORWARD POSTROUTING ) {
2008-12-08 19:45:58 +01:00
new_builtin_chain 'mangle' , $ chain , 'ACCEPT' ;
}
}
}
#
# Add ESTABLISHED,RELATED rules and synparam jumps to the passed chain
#
sub finish_chain_section ($$) {
my ( $ chainref , $ state ) = @ _ ;
my $ chain = $ chainref - > { name } ;
my $ savecomment = $ comment ;
$ comment = '' ;
add_rule $ chainref , "-m state --state $state -j ACCEPT" unless $ config { FASTACCEPT } ;
if ( $ sections { NEW } ) {
if ( $ chainref - > { is_policy } ) {
if ( $ chainref - > { synparams } ) {
my $ synchainref = ensure_chain 'filter' , syn_flood_chain $ chainref ;
if ( $ section eq 'DONE' ) {
if ( $ chainref - > { policy } =~ /^(ACCEPT|CONTINUE|QUEUE|NFQUEUE)/ ) {
add_rule $ chainref , "-p tcp --syn -j $synchainref->{name}" ;
}
} else {
add_rule $ chainref , "-p tcp --syn -j $synchainref->{name}" ;
}
}
} else {
my $ policychainref = $ filter_table - > { $ chainref - > { policychain } } ;
if ( $ policychainref - > { synparams } ) {
my $ synchainref = ensure_chain 'filter' , syn_flood_chain $ policychainref ;
add_rule $ chainref , "-p tcp --syn -j $synchainref->{name}" ;
}
}
}
$ comment = $ savecomment ;
}
#
# Do section-end processing
#
sub finish_section ( $ ) {
my $ sections = $ _ [ 0 ] ;
for my $ section ( split /,/ , $ sections ) {
$ sections { $ section } = 1 ;
}
for my $ zone ( all_zones ) {
for my $ zone1 ( all_zones ) {
my $ chainref = $ chain_table { 'filter' } { "${zone}2${zone1}" } ;
if ( $ chainref - > { referenced } ) {
finish_chain_section $ chainref , $ sections ;
}
}
}
}
#
# Helper for set_mss
#
sub set_mss1 ( $$ ) {
my ( $ chain , $ mss ) = @ _ ;
my $ chainref = ensure_chain 'filter' , $ chain ;
if ( $ chainref - > { policy } ne 'NONE' ) {
my $ match = $ capabilities { TCPMSS_MATCH } ? "-m tcpmss --mss $mss: " : '' ;
insert_rule $ chainref , 1 , "-p tcp --tcp-flags SYN,RST SYN ${match}-j TCPMSS --set-mss $mss"
}
}
#
# Set up rules to set MSS to and/or from zone "$zone"
#
sub set_mss ( $$$ ) {
my ( $ zone , $ mss , $ direction ) = @ _ ;
for my $ z ( all_zones ) {
if ( $ direction eq '_in' ) {
set_mss1 "${zone}2${z}" , $ mss ;
} elsif ( $ direction eq '_out' ) {
set_mss1 "${z}2${zone}" , $ mss ;
} else {
set_mss1 "${z}2${zone}" , $ mss ;
set_mss1 "${zone}2${z}" , $ mss ;
}
}
}
#
# Interate over non-firewall zones and interfaces with 'mss=' setting adding TCPMSS rules as appropriate.
#
sub setup_zone_mss () {
for my $ zone ( all_zones ) {
my $ zoneref = find_zone ( $ zone ) ;
set_mss ( $ zone , $ zoneref - > { options } { in_out } { mss } , '' ) if $ zoneref - > { options } { in_out } { mss } ;
set_mss ( $ zone , $ zoneref - > { options } { in } { mss } , '_in' ) if $ zoneref - > { options } { in } { mss } ;
set_mss ( $ zone , $ zoneref - > { options } { out } { mss } , '_out' ) if $ zoneref - > { options } { out } { mss } ;
}
}
sub newexclusionchain () {
my $ seq = $ exclseq + + ;
"excl${seq}" ;
}
sub clearrule () {
$ iprangematch = 0 ;
}
#
# Handle parsing of PROTO, DEST PORT(S) , SOURCE PORTS(S). Returns the appropriate match string.
#
sub do_proto ( $ $ $ )
{
my ( $ proto , $ ports , $ sports ) = @ _ ;
#
# Return the number of ports represented by the passed list
#
sub port_count ( $ ) {
( $ _ [ 0 ] =~ tr /,:/ , : / ) + 1 ;
}
my $ output = '' ;
$ proto = '' if $ proto eq '-' ;
$ ports = '' if $ ports eq '-' ;
$ sports = '' if $ sports eq '-' ;
if ( $ proto ne '' ) {
my $ synonly = ( $ proto =~ s/:syn$//i ) ;
my $ protonum = resolve_proto $ proto ;
if ( defined $ protonum ) {
#
# Protocol is numeric and <= 65535 or is defined in /etc/protocols or NSS equivalent
#
my $ pname = proto_name ( $ proto = $ protonum ) ;
#
# $proto now contains the protocol number and $pname contains the canonical name of the protocol
#
unless ( $ synonly ) {
$ output = "-p $proto " ;
} else {
fatal_error '":syn" is only allowed with tcp' unless $ proto == TCP ;
$ output = "-p $proto --syn " ;
}
PROTO:
{
2008-12-08 23:51:17 +01:00
if ( $ proto == TCP || $ proto == UDP || $ proto == SCTP || $ proto == DCCP ) {
2008-12-08 19:45:58 +01:00
my $ multiport = 0 ;
if ( $ ports ne '' ) {
if ( $ ports =~ tr /,/ , / > 0 || $sports =~ tr/ , /,/ > 0 ) {
fatal_error "Port lists require Multiport support in your kernel/iptables" unless $ capabilities { MULTIPORT } ;
fatal_error "Multiple ports not supported with SCTP" if $ proto == SCTP ;
$ ports = validate_port_list $ pname , $ ports ;
$ output . = "-m multiport --dports $ports " ;
$ multiport = 1 ;
} else {
$ ports = validate_portpair $ pname , $ ports ;
$ output . = "--dport $ports " ;
}
} else {
$ multiport = ( ( $ sports =~ tr /,/ , / ) > 0 ) ;
}
if ( $ sports ne '' ) {
if ( $ multiport ) {
fatal_error "Too many entries in SOURCE PORT(S) list" if port_count ( $ sports ) > 15 ;
$ sports = validate_port_list $ pname , $ sports ;
$ output . = "-m multiport --sports $sports " ;
} else {
$ sports = validate_portpair $ pname , $ sports ;
$ output . = "--sport $sports " ;
}
}
last PROTO ; }
if ( $ proto == ICMP ) {
2008-12-08 23:51:17 +01:00
fatal_error "ICMP not permitted in an IPv6 configuration" if $ family == F_IPV6 ;
2008-12-08 19:45:58 +01:00
if ( $ ports ne '' ) {
fatal_error 'Multiple ICMP types are not permitted' if $ ports =~ /,/ ;
$ ports = validate_icmp $ ports ;
$ output . = "--icmp-type $ports " ;
}
fatal_error 'SOURCE PORT(S) not permitted with ICMP' if $ sports ne '' ;
last PROTO ; }
2008-12-08 23:51:17 +01:00
if ( $ proto == IPv6_ICMP ) {
fatal_error "IPv6_ICMP not permitted in an IPv4 configuration" if $ family == F_IPV4 ;
if ( $ ports ne '' ) {
fatal_error 'Multiple ICMP types are not permitted' if $ ports =~ /,/ ;
$ ports = validate_icmp6 $ ports ;
$ output . = "--icmpv6-type $ports " ;
}
fatal_error 'SOURCE PORT(S) not permitted with IPv6-ICMP' if $ sports ne '' ;
last PROTO ; }
2008-12-08 19:45:58 +01:00
fatal_error "SOURCE/DEST PORT(S) not allowed with PROTO $pname" if $ ports ne '' || $ sports ne '' ;
} # PROTO
} else {
fatal_error '":syn" is only allowed with tcp' if $ synonly ;
if ( $ proto =~ /^(ipp2p(:(tcp|udp|all))?)$/i ) {
my $ p = $ 2 ? lc $ 3 : 'tcp' ;
require_capability ( 'IPP2P_MATCH' , "PROTO = $proto" , 's' ) ;
$ proto = '-p ' . proto_name ( $ p ) . ' ' ;
$ ports = 'ipp2p' unless $ ports ;
$ output . = "${proto}-m ipp2p --$ports " ;
} else {
fatal_error "Invalid/Unknown protocol ($proto)"
}
}
} else {
#
# No protocol
#
fatal_error "SOURCE/DEST PORT(S) not allowed without PROTO" if $ ports ne '' || $ sports ne '' ;
}
$ output ;
}
sub mac_match ( $ ) {
my $ mac = $ _ [ 0 ] ;
$ mac =~ s/^(!?)~// ;
my $ invert = ( $ 1 ? '! ' : '' ) ;
$ mac =~ tr /-/ : / ;
fatal_error "Invalid MAC address ($mac)" unless $ mac =~ /^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$/ ;
"--match mac --mac-source ${invert}$mac " ;
}
#
# Mark validatation functions
#
sub verify_mark ( $ ) {
my $ mark = $ _ [ 0 ] ;
my $ limit = $ config { HIGH_ROUTE_MARKS } ? 0xFFFF : 0xFF ;
my $ value = numeric_value ( $ mark ) ;
fatal_error "Invalid Mark or Mask value ($mark)"
unless defined ( $ value ) && $ value <= $ limit ;
fatal_error "Invalid High Mark or Mask value ($mark)"
if ( $ value > 0xFF && $ value & 0xFF ) ;
}
sub verify_small_mark ( $ ) {
verify_mark ( ( my $ mark ) = $ _ [ 0 ] ) ;
fatal_error "Mark value ($mark) too large" if numeric_value ( $ mark ) > 0xFF ;
}
sub validate_mark ( $ ) {
for ( split '/' , $ _ [ 0 ] ) {
verify_mark $ _ ;
}
}
#
# Generate an appropriate -m [conn]mark match string for the contents of a MARK column
#
sub do_test ( $ $ )
{
my ( $ testval , $ mask ) = @ _ ;
my $ originaltestval = $ testval ;
return '' unless defined $ testval and $ testval ne '-' ;
$ mask = '' unless defined $ mask ;
my $ invert = $ testval =~ s/^!// ? '! ' : '' ;
my $ match = $ testval =~ s/:C$// ? "-m connmark ${invert}--mark" : "-m mark ${invert}--mark" ;
fatal_error "Invalid MARK value ($originaltestval)" if $ testval eq '/' ;
validate_mark $ testval ;
$ testval = join ( '/' , $ testval , in_hex ( $ mask ) ) unless ( $ testval =~ '/' ) ;
"$match $testval " ;
}
my % norate = ( DROP = > 1 , REJECT = > 1 ) ;
#
# Create a "-m limit" match for the passed LIMIT/BURST
#
sub do_ratelimit ( $$ ) {
my ( $ rate , $ action ) = @ _ ;
return '' unless $ rate and $ rate ne '-' ;
fatal_error "Rate Limiting not available with $action" if $ norate { $ action } ;
if ( $ rate =~ /^(\d+(\/(sec|min|hour|day))?):(\d+)$/ ) {
"-m limit --limit $1 --limit-burst $4 " ;
} elsif ( $ rate =~ /^(\d+)(\/(sec|min|hour|day))?$/ ) {
"-m limit --limit $rate " ;
} else {
fatal_error "Invalid rate ($rate)" ;
}
}
#
# Create a "-m connlimit" match for the passed CONNLIMIT
#
sub do_connlimit ( $ ) {
my ( $ limit ) = @ _ ;
return '' unless $ limit and $ limit ne '-' ;
require_capability 'CONNLIMIT_MATCH' , 'A non-empty CONNLIMIT' , 's' ;
my $ invert = $ limit =~ s/^!// ? '' : '! ' ; # Note Carefully -- we actually do 'connlimit-at-or-below'
if ( $ limit =~ /^(\d+):(\d+)$/ ) {
fatal_error "Invalid Mask ($2)" unless $ 2 > 0 || $ 2 < 31 ;
"-m connlimit ${invert}--connlimit-above $1 --connlimit-mask $2 " ;
} elsif ( $ limit =~ /^(\d+)$/ ) {
"-m connlimit ${invert}--connlimit-above $limit " ;
} else {
fatal_error "Invalid connlimit ($limit)" ;
}
}
sub do_time ( $ ) {
my ( $ time ) = @ _ ;
return '' unless $ time ne '-' ;
require_capability 'TIME_MATCH' , 'A non-empty TIME' , 's' ;
my $ result = '-m time ' ;
for my $ element ( split /&/ , $ time ) {
fatal_error "Invalid time element list ($time)" unless defined $ element && $ element ;
if ( $ element =~ /^(timestart|timestop)=(\d{1,2}:\d{1,2}(:\d{1,2})?)$/ ) {
$ result . = "--$1 $2 " ;
} elsif ( $ element =~ /^weekdays=(.*)$/ ) {
my $ days = $ 1 ;
for my $ day ( split /,/ , $ days ) {
fatal_error "Invalid weekday ($day)" unless $ day =~ /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)$/ || ( $ day =~ /^\d$/ && $ day && $ day <= 7 ) ; 0
}
$ result . = "--weekday $days " ;
} elsif ( $ element =~ /^monthdays=(.*)$/ ) {
my $ days = $ 1 ;
for my $ day ( split /,/ , $ days ) {
fatal_error "Invalid day of the month ($day)" unless $ day =~ /^\d{1,2}$/ && $ day && $ day <= 31 ;
}
} elsif ( $ element =~ /^(datestart|datestop)=(\d{4}(-\d{2}(-\d{2}(T\d{1,2}(:\d{1,2}){0,2})?)?)?)$/ ) {
$ result . = "--$1 $2 " ;
} elsif ( $ element =~ /^(utc|localtz)$/ ) {
$ result . = "--$1 " ;
} else {
fatal_error "Invalid time element ($element)" ;
}
}
$ result ;
}
#
# Create a "-m owner" match for the passed USER/GROUP
#
sub do_user ( $ ) {
my $ user = $ _ [ 0 ] ;
my $ rule = '-m owner ' ;
return '' unless defined $ user and $ user ne '-' ;
if ( $ user =~ /^(!)?(.*)\+(.*)$/ ) {
$ rule . = "! --cmd-owner $2 " if defined $ 2 && $ 2 ne '' ;
$ user = "!$1" ;
} elsif ( $ user =~ /^(.*)\+(.*)$/ ) {
$ rule . = "--cmd-owner $2 " if defined $ 2 && $ 2 ne '' ;
$ user = $ 1 ;
}
if ( $ user =~ /^(!)?(.*):(.*)$/ ) {
my $ invert = $ 1 ? '! ' : '' ;
my $ group = defined $ 3 ? $ 3 : '' ;
if ( defined $ 2 && $ 2 ne '' ) {
$ user = $ 2 ;
fatal_error "Unknown user ($user)" unless $ user =~ /^\d+$/ || $ globals { EXPORT } || defined getpwnam ( $ user ) ;
$ rule . = "${invert}--uid-owner $user " ;
}
if ( $ group ne '' ) {
fatal_error "Unknown group ($group)" unless $ group =~ /\d+$/ || $ globals { EXPORT } || defined getgrnam ( $ group ) ;
$ rule . = "${invert}--gid-owner $group " ;
}
} elsif ( $ user =~ /^(!)?(.*)$/ ) {
my $ invert = $ 1 ? '! ' : '' ;
$ user = $ 2 ;
fatal_error "Invalid USER/GROUP (!)" if $ user eq '' ;
fatal_error "Unknown user ($user)" unless $ user =~ /^\d+$/ || $ globals { EXPORT } || defined getpwnam ( $ user ) ;
$ rule . = "${invert}--uid-owner $user " ;
} else {
fatal_error "Unknown user ($user)" unless $ user =~ /^\d+$/ || $ globals { EXPORT } || defined getpwnam ( $ user ) ;
$ rule . = "--uid-owner $user " ;
}
$ rule ;
}
#
# Create a "-m tos" match for the passed TOS
#
sub do_tos ( $ ) {
my $ tos = $ _ [ 0 ] ;
$ tos ne '-' ? "-m tos --tos $tos " : '' ;
}
my % dir = ( O = > 'original' ,
R = > 'reply' ,
B = > 'both' ) ;
my % mode = ( P = > 'packets' ,
B = > 'bytes' ,
A = > 'avgpkt' ) ;
#
# Create a "-m connbytes" match for the passed argument
#
sub do_connbytes ( $ ) {
my $ connbytes = $ _ [ 0 ] ;
return '' if $ connbytes eq '-' ;
# 1 2 3 5 6
fatal_error "Invalid CONNBYTES ($connbytes)" unless $ connbytes =~ /^(!)? (\d+): (\d+)? ((:[ORB]) (:[PBA])?)?$/x ;
my $ invert = $ 1 || '' ; $ invert = '! ' if $ invert ;
my $ min = $ 2 ; $ min = 0 unless defined $ min ;
my $ max = $ 3 ; $ max = '' unless defined $ max ; fatal_error "Invalid byte range ($min:$max)" if $ max ne '' and $ min > $ max ;
my $ dir = $ 5 || 'B' ;
my $ mode = $ 6 || 'B' ;
$ dir =~ s/:// ;
$ mode =~ s/:// ;
"${invert}-m connbytes --connbytes $min:$max --connbytes-dir $dir{$dir} --connbytes-mode $mode{$mode} " ;
}
#
# Create a "-m helper" match for the passed argument
#
sub do_helper ( $ ) {
my $ helper = shift ;
return '' if $ helper eq '-' ;
qq( -m helper --helper "$helper" ) ;
}
#
# Create a "-m length" match for the passed TOS
#
sub do_length ( $ ) {
my $ length = $ _ [ 0 ] ;
require_capability ( 'LENGTH_MATCH' , 'A Non-empty LENGTH' , 's' ) ;
$ length ne '-' ? "-m length --length $length " : '' ;
}
#
# Match Source Interface
#
sub match_source_dev ( $ ) {
my $ interface = shift ;
return '' if $ interface eq '+' ;
my $ interfaceref = known_interface ( $ interface ) ;
if ( $ interfaceref && $ interfaceref - > { options } { port } ) {
"-i $interfaceref->{bridge} -m physdev --physdev-in $interface " ;
} else {
"-i $interface " ;
}
}
#
# Match Dest device
#
sub match_dest_dev ( $ ) {
my $ interface = shift ;
return '' if $ interface eq '+' ;
my $ interfaceref = known_interface ( $ interface ) ;
if ( $ interfaceref && $ interfaceref - > { options } { port } ) {
if ( $ capabilities { PHYSDEV_BRIDGE } ) {
"-o $interfaceref->{bridge} -m physdev --physdev-is-bridged --physdev-out $interface " ;
} else {
"-o $interfaceref->{bridge} -m physdev --physdev-out $interface " ;
}
} else {
"-o $interface " ;
}
}
#
# Avoid generating a second '-m iprange' in a single rule.
#
sub iprange_match () {
my $ match = '' ;
require_capability ( 'IPRANGE_MATCH' , 'Address Ranges' , '' ) ;
unless ( $ iprangematch ) {
$ match = '-m iprange ' ;
$ iprangematch = 1 unless $ capabilities { KLUDGEFREE } ;
}
$ match ;
}
#
# Get set flags (ipsets).
#
sub get_set_flags ( $$ ) {
my ( $ setname , $ option ) = @ _ ;
my $ options = $ option ;
$ setname =~ s/^!// ; # Caller has already taken care of leading !
if ( $ setname =~ /^(.*)\[([1-6])\]$/ ) {
$ setname = $ 1 ;
my $ count = $ 2 ;
$ options . = ",$option" while - - $ count > 0 ;
} elsif ( $ setname =~ /^(.*)\[(.*)\]$/ ) {
$ setname = $ 1 ;
$ options = $ 2 ;
}
$ setname =~ s/^\+// ;
fatal_error "Invalid ipset name ($setname)" unless $ setname =~ /^[a-zA-Z]\w*/ ;
"--set $setname $options "
}
#
# Match a Source. Handles IP addresses and ranges and MAC addresses
#
sub match_source_net ( $;$ ) {
my ( $ net , $ restriction ) = @ _ ;
$ restriction |= NO_RESTRICT ;
if ( $ net =~ /^(!?)(\d+\.\d+\.\d+\.\d+)-(\d+\.\d+\.\d+\.\d+)$/ ) {
my ( $ addr1 , $ addr2 ) = ( $ 2 , $ 3 ) ;
$ net =~ s/!// if my $ invert = $ 1 ? '! ' : '' ;
validate_range $ addr1 , $ addr2 ;
iprange_match . "${invert}--src-range $net " ;
} elsif ( $ net =~ /^!?~/ ) {
fatal_error "MAC address cannot be used in this context" if $ restriction >= OUTPUT_RESTRICT ;
mac_match $ net ;
} elsif ( $ net =~ /^(!?)\+/ ) {
require_capability ( 'IPSET_MATCH' , 'ipset names in Shorewall configuration files' , '' ) ;
join ( '' , '-m set ' , $ 1 ? '! ' : '' , get_set_flags ( $ net , 'src' ) ) ;
} elsif ( $ net =~ s/^!// ) {
validate_net $ net , 1 ;
"-s ! $net " ;
} else {
validate_net $ net , 1 ;
2008-12-08 22:49:50 +01:00
$ net eq ALLIP ? '' : "-s $net " ;
2008-12-08 19:45:58 +01:00
}
}
#
# Match a Source. Currently only handles IP addresses and ranges
#
sub match_dest_net ( $ ) {
my $ net = $ _ [ 0 ] ;
if ( $ net =~ /^(!?)(\d+\.\d+\.\d+\.\d+)-(\d+\.\d+\.\d+\.\d+)$/ ) {
my ( $ addr1 , $ addr2 ) = ( $ 2 , $ 3 ) ;
$ net =~ s/!// if my $ invert = $ 1 ? '! ' : '' ;
validate_range $ addr1 , $ addr2 ;
iprange_match . "${invert}--dst-range $net " ;
} elsif ( $ net =~ /^(!?)\+/ ) {
require_capability ( 'IPSET_MATCH' , 'ipset names in Shorewall configuration files' , '' ) ;
join ( '' , '-m set ' , $ 1 ? '! ' : '' , get_set_flags ( $ net , 'dst' ) ) ;
} elsif ( $ net =~ /^!/ ) {
$ net =~ s/!// ;
validate_net $ net , 1 ;
"-d ! $net " ;
} else {
validate_net $ net , 1 ;
2008-12-08 22:49:50 +01:00
$ net eq ALLIP ? '' : "-d $net " ;
2008-12-08 19:45:58 +01:00
}
}
#
# Match original destination
#
sub match_orig_dest ( $ ) {
my $ net = $ _ [ 0 ] ;
2008-12-08 22:49:50 +01:00
return '' if $ net eq ALLIP ;
2008-12-08 19:45:58 +01:00
return '' unless $ capabilities { CONNTRACK_MATCH } ;
if ( $ net =~ s/^!// ) {
validate_net $ net , 1 ;
$ capabilities { OLD_CONNTRACK_MATCH } ? "-m conntrack --ctorigdst ! $net " : "-m conntrack ! --ctorigdst $net " ;
} else {
validate_net $ net , 1 ;
2008-12-08 22:49:50 +01:00
$ net eq ALLIP ? '' : "-m conntrack --ctorigdst $net " ;
2008-12-08 19:45:58 +01:00
}
}
#
# Match Source IPSEC
#
sub match_ipsec_in ( $$ ) {
my ( $ zone , $ hostref ) = @ _ ;
my $ match = '-m policy --dir in --pol ' ;
my $ zoneref = find_zone ( $ zone ) ;
my $ optionsref = $ zoneref - > { options } ;
2008-12-08 22:49:50 +01:00
if ( $ zoneref - > { type } eq 'ipsec' ) {
2008-12-08 19:45:58 +01:00
$ match . = "ipsec $optionsref->{in_out}{ipsec}$optionsref->{in}{ipsec}" ;
} elsif ( $ capabilities { POLICY_MATCH } ) {
$ match . = "$hostref->{ipsec} $optionsref->{in_out}{ipsec}$optionsref->{in}{ipsec}" ;
} else {
'' ;
}
}
#
# Match Dest IPSEC
#
sub match_ipsec_out ( $$ ) {
my ( $ zone , $ hostref ) = @ _ ;
my $ match = '-m policy --dir out --pol ' ;
my $ zoneref = find_zone ( $ zone ) ;
my $ optionsref = $ zoneref - > { options } ;
2008-12-08 22:49:50 +01:00
if ( $ zoneref - > { type } eq 'ipsec' ) {
2008-12-08 19:45:58 +01:00
$ match . = "ipsec $optionsref->{in_out}{ipsec}$optionsref->{out}{ipsec}" ;
} elsif ( $ capabilities { POLICY_MATCH } ) {
$ match . = "$hostref->{ipsec} $optionsref->{in_out}{ipsec}$optionsref->{out}{ipsec}"
} else {
'' ;
}
}
#
# Generate a log message
#
sub log_rule_limit ( $$$$$$$$ ) {
my ( $ level , $ chainref , $ chain , $ disposition , $ limit , $ tag , $ command , $ predicates ) = @ _ ;
my $ prefix = '' ;
$ level = validate_level $ level ; # Do this here again because this function can be called directly from user exits.
return 1 if $ level eq '' ;
$ predicates . = ' ' if $ predicates && substr ( $ predicates , - 1 , 1 ) ne ' ' ;
unless ( $ predicates =~ /-m limit / ) {
$ limit = $ globals { LOGLIMIT } unless $ limit && $ limit ne '-' ;
$ predicates . = $ limit if $ limit ;
}
if ( $ config { LOGFORMAT } =~ /^\s*$/ ) {
if ( $ level =~ '^ULOG' ) {
$ prefix = "-j $level " ;
} elsif ( $ level =~ /^NFLOG/ ) {
$ prefix = "-j $level " ;
} else {
$ prefix = "-j LOG $globals{LOGPARMS}--log-level $level " ;
}
} else {
if ( $ tag ) {
if ( $ config { LOGTAGONLY } ) {
$ chain = $ tag ;
$ tag = '' ;
} else {
$ tag . = ' ' ;
}
} else {
$ tag = '' unless defined $ tag ;
}
$ disposition =~ s/\s+.*// ;
if ( $ globals { LOGRULENUMBERS } ) {
$ prefix = ( sprintf $ config { LOGFORMAT } , $ chain , $ chainref - > { log } + + , $ disposition ) . $ tag ;
} else {
$ prefix = ( sprintf $ config { LOGFORMAT } , $ chain , $ disposition ) . $ tag ;
}
if ( length $ prefix > 29 ) {
$ prefix = substr ( $ prefix , 0 , 28 ) . ' ' ;
warning_message "Log Prefix shortened to \"$prefix\"" ;
}
if ( $ level =~ '^ULOG' ) {
$ prefix = "-j $level --ulog-prefix \"$prefix\" " ;
} elsif ( $ level =~ /^NFLOG/ ) {
$ prefix = "-j $level --nflog-prefix \"$prefix\" " ;
} else {
$ prefix = "-j LOG $globals{LOGPARMS}--log-level $level --log-prefix \"$prefix\" " ;
}
}
if ( $ command eq 'add' ) {
add_rule ( $ chainref , $ predicates . $ prefix , 1 ) ;
} else {
insert_rule ( $ chainref , 1 , $ predicates . $ prefix ) ;
}
}
sub log_rule ( $$$$ ) {
my ( $ level , $ chainref , $ disposition , $ predicates ) = @ _ ;
log_rule_limit $ level , $ chainref , $ chainref - > { name } , $ disposition , $ globals { LOGLIMIT } , '' , 'add' , $ predicates ;
}
#
# Split a comma-separated source or destination host list but keep [...] together.
#
sub mysplit ( $ ) {
my @ input = split_list $ _ [ 0 ] , 'host' ;
return @ input unless $ _ [ 0 ] =~ /\[/ ;
my @ result ;
while ( @ input ) {
my $ element = shift @ input ;
if ( $ element =~ /\[/ ) {
while ( substr ( $ element , - 1 , 1 ) ne ']' ) {
last unless @ input ;
$ element . = ( ',' . shift @ input ) ;
}
fatal_error "Invalid Host List ($_[0])" unless substr ( $ element , - 1 , 1 ) eq ']' ;
}
push @ result , $ element ;
}
@ result ;
}
#
# Returns the name of the shell variable holding the first address of the passed interface
#
sub interface_address ( $ ) {
my $ variable = chain_base ( $ _ [ 0 ] ) . '_address' ;
uc $ variable ;
}
#
# Record that the ruleset requires the first IP address on the passed interface
#
sub get_interface_address ( $ ) {
my ( $ interface ) = $ _ [ 0 ] ;
my $ variable = interface_address ( $ interface ) ;
my $ function = interface_is_optional ( $ interface ) ? 'find_first_interface_address_if_any' : 'find_first_interface_address' ;
$ interfaceaddr { $ interface } = "$variable=\$($function $interface)\n" ;
"\$$variable" ;
}
#
# Returns the name of the shell variable holding the broadcast addresses of the passed interface
#
sub interface_bcasts ( $ ) {
my $ variable = chain_base ( $ _ [ 0 ] ) . '_bcasts' ;
uc $ variable ;
}
#
# Record that the ruleset requires the broadcast addresses on the passed interface
#
sub get_interface_bcasts ( $ ) {
my ( $ interface ) = $ _ [ 0 ] ;
my $ variable = interface_bcasts ( $ interface ) ;
$ interfacebcasts { $ interface } = qq( $variable="\$ ( get_interface_bcasts $interface ) 255.255.255.255" ) ;
"\$$variable" ;
}
2008-12-12 22:31:31 +01:00
#
# Returns the name of the shell variable holding the anycast addresses of the passed interface
#
sub interface_acasts ( $ ) {
my $ variable = chain_base ( $ _ [ 0 ] ) . '_acasts' ;
uc $ variable ;
}
#
# Record that the ruleset requires the anycast addresses on the passed interface
#
sub get_interface_acasts ( $ ) {
my ( $ interface ) = $ _ [ 0 ] ;
my $ variable = interface_acasts ( $ interface ) ;
$ interfaceacasts { $ interface } = qq( $variable="\$ ( get_interface_acasts $interface ) ff00::/10" ) ;
"\$$variable" ;
}
2008-12-08 19:45:58 +01:00
#
# Returns the name of the shell variable holding the gateway through the passed interface
#
sub interface_gateway ( $ ) {
my $ variable = chain_base ( $ _ [ 0 ] ) . '_gateway' ;
uc $ variable ;
}
#
# Record that the ruleset requires the gateway address on the passed interface
#
sub get_interface_gateway ( $ ) {
my ( $ interface ) = $ _ [ 0 ] ;
my $ variable = interface_gateway ( $ interface ) ;
if ( interface_is_optional $ interface ) {
$ interfacegateways { $ interface } = qq( [ -n "\$$variable" ] || $variable=\$ ( detect_gateway $interface ) \ n ) ;
} else {
$ interfacegateways { $ interface } = qq( [ -n "\$$variable" ] || $variable=\$ ( detect_gateway $interface )
[ - n "\$$variable" ] || fatal_error "Unable to detect the gateway through interface $interface"
) ;
}
"\$$variable" ;
}
#
# Returns the name of the shell variable holding the addresses of the passed interface
#
sub interface_addresses ( $ ) {
my $ variable = chain_base ( $ _ [ 0 ] ) . '_addresses' ;
uc $ variable ;
}
#
# Record that the ruleset requires the IP addresses on the passed interface
#
sub get_interface_addresses ( $ ) {
my ( $ interface ) = $ _ [ 0 ] ;
my $ variable = interface_addresses ( $ interface ) ;
if ( interface_is_optional $ interface ) {
$ interfaceaddrs { $ interface } = qq( $variable=\$ ( find_interface_addresses $interface ) \ n ) ;
} else {
$ interfaceaddrs { $ interface } = qq( $variable=\$ ( find_interface_addresses $interface )
[ - n "\$$variable" ] || fatal_error "Unable to determine the IP address(es) of $interface"
) ;
}
"\$$variable" ;
}
#
# Returns the name of the shell variable holding the networks routed out of the passed interface
#
sub interface_nets ( $ ) {
my $ variable = chain_base ( $ _ [ 0 ] ) . '_networks' ;
uc $ variable ;
}
#
# Record that the ruleset requires the networks routed out of the passed interface
#
sub get_interface_nets ( $ ) {
my ( $ interface ) = $ _ [ 0 ] ;
my $ variable = interface_nets ( $ interface ) ;
if ( interface_is_optional $ interface ) {
$ interfacenets { $ interface } = qq( $variable=\$ ( get_routed_networks $interface ) \ n ) ;
} else {
$ interfacenets { $ interface } = qq( $variable=\$ ( get_routed_networks $interface )
[ - n "\$$variable" ] || fatal_error "Unable to determine the routes through interface \\" $ interface \ \ ""
) ;
}
"\$$variable" ;
}
#
# Returns the name of the shell variable holding the MAC address of the gateway for the passed provider out of the passed interface
#
sub interface_mac ( $$ ) {
my $ variable = join ( '_' , chain_base ( $ _ [ 0 ] ) , chain_base ( $ _ [ 1 ] ) , 'mac' ) ;
uc $ variable ;
}
#
# Record the fact that the ruleset requires MAC address of the passed gateway IP routed out of the passed interface for the passed provider number
#
sub get_interface_mac ( $$$ ) {
my ( $ ipaddr , $ interface , $ table ) = @ _ ;
my $ variable = interface_mac ( $ interface , $ table ) ;
if ( interface_is_optional $ interface ) {
$ interfacemacs { $ table } = qq( $variable=\$ ( find_mac $ipaddr $interface ) \ n ) ;
} else {
$ interfacemacs { $ table } = qq( $variable=\$ ( find_mac $ipaddr $interface )
[ - n "\$$variable" ] || fatal_error "Unable to determine the MAC address of $ipaddr through interface \\" $ interface \ \ ""
) ;
}
"\$$variable" ;
}
#
# This function provides a uniform way to generate rules (something the original Shorewall sorely needed).
#
# Returns the destination interface specified in the rule, if any.
#
sub expand_rule ( $ $ $ $ $ $ $ $ $ $ $ )
{
my ( $ chainref , # Chain
$ restriction , # Determines what to do with interface names in the SOURCE or DEST
$ rule , # Caller's matches that don't depend on the SOURCE, DEST and ORIGINAL DEST
$ source , # SOURCE
$ dest , # DEST
$ origdest , # ORIGINAL DEST
$ oport , # original destination port
$ target , # Target ('-j' part of the rule)
$ loglevel , # Log level (and tag)
$ disposition , # Primative part of the target (RETURN, ACCEPT, ...)
$ exceptionrule # Caller's matches used in exclusion case
) = @ _ ;
my ( $ iiface , $ diface , $ inets , $ dnets , $ iexcl , $ dexcl , $ onets , $ oexcl ) ;
my $ chain = $ chainref - > { name } ;
our @ ends = ( ) ;
#
# In the generated rules, we sometimes need run-time loops or conditional blocks. This function is used
# to define such a loop or block.
#
# $chainref = Reference to the chain
# $command = The shell command that begins the loop or conditional
# $end = The shell keyword ('done' or 'fi') that ends the loop or conditional
#
# All open loops and conditionals are closed just before expand_rule() exits
#
sub push_command ( $$$ ) {
my ( $ chainref , $ command , $ end ) = @ _ ;
add_command $ chainref , $ command ;
incr_cmd_level $ chainref ;
push @ ends , $ end ;
}
#
# Handle Log Level
#
my $ logtag ;
if ( $ loglevel ne '' ) {
( $ loglevel , $ logtag , my $ remainder ) = split ( /:/ , $ loglevel , 3 ) ;
fatal_error "Invalid log tag" if defined $ remainder ;
if ( $ loglevel =~ /^none!?$/i ) {
return if $ disposition eq 'LOG' ;
$ loglevel = $ logtag = '' ;
} else {
$ loglevel = validate_level ( $ loglevel ) ;
$ logtag = '' unless defined $ logtag ;
}
} elsif ( $ disposition eq 'LOG' ) {
fatal_error "LOG requires a level" ;
}
#
# Mark Target as referenced, if it's a chain
#
if ( $ disposition ) {
my $ targetref = $ chain_table { $ chainref - > { table } } { $ disposition } ;
$ targetref - > { referenced } = 1 if $ targetref ;
}
#
# Isolate Source Interface, if any
#
if ( $ source ) {
if ( $ source eq '-' ) {
$ source = '' ;
2008-12-08 21:48:02 +01:00
} elsif ( $ family == F_IPV4 ) {
2008-12-08 23:51:17 +01:00
if ( $ source =~ /^(.+?):(.+)$/ ) {
2008-12-08 21:48:02 +01:00
$ iiface = $ 1 ;
$ inets = $ 2 ;
} elsif ( $ source =~ /\+|~|\..*\./ ) {
$ inets = $ source ;
} else {
$ iiface = $ source ;
}
2008-12-13 21:45:23 +01:00
} elsif ( $ source =~ /^(.+?):<(.+)>\s*$/ ) {
2008-12-08 19:45:58 +01:00
$ iiface = $ 1 ;
$ inets = $ 2 ;
2008-12-09 22:55:39 +01:00
} elsif ( $ source =~ /:/ ) {
2008-12-13 21:45:23 +01:00
if ( $ source =~ /^<(.+)>$/ ) {
2008-12-11 18:16:06 +01:00
$ inets = $ 1 ;
} else {
$ inets = $ source ;
}
2008-12-08 19:45:58 +01:00
} elsif ( $ source =~ /\+|~|\..*\./ ) {
$ inets = $ source ;
} else {
$ iiface = $ source ;
}
} else {
$ source = '' ;
}
#
# Verify Interface, if any
#
if ( $ iiface ) {
fatal_error "Unknown Interface ($iiface)" unless known_interface $ iiface ;
if ( $ restriction & POSTROUTE_RESTRICT ) {
#
# An interface in the SOURCE column of a masq file
#
fatal_error "Bridge ports may not appear in the SOURCE column of this file" if port_to_bridge ( $ iiface ) ;
my $ networks = get_interface_nets ( $ iiface ) ;
push_command $ chainref , join ( '' , 'for source in ' , $ networks , '; do' ) , 'done' ;
$ rule . = '-s $source ' ;
} else {
fatal_error "Source Interface ($iiface) not allowed when the source zone is the firewall zone" if $ restriction & OUTPUT_RESTRICT ;
$ rule . = match_source_dev ( $ iiface ) ;
}
}
#
# Isolate Destination Interface, if any
#
if ( $ dest ) {
if ( $ dest eq '-' ) {
$ dest = '' ;
} elsif ( ( $ restriction & PREROUTE_RESTRICT ) && $ dest =~ /^detect:(.*)$/ ) {
#
# DETECT_DNAT_IPADDRS=Yes and we're generating the nat rule
#
my @ interfaces = split /\s+/ , $ 1 ;
if ( @ interfaces > 1 ) {
my $ list = "" ;
my $ optional ;
for my $ interface ( @ interfaces ) {
$ optional + + if interface_is_optional $ interface ;
$ list = join ( ' ' , $ list , get_interface_address ( $ interface ) ) ;
}
push_command ( $ chainref , "for address in $list; do" , 'done' ) ;
push_command ( $ chainref , 'if [ $address != 0.0.0.0 ]; then' , 'fi' ) if $ optional ;
$ rule . = '-d $address ' ;
} else {
my $ interface = $ interfaces [ 0 ] ;
my $ variable = get_interface_address ( $ interface ) ;
push_command ( $ chainref , "if [ $variable != 0.0.0.0 ]; then" , 'fi' ) if interface_is_optional ( $ interface ) ;
$ rule . = "-d $variable " ;
}
$ dest = '' ;
2008-12-08 21:48:02 +01:00
} elsif ( $ family == F_IPV4 ) {
2008-12-08 23:51:17 +01:00
if ( $ dest =~ /^(.+?):(.+)$/ ) {
2008-12-08 21:48:02 +01:00
$ diface = $ 1 ;
$ dnets = $ 2 ;
} elsif ( $ dest =~ /\+|~|\..*\./ ) {
$ dnets = $ dest ;
} else {
$ diface = $ dest ;
}
2008-12-13 21:45:23 +01:00
} elsif ( $ dest =~ /^(.+?):<(.+)>\s*$/ ) {
2008-12-08 19:45:58 +01:00
$ diface = $ 1 ;
$ dnets = $ 2 ;
2008-12-09 22:55:39 +01:00
} elsif ( $ dest =~ /:/ ) {
2008-12-13 21:45:23 +01:00
if ( $ dest =~ /^<(.+)>$/ ) {
2008-12-11 18:16:06 +01:00
$ dnets = $ 1 ;
} else {
$ dnets = $ dest ;
}
2008-12-08 19:45:58 +01:00
} elsif ( $ dest =~ /\+|~|\..*\./ ) {
$ dnets = $ dest ;
} else {
$ diface = $ dest ;
}
} else {
$ dest = '' ;
}
#
# Verify Destination Interface, if any
#
if ( $ diface ) {
fatal_error "Unknown Interface ($diface)" unless known_interface $ diface ;
if ( $ restriction & PREROUTE_RESTRICT ) {
#
# ADDRESS 'detect' in the masq file.
#
fatal_error "Bridge port ($diface) not allowed" if port_to_bridge ( $ diface ) ;
push_command ( $ chainref , 'for dest in ' . get_interface_addresses ( $ diface ) . '; do' , 'done' ) ;
$ rule . = '-d $dest ' ;
} else {
fatal_error "Bridge Port ($diface) not allowed in OUTPUT or POSTROUTING rules" if ( $ restriction & ( POSTROUTE_RESTRICT + OUTPUT_RESTRICT ) ) && port_to_bridge ( $ diface ) ;
fatal_error "Destination Interface ($diface) not allowed when the destination zone is the firewall zone" if $ restriction & INPUT_RESTRICT ;
if ( $ iiface ) {
my $ bridge = port_to_bridge ( $ diface ) ;
fatal_error "Source interface ($iiface) is not a port on the same bridge as the destination interface ( $diface )" if $ bridge && $ bridge ne source_port_to_bridge ( $ iiface ) ;
}
$ rule . = match_dest_dev ( $ diface ) ;
}
} else {
$ diface = '' ;
}
$ oport = '' if defined $ oport && $ oport eq '-' ;
if ( $ origdest ) {
if ( $ origdest eq '-' || ! $ capabilities { CONNTRACK_MATCH } ) {
$ origdest = '' ;
$ rule . = "-m conntrack --ctorigdstport $oport " if $ capabilities { NEW_CONNTRACK_MATCH } && $ oport ;
} elsif ( $ origdest =~ /^detect:(.*)$/ ) {
#
# Either the filter part of a DNAT rule or 'detect' was given in the ORIG DEST column
#
my @ interfaces = split /\s+/ , $ 1 ;
if ( @ interfaces > 1 ) {
my $ list = "" ;
my $ optional ;
for my $ interface ( @ interfaces ) {
$ optional + + if interface_is_optional $ interface ;
$ list = join ( ' ' , $ list , get_interface_address ( $ interface ) ) ;
}
push_command ( $ chainref , "for address in $list; do" , 'done' ) ;
push_command ( $ chainref , 'if [ $address != 0.0.0.0 ]; then' , 'fi' ) if $ optional ;
$ rule . = '-m conntrack --ctorigdst $address ' ;
$ rule . = "--ctorigdstport $oport " if $ capabilities { NEW_CONNTRACK_MATCH } && $ oport ;
} else {
my $ interface = $ interfaces [ 0 ] ;
my $ variable = get_interface_address ( $ interface ) ;
push_command ( $ chainref , "if [ $variable != 0.0.0.0 ]; then" , 'fi' ) if interface_is_optional ( $ interface ) ;
$ rule . = "-m conntrack --ctorigdst $variable " ;
$ rule . = "--ctorigdstport $oport " if $ capabilities { NEW_CONNTRACK_MATCH } && $ oport ;
}
$ origdest = '' ;
} else {
fatal_error "Invalid ORIGINAL DEST" if $ origdest =~ /^([^!]+)?,!([^!]+)$/ || $ origdest =~ /.*!.*!/ ;
if ( $ origdest =~ /^([^!]+)?!([^!]+)$/ ) {
#
# Exclusion
#
$ onets = $ 1 ;
$ oexcl = $ 2 ;
} else {
$ oexcl = '' ;
$ onets = $ origdest ;
}
unless ( $ onets ) {
my @ oexcl = mysplit $ oexcl ;
if ( @ oexcl == 1 ) {
$ rule . = match_orig_dest ( "!$oexcl" ) ;
$ oexcl = '' ;
}
}
$ rule . = "-m conntrack --ctorigdstport $oport " if $ capabilities { NEW_CONNTRACK_MATCH } && $ oport ;
}
} else {
$ oexcl = '' ;
$ rule . = "-m conntrack --ctorigdstport $oport " if $ capabilities { NEW_CONNTRACK_MATCH } && $ oport ;
}
#
# Determine if there is Source Exclusion
#
if ( $ inets ) {
fatal_error "Invalid SOURCE" if $ inets =~ /^([^!]+)?,!([^!]+)$/ || $ inets =~ /.*!.*!/ ;
if ( $ inets =~ /^([^!]+)?!([^!]+)$/ ) {
$ inets = $ 1 ;
$ iexcl = $ 2 ;
} else {
$ iexcl = '' ;
}
unless ( $ inets || ( $ iiface && $ restriction & POSTROUTE_RESTRICT ) ) {
my @ iexcl = mysplit $ iexcl ;
if ( @ iexcl == 1 ) {
$ rule . = match_source_net "!$iexcl" , $ restriction ;
$ iexcl = '' ;
}
}
} else {
$ iexcl = '' ;
}
#
# Determine if there is Destination Exclusion
#
if ( $ dnets ) {
fatal_error "Invalid DEST" if $ dnets =~ /^([^!]+)?,!([^!]+)$/ || $ dnets =~ /.*!.*!/ ;
if ( $ dnets =~ /^([^!]+)?!([^!]+)$/ ) {
$ dnets = $ 1 ;
$ dexcl = $ 2 ;
} else {
$ dexcl = '' ;
}
unless ( $ dnets ) {
my @ dexcl = mysplit $ dexcl ;
if ( @ dexcl == 1 ) {
$ rule . = match_dest_net "!$dexcl" ;
$ dexcl = '' ;
}
}
} else {
$ dexcl = '' ;
}
2008-12-08 22:49:50 +01:00
$ inets = ALLIP unless $ inets ;
$ dnets = ALLIP unless $ dnets ;
$ onets = ALLIP unless $ onets ;
2008-12-08 19:45:58 +01:00
2008-12-08 22:49:50 +01:00
fatal_error "Input interface may not be specified with a source IP address in the POSTROUTING chain" if $ restriction == POSTROUTE_RESTRICT && $ iiface && $ inets ne ALLIP ;
fatal_error "Output interface may not be specified with a destination IP address in the PREROUTING chain" if $ restriction == PREROUTE_RESTRICT && $ diface && $ dnets ne ALLIP ;
2008-12-08 19:45:58 +01:00
if ( $ iexcl || $ dexcl || $ oexcl ) {
#
# We have non-trivial exclusion -- need to create an exclusion chain
#
fatal_error "Exclusion is not possible in ACCEPT+/CONTINUE/NONAT rules" if $ disposition eq 'RETURN' ;
my $ echain = newexclusionchain ;
#
# Use the current rule and sent all possible matches to the exclusion chain
#
for my $ onet ( mysplit $ onets ) {
$ onet = match_orig_dest $ onet ;
for my $ inet ( mysplit $ inets ) {
for my $ dnet ( mysplit $ dnets ) {
#
# We evaluate the source net match in the inner loop to accomodate systems without $capabilities{KLUDGEFREE}
#
add_rule ( $ chainref , join ( '' , $ rule , match_source_net ( $ inet , $ restriction ) , match_dest_net ( $ dnet ) , $ onet , "-j $echain" ) , 1 ) ;
}
}
}
#
# Create the Exclusion Chain
#
my $ echainref = new_chain $ chainref - > { table } , $ echain ;
#
# Generate RETURNs for each exclusion
#
add_rule $ echainref , ( match_source_net $ _ , $ restriction ) . '-j RETURN' for ( mysplit $ iexcl ) ;
add_rule $ echainref , ( match_dest_net $ _ ) . '-j RETURN' for ( mysplit $ dexcl ) ;
add_rule $ echainref , ( match_orig_dest $ _ ) . '-j RETURN' for ( mysplit $ oexcl ) ;
#
# Log rule
#
log_rule_limit $ loglevel , $ echainref , $ chain , $ disposition , '' , $ logtag , 'add' , '' if $ loglevel ;
#
# Generate Final Rule
#
add_rule ( $ echainref , $ exceptionrule . $ target , 1 ) unless $ disposition eq 'LOG' ;
} else {
#
# No exclusions
#
for my $ onet ( mysplit $ onets ) {
$ onet = match_orig_dest $ onet ;
for my $ inet ( mysplit $ inets ) {
#
# We defer evaluating the source net match to accomodate system without $capabilities{KLUDGEFREE}
#
for my $ dnet ( mysplit $ dnets ) {
if ( $ loglevel ne '' ) {
log_rule_limit
$ loglevel ,
$ chainref ,
$ chain ,
$ disposition ,
'' ,
$ logtag ,
'add' ,
join ( '' , $ rule , match_source_net ( $ inet , $ restriction ) , match_dest_net ( $ dnet ) , $ onet ) ;
}
unless ( $ disposition eq 'LOG' ) {
add_rule (
$ chainref ,
join ( '' , $ rule , match_source_net ( $ inet , $ restriction ) , match_dest_net ( $ dnet ) , $ onet , $ target ) ,
1 ) ;
}
}
}
}
}
while ( @ ends ) {
decr_cmd_level $ chainref ;
add_command $ chainref , pop @ ends ;
}
$ diface ;
}
#
# If the destination chain exists, then at the end of the source chain add a jump to the destination.
#
sub addnatjump ( $$$ ) {
my ( $ source , $ dest , $ predicates ) = @ _ ;
my $ destref = $ nat_table - > { $ dest } || { } ;
if ( $ destref - > { referenced } ) {
add_rule $ nat_table - > { $ source } , $ predicates . "-j $dest" ;
} else {
clearrule ;
}
}
sub emit_comment () {
emit ( '#' ,
'# Establish the values of shell variables used in the following function calls' ,
'#' ) ;
our $ emitted_comment = 1 ;
}
sub emit_test () {
emit ( 'if [ "$COMMAND" != restore ]; then' ,
'' ) ;
push_indent ;
our $ emitted_test = 1 ;
}
#
# Generate setting of global variables
#
sub set_global_variables () {
our ( $ emitted_comment , $ emitted_test ) = ( 0 , 0 ) ;
for ( values % interfaceaddr ) {
emit_comment unless $ emitted_comment ;
emit $ _ ;
}
for ( values % interfacegateways ) {
emit_comment unless $ emitted_comment ;
emit $ _ ;
}
for ( values % interfacemacs ) {
emit_comment unless $ emitted_comment ;
emit $ _ ;
}
for ( values % interfaceaddrs ) {
emit_comment unless $ emitted_comment ;
emit_test unless $ emitted_test ;
emit $ _ ;
}
for ( values % interfacenets ) {
emit_comment unless $ emitted_comment ;
emit_test unless $ emitted_test ;
emit $ _ ;
}
unless ( $ capabilities { ADDRTYPE } ) {
emit_comment unless $ emitted_comment ;
emit_test unless $ emitted_test ;
2008-12-12 22:31:31 +01:00
if ( $ family == F_IPV4 ) {
emit 'ALL_BCASTS="$(get_all_bcasts) 255.255.255.255"' ;
for ( values % interfacebcasts ) {
emit $ _ ;
}
} else {
emit 'ALL_ACASTS="$(get_all_acasts)"' ;
for ( values % interfaceacasts ) {
emit $ _ ;
}
2008-12-08 19:45:58 +01:00
}
}
pop_indent , emit "fi\n" if $ emitted_test ;
}
#
# What follows is the code that generates the input to iptables-restore
#
# We always write the iptables-restore input into a file then pass the
# file to iptables-restore. That way, if things go wrong, the user (and Shorewall support)
# has (have) something to look at to determine the error
#
# We may have to generate part of the input at run-time. The rules array in each chain
# table entry may contain rules (begin with '-A') or shell source. We alternate between
# writing the rules ('-A') into the temporary file to be bassed to iptables-restore
# (CAT_MODE) and and writing shell source into the generated script (CMD_MODE).
#
# The following two functions are responsible for the mode transitions.
#
sub enter_cat_mode () {
emit '' ;
emit 'cat >&3 << __EOF__' ;
$ mode = CAT_MODE ;
}
sub enter_cmd_mode () {
emit_unindented "__EOF__\n" if $ mode == CAT_MODE ;
$ mode = CMD_MODE ;
}
#
# Emits the passed rule (input to iptables-restore) or command
#
sub emitr ( $$ ) {
my ( $ name , $ rule ) = @ _ ;
if ( $ rule && substr ( $ rule , 0 , 2 ) eq '-A' ) {
#
# A rule
#
enter_cat_mode unless $ mode == CAT_MODE ;
emit_unindented join ( ' ' , '-A' , $ name , substr ( $ rule , 3 ) ) ;
} else {
#
# A command
#
enter_cmd_mode unless $ mode == CMD_MODE ;
emit $ rule ;
}
}
#
# Generate the netfilter input
#
sub create_netfilter_load () {
my @ table_list ;
2008-12-08 21:48:02 +01:00
if ( $ family == F_IPV4 ) {
push @ table_list , 'raw' if $ capabilities { RAW_TABLE } ;
push @ table_list , 'nat' if $ capabilities { NAT_ENABLED } ;
push @ table_list , 'mangle' if $ capabilities { MANGLE_ENABLED } && $ config { MANGLE_ENABLED } ;
push @ table_list , 'filter' ;
} else {
@ table_list = qw( raw mangle filter ) ;
}
2008-12-08 19:45:58 +01:00
$ mode = NULL_MODE ;
emit ( 'setup_netfilter()' ,
'{'
) ;
push_indent ;
2008-12-08 21:48:02 +01:00
my $ utility = $ family == F_IPV4 ? 'iptables-restore' : 'ip6tables-restore' ;
2008-12-09 23:21:36 +01:00
my $ UTILITY = $ family == F_IPV4 ? 'IPTABLES_RESTORE' : 'IP6TABLES_RESTORE' ;
2008-12-08 21:48:02 +01:00
save_progress_message "Preparing $utility input..." ;
2008-12-08 19:45:58 +01:00
emit '' ;
2008-12-08 21:48:02 +01:00
emit "exec 3>\${VARDIR}/.${utility}-input" ;
2008-12-08 19:45:58 +01:00
enter_cat_mode ;
for my $ table ( @ table_list ) {
emit_unindented "*$table" ;
my @ chains ;
#
# iptables-restore seems to be quite picky about the order of the builtin chains
#
for my $ chain ( @ builtins ) {
my $ chainref = $ chain_table { $ table } { $ chain } ;
if ( $ chainref ) {
fatal_error "Internal error in create_netfilter_load()" if $ chainref - > { cmdlevel } ;
emit_unindented ":$chain $chainref->{policy} [0:0]" ;
push @ chains , $ chainref ;
}
}
#
# First create the chains in the current table
#
for my $ chain ( grep $ chain_table { $ table } { $ _ } - > { referenced } , ( sort keys % { $ chain_table { $ table } } ) ) {
my $ chainref = $ chain_table { $ table } { $ chain } ;
unless ( $ chainref - > { builtin } ) {
fatal_error "Internal error in create_netfilter_load()" if $ chainref - > { cmdlevel } ;
emit_unindented ":$chainref->{name} - [0:0]" ;
push @ chains , $ chainref ;
}
}
#
# Then emit the rules
#
for my $ chainref ( @ chains ) {
emitr $ chainref - > { name } , $ _ for ( @ { $ chainref - > { rules } } ) ;
}
#
# Commit the changes to the table
#
enter_cat_mode unless $ mode == CAT_MODE ;
emit_unindented 'COMMIT' ;
}
enter_cmd_mode ;
#
2008-12-08 21:48:02 +01:00
# Now generate the actual ip[6]tables-restore command
2008-12-08 19:45:58 +01:00
#
emit ( 'exec 3>&-' ,
'' ,
2008-12-09 23:21:36 +01:00
'[ -n "$DEBUG" ] && command=debug_restore_input || command=$' . $ UTILITY ,
2008-12-08 19:45:58 +01:00
'' ,
'progress_message2 "Running $command..."' ,
'' ,
2008-12-08 21:48:02 +01:00
"cat \${VARDIR}/.${utility}-input | \$command # Use this nonsensical form to appease SELinux" ,
2008-12-08 19:45:58 +01:00
'if [ $? != 0 ]; then' ,
2008-12-08 21:48:02 +01:00
qq( fatal_error "iptables-restore Failed. Input is in \${VARDIR}/.${utility}-input" ) ,
2008-12-08 19:45:58 +01:00
"fi\n"
) ;
pop_indent ;
emit "}\n" ;
}
#
# Generate the netfilter input for refreshing a list of chains
#
sub create_chainlist_reload ($) {
my $ chains = $ _ [ 0 ] ;
my @ chains = split_list $ chains , 'chain' ;
unless ( @ chains ) {
@ chains = qw( blacklst ) if $ filter_table - > { blacklst } ;
push @ chains , 'mangle:' if $ capabilities { MANGLE_ENABLED } && $ config { MANGLE_ENABLED } ;
$ chains = join ( ',' , @ chains ) if @ chains ;
}
$ mode = NULL_MODE ;
emit ( 'chainlist_reload()' ,
'{'
) ;
push_indent ;
if ( @ chains ) {
if ( @ chains == 1 ) {
progress_message2 "Compiling iptables-restore input for chain @chains..." ;
save_progress_message "Preparing iptables-restore input for chain @chains..." ;
} else {
progress_message2 "Compiling iptables-restore input for chains $chains..." ;
save_progress_message "Preparing iptables-restore input for chains $chains..." ;
}
emit '' ;
my $ table = 'filter' ;
my % chains ;
for my $ chain ( @ chains ) {
( $ table , $ chain ) = split ':' , $ chain if $ chain =~ /:/ ;
fatal_error "Invalid table ( $table )" unless $ table =~ /^(nat|mangle|filter)$/ ;
$ chains { $ table } = [] unless $ chains { $ table } ;
if ( $ chain ) {
fatal_error "No $table chain found with name $chain" unless $ chain_table { $ table } { $ chain } ;
fatal_error "Built-in chains may not be refreshed" if $ chain_table { table } { $ chain } { builtin } ;
push @ { $ chains { $ table } } , $ chain ;
} else {
while ( my ( $ chain , $ chainref ) = each % { $ chain_table { $ table } } ) {
push @ { $ chains { $ table } } , $ chain if $ chainref - > { referenced } && ! $ chainref - > { builtin } ;
}
}
}
emit 'exec 3>${VARDIR}/.iptables-restore-input' ;
enter_cat_mode ;
for $ table qw( nat mangle filter ) {
next unless $ chains { $ table } ;
emit_unindented "*$table" ;
my $ tableref = $ chain_table { $ table } ;
@ chains = sort @ { $ chains { $ table } } ;
for my $ chain ( @ chains ) {
my $ chainref = $ tableref - > { $ chain } ;
emit_unindented ":$chainref->{name} - [0:0]" ;
}
for my $ chain ( @ chains ) {
my $ chainref = $ tableref - > { $ chain } ;
my @ rules = @ { $ chainref - > { rules } } ;
@ rules = ( ) unless @ rules ;
#
# Emit the chain rules
#
emitr $ chain , $ _ for ( @ rules ) ;
}
#
# Commit the changes to the table
#
enter_cat_mode unless $ mode == CAT_MODE ;
emit_unindented 'COMMIT' ;
}
enter_cmd_mode ;
#
# Now generate the actual iptables-restore command
#
emit ( 'exec 3>&-' ,
'' ,
'progress_message2 "Running iptables-restore..."' ,
'' ,
'cat ${VARDIR}/.iptables-restore-input | $IPTABLES_RESTORE -n # Use this nonsensical form to appease SELinux' ,
'if [ $? != 0 ]; then' ,
' fatal_error "iptables-restore Failed. Input is in ${VARDIR}/.iptables-restore-input"' ,
"fi\n"
) ;
} else {
emit ( 'true' ) ;
}
pop_indent ;
emit "}\n" ;
}
1 ;