diff --git a/New/Shorewall/Actions.pm b/New/Shorewall/Actions.pm index c12b521a3..af4abbdb9 100644 --- a/New/Shorewall/Actions.pm +++ b/New/Shorewall/Actions.pm @@ -4,11 +4,27 @@ use Shorewall::Common; use Shorewall::Config; use Shorewall::Zones; use Shorewall::Chains; +use Shorewall::Macros; use strict; our @ISA = qw(Exporter); -our @EXPORT = qw( %usedactions %default_actions ); +our @EXPORT = qw( merge_levels + split_action + isolate_basic_target + add_requiredby + createlogactionchain + createactionchain + find_logactionchain + process_actions1 + process_actions2 + process_actions3 + dump_action_table + + %usedactions + %default_actions + %actions + ); our @EXPORT_OK = qw( ); our @VERSION = 1.00; @@ -24,4 +40,596 @@ our %default_actions = ( DROP => 'none' , ACCEPT => 'none' , QUEUE => 'none' ); +# Action Table +# +# %actions{ => { requires => { = 1, +# = 1, +# ... +# } , +# actchain => # Used for generating unique chain names for each : pair. +# +our %actions; +# +# Contains an entry for each used :[:] that maps to the associated chain. +# +my %logactionchains; +# +# Maps each used macro to it's 'macro. ...' file. +# +# +# This function determines the logging for a subordinate action or a rule within a subordinate 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"; + } + + if ( @supparts == 2 ) { + return "$target:none!" if $level eq 'none!'; + return "$target:$level" if ($level =~ /!$/) || ($subparts < 2); + } + + $subordinate; +} + +# +# Return ( action, level[:tag] ) from passed full action +# +sub split_action ( $ ) { + my $action = $_[0]; + my @a = split /:/ , $action; + fatal_error "Invalid ACTION $action in rule \"$line\"" if ( $action =~ /::/ ) || ( @a > 3 ); + ( shift @a, join ":", @a ); +} + +# +# Get Macro Name +# +sub isolate_basic_target( $ ) { + ( split '/' , $_[0] )[0]; +} + +# +# Define an Action +# +sub new_action( $ ) { + + my $action = $_[0]; + + my %h; + + $h{actchain} = ''; + $h{requires} = {}; + $actions{$action} = \%h; +} + +# +# Add an entry to the requiredby hash +# +sub add_requiredby ( $$ ) { + my ($requires , $requiredby ) = @_; + $actions{$requiredby}{requires}{$requires} = 1; +} + +# +# 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. +# +# For each , we maintain two variables: +# +# _actchain - The action chain number. +# _chains - List of ( level[:tag] , chainname ) pairs +# +# 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; + my $actionref = $actions{$action}; + my $chainref; + + $chain = substr $chain, 0, 28 if ( length $chain ) > 28; + + while ( $chain_table{'%' . $chain . $actionref->{actchain}} ) { + $chain = substr $chain, 0, 27 if $actionref->{actchain} == 10 and length $chain == 28; + } + + $actionref = new_action $action unless $actionref; + + $level = 'none' unless $level; + + $logactionchains{"$action:$level"} = new_chain 'filter', '%' . $chain . $actionref->{actchain}++; + + # + # Fixme -- action file + # +} + +# +# Create an action chain and run it's associated user exit +# +sub createactionchain( $ ) { + my ( $action , $level ) = split_action $_[0]; + + if ( $level ) { + if ( $level eq 'none' ) { + $logactionchains{"$action:none"} = new_chain 'filter', $action; + } else { + createlogactionchain $action , $level; + } + } else { + $logactionchains{"$action:none"} = new_chain 'filter', $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; + + fatal_error "Fatal error in find_logactionchain" unless $logactionchains{"$action:$level"}; +} + +# +# The next three functions 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 +# is merged into the hash, its action chain is created. Where logging is specified, a chain with the name +# %n is used where the 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_actions1() { + + for my $act ( grep $targets{$_} & ACTION , keys %targets ) { + new_action $act; + } + + for my $file qw/actions.std actions/ { + open F, "$ENV{TMP_DIR}/$file" or fatal_error "Unable to open stripped $file file: $!"; + + while ( $line = ) { + chomp $line; + my ( $action , $extra ) = split /\s+/, $line; + fatal_error "Invalid Action: $line" if $extra; + + if ( $action =~ /:/ ) { + warning_message 'Default Actions are now specified in /etc/shorewall/shorewall.conf'; + $action =~ s/:.*$//; + } + + next unless $action; + + if ( $targets{$action} ) { + next if $targets{$action} & ACTION; + fatal_error "Invalid Action Name: $action"; + } + + $targets{$action} = ACTION; + + fatal_error "Invalid Action Name: $action" unless "\L$action" =~ /^[a-z]\w*$/; + + new_action $action; + + my $actionfile = find_file "action.$action"; + + fatal_error "Missing Action File: $actionfile" unless -f $actionfile; + + progress_message2 " Pre-processing $actionfile..."; + + open A, $actionfile or fatal_error "Unable to open $actionfile: $!"; + + while ( $line = ) { + chomp $line; + next if $line =~ /^\s*#/; + next if $line =~ /^\s*$/; + $line =~ s/\s+/ /g; + $line =~ s/#.*$//; + + ( my ($wholetarget, $source, $dest, $proto, $ports, $sports, $rate, $users ) , $extra ) = split /\s+/, $line; + + fatal_error "Invalid action rule \"$line\"\n" if $extra; + + my ( $target, $level ) = split_action $wholetarget; + + $level = 'none' unless $level; + + my $targettype = $targets{$target}; + + if ( defined $targettype ) { + next if ( $targettype == STANDARD ) || ( $targettype == MACRO ) || ( $target eq 'LOG' ); + + fatal_error "Invalid TARGET ($target) in action rule \"$line\"" if $targettype & STANDARD; + + add_requiredby $wholetarget, $action if $targettype & ACTION; + } else { + $target =~ s!/.*$!!; + + if ( find_macro $target ) { + my $macrofile = $macros{$target}; + + progress_message " ..Expanding Macro $macrofile..."; + + open M, $macrofile or fatal_error "Unable to open $macrofile: $!"; + + while ( $line = ) { + next if $line =~ /^\s*#/; + $line =~ s/\s+/ /g; + $line =~ s/#.*$//; + next if $line =~ /^\s*$/; + + my ( $mtarget, $msource, $mdest, $mproto, $mports, $msports, $ mrate, $muser, $mextra ) = split /\s+/, $line; + + fatal_error "Invalid macro rule \"$line\"" if $mextra; + + $mtarget =~ s/:.*$//; + + $targettype = $targets{$mtarget}; + + $targettype = 0 unless defined $targettype; + + fatal_error "Invalid target ($mtarget) in rule \"$line\"" + unless ( $targettype == STANDARD ) || ( $mtarget eq 'PARAM' ) || ( $mtarget eq 'LOG' ); + } + + progress_message " ..End Macro"; + + close M; + } else { + fatal_error "Invalid TARGET ($target) in rule \"$line\""; + } + } + } + close A; + } + close F; + } +} + +sub process_actions2 () { + progress_message2 'Generating Transitive Closure of Used-action List...'; + + my $changed = 1; + + while ( $changed ) { + $changed = 0; + for my $target (keys %usedactions) { + my ($action, $level) = split_action $target; + my $actionref = $actions{$action}; + die "Null Action Reference in process_actions2" unless $actionref; + for my $action1 ( keys %{$actionref->{requires}} ) { + my $action2 = merge_levels $target, $action1; + unless ( $usedactions{ $action2 } ) { + $usedactions{ $action2 } = 1; + createactionchain $action2; + $changed = 1; + } + } + } + } +} + +# +# Generate chain for non-builtin action invocation +# +sub process_action3( $$$$$ ) { + # + # 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; + + expand_rule ( $chainref , + do_proto( $proto, $ports, $sports ) . do_ratelimit( $rate ) . do_user $user , + $source , + $dest , + '', #Original Dest + '-j ' . ($action eq 'REJECT' ? 'reject' : $action eq 'CONTINUE' ? 'RETURN' : $action), + $level , + $action , + '' ); + } + + my ( $chainref, $wholeaction, $action, $level, $tag ) = @_; + my $actionfile = find_file "action.$action"; + my $standard = ( $actionfile =~ /^($env{SHAREDIR})/ ); + + fatal_error "Missing Action File: $actionfile" unless -f $actionfile; + + progress_message2 "Processing $actionfile for chain $chainref->{name}..."; + + open A, $actionfile or fatal_error "Unable to open $actionfile: $!"; + + while ( $line = ) { + chomp $line; + next if $line =~ /^\s*#/; + next if $line =~ /^\s*$/; + $line =~ s/\s+/ /g; + $line =~ s/#.*$//; + $line = expand_shell_variables $line unless $standard; + + my ($target, $source, $dest, $proto, $ports, $sports, $rate, $user , $extra ) = split /\s+/, $line; + + my $target2 = merge_levels $wholeaction, $target; + + my ( $action2 , $level2 ) = split_action $target2; + + my $action2type = $targets{isolate_basic_target $action2}; + + unless ( $action2type == STANDARD ) { + if ( $target eq 'COMMENT' ) { + if ( $capabilities{COMMENTS} ) { + ( $comment = $line ) =~ s/^\s*COMMENT\s*//; + $comment =~ s/\s*$//; + } else { + warning_message "COMMENT ignored -- requires comment support in iptables/Netfilter"; + } + } elsif ( $action2type & ACTION ) { + $target2 = (find_logactionchain ( $target = $target2 ))->{name}; + } else { + die "Internal Error" unless $action2type == MACRO; + } + } + + if ( $action2type == MACRO ) { + ( $action2, my $param ) = split '/', $action2; + + fatal_error "Null Macro" unless my $fn = $macros{$action2}; + + progress_message "..Expanding Macro $fn..."; + + open M, $fn or fatal_error "Can't open $fn: $!"; + + my $standard = ( $fn =~ /^($env{SHAREDIR})/ ); + + while ( $line = ) { + next if $line =~ /^\s*#/; + next if $line =~ /^\s*$/; + $line =~ s/\s+/ /g; + $line =~ s/#.*$//; + $line = expand_shell_variables $line unless $standard; + + my ( $mtarget, $msource, $mdest, $mproto, $mports, $msports, $mrate, $muser ) = split /\s+/, $line; + + if ( $mtarget =~ /^PARAM:?/ ) { + fatal_error 'PARAM requires that a parameter be supplied in macro invocation' unless $param; + $mtarget = substitute_action $param, $mtarget; + } + + 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; + } + + close M; + + progress_message '..End Macro' + + } else { + process_action $chainref, $action, $target2, $source, $dest, $proto, $ports, $sports, $rate, $user; + } + } + + $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 ( $level ) { + log_rule_limit $level, $chainref, 'dropBcast' , 'DROP', '', $tag, 'add', ' -m pkttype --pkt-type broadcast'; + log_rule_limit $level, $chainref, 'dropBcast' , 'DROP', '', $tag, 'add', ' -m pkttype --pkt-type multicast'; + } + + add_rule $chainref, '-m pkttype --pkt-type broadcast -j DROP'; + add_rule $chainref, '-m pkttype --pkt-type multicast -j DROP'; + } + + sub allowBcast( $$$ ) { + my ($chainref, $level, $tag) = @_; + + if ( $level ) { + log_rule_limit $level, $chainref, 'allowBcast' , 'ACCEPT', '', $tag, 'add', ' -m pkttype --pkt-type broadcast'; + log_rule_limit $level, $chainref, 'allowBcast' , 'ACCEPT', '', $tag, 'add', ' -m pkttype --pkt-type multicast'; + } + + add_rule $chainref, '-m pkttype --pkt-type broadcast -j ACCEPT'; + add_rule $chainref, '-m pkttype --pkt-type multicast -j ACCEPT'; + } + + sub dropNotSyn ( $$$ ) { + my ($chainref, $level, $tag) = @_; + + log_rule_limit $level, $chainref, 'dropNotSyn' , 'DROP', '', $tag, 'add', '-p tcp ! --syn ' if $level; + 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; + add_rule $chainref , '-p tcp ! --syn -j REJECT'; + } + + sub dropInvalid ( $$$ ) { + my ($chainref, $level, $tag) = @_; + + log_rule_limit $level, $chainref, 'dropInvalid' , 'DROP', '', $tag, 'add', '-m state --state INVALID ' if $level; + add_rule $chainref , '-m state --state INVALID -j REJECT'; + } + + sub allowInvalid ( $$$ ) { + my ($chainref, $level, $tag) = @_; + + log_rule_limit $level, $chainref, 'allowInvalid' , 'ACCEPT', '', $tag, 'add', '-m state --state INVALID ' if $level; + add_rule $chainref , '-m state --state INVALID -j ACCEPT'; + } + + sub forwardUPnP ( $$$ ) { + } + + sub allowinUPnP ( $$$ ) { + my ($chainref, $level, $tag) = @_; + + if ( $level ) { + 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 ,, as the log tag' unless @tag == 3; + + add_rule $chainref, '-m recent --name $tag[0] --set'; + + if ( $level ) { + 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 $tag[0] --update --seconds $tag[2] --hitcount $(( $tag[1] + 1 )) -j $chainref->{name}%"; + } else { + add_rule $chainref, "-m recent --update --name $tag[0] --seconds $tag[2] --hitcount $(( $tag[1] + 1 )) -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, + ); + + for my $wholeaction ( keys %usedactions ) { + my $chainref = find_logactionchain $wholeaction; + my ( $action, $level, $tag ) = split /:/, $wholeaction; + + $level = '' unless defined $level; + $tag = '' unless defined $tag; + + if ( $targets{$action} & BUILTIN ) { + $level = '' if $level =~ /none!?/; + $builtinops{$action}->($chainref, $level, $tag); + } else { + process_action3 $chainref, $wholeaction, $action, $level, $tag; + } + } +} + +sub dump_action_table() { + my $action; + + print "\n"; + + for $action ( sort keys %actions ) { + print "Action $action\n"; + my $already = 0; + for my $requires ( keys %{$actions{$action}{requires}} ) { + print " Requires:\n" unless $already; + print " $requires\n"; + $already = 1; + } + } + + print "\nAction Chains:\n"; + + for $action ( sort keys %usedactions ) { + $action .= ':none' unless $action =~ /:/; + print " $action = $logactionchains{$action}{name}\n"; + } +} + 1; diff --git a/New/Shorewall/Macros.pm b/New/Shorewall/Macros.pm new file mode 100644 index 000000000..93e952752 --- /dev/null +++ b/New/Shorewall/Macros.pm @@ -0,0 +1,95 @@ +package Shorewall::Macros; +require Exporter; +use Shorewall::Common; +use Shorewall::Config; +use Shorewall::Zones; +use Shorewall::Chains; + +use strict; + +our @ISA = qw(Exporter); +our @EXPORT = qw( find_macro + expand_shell_variables + substitute_action + merge_macro_source_dest + merge_macro_column + + %macros ); +our @EXPORT_OK = qw( ); +our @VERSION = 1.00; + + +our %macros; + +# +# 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]; + my $macrofile = find_file "macro.$macro"; + + if ( -f $macrofile ) { + $macros{$macro} = $macrofile; + $targets{$macro} = MACRO; + } +} + +# +# Macro and action files can have shell variables embedded. This function expands them from %ENV. +# +sub expand_shell_variables( $ ) { + my $line = $_[0]; $line = $1 . ( $ENV{$2} || '' ) . $3 while $line =~ /^(.*?)\$([a-zA-Z]\w*)(.*)$/; $line; +} + +# This function substitutes the second argument for the first part of the first argument up to the first colon (":") +# +# Example: +# +# substitute_action DNAT PARAM:info:FTP +# +# produces "DNAT:info:FTP" +# +sub substitute_action( $$ ) { + 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"; + } + } + + $body || ''; +} + +sub merge_macro_column( $$ ) { + my ( $body, $invocation ) = @_; + + if ( $invocation ) { + return ( $body || '') if $invocation eq '-'; + $invocation || ''; + } else { + $body || ''; + } +} + +1; diff --git a/New/compiler.pl b/New/compiler.pl index 61642107c..227700d2c 100755 --- a/New/compiler.pl +++ b/New/compiler.pl @@ -13,6 +13,7 @@ use Shorewall::Tc; use Shorewall::Tunnels; use Shorewall::Providers; use Shorewall::Policy; +use Shorewall::Macros; use Shorewall::Actions; # @@ -20,232 +21,6 @@ use Shorewall::Actions; # my $sectioned = 0; -# Action Table -# -# %actions{ => { requires => { = 1, -# = 1, -# ... -# } , -# actchain => # Used for generating unique chain names for each : pair. -# -my %actions; -# -# Contains an entry for each used :[:] that maps to the associated chain. -# -my %logactionchains; -# -# Maps each used macro to it's 'macro. ...' file. -# -my %macros; - -# -# This function determines the logging for a subordinate action or a rule within a subordinate 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"; - } - - if ( @supparts == 2 ) { - return "$target:none!" if $level eq 'none!'; - return "$target:$level" if ($level =~ /!$/) || ($subparts < 2); - } - - $subordinate; -} - -# -# Return ( action, level[:tag] ) from passed full action -# -sub split_action ( $ ) { - my $action = $_[0]; - my @a = split /:/ , $action; - fatal_error "Invalid ACTION $action in rule \"$line\"" if ( $action =~ /::/ ) || ( @a > 3 ); - ( shift @a, join ":", @a ); -} - -# -# Get Macro Name -# -sub isolate_basic_target( $ ) { - ( split '/' , $_[0] )[0]; -} - -# This function substitutes the second argument for the first part of the first argument up to the first colon (":") -# -# Example: -# -# substitute_action DNAT PARAM:info:FTP -# -# produces "DNAT:info:FTP" -# -sub substitute_action( $$ ) { - my ( $param, $action ) = @_; - - if ( $action =~ /:/ ) { - my $logpart = (split_action $action)[1]; - $logpart =~ s!/$!!; - return "$param:$logpart"; - } - - $param; -} - -# -# Define an Action -# -sub new_action( $ ) { - - my $action = $_[0]; - - my %h; - - $h{actchain} = ''; - $h{requires} = {}; - $actions{$action} = \%h; -} - -# -# Add an entry to the requiredby hash -# -sub add_requiredby ( $$ ) { - my ($requires , $requiredby ) = @_; - $actions{$requiredby}{requires}{$requires} = 1; -} - -# -# 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. -# -# For each , we maintain two variables: -# -# _actchain - The action chain number. -# _chains - List of ( level[:tag] , chainname ) pairs -# -# 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; - my $actionref = $actions{$action}; - my $chainref; - - $chain = substr $chain, 0, 28 if ( length $chain ) > 28; - - while ( $chain_table{'%' . $chain . $actionref->{actchain}} ) { - $chain = substr $chain, 0, 27 if $actionref->{actchain} == 10 and length $chain == 28; - } - - $actionref = new_action $action unless $actionref; - - $level = 'none' unless $level; - - $logactionchains{"$action:$level"} = new_chain 'filter', '%' . $chain . $actionref->{actchain}++; - - # - # Fixme -- action file - # -} - -# -# Create an action chain and run it's associated user exit -# -sub createactionchain( $ ) { - my ( $action , $level ) = split_action $_[0]; - - if ( $level ) { - if ( $level eq 'none' ) { - $logactionchains{"$action:none"} = new_chain 'filter', $action; - } else { - createlogactionchain $action , $level; - } - } else { - $logactionchains{"$action:none"} = new_chain 'filter', $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; - - fatal_error "Fatal error in find_logactionchain" unless $logactionchains{"$action:$level"}; -} - -# -# 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"; - } - } - - $body || ''; -} - -sub merge_macro_column( $$ ) { - my ( $body, $invocation ) = @_; - - if ( $invocation ) { - return ( $body || '') if $invocation eq '-'; - $invocation || ''; - } else { - $body || ''; - } -} - -# -# 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]; - my $macrofile = find_file "macro.$macro"; - - if ( -f $macrofile ) { - $macros{$macro} = $macrofile; - $targets{$macro} = MACRO; - } -} - sub process_tos() { my $chain = 'pretos'; my $stdchain = 'PREROUTING'; @@ -710,14 +485,6 @@ sub setup_mac_lists( $ ) { } } - -# -# Macro and action files can have shell variables embedded. This function expands them from %ENV. -# -sub expand_shell_variables( $ ) { - my $line = $_[0]; $line = $1 . ( $ENV{$2} || '' ) . $3 while $line =~ /^(.*?)\$([a-zA-Z]\w*)(.*)$/; $line; -} - sub process_rule1 ( $$$$$$$$$ ); # @@ -1161,438 +928,6 @@ sub process_rules() { $section = 'DONE'; } -# -# Generate chain for non-builtin action invocation -# -sub process_action3( $$$$$ ) { - # - # 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; - - expand_rule ( $chainref , - do_proto( $proto, $ports, $sports ) . do_ratelimit( $rate ) . do_user $user , - $source , - $dest , - '', #Original Dest - '-j ' . ($action eq 'REJECT' ? 'reject' : $action eq 'CONTINUE' ? 'RETURN' : $action), - $level , - $action , - '' ); - } - - my ( $chainref, $wholeaction, $action, $level, $tag ) = @_; - my $actionfile = find_file "action.$action"; - my $standard = ( $actionfile =~ /^($env{SHAREDIR})/ ); - - fatal_error "Missing Action File: $actionfile" unless -f $actionfile; - - progress_message2 "Processing $actionfile for chain $chainref->{name}..."; - - open A, $actionfile or fatal_error "Unable to open $actionfile: $!"; - - while ( $line = ) { - chomp $line; - next if $line =~ /^\s*#/; - next if $line =~ /^\s*$/; - $line =~ s/\s+/ /g; - $line =~ s/#.*$//; - $line = expand_shell_variables $line unless $standard; - - my ($target, $source, $dest, $proto, $ports, $sports, $rate, $user , $extra ) = split /\s+/, $line; - - my $target2 = merge_levels $wholeaction, $target; - - my ( $action2 , $level2 ) = split_action $target2; - - my $action2type = $targets{isolate_basic_target $action2}; - - unless ( $action2type == STANDARD ) { - if ( $target eq 'COMMENT' ) { - if ( $capabilities{COMMENTS} ) { - ( $comment = $line ) =~ s/^\s*COMMENT\s*//; - $comment =~ s/\s*$//; - } else { - warning_message "COMMENT ignored -- requires comment support in iptables/Netfilter"; - } - } elsif ( $action2type & ACTION ) { - $target2 = (find_logactionchain ( $target = $target2 ))->{name}; - } else { - die "Internal Error" unless $action2type == MACRO; - } - } - - if ( $action2type == MACRO ) { - ( $action2, my $param ) = split '/', $action2; - - fatal_error "Null Macro" unless my $fn = $macros{$action2}; - - progress_message "..Expanding Macro $fn..."; - - open M, $fn or fatal_error "Can't open $fn: $!"; - - my $standard = ( $fn =~ /^($env{SHAREDIR})/ ); - - while ( $line = ) { - next if $line =~ /^\s*#/; - next if $line =~ /^\s*$/; - $line =~ s/\s+/ /g; - $line =~ s/#.*$//; - $line = expand_shell_variables $line unless $standard; - - my ( $mtarget, $msource, $mdest, $mproto, $mports, $msports, $mrate, $muser ) = split /\s+/, $line; - - if ( $mtarget =~ /^PARAM:?/ ) { - fatal_error 'PARAM requires that a parameter be supplied in macro invocation' unless $param; - $mtarget = substitute_action $param, $mtarget; - } - - 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; - } - - close M; - - progress_message '..End Macro' - - } else { - process_action $chainref, $action, $target2, $source, $dest, $proto, $ports, $sports, $rate, $user; - } - } - - $comment = ''; -} - -# -# The next three functions 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 -# is merged into the hash, its action chain is created. Where logging is specified, a chain with the name -# %n is used where the 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_actions1() { - - for my $act ( grep $targets{$_} & ACTION , keys %targets ) { - new_action $act; - } - - for my $file qw/actions.std actions/ { - open F, "$ENV{TMP_DIR}/$file" or fatal_error "Unable to open stripped $file file: $!"; - - while ( $line = ) { - chomp $line; - my ( $action , $extra ) = split /\s+/, $line; - fatal_error "Invalid Action: $line" if $extra; - - if ( $action =~ /:/ ) { - warning_message 'Default Actions are now specified in /etc/shorewall/shorewall.conf'; - $action =~ s/:.*$//; - } - - next unless $action; - - if ( $targets{$action} ) { - next if $targets{$action} & ACTION; - fatal_error "Invalid Action Name: $action"; - } - - $targets{$action} = ACTION; - - fatal_error "Invalid Action Name: $action" unless "\L$action" =~ /^[a-z]\w*$/; - - new_action $action; - - my $actionfile = find_file "action.$action"; - - fatal_error "Missing Action File: $actionfile" unless -f $actionfile; - - progress_message2 " Pre-processing $actionfile..."; - - open A, $actionfile or fatal_error "Unable to open $actionfile: $!"; - - while ( $line = ) { - chomp $line; - next if $line =~ /^\s*#/; - next if $line =~ /^\s*$/; - $line =~ s/\s+/ /g; - $line =~ s/#.*$//; - - ( my ($wholetarget, $source, $dest, $proto, $ports, $sports, $rate, $users ) , $extra ) = split /\s+/, $line; - - fatal_error "Invalid action rule \"$line\"\n" if $extra; - - my ( $target, $level ) = split_action $wholetarget; - - $level = 'none' unless $level; - - my $targettype = $targets{$target}; - - if ( defined $targettype ) { - next if ( $targettype == STANDARD ) || ( $targettype == MACRO ) || ( $target eq 'LOG' ); - - fatal_error "Invalid TARGET ($target) in action rule \"$line\"" if $targettype & STANDARD; - - add_requiredby $wholetarget, $action if $targettype & ACTION; - } else { - $target =~ s!/.*$!!; - - if ( find_macro $target ) { - my $macrofile = $macros{$target}; - - progress_message " ..Expanding Macro $macrofile..."; - - open M, $macrofile or fatal_error "Unable to open $macrofile: $!"; - - while ( $line = ) { - next if $line =~ /^\s*#/; - $line =~ s/\s+/ /g; - $line =~ s/#.*$//; - next if $line =~ /^\s*$/; - - my ( $mtarget, $msource, $mdest, $mproto, $mports, $msports, $ mrate, $muser, $mextra ) = split /\s+/, $line; - - fatal_error "Invalid macro rule \"$line\"" if $mextra; - - $mtarget =~ s/:.*$//; - - $targettype = $targets{$mtarget}; - - $targettype = 0 unless defined $targettype; - - fatal_error "Invalid target ($mtarget) in rule \"$line\"" - unless ( $targettype == STANDARD ) || ( $mtarget eq 'PARAM' ) || ( $mtarget eq 'LOG' ); - } - - progress_message " ..End Macro"; - - close M; - } else { - fatal_error "Invalid TARGET ($target) in rule \"$line\""; - } - } - } - close A; - } - close F; - } -} - -sub process_actions2 () { - progress_message2 'Generating Transitive Closure of Used-action List...'; - - my $changed = 1; - - while ( $changed ) { - $changed = 0; - for my $target (keys %usedactions) { - my ($action, $level) = split_action $target; - my $actionref = $actions{$action}; - die "Null Action Reference in process_actions2" unless $actionref; - for my $action1 ( keys %{$actionref->{requires}} ) { - my $action2 = merge_levels $target, $action1; - unless ( $usedactions{ $action2 } ) { - $usedactions{ $action2 } = 1; - createactionchain $action2; - $changed = 1; - } - } - } - } -} - -sub process_actions3 () { - # - # The following small functions generate rules for the builtin actions of the same name - # - sub dropBcast( $$$ ) { - my ($chainref, $level, $tag) = @_; - - if ( $level ) { - log_rule_limit $level, $chainref, 'dropBcast' , 'DROP', '', $tag, 'add', ' -m pkttype --pkt-type broadcast'; - log_rule_limit $level, $chainref, 'dropBcast' , 'DROP', '', $tag, 'add', ' -m pkttype --pkt-type multicast'; - } - - add_rule $chainref, '-m pkttype --pkt-type broadcast -j DROP'; - add_rule $chainref, '-m pkttype --pkt-type multicast -j DROP'; - } - - sub allowBcast( $$$ ) { - my ($chainref, $level, $tag) = @_; - - if ( $level ) { - log_rule_limit $level, $chainref, 'allowBcast' , 'ACCEPT', '', $tag, 'add', ' -m pkttype --pkt-type broadcast'; - log_rule_limit $level, $chainref, 'allowBcast' , 'ACCEPT', '', $tag, 'add', ' -m pkttype --pkt-type multicast'; - } - - add_rule $chainref, '-m pkttype --pkt-type broadcast -j ACCEPT'; - add_rule $chainref, '-m pkttype --pkt-type multicast -j ACCEPT'; - } - - sub dropNotSyn ( $$$ ) { - my ($chainref, $level, $tag) = @_; - - log_rule_limit $level, $chainref, 'dropNotSyn' , 'DROP', '', $tag, 'add', '-p tcp ! --syn ' if $level; - 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; - add_rule $chainref , '-p tcp ! --syn -j REJECT'; - } - - sub dropInvalid ( $$$ ) { - my ($chainref, $level, $tag) = @_; - - log_rule_limit $level, $chainref, 'dropInvalid' , 'DROP', '', $tag, 'add', '-m state --state INVALID ' if $level; - add_rule $chainref , '-m state --state INVALID -j REJECT'; - } - - sub allowInvalid ( $$$ ) { - my ($chainref, $level, $tag) = @_; - - log_rule_limit $level, $chainref, 'allowInvalid' , 'ACCEPT', '', $tag, 'add', '-m state --state INVALID ' if $level; - add_rule $chainref , '-m state --state INVALID -j ACCEPT'; - } - - sub forwardUPnP ( $$$ ) { - } - - sub allowinUPnP ( $$$ ) { - my ($chainref, $level, $tag) = @_; - - if ( $level ) { - 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 ,, as the log tag' unless @tag == 3; - - add_rule $chainref, '-m recent --name $tag[0] --set'; - - if ( $level ) { - 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 $tag[0] --update --seconds $tag[2] --hitcount $(( $tag[1] + 1 )) -j $chainref->{name}%"; - } else { - add_rule $chainref, "-m recent --update --name $tag[0] --seconds $tag[2] --hitcount $(( $tag[1] + 1 )) -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, - ); - - for my $wholeaction ( keys %usedactions ) { - my $chainref = find_logactionchain $wholeaction; - my ( $action, $level, $tag ) = split /:/, $wholeaction; - - $level = '' unless defined $level; - $tag = '' unless defined $tag; - - if ( $targets{$action} & BUILTIN ) { - $level = '' if $level =~ /none!?/; - $builtinops{$action}->($chainref, $level, $tag); - } else { - process_action3 $chainref, $wholeaction, $action, $level, $tag; - } - } -} - -sub dump_action_table() { - my $action; - - print "\n"; - - for $action ( sort keys %actions ) { - print "Action $action\n"; - my $already = 0; - for my $requires ( keys %{$actions{$action}{requires}} ) { - print " Requires:\n" unless $already; - print " $requires\n"; - $already = 1; - } - } - - print "\nAction Chains:\n"; - - for $action ( sort keys %usedactions ) { - $action .= ':none' unless $action =~ /:/; - print " $action = $logactionchains{$action}{name}\n"; - } -} - # # Accounting #