diff --git a/Shorewall-perl/Shorewall/Accounting.pm b/Shorewall-perl/Shorewall/Accounting.pm index b3dfaaec7..dd8ebd68b 100644 --- a/Shorewall-perl/Shorewall/Accounting.pm +++ b/Shorewall-perl/Shorewall/Accounting.pm @@ -25,11 +25,9 @@ # package Shorewall::Accounting; require Exporter; -use Shorewall::Common; use Shorewall::Config; use Shorewall::IPAddrs; use Shorewall::Zones; -use Shorewall::Interfaces; use Shorewall::Chains; use strict; diff --git a/Shorewall-perl/Shorewall/Actions.pm b/Shorewall-perl/Shorewall/Actions.pm index 9faa13ba1..a8d962f0c 100644 --- a/Shorewall-perl/Shorewall/Actions.pm +++ b/Shorewall-perl/Shorewall/Actions.pm @@ -21,15 +21,13 @@ # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA # # This module contains the code for dealing with actions (built-in, -# standard and user-defined). +# standard and user-defined) and Macros. # package Shorewall::Actions; require Exporter; -use Shorewall::Common; use Shorewall::Config; use Shorewall::Zones; use Shorewall::Chains; -use Shorewall::Macros; use strict; @@ -44,9 +42,17 @@ our @EXPORT = qw( merge_levels process_actions2 process_actions3 + find_macro + split_action + substitute_param + merge_macro_source_dest + merge_macro_column + %usedactions %default_actions %actions + + %macros ); our @EXPORT_OK = qw( initialize ); our $VERSION = 4.00; @@ -73,6 +79,9 @@ our %actions; # Contains an entry for each used :[:] that maps to the associated chain. # our %logactionchains; + +our %macros; + # # Initialize globals -- we take this novel approach to globals initialization to allow # the compiler to run multiple times in the same process. The @@ -108,6 +117,84 @@ sub merge_levels ($$) { my $subparts = @subparts; my $target = $subparts[0]; +# +# 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; + } 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; + } +} + push @subparts, '' while @subparts < 3; #Avoid undefined values diff --git a/Shorewall-perl/Shorewall/Chains.pm b/Shorewall-perl/Shorewall/Chains.pm index a5e08e6eb..0fb44832c 100644 --- a/Shorewall-perl/Shorewall/Chains.pm +++ b/Shorewall-perl/Shorewall/Chains.pm @@ -27,11 +27,9 @@ package Shorewall::Chains; require Exporter; -use Shorewall::Common; use Shorewall::Config; use Shorewall::Ports; use Shorewall::Zones; -use Shorewall::Interfaces; use Shorewall::IPAddrs; use strict; diff --git a/Shorewall-perl/Shorewall/Common.pm b/Shorewall-perl/Shorewall/Common.pm deleted file mode 100644 index 3d6beafcc..000000000 --- a/Shorewall-perl/Shorewall/Common.pm +++ /dev/null @@ -1,367 +0,0 @@ -# -# Shorewall-perl 4.0 -- /usr/share/shorewall-perl/Shorewall/Common.pm -# -# 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 -# -# This is the lowes level Shorewall module. It provides very basic -# services such as creation of temporary 'object' files, writing -# into those files (emitters) and finalizing those files (renaming -# them to their final name and setting their mode appropriately). -# -package Shorewall::Common; -require Exporter; -use File::Basename; -use File::Temp qw/ tempfile tempdir /; -use Cwd 'abs_path'; - -use strict; - -our @ISA = qw(Exporter); -our @EXPORT = qw( - create_temp_object - finalize_object - emit - emitj - emit_unindented - save_progress_message - save_progress_message_short - set_timestamp - set_verbose - progress_message - progress_message2 - progress_message3 - push_indent - pop_indent - copy - copy1 - create_temp_aux_config - finalize_aux_config - - $command - $doing - $done - $verbose - ); -our @EXPORT_OK = qw( $timestamp initialize ); -our $VERSION = 4.00; - -our ($command, $doing, $done ); -our $verbose; -our $timestamp; -our $object; -our $lastlineblank; -our $indent; -our ( $dir, $file ); # Object's Directory and File -our $tempfile; # Temporary File Name - -# -# 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() { - ( $command, $doing, $done ) = qw/ compile Compiling Compiled/; #describe the current command, it's present progressive, and it's completion. - - $verbose = 0; # Verbosity setting. 0 = almost silent, 1 = major progress messages only, 2 = all progress messages (very noisy) - $timestamp = ''; # If true, we are to timestamp each progress message - $object = 0; # Object (script) file Handle Reference - $lastlineblank = 0; # Avoid extra blank lines in the output - $indent = ''; # Current indentation - ( $dir, $file ) = ('',''); # Object's Directory and File - $tempfile = ''; # Temporary File Name -} - -INIT { - initialize; -} - -# -# Fatal Error -# -sub fatal_error -{ - die " ERROR: @_\n"; -} - -# -# Write the argument to the object file (if any) with the current indentation. -# -# Replaces leading spaces with tabs as appropriate and suppresses consecutive blank lines. -# -sub emit ( $ ) { - if ( $object ) { - # - # 'compile' as opposed to 'check' - # - my $line = $_[0]; # This copy is necessary because the actual arguments are almost always read-only. - - unless ( $line =~ /^\s*$/ ) { - $line =~ s/^\n// if $lastlineblank; - $line =~ s/^/$indent/gm if $indent; - $line =~ s/ /\t/gm; - print $object "$line\n"; - $lastlineblank = ( substr( $line, -1, 1 ) eq "\n" ); - } else { - print $object "\n" unless $lastlineblank; - $lastlineblank = 1; - } - } -} - -# -# Version of emit() that accepts an indefinite number of scalar arguments; each argument will be emitted as a separate line -# -sub emitj { - if ( $object ) { - # - # 'compile' as opposed to 'check' - # - for ( @_ ) { - unless ( /^\s*$/ ) { - my $line = $_; # This copy is necessary because the actual arguments are almost always read-only. - $line =~ s/^\n// if $lastlineblank; - $line =~ s/^/$indent/gm if $indent; - $line =~ s/ /\t/gm; - print $object "$line\n"; - $lastlineblank = ( substr( $line, -1, 1 ) eq "\n" ); - } else { - print $object "\n" unless $lastlineblank; - $lastlineblank = 1; - } - } - } -} - -# -# Write passed message to the object with newline but no indentation. -# - -sub emit_unindented( $ ) { - print $object "$_[0]\n" if $object; -} - -# -# Write a progress_message2 command with surrounding blank lines to the output file. -# -sub save_progress_message( $ ) { - emit "\nprogress_message2 @_\n" if $object; -} - -# -# Write a progress_message command to the output file. -# -sub save_progress_message_short( $ ) { - emit "progress_message $_[0]" if $object; -} - -# -# Set $timestamp -# -sub set_timestamp( $ ) { - $timestamp = shift; -} - -# -# Set $verbose -# -sub set_verbose( $ ) { - $verbose = shift; -} - -# -# Print the current TOD to STDOUT. -# -sub timestamp() { - my ($sec, $min, $hr) = ( localtime ) [0,1,2]; - printf '%02d:%02d:%02d ', $hr, $min, $sec; -} - -# -# Write a message if $verbose >= 2 -# -sub progress_message { - if ( $verbose > 1 ) { - timestamp if $timestamp; - # - # We use this function to display messages containing raw config file images which may contains tabs (including multiple tabs in succession). - # The following makes such messages look more readable and uniform - # - my $line = "@_"; - $line =~ s/\s+/ /g; - print "$line\n"; - } -} - -# -# Write a message if $verbose >= 1 -# -sub progress_message2 { - if ( $verbose > 0 ) { - timestamp if $timestamp; - print "@_\n"; - } -} - -# -# Write a message if $verbose >= 0 -# -sub progress_message3 { - if ( $verbose >= 0 ) { - timestamp if $timestamp; - print "@_\n"; - } -} - -# -# Push/Pop Indent -# -sub push_indent() { - $indent = "$indent "; -} - -sub pop_indent() { - $indent = substr( $indent , 0 , ( length $indent ) - 4 ); -} - -# -# Functions for copying files into the object -# -sub copy( $ ) { - if ( $object ) { - my $file = $_[0]; - - open IF , $file or fatal_error "Unable to open $file: $!"; - - while ( ) { - s/^/$indent/ if $indent; - print $object $_; - } - - close IF; - } -} - -# -# This one handles line continuation. - -sub copy1( $ ) { - if ( $object ) { - my $file = $_[0]; - - open IF , $file or fatal_error "Unable to open $file: $!"; - - my $do_indent = 1; - - while ( ) { - if ( /^\s*$/ ) { - print $object "\n"; - $do_indent = 1; - next; - } - - s/^/$indent/ if $indent && $do_indent; - print $object $_; - $do_indent = ! ( /\\$/ ); - } - - close IF; - } -} - -# -# Create the temporary object file -- the passed file name is the name of the final file. -# We create a temporary file in the same directory so that we can use rename to finalize it. -# -sub create_temp_object( $ ) { - my $objectfile = $_[0]; - my $suffix; - - eval { - ( $file, $dir, $suffix ) = fileparse( $objectfile ); - }; - - die if $@; - - fatal_error "Directory $dir does not exist" unless -d $dir; - fatal_error "Directory $dir is not writable" unless -w _; - fatal_error "$dir is a Symbolic Link" if -l $dir; - fatal_error "$objectfile is a Directory" if -d $objectfile; - fatal_error "$dir is a Symbolic Link" if -l $objectfile; - fatal_error "$objectfile exists and is not a compiled script" if -e _ && ! -x _; - - eval { - $dir = abs_path $dir; - ( $object, $tempfile ) = tempfile ( 'tempfileXXXX' , DIR => $dir ); - }; - - fatal_error "Unable to create temporary file in directory $dir" if $@; - - $file = "$file.$suffix" if $suffix; - $dir .= '/' unless substr( $dir, -1, 1 ) eq '/'; - $file = $dir . $file; - -} - -# -# Finalize the object file -# -sub finalize_object( $ ) { - my $export = $_[0]; - close $object; - $object = 0; - rename $tempfile, $file or fatal_error "Cannot Rename $tempfile to $file: $!"; - chmod 0700, $file or fatal_error "Cannot secure $file for execute access"; - progress_message3 "Shorewall configuration compiled to $file" unless $export; -} - -# -# Create the temporary aux config file. -# -sub create_temp_aux_config() { - eval { - ( $object, $tempfile ) = tempfile ( 'tempfileXXXX' , DIR => $dir ); - }; - - die if $@; - -} - -# -# Finalize the aux config file. -# -sub finalize_aux_config() { - close $object; - $object = 0; - rename $tempfile, "$file.conf" or fatal_error "Cannot Rename $tempfile to $file.conf: $!"; - progress_message3 "Shorewall configuration compiled to $file"; -} - -END { - if ( $object ) { - close $object; - unlink $tempfile; - } -} - -1; diff --git a/Shorewall-perl/Shorewall/Compiler.pm b/Shorewall-perl/Shorewall/Compiler.pm index 06e6cf20d..a17f3a383 100644 --- a/Shorewall-perl/Shorewall/Compiler.pm +++ b/Shorewall-perl/Shorewall/Compiler.pm @@ -24,18 +24,14 @@ package Shorewall::Compiler; require Exporter; -use Shorewall::Common; use Shorewall::Config; use Shorewall::Chains; use Shorewall::Zones; -use Shorewall::Interfaces; -use Shorewall::Hosts; use Shorewall::Policy; use Shorewall::Nat; use Shorewall::Providers; use Shorewall::Tc; use Shorewall::Tunnels; -use Shorewall::Macros; use Shorewall::Actions; use Shorewall::Accounting; use Shorewall::Rules; @@ -59,15 +55,12 @@ use constant { EXPORT => 0x01 , # Reinitilize the package-globals in the other modules # sub reinitialize() { - Shorewall::Common::initialize; Shorewall::Config::initialize; Shorewall::Chains::initialize; Shorewall::Zones::initialize; - Shorewall::Interfaces::initialize; Shorewall::Nat::initialize; Shorewall::Providers::initialize; Shorewall::Tc::initialize; - Shorewall::Macros::initialize; Shorewall::Actions::initialize; Shorewall::Accounting::initialize; Shorewall::Rules::initialize; diff --git a/Shorewall-perl/Shorewall/Config.pm b/Shorewall-perl/Shorewall/Config.pm index 2e9309a43..5b099f1bb 100644 --- a/Shorewall-perl/Shorewall/Config.pm +++ b/Shorewall-perl/Shorewall/Config.pm @@ -23,18 +23,40 @@ # This module is responsible for lower level configuration file handling. # It also exports functions for generating warning and error messages. # The get_configuration function parses the shorewall.conf, capabilities and -# modules files during compiler startup. +# modules files during compiler startup. The module also provides very basic +# services such as creation of temporary 'object' files, writing +# into those files (emitters) and finalizing those files (renaming +# them to their final name and setting their mode appropriately). # package Shorewall::Config; use strict; use warnings; -use Shorewall::Common; use File::Basename; +use File::Temp qw/ tempfile tempdir /; +use Cwd 'abs_path'; use autouse 'Carp' => qw(longmess confess); our @ISA = qw(Exporter); our @EXPORT = qw( + create_temp_object + finalize_object + emit + emitj + emit_unindented + save_progress_message + save_progress_message_short + set_timestamp + set_verbose + progress_message + progress_message2 + progress_message3 + push_indent + pop_indent + copy + create_temp_aux_config + finalize_aux_config + warning_message fatal_error set_shorewall_dir @@ -61,6 +83,11 @@ our @EXPORT = qw( run_user_exit2 generate_aux_config + $command + $doing + $done + $verbose + $currentline %config %globals @@ -69,6 +96,15 @@ our @EXPORT = qw( our @EXPORT_OK = qw( $shorewall_dir initialize read_a_line1 set_config_path ); our $VERSION = 4.00; +our ($command, $doing, $done ); +our $verbose; +our $timestamp; +our $object; +our $lastlineblank; +our $indent; +our ( $dir, $file ); # Object's Directory and File +our $tempfile; # Temporary File Name + # # Misc Globals # @@ -121,6 +157,16 @@ our $debug; # the second and subsequent calls to that function. # sub initialize() { + ( $command, $doing, $done ) = qw/ compile Compiling Compiled/; #describe the current command, it's present progressive, and it's completion. + + $verbose = 0; # Verbosity setting. 0 = almost silent, 1 = major progress messages only, 2 = all progress messages (very noisy) + $timestamp = ''; # If true, we are to timestamp each progress message + $object = 0; # Object (script) file Handle Reference + $lastlineblank = 0; # Avoid extra blank lines in the output + $indent = ''; # Current indentation + ( $dir, $file ) = ('',''); # Object's Directory and File + $tempfile = ''; # Temporary File Name + # # Misc Globals # @@ -343,6 +389,259 @@ sub fatal_error { die " ERROR: @_$currentlineinfo\n"; } +# +# Write the argument to the object file (if any) with the current indentation. +# +# Replaces leading spaces with tabs as appropriate and suppresses consecutive blank lines. +# +sub emit ( $ ) { + if ( $object ) { + # + # 'compile' as opposed to 'check' + # + my $line = $_[0]; # This copy is necessary because the actual arguments are almost always read-only. + + unless ( $line =~ /^\s*$/ ) { + $line =~ s/^\n// if $lastlineblank; + $line =~ s/^/$indent/gm if $indent; + $line =~ s/ /\t/gm; + print $object "$line\n"; + $lastlineblank = ( substr( $line, -1, 1 ) eq "\n" ); + } else { + print $object "\n" unless $lastlineblank; + $lastlineblank = 1; + } + } +} + +# +# Version of emit() that accepts an indefinite number of scalar arguments; each argument will be emitted as a separate line +# +sub emitj { + if ( $object ) { + # + # 'compile' as opposed to 'check' + # + for ( @_ ) { + unless ( /^\s*$/ ) { + my $line = $_; # This copy is necessary because the actual arguments are almost always read-only. + $line =~ s/^\n// if $lastlineblank; + $line =~ s/^/$indent/gm if $indent; + $line =~ s/ /\t/gm; + print $object "$line\n"; + $lastlineblank = ( substr( $line, -1, 1 ) eq "\n" ); + } else { + print $object "\n" unless $lastlineblank; + $lastlineblank = 1; + } + } + } +} + +# +# Write passed message to the object with newline but no indentation. +# + +sub emit_unindented( $ ) { + print $object "$_[0]\n" if $object; +} + +# +# Write a progress_message2 command with surrounding blank lines to the output file. +# +sub save_progress_message( $ ) { + emit "\nprogress_message2 @_\n" if $object; +} + +# +# Write a progress_message command to the output file. +# +sub save_progress_message_short( $ ) { + emit "progress_message $_[0]" if $object; +} + +# +# Set $timestamp +# +sub set_timestamp( $ ) { + $timestamp = shift; +} + +# +# Set $verbose +# +sub set_verbose( $ ) { + $verbose = shift; +} + +# +# Print the current TOD to STDOUT. +# +sub timestamp() { + my ($sec, $min, $hr) = ( localtime ) [0,1,2]; + printf '%02d:%02d:%02d ', $hr, $min, $sec; +} + +# +# Write a message if $verbose >= 2 +# +sub progress_message { + if ( $verbose > 1 ) { + timestamp if $timestamp; + # + # We use this function to display messages containing raw config file images which may contains tabs (including multiple tabs in succession). + # The following makes such messages look more readable and uniform + # + my $line = "@_"; + $line =~ s/\s+/ /g; + print "$line\n"; + } +} + +# +# Write a message if $verbose >= 1 +# +sub progress_message2 { + if ( $verbose > 0 ) { + timestamp if $timestamp; + print "@_\n"; + } +} + +# +# Write a message if $verbose >= 0 +# +sub progress_message3 { + if ( $verbose >= 0 ) { + timestamp if $timestamp; + print "@_\n"; + } +} + +# +# Push/Pop Indent +# +sub push_indent() { + $indent = "$indent "; +} + +sub pop_indent() { + $indent = substr( $indent , 0 , ( length $indent ) - 4 ); +} + +# +# Functions for copying files into the object +# +sub copy( $ ) { + if ( $object ) { + my $file = $_[0]; + + open IF , $file or fatal_error "Unable to open $file: $!"; + + while ( ) { + s/^/$indent/ if $indent; + print $object $_; + } + + close IF; + } +} + +# +# This one handles line continuation. + +sub copy1( $ ) { + if ( $object ) { + my $file = $_[0]; + + open IF , $file or fatal_error "Unable to open $file: $!"; + + my $do_indent = 1; + + while ( ) { + if ( /^\s*$/ ) { + print $object "\n"; + $do_indent = 1; + next; + } + + s/^/$indent/ if $indent && $do_indent; + print $object $_; + $do_indent = ! ( /\\$/ ); + } + + close IF; + } +} + +# +# Create the temporary object file -- the passed file name is the name of the final file. +# We create a temporary file in the same directory so that we can use rename to finalize it. +# +sub create_temp_object( $ ) { + my $objectfile = $_[0]; + my $suffix; + + eval { + ( $file, $dir, $suffix ) = fileparse( $objectfile ); + }; + + die if $@; + + fatal_error "Directory $dir does not exist" unless -d $dir; + fatal_error "Directory $dir is not writable" unless -w _; + fatal_error "$dir is a Symbolic Link" if -l $dir; + fatal_error "$objectfile is a Directory" if -d $objectfile; + fatal_error "$dir is a Symbolic Link" if -l $objectfile; + fatal_error "$objectfile exists and is not a compiled script" if -e _ && ! -x _; + + eval { + $dir = abs_path $dir; + ( $object, $tempfile ) = tempfile ( 'tempfileXXXX' , DIR => $dir ); + }; + + fatal_error "Unable to create temporary file in directory $dir" if $@; + + $file = "$file.$suffix" if $suffix; + $dir .= '/' unless substr( $dir, -1, 1 ) eq '/'; + $file = $dir . $file; + +} + +# +# Finalize the object file +# +sub finalize_object( $ ) { + my $export = $_[0]; + close $object; + $object = 0; + rename $tempfile, $file or fatal_error "Cannot Rename $tempfile to $file: $!"; + chmod 0700, $file or fatal_error "Cannot secure $file for execute access"; + progress_message3 "Shorewall configuration compiled to $file" unless $export; +} + +# +# Create the temporary aux config file. +# +sub create_temp_aux_config() { + eval { + ( $object, $tempfile ) = tempfile ( 'tempfileXXXX' , DIR => $dir ); + }; + + die if $@; + +} + +# +# Finalize the aux config file. +# +sub finalize_aux_config() { + close $object; + $object = 0; + rename $tempfile, "$file.conf" or fatal_error "Cannot Rename $tempfile to $file.conf: $!"; + progress_message3 "Shorewall configuration compiled to $file"; +} + # # Set $globals{CONFIG_PATH} # @@ -1368,4 +1667,11 @@ sub generate_aux_config() { } +END { + if ( $object ) { + close $object; + unlink $tempfile; + } +} + 1; diff --git a/Shorewall-perl/Shorewall/Hosts.pm b/Shorewall-perl/Shorewall/Hosts.pm deleted file mode 100644 index e191331d5..000000000 --- a/Shorewall-perl/Shorewall/Hosts.pm +++ /dev/null @@ -1,171 +0,0 @@ -# -# Shorewall-perl 4.0 -- /usr/share/shorewall-perl/Shorewall/Hosts.pm -# -# 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 -# -# This module contains the code for dealing with the /etc/shorewall/hosts -# file. -# -package Shorewall::Hosts; -require Exporter; -use Shorewall::Common; -use Shorewall::Config; -use Shorewall::IPAddrs; -use Shorewall::Zones; -use Shorewall::Interfaces; - -use strict; - -our @ISA = qw(Exporter); -our @EXPORT = qw( validate_hosts_file find_hosts_by_option ); -our @EXPORT_OK = (); -our $VERSION = 4.00; - -# -# Validates the hosts file. Generates entries in %zone{..}{hosts} -# -sub validate_hosts_file() -{ - my %validoptions = ( - blacklist => 1, - maclist => 1, - norfc1918 => 1, - nosmurfs => 1, - routeback => 1, - routefilter => 1, - tcpflags => 1, - ); - - my $ipsec = 0; - my $first_entry = 1; - - my $fn = open_file 'hosts'; - - while ( read_a_line ) { - - if ( $first_entry ) { - progress_message2 "$doing $fn..."; - $first_entry = 0; - } - - my ($zone, $hosts, $options ) = split_line 2, 3, 'hosts file'; - - my $zoneref = $zones{$zone}; - my $type = $zoneref->{type}; - - fatal_error "Unknown ZONE ($zone)" unless $type; - fatal_error 'Firewall zone not allowed in ZONE column of hosts record' if $type eq 'firewall'; - - my $interface; - - if ( $hosts =~ /^([\w.@%-]+\+?):(.*)$/ ) { - $interface = $1; - $hosts = $2; - $zoneref->{options}{complex} = 1 if $hosts =~ /^\+/; - fatal_error "Unknown interface ($interface)" unless $interfaces{$interface}{root}; - } else { - fatal_error "Invalid HOST(S) column contents: $hosts"; - } - - if ( $type eq 'bport4' ) { - if ( $zoneref->{bridge} eq '' ) { - fatal_error 'Bridge Port Zones may only be associated with bridge ports' unless $interfaces{$interface}{options}{port}; - $zoneref->{bridge} = $interfaces{$interface}{bridge}; - } elsif ( $zoneref->{bridge} ne $interfaces{$interface}{bridge} ) { - fatal_error "Interface $interface is not a port on bridge $zoneref->{bridge}"; - } - } - - my $optionsref = {}; - - if ( $options ne '-' ) { - my @options = split ',', $options; - my %options; - - for my $option ( @options ) - { - if ( $option eq 'ipsec' ) { - $type = 'ipsec'; - $zoneref->{options}{complex} = 1; - $ipsec = 1; - } elsif ( $validoptions{$option}) { - $options{$option} = 1; - } else { - fatal_error "Invalid option ($option)"; - } - } - - $optionsref = \%options; - } - - # - # Looking for the '!' at the beginning of a list element is more straight-foward than looking for it in the middle. - # - # Be sure we don't have a ',!' in the original - # - fatal_error "Invalid hosts list" if $hosts =~ /,!/; - # - # Now add a comma before '!'. Do it globally - add_group_to_zone() correctly checks for multiple exclusions - # - $hosts =~ s/!/,!/g; - # - # Take care of case where the hosts list begins with '!' - # - $hosts = join( '', ALLIPv4 , $hosts ) if substr($hosts, 0, 2 ) eq ',!'; - - add_group_to_zone( $zone, $type , $interface, [ split( ',', $hosts ) ] , $optionsref); - - progress_message " Host \"$currentline\" validated"; - } - - $capabilities{POLICY_MATCH} = '' unless $ipsec || haveipseczones; -} -# -# Returns a reference to a array of host entries. Each entry is a -# reference to an array containing ( interface , polciy match type {ipsec|none} , network ); -# -sub find_hosts_by_option( $ ) { - my $option = $_[0]; - my @hosts; - - for my $zone ( grep $zones{$_}{type} ne 'firewall' , @zones ) { - while ( my ($type, $interfaceref) = each %{$zones{$zone}{hosts}} ) { - while ( my ( $interface, $arrayref) = ( each %{$interfaceref} ) ) { - for my $host ( @{$arrayref} ) { - if ( $host->{options}{$option} ) { - for my $net ( @{$host->{hosts}} ) { - push @hosts, [ $interface, $host->{ipsec} , $net ]; - } - } - } - } - } - } - - for my $interface ( @interfaces ) { - if ( ! $interfaces{$interface}{zone} && $interfaces{$interface}{options}{$option} ) { - push @hosts, [ $interface, 'none', ALLIPv4 ]; - } - } - - \@hosts; -} - -1; diff --git a/Shorewall-perl/Shorewall/IPAddrs.pm b/Shorewall-perl/Shorewall/IPAddrs.pm index cbeba3e0d..2c5dd2ff2 100644 --- a/Shorewall-perl/Shorewall/IPAddrs.pm +++ b/Shorewall-perl/Shorewall/IPAddrs.pm @@ -24,7 +24,6 @@ # package Shorewall::IPAddrs; require Exporter; -use Shorewall::Common; use Shorewall::Config; use strict; diff --git a/Shorewall-perl/Shorewall/Interfaces.pm b/Shorewall-perl/Shorewall/Interfaces.pm deleted file mode 100644 index e39a415ce..000000000 --- a/Shorewall-perl/Shorewall/Interfaces.pm +++ /dev/null @@ -1,469 +0,0 @@ -# -# Shorewall-perl 4.0 -- /usr/share/shorewall-perl/Shorewall/Interfaces.pm -# -# 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 -# -# This Module contains the code for processing the /etc/shorewall/interfaces -# file. It also exports 'add_group_to_zone()' which other modules call to -# alter zone membership. -# - -package Shorewall::Interfaces; -require Exporter; -use Shorewall::Common; -use Shorewall::Config; -use Shorewall::IPAddrs; -use Shorewall::Zones; - -use strict; - -our @ISA = qw(Exporter); -our @EXPORT = qw( add_group_to_zone - validate_interfaces_file - known_interface - port_to_bridge - source_port_to_bridge - interface_is_optional - find_interfaces_by_option - get_interface_option - - @interfaces - @bridges ); -our @EXPORT_OK = qw( initialize ); -our $VERSION = 4.00; - - -# -# Interface Table. -# -# @interfaces lists the interface names in the order that they appear in the interfaces file. -# -# %interfaces { => { root => -# options => { = , -# ... -# } -# zone => -# bridge => -# } -# } -# -our @interfaces; -our %interfaces; -our @bridges; - -# -# 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() { - @interfaces = (); - %interfaces = (); - @bridges = (); -} - -INIT { - initialize; -} - -sub add_group_to_zone($$$$$) -{ - my ($zone, $type, $interface, $networks, $options) = @_; - my $typeref; - my $interfaceref; - my $arrayref; - my $zoneref = $zones{$zone}; - my $zonetype = $zoneref->{type}; - my $ifacezone = $interfaces{$interface}{zone}; - - $zoneref->{interfaces}{$interface} = 1; - - my @newnetworks; - my @exclusions; - my $new = \@newnetworks; - my $switched = 0; - - $ifacezone = '' unless defined $ifacezone; - - for my $host ( @$networks ) { - fatal_error "Invalid Host List" unless defined $host and $host ne ''; - - if ( substr( $host, 0, 1 ) eq '!' ) { - fatal_error "Only one exclusion allowed in a host list" if $switched; - $switched = 1; - $host = substr( $host, 1 ); - $new = \@exclusions; - } - - unless ( $switched ) { - if ( $type eq $zonetype ) { - fatal_error "Duplicate Host Group ($interface:$host) in zone $zone" if $ifacezone eq $zone; - $ifacezone = $zone if $host eq ALLIPv4; - } - } - - if ( substr( $host, 0, 1 ) eq '+' ) { - fatal_error "Invalid ipset name ($host)" unless $host =~ /^\+[a-zA-Z]\w*$/; - } else { - validate_host $host; - } - - push @$new, $switched ? "$interface:$host" : $host; - } - - $zoneref->{options}{in_out}{routeback} = 1 if $options->{routeback}; - - $typeref = ( $zoneref->{hosts} || ( $zoneref->{hosts} = {} ) ); - $interfaceref = ( $typeref->{$type} || ( $interfaceref = $typeref->{$type} = {} ) ); - $arrayref = ( $interfaceref->{$interface} || ( $interfaceref->{$interface} = [] ) ); - - $zoneref->{options}{complex} = 1 if @$arrayref || ( @newnetworks > 1 ) || ( @exclusions ); - - push @{$zoneref->{exclusions}}, @exclusions; - - push @{$arrayref}, { options => $options, - hosts => \@newnetworks, - ipsec => $type eq 'ipsec4' ? 'ipsec' : 'none' }; -} - -# -# Return a list of networks routed out of the passed interface -# -sub get_routed_networks ( $$ ) { - my ( $interface , $error_message ) = @_; - my @networks; - - if ( open IP , '-|' , "/sbin/ip route show dev $interface 2> /dev/null" ) { - while ( my $route = ) { - $route =~ s/^\s+//; - my $network = ( split /\s+/, $route )[0]; - if ( $network eq 'default' ) { - fatal_error $error_message if $error_message; - warning_message "default route ignored on interface $interface"; - } else { - my ( $address, $vlsm ) = split '/', $network; - $vlsm = 32 unless defined $vlsm; - push @networks, "$address/$vlsm"; - } - } - close IP - } - - @networks; -} - -# -# Parse the interfaces file. -# - -sub validate_interfaces_file( $ ) -{ - my $export = shift; - - use constant { SIMPLE_IF_OPTION => 1, - BINARY_IF_OPTION => 2, - ENUM_IF_OPTION => 3, - MASK_IF_OPTION => 3, - - IF_OPTION_ZONEONLY => 4 }; - - my %validoptions = (arp_filter => BINARY_IF_OPTION, - arp_ignore => ENUM_IF_OPTION, - blacklist => SIMPLE_IF_OPTION, - bridge => SIMPLE_IF_OPTION, - detectnets => SIMPLE_IF_OPTION, - dhcp => SIMPLE_IF_OPTION, - maclist => SIMPLE_IF_OPTION, - logmartians => BINARY_IF_OPTION, - norfc1918 => SIMPLE_IF_OPTION, - nosmurfs => SIMPLE_IF_OPTION, - optional => SIMPLE_IF_OPTION, - proxyarp => BINARY_IF_OPTION, - routeback => SIMPLE_IF_OPTION + IF_OPTION_ZONEONLY, - routefilter => BINARY_IF_OPTION, - sourceroute => BINARY_IF_OPTION, - tcpflags => SIMPLE_IF_OPTION, - upnp => SIMPLE_IF_OPTION, - ); - - my $fn = open_file 'interfaces'; - - my $first_entry = 1; - - my @ifaces; - - while ( read_a_line ) { - - if ( $first_entry ) { - progress_message2 "$doing $fn..."; - $first_entry = 0; - } - - my ($zone, $interface, $networks, $options ) = split_line 2, 4, 'interfaces file'; - my $zoneref; - my $bridge = ''; - - if ( $zone eq '-' ) { - $zone = ''; - } else { - $zoneref = $zones{$zone}; - - fatal_error "Unknown zone ($zone)" unless $zoneref; - fatal_error "Firewall zone not allowed in ZONE column of interface record" if $zoneref->{type} eq 'firewall'; - } - - $networks = '' if $networks eq '-'; - $options = '' if $options eq '-'; - - ( $interface, my ($port, $extra) ) = split /:/ , $interface, 3; - - fatal_error "Invalid INTERFACE" if defined $extra || ! $interface; - - fatal_error "Invalid Interface Name ($interface)" if $interface eq '+'; - - if ( defined $port ) { - require_capability( 'PHYSDEV_MATCH', 'Bridge Ports', ''); - require_capability( 'KLUDGEFREE', 'Bridge Ports', ''); - fatal_error "Duplicate Interface ($port)" if $interfaces{$port}; - fatal_error "$interface is not a defined bridge" unless $interfaces{$interface} && $interfaces{$interface}{options}{bridge}; - fatal_error "Bridge Ports may only be associated with 'bport' zones" if $zone && $zoneref->{type} ne 'bport4'; - - if ( $zone ) { - if ( $zoneref->{bridge} ) { - fatal_error "Bridge Port zones may only be associated with a single bridge" if $zoneref->{bridge} ne $interface; - } else { - $zoneref->{bridge} = $interface; - } - } - - fatal_error "Bridge Ports may not have options" if $options && $options ne '-'; - - next if $port eq ''; - - fatal_error "Invalid Interface Name ($interface:$port)" unless $port =~ /^[\w.@%-]+\+?$/; - - $interfaces{$port}{bridge} = $bridge = $interface; - $interface = $port; - } else { - fatal_error "Duplicate Interface ($interface)" if $interfaces{$interface}; - fatal_error "Zones of type 'bport' may only be associated with bridge ports" if $zone && $zoneref->{type} eq 'bport4'; - $interfaces{$interface}{bridge} = $interface; - } - - my $wildcard = 0; - - if ( $interface =~ /\+$/ ) { - $wildcard = 1; - $interfaces{$interface}{root} = substr( $interface, 0, -1 ); - } else { - $interfaces{$interface}{root} = $interface; - } - - unless ( $networks eq '' || $networks eq 'detect' ) { - - for my $address ( split /,/, $networks ) { - fatal_error 'Invalid BROADCAST address' unless $address =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/; - } - - warning_message 'Shorewall no longer uses broadcast addresses in rule generation'; - } - - my $optionsref = {}; - - my %options; - - if ( $options ) { - - for my $option (split ',', $options ) { - next if $option eq '-'; - - ( $option, my $value ) = split /=/, $option; - - fatal_error "Invalid Interface option ($option)" unless my $type = $validoptions{$option}; - - fatal_error "The \"$option\" option may not be specified on a multi-zone interface" if $type & IF_OPTION_ZONEONLY && ! $zone; - - $type &= MASK_IF_OPTION; - - if ( $type == SIMPLE_IF_OPTION ) { - fatal_error "Option $option does not take a value" if defined $value; - $options{$option} = 1; - } elsif ( $type == BINARY_IF_OPTION ) { - $value = 1 unless defined $value; - fatal_error "Option value for $option must be 0 or 1" unless ( $value eq '0' || $value eq '1' ); - fatal_error "The $option option may not be used with a wild-card interface name" if $wildcard; - $options{$option} = $value; - } elsif ( $type == ENUM_IF_OPTION ) { - fatal_error "The $option option may not be used with a wild-card interface name" if $wildcard; - if ( $option eq 'arp_ignore' ) { - if ( defined $value ) { - if ( $value =~ /^[1-3,8]$/ ) { - $options{arp_ignore} = $value; - } else { - fatal_error "Invalid value ($value) for arp_ignore"; - } - } else { - $options{arp_ignore} = 1; - } - } else { - fatal_error "Internal Error in validate_interfaces_file"; - } - } - } - - $zoneref->{options}{in_out}{routeback} = 1 if $zoneref && $options{routeback}; - - if ( $options{bridge} ) { - require_capability( 'PHYSDEV_MATCH', 'The "bridge" option', 's'); - fatal_error "Bridges may not have wildcard names" if $wildcard; - push @bridges, $interface; - } - } elsif ( $port ) { - $options{port} = 1; - } - - $interfaces{$interface}{options} = $optionsref = \%options; - - push @ifaces, $interface; - - my @networks; - - if ( $options{detectnets} ) { - fatal_error "The 'detectnets' option is not allowed on a multi-zone interface" unless $zone; - fatal_error "The 'detectnets' option may not be used with a wild-card interface name" if $wildcard; - fatal_error "The 'detectnets' option may not be used with the '-e' compiler option" if $export; - @networks = get_routed_networks( $interface, 'detectnets not allowed on interface with default route' ); - fatal_error "No routes found through 'detectnets' interface $interface" unless @networks || $options{optional}; - delete $options{maclist} unless @networks; - } else { - @networks = @allipv4; - } - - add_group_to_zone( $zone, $zoneref->{type}, $interface, \@networks, $optionsref ) if $zone && @networks; - - $interfaces{$interface}{zone} = $zone; #Must follow the call to add_group_to_zone() - - progress_message " Interface \"$currentline\" Validated"; - - } - - # - # We now assemble the @interfaces array such that bridge ports immediately precede their associated bridge - # - for my $interface ( @ifaces ) { - my $interfaceref = $interfaces{$interface}; - - if ( $interfaceref->{options}{bridge} ) { - my @ports = grep $interfaces{$_}{options}{port} && $interfaces{$_}{bridge} eq $interface, @ifaces; - - if ( @ports ) { - push @interfaces, @ports; - } else { - $interfaceref->{options}{routeback} = 1; #so the bridge will work properly - } - } - - push @interfaces, $interface unless $interfaceref->{options}{port}; - } -} - -# -# Returns true if passed interface matches an entry in /etc/shorewall/interfaces -# -# If the passed name matches a wildcard, a entry for the name is added in %interfaces to speed up validation of other references to that name. -# -sub known_interface($) -{ - my $interface = $_[0]; - - return 1 if $interfaces{$interface}; - - for my $i ( @interfaces ) { - my $interfaceref = $interfaces{$i}; - my $val = $interfaceref->{root}; - next if $val eq $i; - if ( substr( $interface, 0, length $val ) eq $val ) { - # - # Cache this result for future reference - # - $interfaces{$interface} = { options => $interfaceref->{options}, bridge => $interfaceref->{bridge} }; - return 1; - } - } - - 0; -} - -# -# Return the bridge associated with the passed interface. If the interface is not a bridge port, -# return '' -# -sub port_to_bridge( $ ) { - my $portref = $interfaces{$_[0]}; - return $portref && $portref->{options}{port} ? $portref->{bridge} : ''; -} - -# -# Return the bridge associated with the passed interface. -# -sub source_port_to_bridge( $ ) { - my $portref = $interfaces{$_[0]}; - return $portref ? $portref->{bridge} : ''; -} - -# -# Return the 'optional' setting of the passed interface -# -sub interface_is_optional($) { - my $optionsref = $interfaces{$_[0]}{options}; - $optionsref && $optionsref->{optional}; -} - -# -# Returns reference to array of interfaces with the passed option -# -sub find_interfaces_by_option( $ ) { - my $option = $_[0]; - my @ints = (); - - for my $interface ( @interfaces ) { - my $optionsref = $interfaces{$interface}{options}; - if ( $optionsref && defined $optionsref->{$option} ) { - push @ints , $interface - } - } - - \@ints; -} - -# -# Return the value of an option for an interface -# -sub get_interface_option( $$ ) { - my ( $interface, $option ) = @_; - - $interfaces{$interface}{options}{$option}; -} - -1; diff --git a/Shorewall-perl/Shorewall/Macros.pm b/Shorewall-perl/Shorewall/Macros.pm deleted file mode 100644 index a20dd091d..000000000 --- a/Shorewall-perl/Shorewall/Macros.pm +++ /dev/null @@ -1,143 +0,0 @@ -# -# Shorewall-perl 4.0 -- /usr/share/shorewall-perl/Shorewall/Macros.pm -# -# 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 -# -# This module exports some low-level module-oriented functions. -# -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 - split_action - substitute_param - merge_macro_source_dest - merge_macro_column - - %macros ); -our @EXPORT_OK = qw( initialize ); -our $VERSION = 4.00; - - -our %macros; - -# -# 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() { - %macros = (); -} - -INIT { - initialize; -} - -# -# 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; - } 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; - } -} - -1; diff --git a/Shorewall-perl/Shorewall/Nat.pm b/Shorewall-perl/Shorewall/Nat.pm index b5ac67f51..ccb65d5c6 100644 --- a/Shorewall-perl/Shorewall/Nat.pm +++ b/Shorewall-perl/Shorewall/Nat.pm @@ -25,11 +25,9 @@ # package Shorewall::Nat; require Exporter; -use Shorewall::Common; use Shorewall::Config; use Shorewall::IPAddrs; use Shorewall::Zones; -use Shorewall::Interfaces; use Shorewall::Chains; use Shorewall::IPAddrs; diff --git a/Shorewall-perl/Shorewall/Policy.pm b/Shorewall-perl/Shorewall/Policy.pm index 1d5109067..ebef81d78 100644 --- a/Shorewall-perl/Shorewall/Policy.pm +++ b/Shorewall-perl/Shorewall/Policy.pm @@ -24,7 +24,6 @@ # package Shorewall::Policy; require Exporter; -use Shorewall::Common; use Shorewall::Config; use Shorewall::Zones; use Shorewall::Chains; diff --git a/Shorewall-perl/Shorewall/Proc.pm b/Shorewall-perl/Shorewall/Proc.pm index 6b4e73492..73d2b1ed6 100644 --- a/Shorewall-perl/Shorewall/Proc.pm +++ b/Shorewall-perl/Shorewall/Proc.pm @@ -27,11 +27,9 @@ # package Shorewall::Proc; require Exporter; -use Shorewall::Common; use Shorewall::Config; use Shorewall::Zones; use Shorewall::Chains; -use Shorewall::Interfaces; use strict; diff --git a/Shorewall-perl/Shorewall/Providers.pm b/Shorewall-perl/Shorewall/Providers.pm index dddabe6f0..6279171eb 100644 --- a/Shorewall-perl/Shorewall/Providers.pm +++ b/Shorewall-perl/Shorewall/Providers.pm @@ -25,7 +25,6 @@ # package Shorewall::Providers; require Exporter; -use Shorewall::Common; use Shorewall::Config; use Shorewall::IPAddrs; use Shorewall::Zones; diff --git a/Shorewall-perl/Shorewall/Proxyarp.pm b/Shorewall-perl/Shorewall/Proxyarp.pm index 910815821..fb176a9ab 100644 --- a/Shorewall-perl/Shorewall/Proxyarp.pm +++ b/Shorewall-perl/Shorewall/Proxyarp.pm @@ -23,9 +23,8 @@ # package Shorewall::Proxyarp; require Exporter; -use Shorewall::Common; use Shorewall::Config; -use Shorewall::Interfaces; +use Shorewall::Zones; use strict; diff --git a/Shorewall-perl/Shorewall/Rules.pm b/Shorewall-perl/Shorewall/Rules.pm index 724ddd402..8a424ceb1 100644 --- a/Shorewall-perl/Shorewall/Rules.pm +++ b/Shorewall-perl/Shorewall/Rules.pm @@ -24,15 +24,11 @@ # package Shorewall::Rules; require Exporter; -use Shorewall::Common; use Shorewall::Config; use Shorewall::IPAddrs; use Shorewall::Zones; -use Shorewall::Interfaces; use Shorewall::Chains; -use Shorewall::Hosts; use Shorewall::Actions; -use Shorewall::Macros; use Shorewall::Policy; use Shorewall::Proc; diff --git a/Shorewall-perl/Shorewall/Tc.pm b/Shorewall-perl/Shorewall/Tc.pm index 9480a6cb9..e7f4570f9 100644 --- a/Shorewall-perl/Shorewall/Tc.pm +++ b/Shorewall-perl/Shorewall/Tc.pm @@ -29,11 +29,9 @@ # package Shorewall::Tc; require Exporter; -use Shorewall::Common; use Shorewall::Config; use Shorewall::Zones; use Shorewall::Chains; -use Shorewall::Interfaces; use Shorewall::Providers; use strict; diff --git a/Shorewall-perl/Shorewall/Tunnels.pm b/Shorewall-perl/Shorewall/Tunnels.pm index b92b6e1fa..3cc9a7b4c 100644 --- a/Shorewall-perl/Shorewall/Tunnels.pm +++ b/Shorewall-perl/Shorewall/Tunnels.pm @@ -24,7 +24,6 @@ # package Shorewall::Tunnels; require Exporter; -use Shorewall::Common; use Shorewall::Config; use Shorewall::Zones; use Shorewall::Chains; diff --git a/Shorewall-perl/Shorewall/Zones.pm b/Shorewall-perl/Shorewall/Zones.pm index 2597026c9..872a9142f 100644 --- a/Shorewall-perl/Shorewall/Zones.pm +++ b/Shorewall-perl/Shorewall/Zones.pm @@ -20,12 +20,13 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA # -# This module contains the code which deals with /etc/shorewall/zones. +# This module contains the code which deals with /etc/shorewall/zones, +# /etc/shorewall/interfaces and /etc/shorewall/hosts. # package Shorewall::Zones; require Exporter; -use Shorewall::Common; use Shorewall::Config; +use Shorewall::IPAddrs; use strict; @@ -39,13 +40,23 @@ our @EXPORT = qw( NOTHING determine_zones zone_report dump_zone_contents - haveipseczones single_interface + validate_interfaces_file + known_interface + port_to_bridge + source_port_to_bridge + interface_is_optional + find_interfaces_by_option + get_interface_option + validate_hosts_file + find_hosts_by_option @zones %zones $firewall_zone - %interfaces ); + %interfaces + @interfaces + @bridges ); our @EXPORT_OK = qw( initialize ); our $VERSION = 4.00; @@ -99,6 +110,24 @@ our %reservedName = ( all => 1, SOURCE => 1, DEST => 1 ); +# +# Interface Table. +# +# @interfaces lists the interface names in the order that they appear in the interfaces file. +# +# %interfaces { => { root => +# options => { = , +# ... +# } +# zone => +# bridge => +# } +# } +# +our @interfaces; +our %interfaces; +our @bridges; + # # Initialize globals -- we take this novel approach to globals initialization to allow # the compiler to run multiple times in the same process. The @@ -112,6 +141,10 @@ sub initialize() { @zones = (); %zones = (); $firewall_zone = ''; + + @interfaces = (); + %interfaces = (); + @bridges = (); } INIT { @@ -404,4 +437,513 @@ sub single_interface( $ ) { } } +sub add_group_to_zone($$$$$) +{ + my ($zone, $type, $interface, $networks, $options) = @_; + my $typeref; + my $interfaceref; + my $arrayref; + my $zoneref = $zones{$zone}; + my $zonetype = $zoneref->{type}; + my $ifacezone = $interfaces{$interface}{zone}; + + $zoneref->{interfaces}{$interface} = 1; + + my @newnetworks; + my @exclusions; + my $new = \@newnetworks; + my $switched = 0; + + $ifacezone = '' unless defined $ifacezone; + + for my $host ( @$networks ) { + fatal_error "Invalid Host List" unless defined $host and $host ne ''; + + if ( substr( $host, 0, 1 ) eq '!' ) { + fatal_error "Only one exclusion allowed in a host list" if $switched; + $switched = 1; + $host = substr( $host, 1 ); + $new = \@exclusions; + } + + unless ( $switched ) { + if ( $type eq $zonetype ) { + fatal_error "Duplicate Host Group ($interface:$host) in zone $zone" if $ifacezone eq $zone; + $ifacezone = $zone if $host eq ALLIPv4; + } + } + + if ( substr( $host, 0, 1 ) eq '+' ) { + fatal_error "Invalid ipset name ($host)" unless $host =~ /^\+[a-zA-Z]\w*$/; + } else { + validate_host $host; + } + + push @$new, $switched ? "$interface:$host" : $host; + } + + $zoneref->{options}{in_out}{routeback} = 1 if $options->{routeback}; + + $typeref = ( $zoneref->{hosts} || ( $zoneref->{hosts} = {} ) ); + $interfaceref = ( $typeref->{$type} || ( $interfaceref = $typeref->{$type} = {} ) ); + $arrayref = ( $interfaceref->{$interface} || ( $interfaceref->{$interface} = [] ) ); + + $zoneref->{options}{complex} = 1 if @$arrayref || ( @newnetworks > 1 ) || ( @exclusions ); + + push @{$zoneref->{exclusions}}, @exclusions; + + push @{$arrayref}, { options => $options, + hosts => \@newnetworks, + ipsec => $type eq 'ipsec4' ? 'ipsec' : 'none' }; +} + +# +# Return a list of networks routed out of the passed interface +# +sub get_routed_networks ( $$ ) { + my ( $interface , $error_message ) = @_; + my @networks; + + if ( open IP , '-|' , "/sbin/ip route show dev $interface 2> /dev/null" ) { + while ( my $route = ) { + $route =~ s/^\s+//; + my $network = ( split /\s+/, $route )[0]; + if ( $network eq 'default' ) { + fatal_error $error_message if $error_message; + warning_message "default route ignored on interface $interface"; + } else { + my ( $address, $vlsm ) = split '/', $network; + $vlsm = 32 unless defined $vlsm; + push @networks, "$address/$vlsm"; + } + } + close IP + } + + @networks; +} + +# +# Parse the interfaces file. +# + +sub validate_interfaces_file( $ ) +{ + my $export = shift; + + use constant { SIMPLE_IF_OPTION => 1, + BINARY_IF_OPTION => 2, + ENUM_IF_OPTION => 3, + MASK_IF_OPTION => 3, + + IF_OPTION_ZONEONLY => 4 }; + + my %validoptions = (arp_filter => BINARY_IF_OPTION, + arp_ignore => ENUM_IF_OPTION, + blacklist => SIMPLE_IF_OPTION, + bridge => SIMPLE_IF_OPTION, + detectnets => SIMPLE_IF_OPTION, + dhcp => SIMPLE_IF_OPTION, + maclist => SIMPLE_IF_OPTION, + logmartians => BINARY_IF_OPTION, + norfc1918 => SIMPLE_IF_OPTION, + nosmurfs => SIMPLE_IF_OPTION, + optional => SIMPLE_IF_OPTION, + proxyarp => BINARY_IF_OPTION, + routeback => SIMPLE_IF_OPTION + IF_OPTION_ZONEONLY, + routefilter => BINARY_IF_OPTION, + sourceroute => BINARY_IF_OPTION, + tcpflags => SIMPLE_IF_OPTION, + upnp => SIMPLE_IF_OPTION, + ); + + my $fn = open_file 'interfaces'; + + my $first_entry = 1; + + my @ifaces; + + while ( read_a_line ) { + + if ( $first_entry ) { + progress_message2 "$doing $fn..."; + $first_entry = 0; + } + + my ($zone, $interface, $networks, $options ) = split_line 2, 4, 'interfaces file'; + my $zoneref; + my $bridge = ''; + + if ( $zone eq '-' ) { + $zone = ''; + } else { + $zoneref = $zones{$zone}; + + fatal_error "Unknown zone ($zone)" unless $zoneref; + fatal_error "Firewall zone not allowed in ZONE column of interface record" if $zoneref->{type} eq 'firewall'; + } + + $networks = '' if $networks eq '-'; + $options = '' if $options eq '-'; + + ( $interface, my ($port, $extra) ) = split /:/ , $interface, 3; + + fatal_error "Invalid INTERFACE" if defined $extra || ! $interface; + + fatal_error "Invalid Interface Name ($interface)" if $interface eq '+'; + + if ( defined $port ) { + require_capability( 'PHYSDEV_MATCH', 'Bridge Ports', ''); + require_capability( 'KLUDGEFREE', 'Bridge Ports', ''); + fatal_error "Duplicate Interface ($port)" if $interfaces{$port}; + fatal_error "$interface is not a defined bridge" unless $interfaces{$interface} && $interfaces{$interface}{options}{bridge}; + fatal_error "Bridge Ports may only be associated with 'bport' zones" if $zone && $zoneref->{type} ne 'bport4'; + + if ( $zone ) { + if ( $zoneref->{bridge} ) { + fatal_error "Bridge Port zones may only be associated with a single bridge" if $zoneref->{bridge} ne $interface; + } else { + $zoneref->{bridge} = $interface; + } + } + + fatal_error "Bridge Ports may not have options" if $options && $options ne '-'; + + next if $port eq ''; + + fatal_error "Invalid Interface Name ($interface:$port)" unless $port =~ /^[\w.@%-]+\+?$/; + + $interfaces{$port}{bridge} = $bridge = $interface; + $interface = $port; + } else { + fatal_error "Duplicate Interface ($interface)" if $interfaces{$interface}; + fatal_error "Zones of type 'bport' may only be associated with bridge ports" if $zone && $zoneref->{type} eq 'bport4'; + $interfaces{$interface}{bridge} = $interface; + } + + my $wildcard = 0; + + if ( $interface =~ /\+$/ ) { + $wildcard = 1; + $interfaces{$interface}{root} = substr( $interface, 0, -1 ); + } else { + $interfaces{$interface}{root} = $interface; + } + + unless ( $networks eq '' || $networks eq 'detect' ) { + + for my $address ( split /,/, $networks ) { + fatal_error 'Invalid BROADCAST address' unless $address =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/; + } + + warning_message 'Shorewall no longer uses broadcast addresses in rule generation'; + } + + my $optionsref = {}; + + my %options; + + if ( $options ) { + + for my $option (split ',', $options ) { + next if $option eq '-'; + + ( $option, my $value ) = split /=/, $option; + + fatal_error "Invalid Interface option ($option)" unless my $type = $validoptions{$option}; + + fatal_error "The \"$option\" option may not be specified on a multi-zone interface" if $type & IF_OPTION_ZONEONLY && ! $zone; + + $type &= MASK_IF_OPTION; + + if ( $type == SIMPLE_IF_OPTION ) { + fatal_error "Option $option does not take a value" if defined $value; + $options{$option} = 1; + } elsif ( $type == BINARY_IF_OPTION ) { + $value = 1 unless defined $value; + fatal_error "Option value for $option must be 0 or 1" unless ( $value eq '0' || $value eq '1' ); + fatal_error "The $option option may not be used with a wild-card interface name" if $wildcard; + $options{$option} = $value; + } elsif ( $type == ENUM_IF_OPTION ) { + fatal_error "The $option option may not be used with a wild-card interface name" if $wildcard; + if ( $option eq 'arp_ignore' ) { + if ( defined $value ) { + if ( $value =~ /^[1-3,8]$/ ) { + $options{arp_ignore} = $value; + } else { + fatal_error "Invalid value ($value) for arp_ignore"; + } + } else { + $options{arp_ignore} = 1; + } + } else { + fatal_error "Internal Error in validate_interfaces_file"; + } + } + } + + $zoneref->{options}{in_out}{routeback} = 1 if $zoneref && $options{routeback}; + + if ( $options{bridge} ) { + require_capability( 'PHYSDEV_MATCH', 'The "bridge" option', 's'); + fatal_error "Bridges may not have wildcard names" if $wildcard; + push @bridges, $interface; + } + } elsif ( $port ) { + $options{port} = 1; + } + + $interfaces{$interface}{options} = $optionsref = \%options; + + push @ifaces, $interface; + + my @networks; + + if ( $options{detectnets} ) { + fatal_error "The 'detectnets' option is not allowed on a multi-zone interface" unless $zone; + fatal_error "The 'detectnets' option may not be used with a wild-card interface name" if $wildcard; + fatal_error "The 'detectnets' option may not be used with the '-e' compiler option" if $export; + @networks = get_routed_networks( $interface, 'detectnets not allowed on interface with default route' ); + fatal_error "No routes found through 'detectnets' interface $interface" unless @networks || $options{optional}; + delete $options{maclist} unless @networks; + } else { + @networks = @allipv4; + } + + add_group_to_zone( $zone, $zoneref->{type}, $interface, \@networks, $optionsref ) if $zone && @networks; + + $interfaces{$interface}{zone} = $zone; #Must follow the call to add_group_to_zone() + + progress_message " Interface \"$currentline\" Validated"; + + } + + # + # We now assemble the @interfaces array such that bridge ports immediately precede their associated bridge + # + for my $interface ( @ifaces ) { + my $interfaceref = $interfaces{$interface}; + + if ( $interfaceref->{options}{bridge} ) { + my @ports = grep $interfaces{$_}{options}{port} && $interfaces{$_}{bridge} eq $interface, @ifaces; + + if ( @ports ) { + push @interfaces, @ports; + } else { + $interfaceref->{options}{routeback} = 1; #so the bridge will work properly + } + } + + push @interfaces, $interface unless $interfaceref->{options}{port}; + } +} + +# +# Returns true if passed interface matches an entry in /etc/shorewall/interfaces +# +# If the passed name matches a wildcard, a entry for the name is added in %interfaces to speed up validation of other references to that name. +# +sub known_interface($) +{ + my $interface = $_[0]; + + return 1 if $interfaces{$interface}; + + for my $i ( @interfaces ) { + my $interfaceref = $interfaces{$i}; + my $val = $interfaceref->{root}; + next if $val eq $i; + if ( substr( $interface, 0, length $val ) eq $val ) { + # + # Cache this result for future reference + # + $interfaces{$interface} = { options => $interfaceref->{options}, bridge => $interfaceref->{bridge} }; + return 1; + } + } + + 0; +} + +# +# Return the bridge associated with the passed interface. If the interface is not a bridge port, +# return '' +# +sub port_to_bridge( $ ) { + my $portref = $interfaces{$_[0]}; + return $portref && $portref->{options}{port} ? $portref->{bridge} : ''; +} + +# +# Return the bridge associated with the passed interface. +# +sub source_port_to_bridge( $ ) { + my $portref = $interfaces{$_[0]}; + return $portref ? $portref->{bridge} : ''; +} + +# +# Return the 'optional' setting of the passed interface +# +sub interface_is_optional($) { + my $optionsref = $interfaces{$_[0]}{options}; + $optionsref && $optionsref->{optional}; +} + +# +# Returns reference to array of interfaces with the passed option +# +sub find_interfaces_by_option( $ ) { + my $option = $_[0]; + my @ints = (); + + for my $interface ( @interfaces ) { + my $optionsref = $interfaces{$interface}{options}; + if ( $optionsref && defined $optionsref->{$option} ) { + push @ints , $interface + } + } + + \@ints; +} + +# +# Return the value of an option for an interface +# +sub get_interface_option( $$ ) { + my ( $interface, $option ) = @_; + + $interfaces{$interface}{options}{$option}; +} + +# +# Validates the hosts file. Generates entries in %zone{..}{hosts} +# +sub validate_hosts_file() +{ + my %validoptions = ( + blacklist => 1, + maclist => 1, + norfc1918 => 1, + nosmurfs => 1, + routeback => 1, + routefilter => 1, + tcpflags => 1, + ); + + my $ipsec = 0; + my $first_entry = 1; + + my $fn = open_file 'hosts'; + + while ( read_a_line ) { + + if ( $first_entry ) { + progress_message2 "$doing $fn..."; + $first_entry = 0; + } + + my ($zone, $hosts, $options ) = split_line 2, 3, 'hosts file'; + + my $zoneref = $zones{$zone}; + my $type = $zoneref->{type}; + + fatal_error "Unknown ZONE ($zone)" unless $type; + fatal_error 'Firewall zone not allowed in ZONE column of hosts record' if $type eq 'firewall'; + + my $interface; + + if ( $hosts =~ /^([\w.@%-]+\+?):(.*)$/ ) { + $interface = $1; + $hosts = $2; + $zoneref->{options}{complex} = 1 if $hosts =~ /^\+/; + fatal_error "Unknown interface ($interface)" unless $interfaces{$interface}{root}; + } else { + fatal_error "Invalid HOST(S) column contents: $hosts"; + } + + if ( $type eq 'bport4' ) { + if ( $zoneref->{bridge} eq '' ) { + fatal_error 'Bridge Port Zones may only be associated with bridge ports' unless $interfaces{$interface}{options}{port}; + $zoneref->{bridge} = $interfaces{$interface}{bridge}; + } elsif ( $zoneref->{bridge} ne $interfaces{$interface}{bridge} ) { + fatal_error "Interface $interface is not a port on bridge $zoneref->{bridge}"; + } + } + + my $optionsref = {}; + + if ( $options ne '-' ) { + my @options = split ',', $options; + my %options; + + for my $option ( @options ) + { + if ( $option eq 'ipsec' ) { + $type = 'ipsec'; + $zoneref->{options}{complex} = 1; + $ipsec = 1; + } elsif ( $validoptions{$option}) { + $options{$option} = 1; + } else { + fatal_error "Invalid option ($option)"; + } + } + + $optionsref = \%options; + } + + # + # Looking for the '!' at the beginning of a list element is more straight-foward than looking for it in the middle. + # + # Be sure we don't have a ',!' in the original + # + fatal_error "Invalid hosts list" if $hosts =~ /,!/; + # + # Now add a comma before '!'. Do it globally - add_group_to_zone() correctly checks for multiple exclusions + # + $hosts =~ s/!/,!/g; + # + # Take care of case where the hosts list begins with '!' + # + $hosts = join( '', ALLIPv4 , $hosts ) if substr($hosts, 0, 2 ) eq ',!'; + + add_group_to_zone( $zone, $type , $interface, [ split( ',', $hosts ) ] , $optionsref); + + progress_message " Host \"$currentline\" validated"; + } + + $capabilities{POLICY_MATCH} = '' unless $ipsec || haveipseczones; +} +# +# Returns a reference to a array of host entries. Each entry is a +# reference to an array containing ( interface , polciy match type {ipsec|none} , network ); +# +sub find_hosts_by_option( $ ) { + my $option = $_[0]; + my @hosts; + + for my $zone ( grep $zones{$_}{type} ne 'firewall' , @zones ) { + while ( my ($type, $interfaceref) = each %{$zones{$zone}{hosts}} ) { + while ( my ( $interface, $arrayref) = ( each %{$interfaceref} ) ) { + for my $host ( @{$arrayref} ) { + if ( $host->{options}{$option} ) { + for my $net ( @{$host->{hosts}} ) { + push @hosts, [ $interface, $host->{ipsec} , $net ]; + } + } + } + } + } + } + + for my $interface ( @interfaces ) { + if ( ! $interfaces{$interface}{zone} && $interfaces{$interface}{options}{$option} ) { + push @hosts, [ $interface, 'none', ALLIPv4 ]; + } + } + + \@hosts; +} + 1; diff --git a/Shorewall-perl/shorewall-perl.spec b/Shorewall-perl/shorewall-perl.spec index a17d79252..d8ac20a15 100644 --- a/Shorewall-perl/shorewall-perl.spec +++ b/Shorewall-perl/shorewall-perl.spec @@ -70,12 +70,9 @@ fi %attr(0644,root,root) /usr/share/shorewall-perl/Shorewall/Accounting.pm %attr(0644,root,root) /usr/share/shorewall-perl/Shorewall/Actions.pm %attr(0644,root,root) /usr/share/shorewall-perl/Shorewall/Chains.pm -%attr(0644,root,root) /usr/share/shorewall-perl/Shorewall/Common.pm %attr(0644,root,root) /usr/share/shorewall-perl/Shorewall/Compiler.pm %attr(0644,root,root) /usr/share/shorewall-perl/Shorewall/Config.pm %attr(0644,root,root) /usr/share/shorewall-perl/Shorewall/FallbackPorts.pm -%attr(0644,root,root) /usr/share/shorewall-perl/Shorewall/Hosts.pm -%attr(0644,root,root) /usr/share/shorewall-perl/Shorewall/Interfaces.pm %attr(0644,root,root) /usr/share/shorewall-perl/Shorewall/IPAddrs.pm %attr(0644,root,root) /usr/share/shorewall-perl/Shorewall/Macros.pm %attr(0644,root,root) /usr/share/shorewall-perl/Shorewall/Nat.pm