2008-12-07 19:15:06 +01:00
#
# Shorewall-perl 4.2 -- /usr/share/shorewall-perl/Shorewall/Actions.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 module contains the code for dealing with actions (built-in,
# standard and user-defined) and Macros.
#
package Shorewall::Actions ;
require Exporter ;
use Shorewall::Config qw( :DEFAULT :internal ) ;
use Shorewall::Zones ;
use Shorewall::Chains qw( :DEFAULT :internal ) ;
2008-12-08 01:21:20 +01:00
use Shorewall::IPAddrs ;
2008-12-07 19:15:06 +01:00
use strict ;
our @ ISA = qw( Exporter ) ;
our @ EXPORT = qw( merge_levels
isolate_basic_target
get_target_param
add_requiredby
createactionchain
find_logactionchain
process_actions1
process_actions2
process_actions3
find_macro
split_action
substitute_param
merge_macro_source_dest
merge_macro_column
2008-12-08 01:21:20 +01:00
$ usedactions
$ default_actions
$ actions
2008-12-07 19:15:06 +01:00
2008-12-08 01:21:20 +01:00
$ macros
2008-12-07 19:15:06 +01:00
$ macro_commands
) ;
our @ EXPORT_OK = qw( initialize ) ;
our $ VERSION = 4.1 .1 ;
#
# Used Actions. Each action that is actually used has an entry with value 1.
#
2008-12-08 01:21:20 +01:00
our % usedactions4 ;
our % usedactions6 ;
our $ usedactions ;
2008-12-07 19:15:06 +01:00
#
# Default actions for each policy.
#
2008-12-08 01:21:20 +01:00
our % default_actions4 ;
our % default_actions6 ;
our $ default_actions ;
2008-12-07 19:15:06 +01:00
# Action Table
#
# %actions{ <action1> => { requires => { <requisite1> = 1,
# <requisite2> = 1,
# ...
# } ,
# actchain => <action chain number> # Used for generating unique chain names for each <level>:<tag> pair.
#
2008-12-08 01:21:20 +01:00
our % actions4 ;
our % actions6 ;
our $ actions ;
2008-12-07 19:15:06 +01:00
#
# Contains an entry for each used <action>:<level>[:<tag>] that maps to the associated chain.
#
2008-12-08 01:21:20 +01:00
our % logactionchains4 ;
our % logactionchains6 ;
our $ logactionchains ;
2008-12-07 19:15:06 +01:00
2008-12-08 01:21:20 +01:00
our $ action_prefix ;
our % macros4 ;
our % macros6 ;
our $ macros ;
our $ macro_prefix ;
2008-12-07 19:15:06 +01:00
#
# Commands that can be embedded in a macro file and how many total tokens on the line (0 => unlimited).
#
our $ macro_commands = { COMMENT = > 0 , FORMAT = > 2 } ;
2008-12-08 01:21:20 +01:00
sub use_ipv4_actions () {
$ usedactions = \ % usedactions4 ;
$ default_actions = \ % default_actions4 ;
$ actions = \ % actions4 ;
$ macros = \ % macros4 ;
$ action_prefix = 'action4' ;
$ macro_prefix = 'macro4' ;
}
sub use_ipv6_actions () {
$ usedactions = \ % usedactions6 ;
$ default_actions = \ % default_actions6 ;
$ actions = \ % actions6 ;
$ macros = \ % macros6 ;
$ action_prefix = 'action6' ;
$ macro_prefix = 'macro6' ;
}
2008-12-07 19:15:06 +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.
#
sub initialize () {
2008-12-08 01:21:20 +01:00
% usedactions4 = ( ) ;
% usedactions6 = ( ) ;
% default_actions4 = ( DROP = > 'none' ,
REJECT = > 'none' ,
ACCEPT = > 'none' ,
QUEUE = > 'none' ) ;
% default_actions6 = ( DROP = > 'none' ,
REJECT = > 'none' ,
ACCEPT = > 'none' ,
QUEUE = > 'none' ) ;
% actions4 = ( ) ;
% actions6 = ( ) ;
% logactionchains4 = ( ) ;
% logactionchains6 = ( ) ;
% macros4 = ( ) ;
% macros6 = ( ) ;
use_ipv4_actions ;
2008-12-07 19:15:06 +01:00
}
INIT {
initialize ;
}
#
# This function determines the logging for a subordinate action or a rule within a superior action
#
sub merge_levels ($$) {
my ( $ superior , $ subordinate ) = @ _ ;
my @ supparts = split /:/ , $ superior ;
my @ subparts = split /:/ , $ subordinate ;
my $ subparts = @ subparts ;
my $ target = $ subparts [ 0 ] ;
push @ subparts , '' while @ subparts < 3 ; #Avoid undefined values
my $ level = $ supparts [ 1 ] ;
my $ tag = $ supparts [ 2 ] ;
if ( @ supparts == 3 ) {
return "$target:none!:$tag" if $ level eq 'none!' ;
return "$target:$level:$tag" if $ level =~ /!$/ ;
return $ subordinate if $ subparts >= 2 ;
return "$target:$level:$tag" ;
}
if ( @ supparts == 2 ) {
return "$target:none!" if $ level eq 'none!' ;
return "$target:$level" if ( $ level =~ /!$/ ) || ( $ subparts < 2 ) ;
}
$ subordinate ;
}
#
# Try to find a macro file -- RETURNS false if the file doesn't exist or MACRO if it does.
# If the file exists, the macro is entered into the 'targets' table and the fully-qualified
# name of the file is stored in the 'macro' table.
#
sub find_macro ( $ )
{
my $ macro = $ _ [ 0 ] ;
2008-12-08 01:21:20 +01:00
my $ macrofile = find_file "${macro_prefix}.$macro" ;
$ macrofile = find_file "macro.$macro" unless - f $ macrofile ;
2008-12-07 19:15:06 +01:00
if ( - f $ macrofile ) {
2008-12-08 01:21:20 +01:00
$ macros - > { $ macro } = $ macrofile ;
$ targets - > { $ macro } = MACRO ;
2008-12-07 19:15:06 +01:00
} else {
0 ;
}
}
#
# Return ( action, level[:tag] ) from passed full action
#
sub split_action ( $ ) {
my $ action = $ _ [ 0 ] ;
my @ a = split ( /:/ , $ action , 4 ) ;
fatal_error "Invalid ACTION ($action)" if ( $ action =~ /::/ ) || ( @ a > 3 ) ;
( shift @ a , join ":" , @ a ) ;
}
#
# This function substitutes the second argument for the first part of the first argument up to the first colon (":")
#
# Example:
#
# substitute_param DNAT PARAM:info:FTP
#
# produces "DNAT:info:FTP"
#
sub substitute_param ( $$ ) {
my ( $ param , $ action ) = @ _ ;
if ( $ action =~ /:/ ) {
my $ logpart = ( split_action $ action ) [ 1 ] ;
$ logpart =~ s!/$!! ;
return "$param:$logpart" ;
}
$ param ;
}
#
# Combine fields from a macro body with one from the macro invocation
#
sub merge_macro_source_dest ( $$ ) {
my ( $ body , $ invocation ) = @ _ ;
if ( $ invocation ) {
if ( $ body ) {
return $ body if $ invocation eq '-' ;
return "$body:$invocation" if $ invocation =~ /.*?\.*?\.|^\+|^~|^!~/ ;
return "$invocation:$body" ;
}
return $ invocation ;
}
$ body || '' ;
}
sub merge_macro_column ( $$ ) {
my ( $ body , $ invocation ) = @ _ ;
if ( defined $ invocation && $ invocation ne '' && $ invocation ne '-' ) {
$ invocation ;
} else {
$ body ;
}
}
#
# Get Macro Name -- strips away trailing /* and :* from the first column in a rule, macro or action.
#
sub isolate_basic_target ( $ ) {
my $ target = ( split '[/:]' , $ _ [ 0 ] ) [ 0 ] ;
$ target =~ /^(\w+)[(].*[)]$/ ? $ 1 : $ target ;
}
#
# Split the passed target into the basic target and parameter
#
sub get_target_param ( $ ) {
my ( $ target , $ param ) = split '/' , $ _ [ 0 ] ;
unless ( defined $ param ) {
( $ target , $ param ) = ( $ 1 , $ 2 ) if $ target =~ /^(.*?)[(](.*)[)]$/ ;
}
( $ target , $ param ) ;
}
#
# Define an Action
#
sub new_action ( $ ) {
my $ action = $ _ [ 0 ] ;
2008-12-08 01:21:20 +01:00
$ actions - > { $ action } = { actchain = > '' , requires = > { } } ;
2008-12-07 19:15:06 +01:00
}
#
# Record a 'requires' relationship between a pair of actions.
#
sub add_requiredby ( $$ ) {
my ( $ requiredby , $ requires ) = @ _ ;
2008-12-08 01:21:20 +01:00
$ actions - > { $ requires } { requires } { $ requiredby } = 1 ;
2008-12-07 19:15:06 +01:00
}
#
# Create and record a log action chain -- Log action chains have names
# that are formed from the action name by prepending a "%" and appending
# a 1- or 2-digit sequence number. In the functions that follow,
# the CHAIN, LEVEL and TAG variable serves as arguments to the user's
# exit. We call the exit corresponding to the name of the action but we
# set CHAIN to the name of the iptables chain where rules are to be added.
# Similarly, LEVEL and TAG contain the log level and log tag respectively.
#
# The maximum length of a chain name is 30 characters -- since the log
# action chain name is 2-3 characters longer than the base chain name,
# this function truncates the original chain name where necessary before
# it adds the leading "%" and trailing sequence number.
#
sub createlogactionchain ( $$ ) {
my ( $ action , $ level ) = @ _ ;
my $ chain = $ action ;
2008-12-08 01:21:20 +01:00
my $ actionref = $ actions - > { $ action } ;
2008-12-07 19:15:06 +01:00
my $ chainref ;
my ( $ lev , $ tag ) = split ':' , $ level ;
validate_level $ lev ;
$ actionref = new_action $ action unless $ actionref ;
$ chain = substr $ chain , 0 , 28 if ( length $ chain ) > 28 ;
CHECKDUP:
{
2008-12-08 01:21:20 +01:00
$ actionref - > { actchain } + + while $ filter_table - > { '%' . $ chain . $ actionref - > { actchain } } ;
2008-12-07 19:15:06 +01:00
$ chain = substr ( $ chain , 0 , 27 ) , redo CHECKDUP if ( $ actionref - > { actchain } || 0 ) >= 10 and length $ chain == 28 ;
}
2008-12-08 01:21:20 +01:00
$ logactionchains - > { "$action:$level" } = $ chainref = new_standard_chain '%' . $ chain . $ actionref - > { actchain } + + ;
2008-12-07 19:15:06 +01:00
fatal_error "Too many invocations of Action $action" if $ actionref - > { actchain } > 99 ;
2008-12-08 01:21:20 +01:00
unless ( $ targets - > { $ action } & STANDARD ) {
2008-12-07 19:15:06 +01:00
my $ file = find_file $ chain ;
if ( - f $ file ) {
progress_message "Processing $file..." ;
( $ level , my $ tag ) = split /:/ , $ level ;
$ tag = $ tag || '' ;
unless ( my $ return = eval `cat $file` ) {
fatal_error "Couldn't parse $file: $@" if $@ ;
fatal_error "Couldn't do $file: $!" unless defined $ return ;
fatal_error "Couldn't run $file" unless $ return ;
}
}
}
}
sub createsimpleactionchain ( $ ) {
my $ action = shift ;
my $ chainref = new_standard_chain $ action ;
2008-12-08 01:21:20 +01:00
$ logactionchains - > { "$action:none" } = $ chainref ;
2008-12-07 19:15:06 +01:00
2008-12-08 01:21:20 +01:00
unless ( $ targets - > { $ action } & STANDARD ) {
2008-12-07 19:15:06 +01:00
my $ file = find_file $ action ;
if ( - f $ file ) {
progress_message "Processing $file..." ;
my ( $ level , $ tag ) = ( '' , '' ) ;
unless ( my $ return = eval `cat $file` ) {
fatal_error "Couldn't parse $file: $@" if $@ ;
fatal_error "Couldn't do $file: $!" unless defined $ return ;
fatal_error "Couldn't run $file" unless $ return ;
}
}
}
}
#
# Create an action chain and run it's associated user exit
#
sub createactionchain ( $ ) {
my ( $ action , $ level ) = split_action $ _ [ 0 ] ;
my $ chainref ;
if ( defined $ level && $ level ne '' ) {
if ( $ level eq 'none' ) {
createsimpleactionchain $ action ;
} else {
createlogactionchain $ action , $ level ;
}
} else {
createsimpleactionchain $ action ;
}
}
#
# Find the chain that handles the passed action. If the chain cannot be found,
# a fatal error is generated and the function does not return.
#
sub find_logactionchain ( $ ) {
my $ fullaction = $ _ [ 0 ] ;
my ( $ action , $ level ) = split_action $ fullaction ;
$ level = 'none' unless $ level ;
2008-12-08 01:21:20 +01:00
fatal_error "Fatal error in find_logactionchain" unless $ logactionchains - > { "$action:$level" } ;
2008-12-07 19:15:06 +01:00
}
#
# The functions process_actions1-3() implement the three phases of action processing.
#
# The first phase (process_actions1) occurs before the rules file is processed. ${SHAREDIR}/actions.std
# and ${CONFDIR}/actions are scanned (in that order) and for each action:
#
# a) The related action definition file is located and scanned.
# b) Forward and unresolved action references are trapped as errors.
# c) A dependency graph is created using the 'requires' field in the 'actions' table.
#
# As the rules file is scanned, each action[:level[:tag]] is merged onto the 'usedactions' hash. When an <action>
# is merged into the hash, its action chain is created. Where logging is specified, a chain with the name
# %<action>n is used where the <action> name is truncated on the right where necessary to ensure that the total
# length of the chain name does not exceed 30 characters.
#
# The second phase (process_actions2) occurs after the rules file is scanned. The transitive closure of
# %usedactions is generated; again, as new actions are merged into the hash, their action chains are created.
#
# The final phase (process_actions3) is to traverse the keys of %usedactions populating each chain appropriately
# by reading the action definition files and creating rules. Note that a given action definition file is
# processed once for each unique [:level[:tag]] applied to an invocation of the action.
#
sub process_macro1 ( $$ ) {
my ( $ action , $ macrofile ) = @ _ ;
progress_message " ..Expanding Macro $macrofile..." ;
push_open ( $ macrofile ) ;
while ( read_a_line ) {
my ( $ mtarget , $ msource , $ mdest , $ mproto , $ mports , $ msports , $ morigdest , $ mrate , $ muser ) = split_line1 1 , 9 , 'macro file' , $ macro_commands ;
next if $ mtarget eq 'COMMENT' || $ mtarget eq 'FORMAT' ;
$ mtarget =~ s/:.*$// ;
$ mtarget = ( split '/' , $ mtarget ) [ 0 ] ;
2008-12-08 01:21:20 +01:00
my $ targettype = $ targets - > { $ mtarget } ;
2008-12-07 19:15:06 +01:00
$ targettype = 0 unless defined $ targettype ;
fatal_error "Invalid target ($mtarget)"
unless ( $ targettype == STANDARD ) || ( $ mtarget eq 'PARAM' ) || ( $ targettype & ( LOGRULE | NFQ | CHAIN ) ) ;
}
progress_message " ..End Macro $macrofile" ;
pop_open ;
}
sub process_action1 ( $$ ) {
my ( $ action , $ wholetarget ) = @ _ ;
my ( $ target , $ level ) = split_action $ wholetarget ;
$ level = 'none' unless $ level ;
2008-12-08 01:21:20 +01:00
my $ targettype = $ targets - > { $ target } ;
2008-12-07 19:15:06 +01:00
if ( defined $ targettype ) {
return if ( $ targettype == STANDARD ) || ( $ targettype & ( MACRO | LOGRULE | NFQ | CHAIN ) ) ;
fatal_error "Invalid TARGET ($target)" if $ targettype & STANDARD ;
fatal_error "An action may not invoke itself" if $ target eq $ action ;
add_requiredby $ wholetarget , $ action if $ targettype & ACTION ;
} elsif ( $ target eq 'COMMENT' ) {
fatal_error "Invalid TARGET ($wholetarget)" unless $ wholetarget eq $ target ;
} else {
( $ target , my $ param ) = get_target_param $ target ;
return if $ target eq 'NFQUEUE' ;
if ( defined $ param ) {
2008-12-08 01:21:20 +01:00
my $ paramtype = $ targets - > { $ param } || 0 ;
2008-12-07 19:15:06 +01:00
fatal_error "Parameter value not allowed in action files ($param)" if $ paramtype & NATRULE ;
}
fatal_error "Invalid or missing ACTION ($wholetarget)" unless defined $ target ;
if ( find_macro $ target ) {
2008-12-08 01:21:20 +01:00
process_macro1 ( $ action , $ macros - > { $ target } ) ;
2008-12-07 19:15:06 +01:00
} else {
fatal_error "Invalid TARGET ($target)" ;
}
}
}
sub process_actions1 () {
progress_message2 "Preprocessing Action Files..." ;
2008-12-08 01:21:20 +01:00
for my $ act ( grep $ targets - > { $ _ } & ACTION , keys % { $ targets } ) {
2008-12-07 19:15:06 +01:00
new_action $ act ;
}
for my $ file ( qw/actions.std actions/ ) {
open_file $ file ;
while ( read_a_line ) {
my ( $ action ) = split_line 1 , 1 , 'action file' ;
if ( $ action =~ /:/ ) {
warning_message 'Default Actions are now specified in /etc/shorewall/shorewall.conf' ;
$ action =~ s/:.*$// ;
}
next unless $ action ;
2008-12-08 01:21:20 +01:00
if ( $ targets - > { $ action } ) {
warning_message "Duplicate Action Name ($action) Ignored" unless $ targets - > { $ action } & ACTION ;
2008-12-07 19:15:06 +01:00
next ;
}
2008-12-08 01:21:20 +01:00
$ targets - > { $ action } = ACTION ;
2008-12-07 19:15:06 +01:00
fatal_error "Invalid Action Name ($action)" unless "\L$action" =~ /^[a-z]\w*$/ ;
new_action $ action ;
2008-12-08 01:21:20 +01:00
my $ actionfile = find_file "${action_prefix}.$action" ;
$ actionfile = find_file "action.$action" unless - f $ actionfile ;
2008-12-07 19:15:06 +01:00
fatal_error "Missing Action File ($actionfile)" unless - f $ actionfile ;
progress_message2 " Pre-processing $actionfile..." ;
push_open ( $ actionfile ) ;
while ( read_a_line ) {
my ( $ wholetarget , $ source , $ dest , $ proto , $ ports , $ sports , $ rate , $ users ) = split_line 1 , 8 , 'action file' ;
process_action1 ( $ action , $ wholetarget ) ;
}
pop_open ;
}
}
}
sub process_actions2 () {
progress_message2 'Generating Transitive Closure of Used-action List...' ;
my $ changed = 1 ;
while ( $ changed ) {
$ changed = 0 ;
2008-12-08 01:21:20 +01:00
for my $ target ( keys % { $ usedactions } ) {
2008-12-07 19:15:06 +01:00
my ( $ action , $ level ) = split_action $ target ;
2008-12-08 01:21:20 +01:00
my $ actionref = $ actions - > { $ action } ;
2008-12-07 19:15:06 +01:00
fatal_error "Null Action Reference in process_actions2" unless $ actionref ;
for my $ action1 ( keys % { $ actionref - > { requires } } ) {
my $ action2 = merge_levels $ target , $ action1 ;
2008-12-08 01:21:20 +01:00
unless ( $ usedactions - > { $ action2 } ) {
$ usedactions - > { $ action2 } = 1 ;
2008-12-07 19:15:06 +01:00
createactionchain $ action2 ;
$ changed = 1 ;
}
}
}
}
}
#
# This function is called to process each rule generated from an action file.
#
sub process_action ( $$$$$$$$$$ ) {
my ( $ chainref , $ actionname , $ target , $ source , $ dest , $ proto , $ ports , $ sports , $ rate , $ user ) = @ _ ;
my ( $ action , $ level ) = split_action $ target ;
if ( $ action eq 'REJECT' ) {
$ action = 'reject' ;
} elsif ( $ action eq 'CONTINUE' ) {
$ action = 'RETURN' ;
} elsif ( $ action =~ /^NFQUEUE/ ) {
( $ action , my $ param ) = get_target_param $ action ;
$ param = 1 unless defined $ param ;
$ action = "NFQUEUE --queue-num $param" ;
}
expand_rule ( $ chainref ,
NO_RESTRICT ,
do_proto ( $ proto , $ ports , $ sports ) . do_ratelimit ( $ rate , $ action ) . do_user $ user ,
$ source ,
$ dest ,
'' , #Original Dest
'' , #Original Dest port
"-j $action" ,
$ level ,
$ action ,
'' ) ;
}
#
# Expand Macro in action files.
#
sub process_macro3 ( $$$$$$$$$$$ ) {
my ( $ macro , $ param , $ chainref , $ action , $ source , $ dest , $ proto , $ ports , $ sports , $ rate , $ user ) = @ _ ;
my $ nocomment = no_comment ;
my $ format = 1 ;
macro_comment $ macro ;
2008-12-08 01:21:20 +01:00
my $ fn = $ macros - > { $ macro } ;
2008-12-07 19:15:06 +01:00
progress_message "..Expanding Macro $fn..." ;
push_open $ fn ;
while ( read_a_line ) {
my ( $ mtarget , $ msource , $ mdest , $ mproto , $ mports , $ msports , $ morigdest , $ mrate , $ muser ) ;
if ( $ format == 1 ) {
( $ mtarget , $ msource , $ mdest , $ mproto , $ mports , $ msports , $ mrate , $ muser , $ morigdest ) = split_line1 1 , 9 , 'macro file' , $ macro_commands ;
} else {
( $ mtarget , $ msource , $ mdest , $ mproto , $ mports , $ msports , $ morigdest , $ mrate , $ muser ) = split_line1 1 , 9 , 'macro file' , $ macro_commands ;
}
if ( $ mtarget eq 'COMMENT' ) {
process_comment unless $ nocomment ;
next ;
}
if ( $ mtarget eq 'FORMAT' ) {
fatal_error "Invalid FORMAT ($msource)" unless $ msource =~ /^[12]$/ ;
$ format = $ msource ;
next ;
}
fatal_error "Invalid macro file entry (too many columns)" if $ morigdest ne '-' && $ format == 1 ;
if ( $ mtarget =~ /^PARAM:?/ ) {
fatal_error 'PARAM requires that a parameter be supplied in macro invocation' unless $ param ;
$ mtarget = substitute_param $ param , $ mtarget ;
}
fatal_error "Macros used within Actions may not specify an ORIGINAL DEST " if $ morigdest ne '-' ;
if ( $ msource ) {
if ( ( $ msource eq '-' ) || ( $ msource eq 'SOURCE' ) ) {
$ msource = $ source || '' ;
} elsif ( $ msource eq 'DEST' ) {
$ msource = $ dest || '' ;
} else {
$ msource = merge_macro_source_dest $ msource , $ source ;
}
} else {
$ msource = '' ;
}
$ msource = '' if $ msource eq '-' ;
if ( $ mdest ) {
if ( ( $ mdest eq '-' ) || ( $ mdest eq 'DEST' ) ) {
$ mdest = $ dest || '' ;
} elsif ( $ mdest eq 'SOURCE' ) {
$ mdest = $ source || '' ;
} else {
$ mdest = merge_macro_source_dest $ mdest , $ dest ;
}
} else {
$ mdest = '' ;
}
$ mdest = '' if $ mdest eq '-' ;
$ mproto = merge_macro_column $ mproto , $ proto ;
$ mports = merge_macro_column $ mports , $ ports ;
$ msports = merge_macro_column $ msports , $ sports ;
$ mrate = merge_macro_column $ mrate , $ rate ;
$ muser = merge_macro_column $ muser , $ user ;
process_action $ chainref , $ action , $ mtarget , $ msource , $ mdest , $ mproto , $ mports , $ msports , $ mrate , $ muser ;
}
pop_open ;
progress_message '..End Macro' ;
clear_comment unless $ nocomment ;
}
#
# Generate chain for non-builtin action invocation
#
sub process_action3 ( $$$$$ ) {
my ( $ chainref , $ wholeaction , $ action , $ level , $ tag ) = @ _ ;
2008-12-08 01:21:20 +01:00
my $ actionfile = find_file "${action_prefix}.$action" ;
$ actionfile = find_file "action.$action" unless - f $ actionfile ;
2008-12-07 19:15:06 +01:00
fatal_error "Missing Action File ($actionfile)" unless - f $ actionfile ;
progress_message2 "Processing $actionfile for chain $chainref->{name}..." ;
open_file $ actionfile ;
while ( read_a_line ) {
my ( $ target , $ source , $ dest , $ proto , $ ports , $ sports , $ rate , $ user ) = split_line1 1 , 8 , 'action file' ;
if ( $ target eq 'COMMENT' ) {
process_comment ;
next ;
}
my $ target2 = merge_levels $ wholeaction , $ target ;
my ( $ action2 , $ level2 ) = split_action $ target2 ;
( $ action2 , my $ param ) = get_target_param $ action2 ;
2008-12-08 01:21:20 +01:00
my $ action2type = $ targets - > { $ action2 } || 0 ;
2008-12-07 19:15:06 +01:00
unless ( $ action2type == STANDARD ) {
if ( $ action2type & ACTION ) {
$ target2 = ( find_logactionchain ( $ target = $ target2 ) ) - > { name } ;
} else {
fatal_error "Internal Error" unless $ action2type & ( MACRO | LOGRULE | NFQ | CHAIN ) ;
}
}
if ( $ action2type == MACRO ) {
process_macro3 ( $ action2 , $ param , $ chainref , $ action , $ source , $ dest , $ proto , $ ports , $ sports , $ rate , $ user ) ;
} else {
process_action $ chainref , $ action , $ target2 , $ source , $ dest , $ proto , $ ports , $ sports , $ rate , $ user ;
}
}
clear_comment ;
}
sub process_actions3 () {
#
# The following small functions generate rules for the builtin actions of the same name
#
sub dropBcast ( $$$ ) {
my ( $ chainref , $ level , $ tag ) = @ _ ;
if ( $ capabilities { ADDRTYPE } ) {
if ( $ level ne '' ) {
log_rule_limit $ level , $ chainref , 'dropBcast' , 'DROP' , '' , $ tag , 'add' , ' -m addrtype --dst-type BROADCAST ' ;
log_rule_limit $ level , $ chainref , 'dropBcast' , 'DROP' , '' , $ tag , 'add' , ' -d 224.0.0.0/4 ' ;
}
add_rule $ chainref , '-m addrtype --dst-type BROADCAST -j DROP' ;
} else {
add_command $ chainref , 'for address in $ALL_BCASTS; do' ;
incr_cmd_level $ chainref ;
log_rule_limit $ level , $ chainref , 'dropBcast' , 'DROP' , '' , $ tag , 'add' , ' -d $address ' if $ level ne '' ;
add_rule $ chainref , '-d $address -j DROP' ;
decr_cmd_level $ chainref ;
add_command $ chainref , 'done' ;
log_rule_limit $ level , $ chainref , 'dropBcast' , 'DROP' , '' , $ tag , 'add' , ' -d 224.0.0.0/4 ' if $ level ne '' ;
}
add_rule $ chainref , '-d 224.0.0.0/4 -j DROP' ;
}
sub allowBcast ( $$$ ) {
my ( $ chainref , $ level , $ tag ) = @ _ ;
if ( $ capabilities { ADDRTYPE } ) {
if ( $ level ne '' ) {
log_rule_limit $ level , $ chainref , 'allowBcast' , 'ACCEPT' , '' , $ tag , 'add' , ' -m addrtype --dst-type BROADCAST ' ;
log_rule_limit $ level , $ chainref , 'allowBcast' , 'ACCEPT' , '' , $ tag , 'add' , ' -d 224.0.0.0/4 ' ;
}
add_rule $ chainref , '-m addrtype --dst-type BROADCAST -j ACCEPT' ;
} else {
add_command $ chainref , 'for address in $ALL_BCASTS; do' ;
incr_cmd_level $ chainref ;
log_rule_limit $ level , $ chainref , 'allowBcast' , 'ACCEPT' , '' , $ tag , 'add' , ' -d $address ' if $ level ne '' ;
add_rule $ chainref , '-d $address -j ACCEPT' ;
decr_cmd_level $ chainref ;
add_command $ chainref , 'done' ;
log_rule_limit $ level , $ chainref , 'allowBcast' , 'ACCEPT' , '' , $ tag , 'add' , ' -d 224.0.0.0/4 ' if $ level ne '' ;
}
add_rule $ chainref , '-d 224.0.0.0/4 -j ACCEPT' ;
}
sub dropNotSyn ( $$$ ) {
my ( $ chainref , $ level , $ tag ) = @ _ ;
log_rule_limit $ level , $ chainref , 'dropNotSyn' , 'DROP' , '' , $ tag , 'add' , '-p tcp ! --syn ' if $ level ne '' ;
add_rule $ chainref , '-p tcp ! --syn -j DROP' ;
}
sub rejNotSyn ( $$$ ) {
my ( $ chainref , $ level , $ tag ) = @ _ ;
log_rule_limit $ level , $ chainref , 'rejNotSyn' , 'REJECT' , '' , $ tag , 'add' , '-p tcp ! --syn ' if $ level ne '' ;
add_rule $ chainref , '-p tcp ! --syn -j REJECT --reject-with tcp-reset' ;
}
sub dropInvalid ( $$$ ) {
my ( $ chainref , $ level , $ tag ) = @ _ ;
log_rule_limit $ level , $ chainref , 'dropInvalid' , 'DROP' , '' , $ tag , 'add' , '-m state --state INVALID ' if $ level ne '' ;
add_rule $ chainref , '-m state --state INVALID -j DROP' ;
}
sub allowInvalid ( $$$ ) {
my ( $ chainref , $ level , $ tag ) = @ _ ;
log_rule_limit $ level , $ chainref , 'allowInvalid' , 'ACCEPT' , '' , $ tag , 'add' , '-m state --state INVALID ' if $ level ne '' ;
add_rule $ chainref , '-m state --state INVALID -j ACCEPT' ;
}
sub forwardUPnP ( $$$ ) {
}
sub allowinUPnP ( $$$ ) {
my ( $ chainref , $ level , $ tag ) = @ _ ;
if ( $ level ne '' ) {
log_rule_limit $ level , $ chainref , 'allowinUPnP' , 'ACCEPT' , '' , $ tag , 'add' , '-p udp --dport 1900 ' ;
log_rule_limit $ level , $ chainref , 'allowinUPnP' , 'ACCEPT' , '' , $ tag , 'add' , '-p tcp --dport 49152 ' ;
}
add_rule $ chainref , '-p udp --dport 1900 -j ACCEPT' ;
add_rule $ chainref , '-p tcp --dport 49152 -j ACCEPT' ;
}
sub Limit ( $$$ ) {
my ( $ chainref , $ level , $ tag ) = @ _ ;
my @ tag = split /,/ , $ tag ;
fatal_error 'Limit rules must include <set name>,<max connections>,<interval> as the log tag (' . join ( ':' , 'Limit' , $ level eq '' ? 'none' : $ level , $ tag ) . ')' unless @ tag == 3 ;
my $ set = $ tag [ 0 ] ;
for ( @ tag [ 1 , 2 ] ) {
fatal_error 'Max connections and interval in Limit rules must be numeric (' . join ( ':' , 'Limit' , $ level eq '' ? 'none' : $ level , $ tag ) . ')' unless /^\d+$/
}
my $ count = $ tag [ 1 ] + 1 ;
require_capability ( 'RECENT_MATCH' , 'Limit rules' , '' ) ;
add_rule $ chainref , "-m recent --name $set --set" ;
if ( $ level ne '' ) {
my $ xchainref = new_chain 'filter' , "$chainref->{name}%" ;
log_rule_limit $ level , $ xchainref , $ tag [ 0 ] , 'DROP' , '' , '' , 'add' , '' ;
add_rule $ xchainref , '-j DROP' ;
add_rule $ chainref , "-m recent --name $set --update --seconds $tag[2] --hitcount $count -j $xchainref->{name}" ;
} else {
add_rule $ chainref , "-m recent --update --name $set --seconds $tag[2] --hitcount $count -j DROP" ;
}
add_rule $ chainref , '-j ACCEPT' ;
}
my % builtinops = ( 'dropBcast' = > \ & dropBcast ,
'allowBcast' = > \ & allowBcast ,
'dropNotSyn' = > \ & dropNotSyn ,
'rejNotSyn' = > \ & rejNotSyn ,
'dropInvalid' = > \ & dropInvalid ,
'allowInvalid' = > \ & allowInvalid ,
'allowinUPnP' = > \ & allowinUPnP ,
'forwardUPnP' = > \ & forwardUPnP ,
'Limit' = > \ & Limit , ) ;
2008-12-08 01:21:20 +01:00
for my $ wholeaction ( keys % { $ usedactions } ) {
2008-12-07 19:15:06 +01:00
my $ chainref = find_logactionchain $ wholeaction ;
my ( $ action , $ level , $ tag ) = split /:/ , $ wholeaction ;
$ level = '' unless defined $ level ;
$ tag = '' unless defined $ tag ;
2008-12-08 01:21:20 +01:00
if ( $ targets - > { $ action } & BUILTIN ) {
2008-12-07 19:15:06 +01:00
$ level = '' if $ level =~ /none!?/ ;
$ builtinops { $ action } - > ( $ chainref , $ level , $ tag ) ;
} else {
process_action3 $ chainref , $ wholeaction , $ action , $ level , $ tag ;
}
}
}
1 ;