2007-03-15 22:55:22 +01:00
#
2007-06-11 21:39:30 +02:00
# Shorewall-perl 4.0 -- /usr/share/shorewall-perl/Shorewall/Chains.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-04-19 01:43:54 +02:00
# 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.
2007-03-15 22:55:22 +01:00
#
2007-03-14 04:12:22 +01:00
package Shorewall::Chains ;
require Exporter ;
2007-03-15 01:34:17 +01:00
use Shorewall::Config ;
2007-06-20 23:07:08 +02:00
use Shorewall::Ports ;
2007-03-15 01:34:17 +01:00
use Shorewall::Zones ;
2007-05-08 20:25:16 +02:00
use Shorewall::IPAddrs ;
2007-03-15 01:34:17 +01:00
use strict ;
2007-03-14 04:12:22 +01:00
our @ ISA = qw( Exporter ) ;
2007-03-15 01:34:17 +01:00
our @ EXPORT = qw( STANDARD
NATRULE
BUILTIN
NONAT
NATONLY
REDIRECT
ACTION
MACRO
LOGRULE
2007-03-18 19:50:34 +01:00
NO_RESTRICT
PREROUTE_RESTRICT
2007-03-25 21:27:25 +02:00
INPUT_RESTRICT
OUTPUT_RESTRICT
2007-03-18 19:50:34 +01:00
POSTROUTE_RESTRICT
2007-03-25 23:04:24 +02:00
ALL_RESTRICT
2007-03-27 01:17:46 +02:00
2007-05-09 20:22:40 +02:00
process_comment
2007-04-28 15:48:28 +02:00
push_cmd_mode
pop_cmd_mode
2007-03-19 00:06:46 +01:00
add_command
2007-04-27 18:18:42 +02:00
add_commands
2007-05-03 16:00:08 +02:00
mark_referenced
2007-04-10 20:06:09 +02:00
add_file
2007-03-15 01:34:17 +01:00
add_rule
2007-03-14 04:12:22 +01:00
insert_rule
chain_base
forward_chain
input_chain
output_chain
masq_chain
2007-07-19 20:36:04 +02:00
syn_flood_chain
2007-03-14 04:12:22 +01:00
mac_chain
macrecent_target
dynamic_fwd
dynamic_in
dynamic_out
2007-03-14 05:06:32 +01:00
dynamic_chains
2007-03-14 04:12:22 +01:00
dnat_chain
snat_chain
ecn_chain
first_chains
2007-03-14 05:06:32 +01:00
new_chain
ensure_chain
ensure_filter_chain
2007-03-21 00:13:17 +01:00
ensure_mangle_chain
2007-03-14 05:06:32 +01:00
new_standard_chain
new_builtin_chain
initialize_chain_table
finish_section
2007-05-08 04:08:44 +02:00
setup_zone_mss
2007-03-15 01:34:17 +01:00
newexclusionchain
clearrule
do_proto
mac_match
numeric_value
verify_mark
verify_small_mark
validate_mark
do_test
do_ratelimit
do_user
2007-03-21 17:43:42 +01:00
do_tos
2007-06-06 17:34:47 +02:00
match_source_dev
match_dest_dev
2007-03-15 01:34:17 +01:00
iprange_match
match_source_net
match_dest_net
match_orig_dest
match_ipsec_in
match_ipsec_out
log_rule_limit
log_rule
expand_rule
addnatjump
insertnatjump
2007-04-29 16:28:11 +02:00
get_interface_address
2007-03-27 20:41:55 +02:00
get_interface_addresses
2007-05-08 22:36:57 +02:00
set_global_variables
2007-03-15 02:18:29 +01:00
create_netfilter_load
2007-07-03 20:59:42 +02:00
create_blacklist_reload
2007-03-27 01:17:46 +02:00
2007-04-08 16:42:26 +02:00
@ policy_chains
% chain_table
$ nat_table
$ mangle_table
2007-03-14 05:29:14 +01:00
$ filter_table
2007-03-15 01:34:17 +01:00
$ section
% sections
2007-04-08 16:42:26 +02:00
$ comment
2007-03-15 01:34:17 +01:00
% targets
) ;
2007-06-14 01:02:39 +02:00
our @ EXPORT_OK = qw( initialize ) ;
2007-07-21 16:27:04 +02:00
our $ VERSION = 4.01 ;
2007-03-14 04:12:22 +01:00
#
# Chain Table
#
# @policy_chains is a list of references to policy chains in the filter table
#
# %chain_table { <table> => { <chain1> => { name => <chain name>
2007-03-31 00:38:09 +02:00
# table => <table name>
2007-03-14 04:12:22 +01:00
# is_policy => 0|1
# is_optionsl => 0|1
2007-04-08 16:42:26 +02:00
# referenced => 0|1
2007-03-14 04:12:22 +01:00
# policy => <policy>
# loglevel => <level>
# synparams => <burst/limit>
2007-07-19 20:36:04 +02:00
# synchain => <name of synparam chain>
2007-03-14 04:12:22 +01:00
# default => <default action>
# policy_chain => <ref to policy chain -- self-reference if this is a policy chain>
2007-04-28 16:59:18 +02:00
# loopcount => <number of open loops in runtime commands>
# cmdcount => <number of client open loops or blocks in runtime commands>
2007-03-14 04:12:22 +01:00
# rules => [ <rule1>
# <rule2>
# ...
# ]
2007-03-31 00:38:09 +02:00
# } ,
# <chain2> => ...
2007-03-30 22:18:48 +02:00
# }
# }
2007-03-14 04:12:22 +01:00
#
# '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 optional.
#
2007-04-16 21:50:29 +02:00
# Only 'referenced' chains get written to the iptables-restore input.
2007-03-14 04:12:22 +01:00
#
2007-07-19 20:36:04 +02:00
# 'loglevel', 'synparams', 'synchain' and 'default' only apply to policy chains.
2007-03-14 04:12:22 +01:00
#
2007-03-14 05:29:14 +01:00
our @ policy_chains ;
2007-06-14 01:02:39 +02:00
our % chain_table ;
2007-06-15 00:07:45 +02:00
our $ nat_table ;
our $ mangle_table ;
our $ filter_table ;
2007-07-21 17:13:50 +02:00
#
# 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
# in Shorewall::Rules.
#
2007-06-14 01:02:39 +02:00
our % sections ;
our $ section ;
2007-07-21 17:13:50 +02:00
2007-06-15 00:07:45 +02:00
our $ comment ;
2007-06-14 01:02:39 +02:00
2007-03-15 01:34:17 +01:00
use constant { STANDARD = > 1 , #defined by Netfilter
2007-05-03 18:30:59 +02:00
NATRULE = > 2 , #Involves NAT
2007-03-15 01:34:17 +01:00
BUILTIN = > 4 , #A built-in action
NONAT = > 8 , #'NONAT' or 'ACCEPT+'
NATONLY = > 16 , #'DNAT-' or 'REDIRECT-'
REDIRECT = > 32 , #'REDIRECT'
2007-05-03 18:30:59 +02:00
ACTION = > 64 , #An action (may be built-in)
2007-03-15 01:34:17 +01:00
MACRO = > 128 , #A Macro
LOGRULE = > 256 , #'LOG'
} ;
2007-06-15 00:07:45 +02:00
2007-06-14 01:02:39 +02:00
our % targets ;
2007-03-15 01:34:17 +01:00
#
2007-06-14 01:02:39 +02:00
# expand_rule() restrictions
2007-03-15 01:34:17 +01:00
#
2007-06-14 01:02:39 +02:00
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
} ;
2007-06-15 00:07:45 +02:00
our $ exclseq ;
our $ iprangematch ;
2007-06-14 01:02:39 +02:00
our $ chainseq ;
2007-06-23 18:06:16 +02:00
our % interfaceaddr ;
our % interfaceaddrs ;
our % interfacenets ;
2007-06-23 18:32:37 +02:00
our @ builtins = qw( PREROUTING INPUT FORWARD OUTPUT POSTROUTING ) ;
#
# State of the generator.
#
use constant { NULL_STATE = > 0 , # Generating neither shell commands nor iptables-restore input
CAT_STATE = > 1 , # Generating iptables-restore input
CMD_STATE = > 2 } ; # Generating shell commands.
our $ state ;
2007-06-24 16:07:53 +02:00
our $ emitted_comment ;
2007-06-23 18:32:37 +02:00
2007-06-15 00:07:45 +02: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.
#
2007-06-14 01:02:39 +02:00
sub initialize () {
@ policy_chains = ( ) ;
% 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 = '' ;
#
# As new targets (Actions and Macros) are discovered, they are added to the table
#
% targets = ( 'ACCEPT' = > STANDARD ,
2007-03-15 01:34:17 +01:00
'ACCEPT+' = > STANDARD + NONAT ,
'ACCEPT!' = > STANDARD ,
2007-04-19 18:23:40 +02:00
'NONAT' = > STANDARD + NONAT + NATONLY ,
2007-03-15 01:34:17 +01:00
'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 ,
2007-04-30 17:07:51 +02:00
'CONTINUE!' = > STANDARD ,
2007-03-15 01:34:17 +01:00
'QUEUE' = > STANDARD ,
2007-04-30 17:07:51 +02:00
'QUEUE!' = > STANDARD ,
2007-03-15 01:34:17 +01:00
'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 ,
) ;
2007-06-14 01:02:39 +02:00
#
# Used to sequence 'exclusion' chains with names 'excl0', 'excl1', ...
#
$ exclseq = 0 ;
#
# Used to suppress duplicate match specifications.
#
$ iprangematch = 0 ;
#
# Sequence for naming temporary chains
#
2007-06-19 01:04:17 +02:00
$ chainseq = undef ;
2007-06-23 18:06:16 +02:00
#
# Keep track of which interfaces have active 'address', 'addresses' and 'networks' variables
#
% interfaceaddr = ( ) ;
% interfaceaddrs = ( ) ;
% interfacenets = ( ) ;
2007-06-23 18:32:37 +02:00
#
2007-06-24 16:07:53 +02:00
# When true, we've emitted a comment about global variable initialization
#
$ emitted_comment = 0 ;
2007-06-14 01:02:39 +02:00
}
INIT {
initialize ;
}
2007-03-18 19:50:34 +01:00
2007-03-14 04:12:22 +01:00
#
2007-03-18 22:16:46 +01:00
# Add a run-time command to a chain. Arguments are:
#
# Chain reference , Command
#
2007-03-23 18:30:12 +01:00
2007-05-09 20:22:40 +02:00
#
2007-07-07 18:34:38 +02:00
# Process a COMMENT line (in $currentline)
2007-05-09 20:22:40 +02:00
#
sub process_comment () {
if ( $ capabilities { COMMENTS } ) {
2007-07-07 18:34:38 +02:00
( $ comment = $ currentline ) =~ s/^\s*COMMENT\s*// ;
2007-06-30 18:43:08 +02:00
$ comment =~ s/\s*$// ;
2007-05-09 20:22:40 +02:00
} else {
warning_message "COMMENT ignored -- requires comment support in iptables/Netfilter" ;
}
}
2007-04-28 15:48:28 +02:00
#
# Functions to manipulate cmdcount
#
2007-04-28 16:59:18 +02:00
sub push_cmd_mode ( $ ) {
$ _ [ 0 ] - > { cmdcount } + + ;
2007-04-28 15:48:28 +02:00
}
2007-04-28 17:08:17 +02:00
sub pop_cmd_mode ( $ ) {
2007-04-28 16:59:18 +02:00
fatal_error "Internal error in pop_cmd_mode()" if - - $ _ [ 0 ] - > { cmdcount } < 0 ;
2007-04-28 15:48:28 +02:00
}
2007-03-18 22:16:46 +01:00
sub add_command ($$)
2007-03-14 04:12:22 +01:00
{
2007-03-18 22:16:46 +01:00
my ( $ chainref , $ command ) = @ _ ;
2007-03-27 01:17:46 +02:00
2007-04-28 16:59:18 +02:00
push @ { $ chainref - > { rules } } , join ( '' , '~' , ' ' x ( $ chainref - > { loopcount } + $ chainref - > { cmdcount } ) , $ command ) ;
2007-03-14 04:12:22 +01:00
$ chainref - > { referenced } = 1 ;
}
2007-03-23 20:19:43 +01:00
2007-04-27 18:18:42 +02:00
sub add_commands {
my $ chainref = shift @ _ ;
for my $ command ( @ _ ) {
2007-04-28 16:59:18 +02:00
push @ { $ chainref - > { rules } } , join ( '' , '~' , ' ' x ( $ chainref - > { loopcount } + $ chainref - > { cmdcount } ) , $ command ) ;
2007-04-27 18:18:42 +02:00
}
$ chainref - > { referenced } = 1 ;
}
2007-05-03 16:00:08 +02:00
sub mark_referenced ( $ ) {
my $ chainref = shift @ _ ;
$ chainref - > { referenced } = 1 ;
}
2007-04-27 18:18:42 +02:00
2007-04-10 20:06:09 +02:00
#
# Copy a file into a chain's rules as a set of run-time commands
#
sub add_file ( $$ ) {
my $ chainref = $ _ [ 0 ] ;
my $ file = find_file $ _ [ 1 ] ;
if ( - f $ file ) {
2007-05-09 22:55:54 +02:00
open EF , '<' , $ file or fatal_error "Unable to open $file: $!" ;
2007-04-10 20:06:09 +02:00
2007-04-27 18:18:42 +02:00
add_commands ( $ chainref ,
qq( progress_message "Processing $file..." ) ,
'' ) ;
2007-04-10 20:06:09 +02:00
2007-07-07 18:34:38 +02:00
while ( my $ line = <EF> ) {
2007-04-11 00:52:45 +02:00
chomp $ line ;
2007-04-10 20:06:09 +02:00
add_command $ chainref , $ line ;
}
add_command $ chainref , '' ;
close EF ;
}
}
2007-03-18 19:50:34 +01:00
#
2007-03-18 22:16:46 +01:00
# Add a rule to a chain. Arguments are:
2007-03-18 19:50:34 +01:00
#
2007-03-18 22:16:46 +01:00
# Chain reference , Rule
2007-03-18 19:50:34 +01:00
#
2007-03-18 22:16:46 +01:00
sub add_rule ($$)
2007-03-18 19:50:34 +01:00
{
2007-03-18 22:16:46 +01:00
my ( $ chainref , $ rule ) = @ _ ;
2007-03-27 01:17:46 +02:00
2007-06-20 20:58:44 +02:00
$ iprangematch = 0 ;
2007-04-28 16:59:18 +02:00
if ( $ chainref - > { loopcount } || $ chainref - > { cmdcount } ) {
2007-05-09 19:30:28 +02:00
$ rule . = " -m comment --comment \\\"$comment\\\"" if $ comment ;
2007-03-23 18:30:12 +01:00
add_command $ chainref , qq( echo "-A $chainref->{name} $rule" >&3 ) ;
} else {
2007-05-09 19:30:28 +02:00
$ rule . = " -m comment --comment \"$comment\"" if $ comment ;
2007-03-23 18:30:12 +01:00
push @ { $ chainref - > { rules } } , $ rule ;
$ chainref - > { referenced } = 1 ;
}
2007-03-18 19:50:34 +01:00
}
2007-03-14 04:12:22 +01:00
#
# Insert a rule into a chain. Arguments are:
#
# Table , Chain , Rule Number, Rule
#
sub insert_rule ($$$)
{
my ( $ chainref , $ number , $ rule ) = @ _ ;
2007-03-23 18:30:12 +01:00
2007-04-28 16:59:18 +02:00
fatal_error 'Internal Error in insert_rule()' if $ chainref - > { loopcount } || $ chainref - > { cmdcount } ;
2007-03-27 01:17:46 +02:00
2007-03-14 04:12:22 +01:00
$ rule . = "-m comment --comment \"$comment\"" if $ comment ;
splice @ { $ chainref - > { rules } } , $ number - 1 , 0 , $ rule ;
2007-06-20 20:58:44 +02:00
$ iprangematch = 0 ;
2007-03-14 04:12:22 +01:00
$ chainref - > { referenced } = 1 ;
2007-03-27 01:17:46 +02:00
2007-03-14 04:12:22 +01:00
}
#
2007-04-08 16:42:26 +02:00
# Form the name of a chain.
2007-03-14 04:12:22 +01:00
#
sub chain_base ($) {
my $ chain = $ _ [ 0 ] ;
$ chain =~ s/^@/at_/ ;
2007-03-26 17:22:36 +02:00
$ chain =~ tr /[.\-%@]/ _ / ;
2007-03-25 23:04:24 +02:00
$ chain =~ s/\+$// ;
2007-03-14 04:12:22 +01:00
$ chain ;
}
#
# Forward Chain for an interface
#
sub forward_chain ($)
{
2007-03-26 17:22:36 +02:00
chain_base ( $ _ [ 0 ] ) . '_fwd' ;
2007-03-14 04:12:22 +01:00
}
#
# Input Chain for an interface
#
sub input_chain ($)
{
2007-03-26 17:22:36 +02:00
chain_base ( $ _ [ 0 ] ) . '_in' ;
2007-03-14 04:12:22 +01:00
}
#
# Output Chain for an interface
#
sub output_chain ($)
{
2007-03-26 17:22:36 +02:00
chain_base ( $ _ [ 0 ] ) . '_out' ;
2007-03-14 04:12:22 +01:00
}
#
# Masquerade Chain for an interface
#
sub masq_chain ($)
{
2007-03-26 17:22:36 +02:00
chain_base ( $ _ [ 0 ] ) . '_masq' ;
2007-03-14 04:12:22 +01:00
}
#
2007-07-19 20:36:04 +02:00
# Syn_flood_chain -- differs from the other _chain functions in that the argument is a chain table reference
2007-03-14 04:12:22 +01:00
#
2007-07-19 20:36:04 +02:00
sub syn_flood_chain ( $ ) {
'@' . $ _ [ 0 ] - > { synchain } ;
2007-03-14 04:12:22 +01:00
}
#
# MAC Verification Chain for an interface
#
sub mac_chain ( $ )
{
2007-03-26 17:22:36 +02:00
chain_base ( $ _ [ 0 ] ) . '_mac' ;
2007-03-14 04:12:22 +01:00
}
sub macrecent_target ($)
{
2007-03-26 17:22:36 +02:00
$ config { MACLIST_TTL } ? chain_base ( $ _ [ 0 ] ) . '_rec' : 'RETURN' ;
2007-03-14 04:12:22 +01:00
}
#
# Functions for creating dynamic zone rules
#
sub dynamic_fwd ( $ )
{
2007-03-26 17:22:36 +02:00
chain_base ( $ _ [ 0 ] ) . '_dynf' ;
2007-03-14 04:12:22 +01:00
}
sub dynamic_in ( $ )
{
2007-03-26 17:22:36 +02:00
chain_base ( $ _ [ 0 ] ) . '_dyni' ;
2007-03-14 04:12:22 +01:00
}
sub dynamic_out ( $ ) # $ 1 = interface
{
2007-04-17 17:37:32 +02:00
chain_base ( $ _ [ 0 ] ) . '_dyno' ;
2007-03-14 04:12:22 +01:00
}
sub dynamic_chains ( $ ) # $ 1 = interface
{
2007-03-26 17:22:36 +02:00
my $ c = chain_base ( $ _ [ 0 ] ) ;
2007-03-14 04:12:22 +01:00
[ $ c . '_dyni' , $ c . '_dynf' , $ c . '_dyno' ] ;
}
#
# DNAT Chain from a zone
#
sub dnat_chain ( $ )
{
2007-03-26 17:22:36 +02:00
chain_base ( $ _ [ 0 ] ) . '_dnat' ;
2007-03-14 04:12:22 +01:00
}
#
# SNAT Chain to an interface
#
sub snat_chain ( $ )
{
2007-03-26 17:22:36 +02:00
chain_base ( $ _ [ 0 ] ) . '_snat' ;
2007-03-14 04:12:22 +01:00
}
#
# ECN Chain to an interface
#
sub ecn_chain ( $ )
{
2007-03-26 17:22:36 +02:00
chain_base ( $ _ [ 0 ] ) . '_ecn' ;
2007-03-14 04:12:22 +01:00
}
#
# First chains for an interface
#
sub first_chains ( $ ) # $ 1 = interface
{
my $ c = chain_base $ _ [ 0 ] ;
[ $ c . '_fwd' , $ c . '_in' ] ;
}
2007-03-14 05:06:32 +01:00
#
# Create a new chain and return a reference to it.
#
sub new_chain ($$)
{
my ( $ table , $ chain ) = @ _ ;
2007-03-27 01:17:46 +02:00
2007-06-23 18:06:16 +02:00
$ chain_table { $ table } { $ chain } = { name = > $ chain ,
rules = > [] ,
table = > $ table ,
loglevel = > '' ,
log = > 1 ,
loopcount = > 0 ,
cmdcount = > 0 } ;
2007-03-14 05:06:32 +01:00
}
2007-03-18 22:16:46 +01:00
#
# Create an anonymous chain
#
sub new_anon_chain ( $ ) {
my $ chainref = $ _ [ 0 ] ;
my $ seq = $ chainseq + + ;
new_chain ( $ chainref - > { table } , 'chain' . "$seq" ) ;
}
#
2007-03-14 05:06:32 +01:00
#
# Create a chain if it doesn't exist already
#
sub ensure_chain ($$)
{
my ( $ table , $ chain ) = @ _ ;
my $ ref = $ chain_table { $ table } { $ chain } ;
2007-03-27 01:17:46 +02:00
2007-03-14 05:06:32 +01:00
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' ;
2007-05-08 02:48:05 +02:00
} elsif ( $ section eq 'RELATED' ) {
2007-03-14 05:06:32 +01:00
finish_chain_section $ chainref , 'ESTABLISHED' ;
}
}
$ chainref - > { referenced } = 1 ;
2007-03-27 01:17:46 +02:00
2007-03-14 05:06:32 +01:00
$ chainref ;
}
2007-03-21 00:13:17 +01:00
sub ensure_mangle_chain ($) {
my $ chain = $ _ [ 0 ] ;
my $ chainref = ensure_chain 'mangle' , $ chain ;
2007-03-27 01:17:46 +02:00
2007-03-21 00:13:17 +01:00
$ chainref - > { referenced } = 1 ;
$ chainref ;
}
2007-03-14 05:06:32 +01:00
#
# Add a builtin chain
#
sub new_builtin_chain ($$$)
{
2007-03-25 03:03:43 +02:00
my ( $ table , $ chain , $ policy ) = @ _ ;
2007-03-27 01:17:46 +02:00
2007-03-25 03:03:43 +02:00
my $ chainref = new_chain $ table , $ chain ;
2007-03-14 05:06:32 +01:00
$ chainref - > { referenced } = 1 ;
2007-03-25 03:03:43 +02:00
$ chainref - > { policy } = $ policy ;
2007-03-14 05:06:32 +01:00
$ chainref - > { builtin } = 1 ;
}
sub new_standard_chain ($) {
my $ chainref = new_chain 'filter' , $ _ [ 0 ] ;
$ chainref - > { referenced } = 1 ;
$ chainref ;
2007-03-27 01:17:46 +02:00
}
2007-03-14 05:06:32 +01:00
#
# Add all builtin chains to the chain table
#
#
sub initialize_chain_table ()
{
2007-07-21 17:13:50 +02:00
for my $ chain qw( OUTPUT PREROUTING ) {
2007-03-14 05:06:32 +01:00
new_builtin_chain 'raw' , $ chain , 'ACCEPT' ;
}
2007-07-21 17:13:50 +02:00
for my $ chain qw( INPUT OUTPUT FORWARD ) {
2007-03-14 05:06:32 +01:00
new_builtin_chain 'filter' , $ chain , 'DROP' ;
}
2007-07-21 17:13:50 +02:00
for my $ chain qw( PREROUTING POSTROUTING OUTPUT ) {
2007-03-14 05:06:32 +01:00
new_builtin_chain 'nat' , $ chain , 'ACCEPT' ;
}
2007-07-21 17:13:50 +02:00
for my $ chain qw( PREROUTING INPUT FORWARD OUTPUT POSTROUTING ) {
2007-03-14 05:06:32 +01:00
new_builtin_chain 'mangle' , $ chain , 'ACCEPT' ;
}
2007-03-27 01:17:46 +02:00
2007-03-14 05:06:32 +01:00
if ( $ capabilities { MANGLE_FORWARD } ) {
2007-07-21 17:13:50 +02:00
for my $ chain qw( FORWARD POSTROUTING ) {
2007-03-14 05:06:32 +01:00
new_builtin_chain 'mangle' , $ chain , 'ACCEPT' ;
}
}
}
#
2007-04-08 16:42:26 +02:00
# Add ESTABLISHED,RELATED rules and synparam jumps to the passed chain
2007-03-14 05:06:32 +01:00
#
sub finish_chain_section ($$) {
my ( $ chainref , $ state ) = @ _ ;
my $ chain = $ chainref - > { name } ;
add_rule $ chainref , "-m state --state $state -j ACCEPT" unless $ config { FASTACCEPT } ;
2007-03-27 01:17:46 +02:00
2007-03-14 05:06:32 +01:00
if ( $ sections { RELATED } ) {
if ( $ chainref - > { is_policy } ) {
if ( $ chainref - > { synparams } ) {
2007-07-19 20:36:04 +02:00
my $ synchainref = ensure_chain 'filter' , syn_flood_chain $ chainref ;
2007-03-14 05:06:32 +01:00
if ( $ section eq 'DONE' ) {
if ( $ chainref - > { policy } =~ /^(ACCEPT|CONTINUE|QUEUE)$/ ) {
add_rule $ chainref , "-p tcp --syn -j $synchainref->{name}" ;
2007-04-08 16:42:26 +02:00
}
2007-03-14 05:06:32 +01:00
} else {
add_rule $ chainref , "-p tcp --syn -j $synchainref->{name}" ;
}
}
} else {
2007-06-14 01:02:39 +02:00
my $ policychainref = $ filter_table - > { $ chainref - > { policychain } } ;
2007-03-14 05:06:32 +01:00
if ( $ policychainref - > { synparams } ) {
2007-07-19 20:36:04 +02:00
my $ synchainref = ensure_chain 'filter' , syn_flood_chain $ policychainref ;
2007-05-26 04:57:27 +02:00
add_rule $ chainref , "-p tcp --syn -j $synchainref->{name}" ;
2007-03-14 05:06:32 +01:00
}
}
}
2007-03-27 01:17:46 +02:00
}
2007-03-14 05:06:32 +01:00
#
# Do section-end processing
2007-04-08 16:42:26 +02:00
#
2007-03-14 05:06:32 +01:00
sub finish_section ( $ ) {
my $ sections = $ _ [ 0 ] ;
2007-05-10 17:29:41 +02:00
for my $ section ( split /,/ , $ sections ) {
$ sections { $ section } = 1 ;
}
2007-03-14 05:06:32 +01:00
for my $ zone ( @ zones ) {
for my $ zone1 ( @ zones ) {
2007-05-08 02:48:05 +02:00
my $ chainref = $ chain_table { 'filter' } { "${zone}2${zone1}" } ;
2007-03-14 05:06:32 +01:00
if ( $ chainref - > { referenced } ) {
finish_chain_section $ chainref , $ sections ;
}
}
}
}
2007-05-08 04:08:44 +02:00
#
# Helper for set_mss
#
sub set_mss1 ( $$ ) {
2007-05-08 05:38:18 +02:00
my ( $ chain , $ mss ) = @ _ ;
2007-05-08 04:08:44 +02:00
my $ chainref = ensure_chain 'filter' , $ chain ;
2007-05-08 17:40:27 +02:00
if ( $ chainref - > { policy } ne 'NONE' ) {
2007-06-16 16:27:02 +02:00
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"
2007-05-08 04:08:44 +02:00
}
}
#
# Set up rules to set MSS to and/or from zone "$zone"
#
sub set_mss ( $$$ ) {
my ( $ zone , $ mss , $ direction ) = @ _ ;
for my $ z ( @ 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 ;
}
}
}
2007-05-08 05:38:18 +02:00
#
# Interate over non-firewall zones adding TCPMSS rules as appropriate
#
2007-05-08 04:08:44 +02:00
sub setup_zone_mss () {
2007-05-08 05:44:18 +02:00
for my $ zone ( @ zones ) {
2007-05-08 04:08:44 +02:00
my $ zoneref = $ zones { $ 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 } ;
}
}
2007-03-15 01:34:17 +01:00
sub newexclusionchain () {
my $ seq = $ exclseq + + ;
"excl${seq}" ;
}
sub clearrule () {
2007-03-24 23:26:32 +01:00
$ iprangematch = 0 ;
2007-03-15 01:34:17 +01:00
}
2007-04-24 21:53:13 +02:00
sub validate_proto ( $ ) {
my $ proto = $ _ [ 0 ] ;
my $ value = $ protocols { $ proto } ;
return $ value if defined $ value ;
return $ proto if $ proto =~ /^(\d+)$/ && $ proto <= 65535 ;
return $ proto if $ proto eq 'all' ;
2007-06-20 05:25:43 +02:00
fatal_error "Invalid/Unknown protocol ($proto)" ;
2007-04-24 21:53:13 +02:00
}
sub validate_portpair ( $ ) {
my $ portpair = $ _ [ 0 ] ;
fatal_error "Invalid port range ($portpair)" if $ portpair =~ tr /:/ : / > 1 ;
$ portpair = "0$portpair" if substr ( $ portpair , 0 , 1 ) eq ':' ;
$ portpair = "${portpair}65535" if substr ( $ portpair , - 1 , 1 ) eq ':' ;
2007-06-20 05:25:43 +02:00
my @ ports = split /:/ , $ portpair , 2 ;
2007-05-17 16:10:46 +02:00
2007-06-20 20:10:23 +02:00
for my $ port ( @ ports ) {
my $ value = $ services { $ port } ;
unless ( defined $ value ) {
$ value = $ port if $ port =~ /^(\d+)$/ && $ port <= 65535 ;
2007-06-20 03:12:35 +02:00
}
2007-06-20 20:10:23 +02:00
fatal_error "Invalid/Unknown port/service ($port)" unless defined $ value ;
$ port = $ value ;
}
2007-04-24 21:53:13 +02:00
2007-06-20 20:10:23 +02:00
if ( @ ports == 2 ) {
fatal_error "Invalid port range ($portpair)" unless $ ports [ 0 ] < $ ports [ 1 ] ;
2007-04-24 21:53:13 +02:00
}
join ':' , @ ports ;
}
sub validate_port_list ( $ ) {
my $ result = '' ;
for my $ port ( split /,/ , $ _ [ 0 ] ) {
my $ value = validate_portpair ( $ port ) ;
$ result = $ result ? join ',' , $ result , $ value : $ value ;
}
$ result ;
}
my % icmp_types = ( any = > 'any' ,
'echo-reply' = > 0 ,
'destination-unreachable' = > 3 ,
'network-unreachable' = > '3/0' ,
'host-unreachable' = > '3/1' ,
'protocol-unreachable' = > '3/2' ,
'port-unreachable' = > '3/3' ,
'fragmentation-needed' = > '3/4' ,
'source-route-failed' = > '3/5' ,
'network-unknown' = > '3/6' ,
'host-unknown' = > '3/7' ,
'network-prohibited' = > '3/9' ,
'host-prohibited' = > '3/10' ,
'TOS-network-unreachable' = > '3/11' ,
'TOS-host-unreachable' = > '3/12' ,
'communication-prohibited' = > '3/13' ,
'host-precedence-violation' = > '3/14' ,
'precedence-cutoff' = > '3/15' ,
'source-quench' = > 4 ,
'redirect' = > 5 ,
'network-redirect' = > '5/0' ,
'host-redirect' = > '5/1' ,
'TOS-network-redirect' = > '5/2' ,
'TOS-host-redirect' = > '5/3' ,
'echo-request' = > '8' ,
'router-advertisement' = > 9 ,
'router-solicitation' = > 10 ,
'time-exceeded' = > 11 ,
'ttl-zero-during-transit' = > '11/0' ,
'ttl-zero-during-reassembly' = > '11/1' ,
'parameter-problem' = > 12 ,
'ip-header-bad' = > '12/0' ,
'required-option-missing' = > '12/1' ,
'timestamp-request' = > 13 ,
'timestamp-reply' = > 14 ,
'address-mask-request' = > 17 ,
'address-mask-reply' = > 18 ) ;
sub validate_icmp ( $ ) {
my $ type = $ _ [ 0 ] ;
my $ value = $ icmp_types { $ type } ;
return $ value if defined $ value ;
2007-06-22 16:47:16 +02:00
if ( $ type =~ /^(\d+)(\/(\d+))?$/ ) {
return $ type if $ 1 < 256 && ( ! $ 2 || $ 3 < 256 ) ;
}
2007-04-24 21:53:13 +02:00
fatal_error "Invalid ICMP Type ($type)"
}
2007-03-15 01:34:17 +01:00
#
# Handle parsing of PROTO, DEST PORT(S) , SOURCE PORTS(S). Returns the appropriate match string.
#
sub do_proto ( $ $ $ )
{
my ( $ proto , $ ports , $ sports ) = @ _ ;
2007-04-18 05:39:25 +02:00
#
# Return the number of ports represented by the passed list
#
2007-04-18 05:24:38 +02:00
sub port_count ( $ ) {
2007-04-18 05:39:25 +02:00
( $ _ [ 0 ] =~ tr /,:/ , : / ) + 1 ;
2007-04-18 05:24:38 +02:00
}
2007-03-15 01:34:17 +01:00
my $ output = '' ;
2007-03-27 01:17:46 +02:00
2007-03-15 01:34:17 +01:00
$ proto = '' if $ proto eq '-' ;
$ ports = '' if $ ports eq '-' ;
$ sports = '' if $ sports eq '-' ;
if ( $ proto ) {
2007-05-01 03:02:55 +02:00
if ( $ proto =~ /^(((tcp|6)((:syn)?))|(udp|17))$/ ) {
2007-04-19 23:14:18 +02:00
2007-05-01 03:02:55 +02:00
if ( $ 4 ) {
2007-04-24 21:53:13 +02:00
$ output = '-p 6 --syn ' ;
2007-04-19 23:14:18 +02:00
} else {
2007-04-24 21:53:13 +02:00
$ proto = $ protocols { $ proto } if defined $ protocols { $ proto } ;
2007-04-19 23:14:18 +02:00
$ output = "-p $proto " ;
}
2007-04-18 01:14:42 +02:00
2007-05-01 19:24:44 +02:00
my $ multiport = 0 ;
2007-05-01 18:18:45 +02:00
2007-04-21 16:07:37 +02:00
if ( $ ports ne '' ) {
2007-05-01 19:24:44 +02:00
if ( $ ports =~ tr /,/ , / > 0 || $sports =~ tr/ , /,/ > 0 ) {
2007-06-16 23:08:12 +02:00
fatal_error "Port list requires Multiport support in your kernel/iptables ($ports)" unless $ capabilities { MULTIPORT } ;
fatal_error "Too many entries in port list ($ports)" if port_count ( $ ports ) > 15 ;
2007-04-24 21:53:13 +02:00
$ ports = validate_port_list $ ports ;
2007-03-15 01:34:17 +01:00
$ output . = "-m multiport --dports $ports " ;
2007-05-01 19:24:44 +02:00
$ multiport = 1 ;
2007-03-15 01:34:17 +01:00
} else {
2007-04-24 21:53:13 +02:00
$ ports = validate_portpair $ ports ;
2007-03-15 01:34:17 +01:00
$ output . = "--dport $ports " ;
}
2007-05-14 16:14:38 +02:00
} else {
$ multiport = ( ( $ sports =~ tr /,/ , / ) > 0 ) ;
2007-03-15 01:34:17 +01:00
}
2007-03-27 01:17:46 +02:00
2007-04-21 16:07:37 +02:00
if ( $ sports ne '' ) {
2007-05-11 00:28:03 +02:00
if ( $ multiport ) {
2007-06-16 23:08:12 +02:00
fatal_error "Too many entries in port list ($sports)" if port_count ( $ sports ) > 15 ;
2007-04-24 21:53:13 +02:00
$ sports = validate_port_list $ sports ;
2007-03-15 01:34:17 +01:00
$ output . = "-m multiport --sports $sports " ;
} else {
2007-04-24 21:53:13 +02:00
$ sports = validate_portpair $ sports ;
2007-03-15 01:34:17 +01:00
$ output . = "--sport $sports " ;
}
}
} elsif ( $ proto =~ /^(icmp|1)$/i ) {
2007-04-18 01:30:09 +02:00
fatal_error 'Multiple ICMP types are not permitted' if $ ports =~ /,/ ;
2007-04-17 17:07:11 +02:00
$ output . = "-p icmp " ;
2007-04-24 21:53:13 +02:00
if ( $ ports ne '' ) {
$ ports = validate_icmp $ ports ;
$ output . = "--icmp-type $ports " ;
}
2007-04-17 22:59:10 +02:00
fatal_error 'SOURCE PORT(S) not permitted with ICMP' if $ sports ne '' ;
2007-05-01 03:02:55 +02:00
} elsif ( $ proto =~ /^(ipp2p(:(tcp|udp|all))?)$/i ) {
2007-05-04 17:12:29 +02:00
require_capability ( 'IPP2P_MATCH' , 'PROTO = ipp2p' , 's' ) ;
2007-03-15 01:34:17 +01:00
$ proto = $ 2 ? $ 3 : 'tcp' ;
$ ports = 'ipp2p' unless $ ports ;
$ output . = "-p $proto -m ipp2p --$ports " ;
2007-03-20 21:06:52 +01:00
} else {
2007-05-15 22:04:34 +02:00
fatal_error "SOURCE/DEST PORT(S) not allowed with PROTO $proto" if $ ports ne '' || $ sports ne '' ;
2007-04-24 21:53:13 +02:00
$ proto = validate_proto $ proto ;
2007-03-20 21:06:52 +01:00
$ output . = "-p $proto " ;
2007-03-15 01:34:17 +01:00
}
2007-04-17 22:59:10 +02:00
} elsif ( $ ports ne '' || $ sports ne '' ) {
2007-05-15 22:04:34 +02:00
fatal_error "SOURCE/DEST PORT(S) not allowed without PROTO"
2007-03-15 01:34:17 +01:00
}
$ output ;
}
sub mac_match ( $ ) {
my $ mac = $ _ [ 0 ] ;
$ mac =~ s/^(!?)~// ;
2007-04-08 16:42:26 +02:00
$ mac =~ s/^!// if my $ invert = ( $ 1 ? '! ' : '' ) ;
2007-04-18 01:30:09 +02:00
$ mac =~ tr /-/ : / ;
2007-03-15 01:34:17 +01:00
"--match mac --mac-source ${invert}$mac " ;
}
#
# Convert value to decimal number
#
sub numeric_value ( $ ) {
my $ mark = $ _ [ 0 ] ;
2007-05-03 19:30:30 +02:00
fatal_error "Invalid Numeric Value ($mark)" unless "\L$mark" =~ /^(0x[a-f0-9]+|0[0-7]*|[1-9]\d*)$/ ;
2007-03-15 01:34:17 +01:00
$ mark =~ /^0x/ ? hex $ mark : $ mark =~ /^0/ ? oct $ mark : $ mark ;
}
#
# Mark validatation functions
#
sub verify_mark ( $ ) {
my $ mark = $ _ [ 0 ] ;
my $ limit = $ config { HIGH_ROUTE_MARKS } ? 0xFFFF : 0xFF ;
2007-06-16 23:08:12 +02:00
fatal_error "Invalid Mark or Mask value ($mark)"
2007-03-31 00:38:09 +02:00
unless numeric_value ( $ mark ) <= $ limit ;
2007-03-15 01:34:17 +01:00
}
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 ) = @ _ ;
2007-03-27 01:17:46 +02:00
2007-03-15 01:34:17 +01:00
return '' unless $ testval and $ testval ne '-' ;
my $ invert = $ testval =~ s/^!// ? '! ' : '' ;
2007-05-01 17:55:41 +02:00
my $ match = $ testval =~ s/:C$// ? "-m connmark ${invert}--mark" : "-m mark ${invert}--mark" ;
2007-03-27 01:17:46 +02:00
2007-05-03 19:10:07 +02:00
validate_mark $ testval ;
2007-03-15 01:34:17 +01:00
$ testval . = '/0xFF' unless ( $ testval =~ '/' ) ;
2007-05-01 17:55:41 +02:00
"$match $testval " ;
2007-03-15 01:34:17 +01:00
}
2007-03-27 01:17:46 +02:00
2007-05-03 18:33:49 +02:00
my % norate = ( DROP = > 1 , REJECT = > 1 ) ;
2007-03-15 01:34:17 +01:00
#
# Create a "-m limit" match for the passed LIMIT/BURST
#
2007-05-03 18:30:59 +02:00
sub do_ratelimit ( $$ ) {
my ( $ rate , $ action ) = @ _ ;
2007-03-15 01:34:17 +01:00
return '' unless $ rate and $ rate ne '-' ;
2007-03-27 01:17:46 +02:00
2007-05-03 18:33:49 +02:00
fatal_error "Rate Limiting not available with $action" if $ norate { $ action } ;
2007-05-03 18:30:59 +02:00
2007-05-03 19:01:36 +02:00
if ( $ rate =~ /^(\d+(\/(sec|hour|day))?):(\d+)$/ ) {
2007-05-03 18:56:05 +02:00
"-m limit --limit $1 --limit-burst $4 " ;
} elsif ( $ rate =~ /^(\d+)(\/(sec|hour|day))?$/ ) {
2007-03-15 01:34:17 +01:00
"-m limit --limit $rate " ;
2007-05-03 18:56:05 +02:00
} else {
fatal_error "Invalid rate ($rate)" ;
2007-03-15 01:34:17 +01:00
}
}
#
# Create a "-m owner" match for the passed USER/GROUP
#
sub do_user ( $ ) {
my $ user = $ _ [ 0 ] ;
2007-04-18 17:43:04 +02:00
my $ rule = '-m owner ' ;
2007-03-15 01:34:17 +01:00
2007-04-18 17:49:07 +02:00
return '' unless defined $ user and $ user ne '-' ;
2007-03-15 01:34:17 +01:00
if ( $ user =~ /^(!)?(.*)\+(.*)$/ ) {
2007-05-02 02:33:30 +02:00
$ rule . = "! --cmd-owner $2 " if defined $ 2 && $ 2 ne '' ;
2007-03-15 01:34:17 +01:00
$ user = "!$1" ;
} elsif ( $ user =~ /^(.*)\+(.*)$/ ) {
2007-05-02 02:33:30 +02:00
$ rule . = "--cmd-owner $2 " if defined $ 2 && $ 2 ne '' ;
2007-03-15 01:34:17 +01:00
$ user = $ 1 ;
}
2007-03-27 01:17:46 +02:00
2007-03-15 01:34:17 +01:00
if ( $ user =~ /^!(.*):(.*)$/ ) {
2007-05-02 02:33:30 +02:00
$ rule . = "! --uid-owner $1 " if defined $ 1 && $ 1 ne '' ;
$ rule . = "! --gid-owner $2 " if defined $ 2 && $ 2 ne '' ;
2007-03-15 01:34:17 +01:00
} elsif ( $ user =~ /^(.*):(.*)$/ ) {
2007-05-02 02:33:30 +02:00
$ rule . = "--uid-owner $1 " if defined $ 1 && $ 1 ne '' ;
$ rule . = "--gid-owner $2 " if defined $ 2 && $ 2 ne '' ;
2007-03-15 01:34:17 +01:00
} elsif ( $ user =~ /^!/ ) {
$ rule . = "! --uid-owner $user " ;
} else {
$ rule . = "--uid-owner $user " ;
}
$ rule ;
}
2007-03-21 17:43:42 +01:00
#
2007-03-21 18:09:52 +01:00
# Create a "-m tos" match for the passed TOS
2007-03-21 17:43:42 +01:00
#
sub do_tos ( $ ) {
my $ tos = $ _ [ 0 ] ;
2007-03-27 01:17:46 +02:00
2007-03-21 18:09:52 +01:00
$ tos ne '-' ? "-m tos --tos $tos " : '' ;
2007-03-27 01:17:46 +02:00
}
2007-06-06 17:34:47 +02:00
#
# Match Source Interface
#
sub match_source_dev ( $ ) {
my $ interface = shift ;
my $ interfaceref = $ interfaces { $ interface } ;
2007-06-06 22:06:16 +02:00
if ( $ interfaceref && $ interfaceref - > { options } { port } ) {
2007-06-06 17:34:47 +02:00
"-i $interfaceref->{bridge} -m physdev --physdev-in $interface " ;
} else {
"-i $interface " ;
}
}
#
# Match Dest device
#
sub match_dest_dev ( $ ) {
my $ interface = shift ;
my $ interfaceref = $ interfaces { $ interface } ;
2007-06-06 22:06:16 +02:00
if ( $ interfaceref && $ interfaceref - > { options } { port } ) {
2007-06-06 17:34:47 +02:00
"-o $interfaceref->{bridge} -m physdev --physdev-out $interface " ;
} else {
"-o $interface " ;
}
}
2007-03-15 01:34:17 +01:00
#
# Avoid generating a second '-m iprange' in a single rule.
#
sub iprange_match () {
my $ match = '' ;
2007-03-21 18:09:52 +01:00
2007-05-04 17:12:29 +02:00
require_capability ( 'IPRANGE_MATCH' , 'Address Ranges' , '' ) ;
2007-03-15 01:34:17 +01:00
unless ( $ iprangematch ) {
$ match = '-m iprange ' ;
2007-04-25 23:03:40 +02:00
$ iprangematch = 1 unless $ capabilities { KLUDGEFREE } ;
2007-03-15 01:34:17 +01:00
}
$ match ;
}
2007-03-24 16:59:17 +01:00
#
# Get set flags (ipsets).
#
sub get_set_flags ( $$ ) {
my ( $ setname , $ option ) = @ _ ;
my $ options = $ option ;
2007-07-21 17:35:45 +02:00
if ( $ setname =~ /^(.*)\[([1-6])\]$/ ) {
2007-03-24 16:59:17 +01:00
$ setname = $ 1 ;
my $ count = $ 2 ;
$ options . = ",$option" while - - $ count > 0 ;
2007-07-21 17:35:45 +02:00
} elsif ( $ setname =~ /^(.*)\[(.*)\]$/ ) {
2007-03-24 16:59:17 +01:00
$ setname = $ 1 ;
$ options = $ 2 ;
}
2007-03-27 01:17:46 +02:00
2007-03-24 16:59:17 +01:00
$ setname =~ s/^\+// ;
2007-07-21 17:35:45 +02:00
fatal_error "Invalid ipset name ($setname)" unless $ setname =~ /^[a-zA-Z]\w*/ ;
2007-05-01 18:39:41 +02:00
"--set $setname $options "
2007-03-24 16:59:17 +01:00
}
2007-03-15 01:34:17 +01:00
#
# Match a Source. Currently only handles IP addresses and ranges
#
sub match_source_net ( $ ) {
my $ net = $ _ [ 0 ] ;
2007-04-08 16:42:26 +02:00
2007-05-08 20:25:16 +02:00
if ( $ net =~ /^(!?)(\d+\.\d+\.\d+\.\d+)-(\d+\.\d+\.\d+\.\d+)$/ ) {
my ( $ addr1 , $ addr2 ) = ( $ 2 , $ 3 ) ;
2007-03-15 01:34:17 +01:00
$ net =~ s/!// if my $ invert = $ 1 ? '! ' : '' ;
2007-05-08 20:25:16 +02:00
validate_range $ addr1 , $ addr2 ;
2007-03-15 01:34:17 +01:00
iprange_match . "${invert}--src-range $net " ;
} elsif ( $ net =~ /^(!?)~(.*)$/ ) {
2007-04-19 00:28:01 +02:00
( $ net = $ 2 ) =~ tr /-/ : / ;
my $ invert = $ 1 ? '! ' : '' ;
"-m mac --mac-source ${invert}$net " ;
2007-03-24 16:59:17 +01:00
} elsif ( $ net =~ /^(!?)\+/ ) {
2007-05-04 17:12:29 +02:00
require_capability ( 'IPSET_MATCH' , 'ipset names in Shorewall configuration files' , '' ) ;
2007-03-26 02:46:15 +02:00
join ( '' , '-m set ' , $ 1 ? '! ' : '' , get_set_flags ( $ net , 'src' ) ) ;
2007-03-15 01:34:17 +01:00
} elsif ( $ net =~ /^!/ ) {
$ net =~ s/!// ;
2007-05-08 20:25:16 +02:00
validate_net $ net ;
2007-03-15 01:34:17 +01:00
"-s ! $net " ;
} else {
2007-05-08 20:25:16 +02:00
validate_net $ net ;
2007-03-15 01:34:17 +01:00
$ net eq ALLIPv4 ? '' : "-s $net " ;
}
}
#
# Match a Source. Currently only handles IP addresses and ranges
#
sub match_dest_net ( $ ) {
my $ net = $ _ [ 0 ] ;
2007-03-27 01:17:46 +02:00
2007-05-08 20:25:16 +02:00
if ( $ net =~ /^(!?)(\d+\.\d+\.\d+\.\d+)-(\d+\.\d+\.\d+\.\d+)$/ ) {
my ( $ addr1 , $ addr2 ) = ( $ 2 , $ 3 ) ;
2007-03-15 01:34:17 +01:00
$ net =~ s/!// if my $ invert = $ 1 ? '! ' : '' ;
2007-05-08 20:25:16 +02:00
validate_range $ addr1 , $ addr2 ;
2007-03-21 18:09:52 +01:00
iprange_match . "${invert}--dst-range $net " ;
2007-03-24 16:59:17 +01:00
} elsif ( $ net =~ /^(!?)\+/ ) {
2007-05-04 17:12:29 +02:00
require_capability ( 'IPSET_MATCH' , 'ipset names in Shorewall configuration files' , '' ) ;
2007-03-26 02:46:15 +02:00
join ( '' , '-m set ' , $ 1 ? '! ' : '' , get_set_flags ( $ net , 'dst' ) ) ;
2007-03-15 01:34:17 +01:00
} elsif ( $ net =~ /^!/ ) {
$ net =~ s/!// ;
2007-05-08 20:25:16 +02:00
validate_net $ net ;
2007-03-15 01:34:17 +01:00
"-d ! $net " ;
} else {
2007-05-08 20:25:16 +02:00
validate_net $ net ;
2007-03-15 01:34:17 +01:00
$ net eq ALLIPv4 ? '' : "-d $net " ;
}
}
#
# Match original destination
#
sub match_orig_dest ( $ ) {
my $ net = $ _ [ 0 ] ;
return '' if $ net eq ALLIPv4 ;
2007-03-29 01:21:37 +02:00
return '' unless $ capabilities { CONNTRACK_MATCH } ;
2007-04-08 16:42:26 +02:00
2007-03-15 01:34:17 +01:00
if ( $ net =~ /^!/ ) {
$ net =~ s/!// ;
"-m conntrack --ctorigdst ! $net " ;
} else {
$ net eq ALLIPv4 ? '' : "-m conntrack --ctorigdst $net " ;
}
}
#
# Match Source IPSEC
#
sub match_ipsec_in ( $$ ) {
my ( $ zone , $ hostref ) = @ _ ;
my $ match = '-m policy --dir in --pol ' ;
my $ zoneref = $ zones { $ zone } ;
my $ optionsref = $ zoneref - > { options } ;
if ( $ zoneref - > { type } eq 'ipsec4' ) {
2007-05-07 01:57:47 +02:00
$ match . = "ipsec $optionsref->{in_out}{ipsec}$optionsref->{in}{ipsec}" ;
2007-04-08 16:42:26 +02:00
} elsif ( $ capabilities { POLICY_MATCH } ) {
2007-05-07 01:57:47 +02:00
$ match . = "$hostref->{ipsec} $optionsref->{in_out}{ipsec}$optionsref->{in}{ipsec}" ;
2007-03-15 01:34:17 +01:00
} else {
'' ;
}
}
2007-04-08 16:42:26 +02:00
2007-03-15 01:34:17 +01:00
#
# Match Dest IPSEC
#
sub match_ipsec_out ( $$ ) {
my ( $ zone , $ hostref ) = @ _ ;
my $ match = '-m policy --dir out --pol ' ;
my $ zoneref = $ zones { $ zone } ;
my $ optionsref = $ zoneref - > { options } ;
if ( $ zoneref - > { type } eq 'ipsec4' ) {
2007-05-07 01:57:47 +02:00
$ match . = "ipsec $optionsref->{in_out}{ipsec}$optionsref->{out}{ipsec}" ;
2007-04-08 16:42:26 +02:00
} elsif ( $ capabilities { POLICY_MATCH } ) {
2007-05-07 01:57:47 +02:00
$ match . = "$hostref->{ipsec} $optionsref->{in_out}{ipsec}$optionsref->{out}{ipsec}"
2007-03-15 01:34:17 +01:00
} else {
'' ;
}
}
2007-03-27 01:17:46 +02:00
2007-03-15 01:34:17 +01:00
#
# Generate a log message
#
sub log_rule_limit ( $$$$$$$$ ) {
my ( $ level , $ chainref , $ chain , $ disposition , $ limit , $ tag , $ command , $ predicates ) = @ _ ;
my $ prefix ;
2007-05-08 16:16:20 +02:00
unless ( $ predicates =~ /-m limit / ) {
$ limit = $ globals { LOGLIMIT } unless $ limit && $ limit ne '-' ;
2007-05-08 20:25:16 +02:00
$ predicates . = $ limit if $ limit ;
2007-05-08 16:16:20 +02:00
}
2007-03-15 01:34:17 +01:00
if ( $ tag ) {
if ( $ config { LOGTAGONLY } ) {
$ chain = $ tag ;
$ tag = '' ;
} else {
$ tag . = ' ' ;
}
} else {
$ tag = '' unless defined $ tag ;
}
2007-03-31 19:44:16 +02:00
if ( $ globals { LOGRULENUMBERS } ) {
2007-03-15 01:34:17 +01:00
$ 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 , 29 ;
warning_message "Log Prefix shortened to \"$prefix\"" ;
}
2007-05-09 19:30:28 +02:00
if ( $ chainref - > { loopcount } || $ chainref - > { cmdcount } ) {
#
# The rule will be converted to an "echo" shell command. We must insure that the
# quotes are preserved in the iptables-input file.
#
if ( $ level eq 'ULOG' ) {
$ prefix = "-j ULOG $globals{LOGPARMS}--ulog-prefix \\\"$prefix\\\" " ;
} else {
$ prefix = "-j LOG $globals{LOGPARMS}--log-level $level --log-prefix \\\"$prefix\\\" " ;
}
2007-03-15 01:34:17 +01:00
} else {
2007-05-09 19:30:28 +02:00
if ( $ level eq 'ULOG' ) {
$ prefix = "-j ULOG $globals{LOGPARMS}--ulog-prefix \"$prefix\" " ;
} else {
$ prefix = "-j LOG $globals{LOGPARMS}--log-level $level --log-prefix \"$prefix\" " ;
}
2007-03-15 01:34:17 +01:00
}
if ( $ command eq 'add' ) {
add_rule ( $ chainref , $ predicates . $ prefix ) ;
} else {
insert_rule ( $ chainref , 1 , $ predicates . $ prefix ) ;
}
}
sub log_rule ( $$$$ ) {
my ( $ level , $ chainref , $ disposition , $ predicates ) = @ _ ;
2007-03-31 19:44:16 +02:00
log_rule_limit $ level , $ chainref , $ chainref - > { name } , $ disposition , $ globals { LOGLIMIT } , '' , 'add' , $ predicates ;
2007-03-15 01:34:17 +01:00
}
2007-03-23 23:47:21 +01:00
2007-03-25 18:38:00 +02:00
#
# Split a comma-separated source or destination host list but keep [...] together.
#
sub mysplit ( $ ) {
my @ input = split /,/ , $ _ [ 0 ] ;
return @ input unless $ _ [ 0 ] =~ /\[/ ;
my @ result ;
while ( @ input ) {
my $ element = shift @ input ;
if ( $ element =~ /\[/ ) {
2007-04-24 21:53:13 +02:00
while ( substr ( $ element , - 1 , 1 ) ne ']' ) {
2007-03-25 18:38:00 +02:00
last unless @ input ;
$ element . = ( ',' . shift @ input ) ;
}
fatal_error "Invalid Host List ($_[0])" unless substr ( $ element , - 1 , 1 ) eq ']' ;
}
2007-03-27 01:17:46 +02:00
2007-03-25 18:38:00 +02:00
push @ result , $ element ;
}
@ result ;
}
2007-03-23 23:47:21 +01:00
#
# Returns the name of the shell variable holding the first address of the passed interface
#
sub interface_address ( $ ) {
chain_base ( $ _ [ 0 ] ) . '_address' ;
}
#
2007-04-08 16:42:26 +02:00
# Record that the ruleset requires the first IP address on the passed interface
2007-03-23 23:47:21 +01:00
#
2007-03-26 21:01:38 +02:00
sub get_interface_address ( $ ) {
my ( $ interface ) = $ _ [ 0 ] ;
2007-04-08 16:42:26 +02:00
2007-03-27 20:41:55 +02:00
my $ variable = interface_address ( $ interface ) ;
2007-04-18 17:43:04 +02:00
my $ function = interface_is_optional ( $ interface ) ? 'find_first_interface_address_if_any' : 'find_first_interface_address' ;
2007-03-23 23:47:21 +01:00
2007-04-18 17:43:04 +02:00
$ interfaceaddr { $ interface } = "$variable=\$($function $interface)" ;
2007-03-27 20:41:55 +02:00
"\$$variable" ;
}
#
# Returns the name of the shell variable holding the addresses of the passed interface
#
sub interface_addresses ( $ ) {
chain_base ( $ _ [ 0 ] ) . '_addresses' ;
}
#
2007-04-08 16:42:26 +02:00
# Record that the ruleset requires the IP addresses on the passed interface
2007-03-27 20:41:55 +02:00
#
sub get_interface_addresses ( $ ) {
my ( $ interface ) = $ _ [ 0 ] ;
2007-04-08 16:42:26 +02:00
2007-03-27 20:41:55 +02:00
my $ variable = interface_addresses ( $ interface ) ;
2007-04-18 17:43:04 +02:00
if ( interface_is_optional $ interface ) {
2007-04-23 20:20:54 +02:00
$ interfaceaddrs { $ interface } = qq( $variable=\$ ( find_interface_addresses $interface ) \ n ) ;
2007-04-18 17:43:04 +02:00
} else {
2007-04-23 20:20:54 +02:00
$ interfaceaddrs { $ interface } = qq( $variable=\$ ( find_interface_addresses $interface )
2007-03-27 20:41:55 +02:00
[ - n "\$$variable" ] || fatal_error "Unable to determine the IP address(es) of $interface"
) ;
2007-04-18 17:43:04 +02:00
}
2007-03-27 20:41:55 +02:00
"\$$variable" ;
}
#
# Returns the name of the shell variable holding the networks routed out of the passed interface
#
sub interface_nets ( $ ) {
chain_base ( $ _ [ 0 ] ) . '_networks' ;
}
#
2007-04-08 16:42:26 +02:00
# Record that the ruleset requires the first IP address on the passed interface
2007-03-27 20:41:55 +02:00
#
sub get_interface_nets ( $ ) {
my ( $ interface ) = $ _ [ 0 ] ;
my $ variable = interface_nets ( $ interface ) ;
2007-04-18 17:43:04 +02:00
if ( interface_is_optional $ interface ) {
2007-04-18 18:02:09 +02:00
$ interfacenets { $ interface } = qq( $variable=\$ ( get_routed_networks $interface ) \ n ) ;
2007-04-18 17:43:04 +02:00
} else {
2007-04-18 18:02:09 +02:00
$ interfacenets { $ interface } = qq( $variable=\$ ( get_routed_networks $interface )
2007-03-27 20:41:55 +02:00
[ - n "\$$variable" ] || fatal_error "Unable to determine the routes through interface \\" $ interface \ \ ""
) ;
2007-04-18 17:43:04 +02:00
}
2007-03-27 20:41:55 +02:00
"\$$variable" ;
2007-04-08 16:42:26 +02:00
2007-03-23 23:47:21 +01:00
}
2007-03-27 01:17:46 +02:00
2007-03-15 01:34:17 +01:00
#
# This function provides a uniform way to generate rules (something the original Shorewall sorely needed).
2007-04-08 16:42:26 +02:00
#
2007-06-13 20:56:27 +02:00
# Returns the destination interface specified in the rule, if any.
#
2007-03-18 19:50:34 +01:00
sub expand_rule ( $ $ $ $ $ $ $ $ $ $ )
2007-03-15 01:34:17 +01:00
{
2007-06-03 23:44:17 +02:00
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
$ 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
) = @ _ ;
2007-06-11 20:07:34 +02:00
2007-03-15 01:34:17 +01:00
my ( $ iiface , $ diface , $ inets , $ dnets , $ iexcl , $ dexcl , $ onets , $ oexcl ) ;
2007-03-18 22:16:46 +01:00
my $ chain = $ chainref - > { name } ;
2007-06-11 20:07:34 +02:00
2007-03-23 17:12:36 +01:00
#
# Handle Log Level
#
my $ logtag ;
2007-04-24 16:53:45 +02:00
if ( $ loglevel ne '' ) {
2007-05-17 16:10:46 +02:00
( $ loglevel , $ logtag , my $ remainder ) = split ( /:/ , $ loglevel , 3 ) ;
fatal_error "Invalid log tag" if defined $ remainder ;
2007-03-27 01:17:46 +02:00
2007-03-23 17:12:36 +01:00
if ( $ loglevel =~ /^none!?$/i ) {
2007-03-23 20:19:43 +01:00
return if $ disposition eq 'LOG' ;
2007-03-23 17:12:36 +01:00
$ loglevel = $ logtag = '' ;
2007-05-15 22:04:34 +02:00
} else {
$ loglevel = validate_level ( $ loglevel ) ;
$ logtag = '' unless defined $ logtag ;
2007-03-23 17:12:36 +01:00
}
2007-05-03 04:07:04 +02:00
} elsif ( $ disposition eq 'LOG' ) {
fatal_error "LOG requires a level" ;
2007-03-23 17:12:36 +01:00
}
2007-03-15 01:34:17 +01:00
#
# Isolate Source Interface, if any
#
if ( $ source ) {
if ( $ source eq '-' ) {
$ source = '' ;
} elsif ( $ source =~ /^([^:]+):([^:]+)$/ ) {
$ iiface = $ 1 ;
$ inets = $ 2 ;
} elsif ( $ source =~ /\+|~|\..*\./ ) {
$ inets = $ source ;
} else {
$ iiface = $ source ;
}
} else {
$ source = '' ;
}
2007-03-23 17:12:36 +01:00
2007-03-18 22:16:46 +01:00
#
2007-05-15 22:04:34 +02:00
# Verify Interface, if any
2007-03-15 01:34:17 +01:00
#
if ( $ iiface ) {
2007-05-15 22:04:34 +02:00
fatal_error "Unknown Interface ($iiface)" unless known_interface $ iiface ;
2007-03-18 22:57:39 +01:00
2007-03-25 21:27:25 +02:00
if ( $ restriction & POSTROUTE_RESTRICT ) {
2007-03-23 23:47:21 +01:00
#
# An interface in the SOURCE column of a masq file
#
2007-06-06 17:34:47 +02:00
fatal_error "Bridge ports may not appear in the SOURCE column of this file" if port_to_bridge ( $ iiface ) ;
2007-03-27 20:41:55 +02:00
my $ networks = get_interface_nets ( $ iiface ) ;
add_command ( $ chainref , join ( '' , 'for source in ' , $ networks , '; do' ) ) ;
2007-03-23 23:47:21 +01:00
$ rule . = '-s $source ' ;
#
2007-04-08 16:42:26 +02:00
# While $loopcount > 0, calls to 'add_rule()' will be converted to calls to 'add_command()'
2007-03-23 23:47:21 +01:00
#
2007-04-28 16:59:18 +02:00
$ chainref - > { loopcount } + + ;
2007-03-18 22:16:46 +01:00
} else {
2007-06-06 17:34:47 +02:00
fatal_error "Source Interface ($iiface) not allowed when the source zone is $firewall_zone" if $ restriction & OUTPUT_RESTRICT ;
$ rule . = match_source_dev ( $ iiface ) ;
2007-03-18 22:16:46 +01:00
}
2007-03-15 01:34:17 +01:00
}
#
# Isolate Destination Interface, if any
#
if ( $ dest ) {
if ( $ dest eq '-' ) {
$ dest = '' ;
2007-03-25 21:27:25 +02:00
} elsif ( ( $ restriction & PREROUTE_RESTRICT ) && $ dest =~ /^detect:(.*)$/ ) {
2007-03-23 17:12:36 +01:00
#
# DETECT_DNAT_IPADDRS=Yes and we're generating the nat rule
#
my @ interfaces = split /\s+/ , $ 1 ;
if ( @ interfaces > 1 ) {
2007-03-27 20:41:55 +02:00
my $ list = "" ;
2007-03-27 01:17:46 +02:00
2007-03-23 17:12:36 +01:00
for my $ interface ( @ interfaces ) {
2007-03-27 20:41:55 +02:00
$ list = join ( ' ' , $ list , get_interface_address ( $ interface ) ) ;
2007-03-23 17:12:36 +01:00
}
2007-03-27 20:41:55 +02:00
add_command ( $ chainref , "for address in $list; do" ) ;
2007-03-23 23:47:21 +01:00
$ rule . = '-d $address ' ;
2007-04-28 16:59:18 +02:00
$ chainref - > { loopcount } + + ;
2007-03-23 17:12:36 +01:00
} else {
2007-03-27 20:41:55 +02:00
$ rule . = join ( '' , '-d ' , get_interface_address ( $ interfaces [ 0 ] ) , ' ' ) ;
2007-03-23 17:12:36 +01:00
}
$ dest = '' ;
2007-03-15 01:34:17 +01:00
} elsif ( $ dest =~ /^([^:]+):([^:]+)$/ ) {
$ diface = $ 1 ;
$ dnets = $ 2 ;
} elsif ( $ dest =~ /\+|~|\..*\./ ) {
$ dnets = $ dest ;
} else {
$ diface = $ dest ;
}
} else {
$ dest = '' ;
}
2007-03-23 23:47:21 +01:00
2007-03-15 01:34:17 +01:00
#
# Verify Destination Interface, if any
#
if ( $ diface ) {
2007-05-15 22:04:34 +02:00
fatal_error "Unknown Interface ($diface)" unless known_interface $ diface ;
2007-03-18 22:57:39 +01:00
2007-03-25 21:27:25 +02:00
if ( $ restriction & PREROUTE_RESTRICT ) {
2007-03-23 23:47:21 +01:00
#
# ADDRESS 'detect' in the masq file.
#
2007-06-16 23:08:12 +02:00
fatal_error "Bridge port ($diface) not allowed" if port_to_bridge ( $ diface ) ;
2007-04-27 18:18:42 +02:00
add_command ( $ chainref , 'for dest in ' . get_interface_addresses ( $ diface ) . '; do' ) ;
2007-06-10 19:09:09 +02:00
$ rule . = '-d $dest ' ;
2007-04-28 16:59:18 +02:00
$ chainref - > { loopcount } + + ;
2007-03-18 22:16:46 +01:00
} else {
2007-06-16 23:08:12 +02:00
fatal_error "Bridge Port ($diface) not allowed in OUTPUT or POSTROUTING rules" if ( $ restriction & ( POSTROUTE_RESTRICT + OUTPUT_RESTRICT ) ) && port_to_bridge ( $ diface ) ;
2007-06-06 17:34:47 +02:00
fatal_error "Destination Interface ($diface) not allowed when the destination zone is $firewall_zone" if $ restriction & INPUT_RESTRICT ;
if ( $ iiface ) {
my $ bridge = port_to_bridge ( $ diface ) ;
2007-06-16 23:08:12 +02:00
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 ) ;
2007-06-06 17:34:47 +02:00
}
$ rule . = match_dest_dev ( $ diface ) ;
2007-03-18 22:16:46 +01:00
}
2007-06-13 20:56:27 +02:00
} else {
$ diface = '' ;
2007-03-27 01:17:46 +02:00
}
2007-03-18 22:16:46 +01:00
2007-03-23 17:12:36 +01:00
if ( $ origdest ) {
2007-03-29 01:21:37 +02:00
if ( $ origdest eq '-' || ! $ capabilities { CONNTRACK_MATCH } ) {
2007-03-27 01:17:46 +02:00
$ origdest = '' ;
2007-03-23 17:12:36 +01:00
} elsif ( $ origdest =~ /^detect:(.*)$/ ) {
2007-03-23 23:47:21 +01:00
#
# Either the filter part of a DNAT rule or 'detect' was given in the ORIG DEST column
#
2007-03-23 17:12:36 +01:00
my @ interfaces = split /\s+/ , $ 1 ;
2007-03-18 22:16:46 +01:00
2007-03-23 17:12:36 +01:00
if ( @ interfaces > 1 ) {
2007-03-27 20:41:55 +02:00
my $ list = "" ;
2007-03-18 22:16:46 +01:00
2007-03-23 20:19:43 +01:00
for my $ interface ( @ interfaces ) {
2007-03-27 20:41:55 +02:00
$ list = join ( ' ' , $ list , get_interface_address ( $ interface ) ) ;
2007-03-23 17:12:36 +01:00
}
2007-03-23 23:47:21 +01:00
2007-03-27 20:41:55 +02:00
add_command ( $ chainref , "for address in $list; do" ) ;
2007-03-23 23:47:21 +01:00
$ rule . = '-m conntrack --ctorigdst $address ' ;
2007-04-28 16:59:18 +02:00
$ chainref - > { loopcount } + + ;
2007-03-23 17:12:36 +01:00
} else {
2007-03-26 21:01:38 +02:00
get_interface_address $ interfaces [ 0 ] ;
2007-03-26 02:46:15 +02:00
$ rule . = join ( '' , '-m conntrack --ctorigdst $' , interface_address ( $ interfaces [ 0 ] ) , ' ' ) ;
2007-04-08 16:42:26 +02:00
}
2007-03-15 01:34:17 +01:00
2007-03-23 17:12:36 +01:00
$ origdest = '' ;
} else {
2007-04-19 03:01:03 +02:00
fatal_error "Invalid ORIGINAL DEST" if $ origdest =~ /^([^!]+)?,!([^!]+)$/ || $ origdest =~ /.*!.*!/ ;
2007-04-19 02:49:38 +02:00
2007-03-23 23:47:21 +01:00
if ( $ origdest =~ /^([^!]+)?!([^!]+)$/ ) {
#
# Exclusion
#
$ onets = $ 1 ;
$ oexcl = $ 2 ;
} else {
2007-03-23 17:12:36 +01:00
$ oexcl = '' ;
2007-03-29 05:07:48 +02:00
$ onets = $ origdest ;
2007-03-23 17:12:36 +01:00
}
2007-03-23 23:47:21 +01:00
2007-03-24 16:59:17 +01:00
unless ( $ onets ) {
2007-03-24 04:56:16 +01:00
my @ oexcl = mysplit $ oexcl ;
2007-03-23 23:47:21 +01:00
if ( @ oexcl == 1 ) {
$ rule . = "-m conntrack --ctorigdst ! $oexcl " ;
$ oexcl = '' ;
}
}
2007-03-15 01:34:17 +01:00
}
2007-03-23 17:12:36 +01:00
} else {
$ oexcl = '' ;
2007-03-15 01:34:17 +01:00
}
#
# Determine if there is Source Exclusion
#
if ( $ inets ) {
2007-04-19 03:01:03 +02:00
fatal_error "Invalid SOURCE" if $ inets =~ /^([^!]+)?,!([^!]+)$/ || $ inets =~ /.*!.*!/ ;
2007-04-19 02:49:38 +02:00
2007-03-15 01:34:17 +01:00
if ( $ inets =~ /^([^!]+)?!([^!]+)$/ ) {
$ inets = $ 1 ;
$ iexcl = $ 2 ;
} else {
$ iexcl = '' ;
}
2007-04-28 16:59:18 +02:00
unless ( $ inets || ( $ iiface && $ restriction & POSTROUTE_RESTRICT ) ) {
2007-03-24 04:56:16 +01:00
my @ iexcl = mysplit $ iexcl ;
2007-03-15 01:34:17 +01:00
if ( @ iexcl == 1 ) {
2007-05-08 20:25:16 +02:00
$ rule . = match_source_net "!$iexcl" ;
2007-03-15 01:34:17 +01:00
$ iexcl = '' ;
}
2007-04-19 02:49:38 +02:00
2007-03-15 01:34:17 +01:00
}
} else {
$ iexcl = '' ;
}
#
# Determine if there is Destination Exclusion
2007-03-18 22:16:46 +01:00
#
2007-03-15 01:34:17 +01:00
if ( $ dnets ) {
2007-04-19 03:01:03 +02:00
fatal_error "Invalid DEST" if $ dnets =~ /^([^!]+)?,!([^!]+)$/ || $ dnets =~ /.*!.*!/ ;
2007-04-19 02:49:38 +02:00
2007-03-15 01:34:17 +01:00
if ( $ dnets =~ /^([^!]+)?!([^!]+)$/ ) {
$ dnets = $ 1 ;
$ dexcl = $ 2 ;
} else {
$ dexcl = '' ;
}
2007-03-24 16:59:17 +01:00
unless ( $ dnets ) {
2007-03-24 04:56:16 +01:00
my @ dexcl = mysplit $ dexcl ;
2007-03-15 01:34:17 +01:00
if ( @ dexcl == 1 ) {
2007-05-08 20:25:16 +02:00
$ rule . = match_dest_net "!$dexcl" ;
2007-03-15 01:34:17 +01:00
$ dexcl = '' ;
}
}
} else {
$ dexcl = '' ;
}
$ inets = ALLIPv4 unless $ inets ;
$ dnets = ALLIPv4 unless $ dnets ;
$ onets = ALLIPv4 unless $ onets ;
if ( $ iexcl || $ dexcl || $ oexcl ) {
#
# We have non-trivial exclusion -- need to create an exclusion chain
#
2007-05-09 00:42:35 +02:00
fatal_error "Exclusion is not possible in ACCEPT+/CONTINUE/NONAT rules" if $ disposition eq 'RETURN' ;
2007-04-19 02:49:38 +02:00
2007-03-15 01:34:17 +01:00
my $ echain = newexclusionchain ;
2007-03-27 01:17:46 +02:00
2007-03-15 01:34:17 +01:00
#
# Use the current rule and sent all possible matches to the exclusion chain
#
2007-03-24 04:56:16 +01:00
for my $ onet ( mysplit $ onets ) {
2007-03-15 01:34:17 +01:00
$ onet = match_orig_dest $ onet ;
2007-03-24 04:56:16 +01:00
for my $ inet ( mysplit $ inets ) {
for my $ dnet ( mysplit $ dnets ) {
2007-05-15 22:04:34 +02:00
#
# We defer evaluating the source net match to accomodate system without $capabilities{KLUDGEFREE}
#
add_rule $ chainref , join ( '' , $ rule , match_source_net ( $ inet ) , match_dest_net ( $ dnet ) , $ onet , "-j $echain" ) ;
2007-03-15 01:34:17 +01:00
}
}
}
2007-03-23 17:12:36 +01:00
2007-03-15 01:34:17 +01:00
#
2007-04-08 16:42:26 +02:00
# The final rule in the exclusion chain will not qualify the source or destination
2007-03-15 01:34:17 +01:00
#
$ inets = ALLIPv4 ;
$ dnets = ALLIPv4 ;
2007-03-27 01:17:46 +02:00
2007-03-15 01:34:17 +01:00
#
# Create the Exclusion Chain
#
my $ echainref = new_chain $ chainref - > { table } , $ echain ;
#
# Generate RETURNs for each exclusion
#
2007-03-24 04:56:16 +01:00
for my $ net ( mysplit $ iexcl ) {
2007-03-23 18:30:12 +01:00
add_rule $ echainref , ( match_source_net $ net ) . '-j RETURN' ;
}
2007-03-15 01:34:17 +01:00
2007-03-24 04:56:16 +01:00
for my $ net ( mysplit $ dexcl ) {
2007-03-23 18:30:12 +01:00
add_rule $ echainref , ( match_dest_net $ net ) . '-j RETURN' ;
}
2007-03-15 01:34:17 +01:00
2007-03-24 04:56:16 +01:00
for my $ net ( mysplit $ oexcl ) {
2007-03-23 18:30:12 +01:00
add_rule $ echainref , ( match_orig_dest $ net ) . '-j RETURN' ;
2007-03-23 17:12:36 +01:00
}
2007-03-23 18:30:12 +01:00
#
# Log rule
#
log_rule_limit $ loglevel , $ echainref , $ chain , $ disposition , '' , $ logtag , 'add' , '' if $ loglevel ;
#
# Generate Final Rule
2007-04-08 16:42:26 +02:00
#
2007-06-11 20:07:34 +02:00
add_rule ( $ echainref , $ exceptionrule . $ target ) unless $ disposition eq 'LOG' ;
2007-03-15 01:34:17 +01:00
} else {
#
# No exclusions
#
2007-03-24 04:56:16 +01:00
for my $ onet ( mysplit $ onets ) {
2007-03-15 01:34:17 +01:00
$ onet = match_orig_dest $ onet ;
2007-03-24 04:56:16 +01:00
for my $ inet ( mysplit $ inets ) {
2007-05-15 22:04:34 +02:00
#
# We defer evaluating the source net match to accomodate system without $capabilities{KLUDGEFREE}
#
2007-03-24 04:56:16 +01:00
for my $ dnet ( mysplit $ dnets ) {
2007-05-02 17:39:36 +02:00
if ( $ loglevel ne '' ) {
2007-04-08 16:42:26 +02:00
log_rule_limit
$ loglevel ,
$ chainref ,
$ chain ,
$ disposition ,
'' ,
$ logtag ,
'add' ,
2007-05-15 22:04:34 +02:00
join ( '' , $ rule , match_source_net ( $ inet ) , match_dest_net ( $ dnet ) , $ onet ) ;
2007-03-26 02:46:15 +02:00
}
unless ( $ disposition eq 'LOG' ) {
2007-04-08 16:42:26 +02:00
add_rule
$ chainref ,
2007-05-15 22:04:34 +02:00
join ( '' , $ rule , match_source_net ( $ inet ) , match_dest_net ( $ dnet ) , $ onet , $ target ) ;
2007-03-26 02:46:15 +02:00
}
2007-03-15 01:34:17 +01:00
}
}
}
2007-03-23 17:12:36 +01:00
}
2007-04-28 16:59:18 +02:00
while ( $ chainref - > { loopcount } > 0 ) {
$ chainref - > { loopcount } - - ;
2007-03-23 20:54:08 +01:00
add_command $ chainref , 'done' ;
}
2007-06-13 20:56:27 +02:00
$ diface ;
2007-03-15 01:34:17 +01:00
}
#
# 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 } || { } ;
2007-03-27 01:17:46 +02:00
2007-03-15 01:34:17 +01:00
if ( $ destref - > { referenced } ) {
add_rule $ nat_table - > { $ source } , $ predicates . "-j $dest" ;
} else {
clearrule ;
}
}
2007-03-28 21:09:27 +02:00
2007-03-15 01:34:17 +01:00
#
# If the destination chain exists, then at the position in the source chain given by $$countref, add a jump to the destination.
#
sub insertnatjump ( $$$$ ) {
my ( $ source , $ dest , $ countref , $ predicates ) = @ _ ;
2007-03-27 01:17:46 +02:00
2007-03-15 01:34:17 +01:00
my $ destref = $ nat_table - > { $ dest } || { } ;
2007-03-27 01:17:46 +02:00
2007-03-15 01:34:17 +01:00
if ( $ destref - > { referenced } ) {
insert_rule $ nat_table - > { $ source } , ( $$ countref ) + + , $ predicates . "-j $dest" ;
} else {
clearrule ;
}
}
2007-04-16 21:50:29 +02:00
#
# What follows is the code that generates the input to iptables-restore
#
2007-03-22 19:24:33 +01:00
2007-04-16 21:50:29 +02:00
#
# Emits the passed 'rule'
#
2007-03-22 19:24:33 +01:00
sub emitr ( $ ) {
my $ rule = $ _ [ 0 ] ;
2007-03-29 21:45:53 +02:00
if ( substr ( $ rule , 0 , 1 ) eq '~' ) {
2007-03-22 19:24:33 +01:00
#
# A command
#
unless ( $ state == CMD_STATE ) {
emit_unindented "__EOF__\n" if $ state == CAT_STATE ;
$ state = CMD_STATE ;
}
2007-03-29 19:02:13 +02:00
$ rule = substr ( $ rule , 1 ) ;
2007-03-22 19:24:33 +01:00
emit $ rule ;
} else {
unless ( $ state == CAT_STATE ) {
2007-03-22 20:43:24 +01:00
emit '' ;
2007-03-22 19:24:33 +01:00
emit 'cat >&3 << __EOF__' ;
$ state = CAT_STATE ;
}
emit_unindented $ rule ;
}
}
2007-05-09 00:28:48 +02:00
sub emit_comment () {
unless ( $ emitted_comment ) {
emitj ( '#' ,
'# Establish the values of shell variables used in the following function calls' ,
'#' ) ;
$ emitted_comment = 1 ;
}
}
2007-04-16 21:50:29 +02:00
#
2007-05-09 00:28:48 +02:00
# Generate setting of global variables
2007-04-16 21:50:29 +02:00
#
2007-05-08 22:36:57 +02:00
sub set_global_variables () {
2007-03-27 01:17:46 +02:00
2007-03-27 20:41:55 +02:00
for ( values % interfaceaddr ) {
2007-05-09 00:28:48 +02:00
emit_comment ;
2007-03-27 20:41:55 +02:00
emit $ _ ;
}
2007-03-26 21:01:38 +02:00
for ( values % interfaceaddrs ) {
2007-05-09 00:28:48 +02:00
emit_comment ;
2007-03-26 21:01:38 +02:00
emit $ _ ;
}
2007-03-27 20:41:55 +02:00
for ( values % interfacenets ) {
2007-05-09 00:28:48 +02:00
emit_comment ;
2007-03-27 20:41:55 +02:00
emit $ _ ;
}
2007-05-08 22:36:57 +02:00
}
#
# Generate the netfilter input
#
sub create_netfilter_load () {
2007-07-03 20:59:42 +02:00
$ state = NULL_STATE ;
2007-05-08 22:36:57 +02:00
emitj ( 'setup_netfilter()' ,
'{'
) ;
push_indent ;
save_progress_message "Preparing iptables-restore input..." ;
2007-03-26 21:01:38 +02:00
emit '' ;
2007-04-16 21:50:29 +02:00
#
# We always write the input into a file then pass the file to iptables-restore. That way, if things go wrong,
# the user (and Shorewall support) has something to look at to determine the error
#
2007-03-29 21:45:53 +02:00
emit 'exec 3>${VARDIR}/.iptables-restore-input' ;
2007-03-22 19:24:33 +01:00
2007-06-08 02:27:43 +02:00
my @ table_list ;
push @ table_list , 'raw' if $ capabilities { RAW_TABLE } ;
push @ table_list , 'nat' if $ capabilities { NAT_ENABLED } ;
push @ table_list , 'mangle' if $ capabilities { MANGLE_ENABLED } ;
push @ table_list , 'filter' ;
for my $ table ( @ table_list ) {
2007-03-22 19:24:33 +01:00
emitr "*$table" ;
2007-03-27 01:17:46 +02:00
2007-03-15 02:18:29 +01:00
my @ chains ;
2007-04-16 21:50:29 +02:00
#
# iptables-restore seems to be quite picky about the order of the builtin chains
#
2007-03-21 00:44:12 +01:00
for my $ chain ( @ builtins ) {
my $ chainref = $ chain_table { $ table } { $ chain } ;
if ( $ chainref ) {
2007-03-22 19:24:33 +01:00
emitr ":$chain $chainref->{policy} [0:0]" ;
2007-03-21 00:44:12 +01:00
push @ chains , $ chainref ;
}
}
2007-04-16 21:50:29 +02:00
#
# First create the chains in the current table
#
2007-03-15 02:18:29 +01:00
for my $ chain ( grep $ chain_table { $ table } { $ _ } - > { referenced } , ( sort keys % { $ chain_table { $ table } } ) ) {
my $ chainref = $ chain_table { $ table } { $ chain } ;
2007-03-21 00:44:12 +01:00
unless ( $ chainref - > { builtin } ) {
2007-03-22 19:24:33 +01:00
emitr ":$chainref->{name} - [0:0]" ;
2007-03-21 00:44:12 +01:00
push @ chains , $ chainref ;
2007-03-15 02:18:29 +01:00
}
}
2007-04-16 21:50:29 +02:00
#
# then emit the rules
#
2007-03-15 02:18:29 +01:00
for my $ chainref ( @ chains ) {
my $ name = $ chainref - > { name } ;
for my $ rule ( @ { $ chainref - > { rules } } ) {
2007-07-03 20:59:42 +02:00
emitr ( substr ( $ rule , 0 , 1 ) eq '~' ? $ rule : "-A $name $rule" ) ;
2007-03-15 02:18:29 +01:00
}
}
2007-04-16 21:50:29 +02:00
#
# Commit the changes to the table
#
2007-03-22 19:24:33 +01:00
emitr 'COMMIT' ;
2007-03-15 02:18:29 +01:00
}
2007-03-22 19:24:33 +01:00
emit_unindented '__EOF__' unless $ state == CMD_STATE ;
2007-03-21 20:46:44 +01:00
emit '' ;
2007-04-16 21:50:29 +02:00
#
2007-07-03 19:13:14 +02:00
# Now generate the actual iptables-restore command
2007-04-16 21:50:29 +02:00
#
2007-05-08 23:23:18 +02:00
emitj ( 'exec 3>&-' ,
2007-05-03 20:56:56 +02:00
'' ,
'progress_message2 "Running iptables-restore..."' ,
2007-03-29 21:45:53 +02:00
'' ,
2007-07-03 19:13:14 +02:00
'cat ${VARDIR}/.iptables-restore-input | $IPTABLES_RESTORE # Use this nonsensical form to appease SELinux'
2007-03-29 21:45:53 +02:00
) ;
2007-03-25 19:06:23 +02:00
2007-03-27 06:02:58 +02:00
emitj ( 'if [ $? != 0 ]; then' ,
2007-03-29 21:45:53 +02:00
' fatal_error "iptables-restore Failed. Input is in ${VARDIR}/.iptables-restore-input"' ,
2007-03-27 06:02:58 +02:00
"fi\n"
) ;
2007-03-21 18:54:17 +01:00
2007-03-22 19:24:33 +01:00
pop_indent ;
2007-03-15 02:18:29 +01:00
emit "}\n" ;
}
2007-03-27 01:17:46 +02:00
2007-07-03 20:59:42 +02:00
#
2007-07-21 17:13:50 +02:00
# Generate the netfilter input for refreshing the blacklist
2007-07-03 20:59:42 +02:00
#
sub create_blacklist_reload () {
$ state = NULL_STATE ;
emitj ( 'blacklist_reload()' ,
'{'
) ;
push_indent ;
save_progress_message "Preparing iptables-restore input..." ;
emit '' ;
#
# We always write the input into a file then pass the file to iptables-restore. That way, if things go wrong,
# the user (and Shorewall support) has something to look at to determine the error
#
emit 'exec 3>${VARDIR}/.iptables-restore-input' ;
emitr '*filter' ;
emitr ':blacklst - [0:0]' ;
2007-07-03 21:55:01 +02:00
for my $ rule ( @ { $ filter_table - > { blacklst } { rules } } ) {
2007-07-03 20:59:42 +02:00
emitr ( substr ( $ rule , 0 , 1 ) eq '~' ? $ rule : "-A blacklst $rule" ) ;
}
#
# Commit the changes to the table
#
emitr 'COMMIT' ;
emit_unindented '__EOF__' unless $ state == CMD_STATE ;
emit '' ;
#
# Now generate the actual iptables-restore command
#
emitj ( 'exec 3>&-' ,
'' ,
'progress_message2 "Running iptables-restore..."' ,
'' ,
'cat ${VARDIR}/.iptables-restore-input | $IPTABLES_RESTORE -n # Use this nonsensical form to appease SELinux'
) ;
emitj ( 'if [ $? != 0 ]; then' ,
' fatal_error "iptables-restore Failed. Input is in ${VARDIR}/.iptables-restore-input"' ,
"fi\n"
) ;
pop_indent ;
emit "}\n" ;
}
2007-03-14 04:12:22 +01:00
1 ;