mirror of
https://gitlab.com/shorewall/code.git
synced 2024-11-27 18:13:13 +01:00
71155835a1
git-svn-id: https://shorewall.svn.sourceforge.net/svnroot/shorewall/trunk@8969 fbd18981-670d-0410-9b5c-8dc0c1a9a2bb
482 lines
15 KiB
Perl
482 lines
15 KiB
Perl
#
|
|
# Shorewall-perl 4.2 -- /usr/share/shorewall-perl/Shorewall/Policy.pm
|
|
#
|
|
# This program is under GPL [http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt]
|
|
#
|
|
# (c) 2007,2008 - Tom Eastep (teastep@shorewall.net)
|
|
#
|
|
# Complete documentation is available at http://shorewall.net
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of Version 2 of the GNU General Public License
|
|
# as published by the Free Software Foundation.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
#
|
|
# This module deals with the /etc/shorewall/policy file.
|
|
#
|
|
package Shorewall::Policy;
|
|
require Exporter;
|
|
use Shorewall::Config qw(:DEFAULT :internal);
|
|
use Shorewall::Zones;
|
|
use Shorewall::Chains qw( :DEFAULT :internal) ;
|
|
use Shorewall::Actions;
|
|
|
|
use strict;
|
|
|
|
our @ISA = qw(Exporter);
|
|
our @EXPORT = qw( validate_policy apply_policy_rules complete_standard_chain sub setup_syn_flood_chains );
|
|
our @EXPORT_OK = qw( );
|
|
our $VERSION = 4.3.0;
|
|
|
|
# @policy_chains is a list of references to policy chains in the filter table
|
|
|
|
our @policy_chains;
|
|
|
|
#
|
|
# 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() {
|
|
@policy_chains = ();
|
|
}
|
|
|
|
INIT {
|
|
initialize;
|
|
}
|
|
|
|
#
|
|
# Convert a chain into a policy chain.
|
|
#
|
|
sub convert_to_policy_chain($$$$$)
|
|
{
|
|
my ($chainref, $source, $dest, $policy, $optional ) = @_;
|
|
|
|
$chainref->{is_policy} = 1;
|
|
$chainref->{policy} = $policy;
|
|
$chainref->{is_optional} = $optional;
|
|
$chainref->{policychain} = $chainref->{name};
|
|
$chainref->{policypair} = [ $source, $dest ];
|
|
}
|
|
|
|
#
|
|
# Create a new policy chain and return a reference to it.
|
|
#
|
|
sub new_policy_chain($$$$)
|
|
{
|
|
my ($source, $dest, $policy, $optional) = @_;
|
|
|
|
my $chainref = new_chain( 'filter', "${source}2${dest}" );
|
|
|
|
convert_to_policy_chain( $chainref, $source, $dest, $policy, $optional );
|
|
|
|
$chainref;
|
|
}
|
|
|
|
#
|
|
# Set the passed chain's policychain and policy to the passed values.
|
|
#
|
|
sub set_policy_chain($$$$$)
|
|
{
|
|
my ($source, $dest, $chain1, $chainref, $policy ) = @_;
|
|
|
|
my $chainref1 = $filter_table->{$chain1};
|
|
|
|
$chainref1 = new_chain 'filter', $chain1 unless $chainref1;
|
|
|
|
unless ( $chainref1->{policychain} ) {
|
|
if ( $config{EXPAND_POLICIES} ) {
|
|
#
|
|
# We convert the canonical chain into a policy chain, using the settings of the
|
|
# passed policy chain.
|
|
#
|
|
$chainref1->{policychain} = $chain1;
|
|
$chainref1->{loglevel} = $chainref->{loglevel} if defined $chainref->{loglevel};
|
|
|
|
if ( defined $chainref->{synparams} ) {
|
|
$chainref1->{synparams} = $chainref->{synparams};
|
|
$chainref1->{synchain} = $chainref->{synchain};
|
|
}
|
|
|
|
$chainref1->{default} = $chainref->{default} if defined $chainref->{default};
|
|
$chainref1->{is_policy} = 1;
|
|
push @policy_chains, $chainref1;
|
|
} else {
|
|
$chainref1->{policychain} = $chainref->{name};
|
|
}
|
|
|
|
$chainref1->{policy} = $policy;
|
|
$chainref1->{policypair} = [ $source, $dest ];
|
|
}
|
|
}
|
|
|
|
#
|
|
# Process the policy file
|
|
#
|
|
use constant { OPTIONAL => 1 };
|
|
|
|
sub add_or_modify_policy_chain( $$ ) {
|
|
my ( $zone, $zone1 ) = @_;
|
|
my $chain = "${zone}2${zone1}";
|
|
my $chainref = $filter_table->{$chain};
|
|
|
|
if ( $chainref ) {
|
|
unless( $chainref->{is_policy} ) {
|
|
convert_to_policy_chain( $chainref, $zone, $zone1, 'CONTINUE', OPTIONAL );
|
|
push @policy_chains, $chainref;
|
|
}
|
|
} else {
|
|
push @policy_chains, ( new_policy_chain $zone, $zone1, 'CONTINUE', OPTIONAL );
|
|
}
|
|
}
|
|
|
|
sub print_policy($$$$) {
|
|
my ( $source, $dest, $policy , $chain ) = @_;
|
|
unless ( ( $source eq 'all' ) || ( $dest eq 'all' ) ) {
|
|
if ( $policy eq 'CONTINUE' ) {
|
|
my ( $sourceref, $destref ) = ( find_zone($source) ,find_zone( $dest ) );
|
|
warning_message "CONTINUE policy between two un-nested zones ($source, $dest)" if ! ( @{$sourceref->{parents}} || @{$destref->{parents}} );
|
|
}
|
|
progress_message_nocompress " Policy for $source to $dest is $policy using chain $chain" unless $source eq $dest;
|
|
}
|
|
}
|
|
|
|
sub validate_policy()
|
|
{
|
|
my %validpolicies = (
|
|
ACCEPT => undef,
|
|
REJECT => undef,
|
|
DROP => undef,
|
|
CONTINUE => undef,
|
|
QUEUE => undef,
|
|
NFQUEUE => undef,
|
|
NONE => undef
|
|
);
|
|
|
|
my %map = ( DROP_DEFAULT => 'DROP' ,
|
|
REJECT_DEFAULT => 'REJECT' ,
|
|
ACCEPT_DEFAULT => 'ACCEPT' ,
|
|
QUEUE_DEFAULT => 'QUEUE' ,
|
|
NFQUEUE_DEFAULT => 'NFQUEUE' );
|
|
|
|
my $zone;
|
|
my @zonelist = $config{EXPAND_POLICIES} ? all_zones : ( all_zones, 'all' );
|
|
|
|
for my $option qw/DROP_DEFAULT REJECT_DEFAULT ACCEPT_DEFAULT QUEUE_DEFAULT NFQUEUE_DEFAULT/ {
|
|
my $action = $config{$option};
|
|
next if $action eq 'none';
|
|
my $actiontype = $targets{$action};
|
|
|
|
if ( defined $actiontype ) {
|
|
fatal_error "Invalid setting ($action) for $option" unless $actiontype & ACTION;
|
|
} else {
|
|
fatal_error "Default Action $option=$action not found";
|
|
}
|
|
|
|
unless ( $usedactions{$action} ) {
|
|
$usedactions{$action} = 1;
|
|
createactionchain $action;
|
|
}
|
|
|
|
$default_actions{$map{$option}} = $action;
|
|
}
|
|
|
|
for $zone ( all_zones ) {
|
|
push @policy_chains, ( new_policy_chain $zone, $zone, 'ACCEPT', OPTIONAL );
|
|
|
|
if ( $config{IMPLICIT_CONTINUE} && ( @{find_zone( $zone )->{parents}} ) ) {
|
|
for my $zone1 ( all_zones ) {
|
|
unless( $zone eq $zone1 ) {
|
|
add_or_modify_policy_chain( $zone, $zone1 );
|
|
add_or_modify_policy_chain( $zone1, $zone );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
my $fn = open_file 'policy';
|
|
|
|
first_entry "$doing $fn...";
|
|
|
|
while ( read_a_line ) {
|
|
|
|
my ( $client, $server, $originalpolicy, $loglevel, $synparams, $connlimit ) = split_line 3, 6, 'policy file';
|
|
|
|
$loglevel = '' if $loglevel eq '-';
|
|
$synparams = '' if $synparams eq '-';
|
|
$connlimit = '' if $connlimit eq '-';
|
|
|
|
my $clientwild = ( "\L$client" eq 'all' );
|
|
|
|
fatal_error "Undefined zone ($client)" unless $clientwild || defined_zone( $client );
|
|
|
|
my $serverwild = ( "\L$server" eq 'all' );
|
|
|
|
fatal_error "Undefined zone ($server)" unless $serverwild || defined_zone( $server );
|
|
|
|
my ( $policy, $default, $remainder ) = split( /:/, $originalpolicy, 3 );
|
|
|
|
fatal_error "Invalid or missing POLICY ($originalpolicy)" unless $policy;
|
|
|
|
fatal_error "Invalid default action ($default:$remainder)" if defined $remainder;
|
|
|
|
( $policy , my $queue ) = get_target_param $policy;
|
|
|
|
if ( $default ) {
|
|
if ( "\L$default" eq 'none' ) {
|
|
$default = 'none';
|
|
} else {
|
|
my $defaulttype = $targets{$default} || 0;
|
|
|
|
if ( $defaulttype & ACTION ) {
|
|
unless ( $usedactions{$default} ) {
|
|
$usedactions{$default} = 1;
|
|
createactionchain $default;
|
|
}
|
|
} else {
|
|
fatal_error "Unknown Default Action ($default)";
|
|
}
|
|
}
|
|
} else {
|
|
$default = $default_actions{$policy} || '';
|
|
}
|
|
|
|
fatal_error "Invalid policy ($policy)" unless exists $validpolicies{$policy};
|
|
|
|
if ( defined $queue ) {
|
|
fatal_error "Invalid policy ($policy($queue))" unless $policy eq 'NFQUEUE';
|
|
require_capability( 'NFQUEUE_TARGET', 'An NFQUEUE Policy', 's' );
|
|
my $queuenum = numeric_value( $queue );
|
|
fatal_error "Invalid NFQUEUE queue number ($queue)" unless defined( $queuenum) && $queuenum <= 65535;
|
|
$policy = "NFQUEUE --queue-num $queuenum";
|
|
} elsif ( $policy eq 'NONE' ) {
|
|
fatal_error "NONE policy not allowed with \"all\""
|
|
if $clientwild || $serverwild;
|
|
fatal_error "NONE policy not allowed to/from firewall zone"
|
|
if ( zone_type( $client ) eq 'firewall' ) || ( zone_type( $server ) eq 'firewall' );
|
|
}
|
|
|
|
unless ( $clientwild || $serverwild ) {
|
|
if ( zone_type( $server ) eq 'bport' ) {
|
|
fatal_error "Invalid policy - DEST zone is a Bridge Port zone but the SOURCE zone is not associated with the same bridge"
|
|
unless find_zone( $client )->{bridge} eq find_zone( $server)->{bridge} || single_interface( $client ) eq find_zone( $server )->{bridge};
|
|
}
|
|
}
|
|
|
|
my $chain = "${client}2${server}";
|
|
my $chainref;
|
|
|
|
if ( defined $filter_table->{$chain} ) {
|
|
$chainref = $filter_table->{$chain};
|
|
|
|
if ( $chainref->{is_policy} ) {
|
|
if ( $chainref->{is_optional} ) {
|
|
$chainref->{is_optional} = 0;
|
|
$chainref->{policy} = $policy;
|
|
} else {
|
|
fatal_error qq(Policy "$client $server $policy" duplicates earlier policy "@{$chainref->{policypair}} $chainref->{policy}");
|
|
}
|
|
} elsif ( $chainref->{policy} ) {
|
|
fatal_error qq(Policy "$client $server $policy" duplicates earlier policy "@{$chainref->{policypair}} $chainref->{policy}");
|
|
} else {
|
|
convert_to_policy_chain( $chainref, $client, $server, $policy, 0 );
|
|
push @policy_chains, ( $chainref ) unless $config{EXPAND_POLICIES} && ( $clientwild || $serverwild );
|
|
}
|
|
} else {
|
|
$chainref = new_policy_chain $client, $server, $policy, 0;
|
|
push @policy_chains, ( $chainref ) unless $config{EXPAND_POLICIES} && ( $clientwild || $serverwild );
|
|
}
|
|
|
|
$chainref->{loglevel} = validate_level( $loglevel ) if defined $loglevel && $loglevel ne '';
|
|
|
|
if ( $synparams ne '' || $connlimit ne '' ) {
|
|
my $value = '';
|
|
fatal_error "Invalid CONNLIMIT ($connlimit)" if $connlimit =~ /^!/;
|
|
$value = do_ratelimit $synparams, 'ACCEPT' if $synparams ne '';
|
|
$value .= do_connlimit $connlimit if $connlimit ne '';
|
|
$chainref->{synparams} = $value;
|
|
$chainref->{synchain} = $chain
|
|
}
|
|
|
|
$chainref->{default} = $default if $default;
|
|
|
|
if ( $clientwild ) {
|
|
if ( $serverwild ) {
|
|
for my $zone ( @zonelist ) {
|
|
for my $zone1 ( @zonelist ) {
|
|
set_policy_chain $client, $server, "${zone}2${zone1}", $chainref, $policy;
|
|
print_policy $zone, $zone1, $policy, $chain;
|
|
}
|
|
}
|
|
} else {
|
|
for my $zone ( all_zones ) {
|
|
set_policy_chain $client, $server, "${zone}2${server}", $chainref, $policy;
|
|
print_policy $zone, $server, $policy, $chain;
|
|
}
|
|
}
|
|
} elsif ( $serverwild ) {
|
|
for my $zone ( @zonelist ) {
|
|
set_policy_chain $client, $server, "${client}2${zone}", $chainref, $policy;
|
|
print_policy $client, $zone, $policy, $chain;
|
|
}
|
|
|
|
} else {
|
|
print_policy $client, $server, $policy, $chain;
|
|
}
|
|
}
|
|
|
|
for $zone ( all_zones ) {
|
|
for my $zone1 ( all_zones ) {
|
|
fatal_error "No policy defined from zone $zone to zone $zone1" unless $filter_table->{"${zone}2${zone1}"}{policy};
|
|
}
|
|
}
|
|
}
|
|
|
|
#
|
|
# Policy Rule application
|
|
#
|
|
sub policy_rules( $$$$$ ) {
|
|
my ( $chainref , $target, $loglevel, $default, $dropmulticast ) = @_;
|
|
|
|
unless ( $target eq 'NONE' ) {
|
|
add_rule $chainref, "-d 224.0.0.0/24 -j RETURN" if $dropmulticast && $target ne 'CONTINUE';
|
|
add_rule $chainref, "-j $default" if $default && $default ne 'none';
|
|
log_rule $loglevel , $chainref , $target , '' if $loglevel ne '';
|
|
fatal_error "Null target in policy_rules()" unless $target;
|
|
$target = 'reject' if $target eq 'REJECT';
|
|
|
|
add_jump( $chainref , $target, 1 ) unless $target eq 'CONTINUE';
|
|
}
|
|
}
|
|
|
|
sub report_syn_flood_protection() {
|
|
progress_message_nocompress ' Enabled SYN flood protection';
|
|
}
|
|
|
|
sub default_policy( $$$ ) {
|
|
my $chainref = $_[0];
|
|
my $policyref = $filter_table->{$chainref->{policychain}};
|
|
my $synparams = $policyref->{synparams};
|
|
my $default = $policyref->{default};
|
|
my $policy = $policyref->{policy};
|
|
my $loglevel = $policyref->{loglevel};
|
|
|
|
fatal_error "Internal error in default_policy()" unless $policyref;
|
|
|
|
if ( $chainref eq $policyref ) {
|
|
policy_rules $chainref , $policy, $loglevel , $default, $config{MULTICAST};
|
|
} else {
|
|
if ( $policy eq 'ACCEPT' || $policy eq 'QUEUE' || $policy =~ /^NFQUEUE/ ) {
|
|
if ( $synparams ) {
|
|
report_syn_flood_protection;
|
|
policy_rules $chainref , $policy , $loglevel , $default, $config{MULTICAST};
|
|
} else {
|
|
add_jump $chainref, $policyref, 1;
|
|
$chainref = $policyref;
|
|
}
|
|
} elsif ( $policy eq 'CONTINUE' ) {
|
|
report_syn_flood_protection if $synparams;
|
|
policy_rules $chainref , $policy , $loglevel , $default, $config{MULTICAST};
|
|
} else {
|
|
report_syn_flood_protection if $synparams;
|
|
add_jump $chainref , $policyref, 1;
|
|
$chainref = $policyref;
|
|
}
|
|
}
|
|
|
|
progress_message_nocompress " Policy $policy from $_[1] to $_[2] using chain $chainref->{name}";
|
|
|
|
}
|
|
|
|
sub apply_policy_rules() {
|
|
progress_message2 'Applying Policies...';
|
|
|
|
for my $chainref ( @policy_chains ) {
|
|
my $policy = $chainref->{policy};
|
|
my $loglevel = $chainref->{loglevel};
|
|
my $optional = $chainref->{is_optional};
|
|
my $default = $chainref->{default};
|
|
my $name = $chainref->{name};
|
|
|
|
if ( $policy ne 'NONE' ) {
|
|
if ( ! $chainref->{referenced} && ( ! $optional && $policy ne 'CONTINUE' ) ) {
|
|
ensure_filter_chain $name, 1;
|
|
}
|
|
|
|
if ( $name =~ /^all2|2all$/ ) {
|
|
run_user_exit $chainref;
|
|
policy_rules $chainref , $policy, $loglevel , $default, $config{MULTICAST};
|
|
}
|
|
}
|
|
}
|
|
|
|
for my $zone ( all_zones ) {
|
|
for my $zone1 ( all_zones ) {
|
|
my $chainref = $filter_table->{"${zone}2${zone1}"};
|
|
|
|
if ( $chainref->{referenced} ) {
|
|
run_user_exit $chainref;
|
|
default_policy $chainref, $zone, $zone1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#
|
|
# Complete a standard chain
|
|
#
|
|
# - run any supplied user exit
|
|
# - search the policy file for an applicable policy and add rules as
|
|
# appropriate
|
|
# - If no applicable policy is found, add rules for an assummed
|
|
# policy of DROP INFO
|
|
#
|
|
sub complete_standard_chain ( $$$$ ) {
|
|
my ( $stdchainref, $zone, $zone2, $default ) = @_;
|
|
|
|
add_rule $stdchainref, '-m state --state ESTABLISHED,RELATED -j ACCEPT' unless $config{FASTACCEPT};
|
|
|
|
run_user_exit $stdchainref;
|
|
|
|
my $ruleschainref = $filter_table->{"${zone}2${zone2}"};
|
|
my ( $policy, $loglevel, $defaultaction ) = ( $default , 6, $config{$default . '_DEFAULT'} );
|
|
my $policychainref;
|
|
|
|
$policychainref = $filter_table->{$ruleschainref->{policychain}} if $ruleschainref;
|
|
|
|
( $policy, $loglevel, $defaultaction ) = @{$policychainref}{'policy', 'loglevel', 'default' } if $policychainref;
|
|
|
|
policy_rules $stdchainref , $policy , $loglevel, $defaultaction, 0;
|
|
}
|
|
|
|
#
|
|
# Create and populate the synflood chains corresponding to entries in /etc/shorewall/policy
|
|
#
|
|
sub setup_syn_flood_chains() {
|
|
for my $chainref ( @policy_chains ) {
|
|
my $limit = $chainref->{synparams};
|
|
if ( $limit && ! $filter_table->{syn_flood_chain $chainref} ) {
|
|
my $level = $chainref->{loglevel};
|
|
my $synchainref = new_chain 'filter' , syn_flood_chain $chainref;
|
|
add_rule $synchainref , "${limit}-j RETURN";
|
|
log_rule_limit $level , $synchainref , $chainref->{name} , 'DROP', '-m limit --limit 5/min --limit-burst 5 ' , '' , 'add' , ''
|
|
if $level ne '';
|
|
add_rule $synchainref, '-j DROP';
|
|
}
|
|
}
|
|
}
|
|
|
|
1;
|