mirror of
https://gitlab.com/shorewall/code.git
synced 2024-12-11 08:51:13 +01:00
6798c2f2b0
Previously, the code assumed that either DOCKER-ISOLATION or DOCKER-ISOLATION-STAGE-* would exist but not both. That has been corrected
2814 lines
87 KiB
Perl
2814 lines
87 KiB
Perl
#
|
|
# Shorewall 5.2 -- /usr/share/shorewall/Shorewall/Misc.pm
|
|
#
|
|
# This program is under GPL [http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt]
|
|
#
|
|
# (c) 2007-2019 - Tom Eastep (teastep@shorewall.net)
|
|
#
|
|
# Complete documentation is available at http://shorewall.net
|
|
#
|
|
# This program is part of Shorewall.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by the
|
|
# Free Software Foundation, either version 2 of the license or, at your
|
|
# option, any later version.
|
|
#
|
|
# 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, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
# This module contains those routines that don't seem to fit well elsewhere. It
|
|
# was carved from the Rules module in 4.4.16.
|
|
#
|
|
package Shorewall::Misc;
|
|
require Exporter;
|
|
|
|
use Shorewall::Config qw(:DEFAULT :internal);
|
|
use Shorewall::IPAddrs;
|
|
use Shorewall::Zones;
|
|
use Shorewall::Chains qw(:DEFAULT :internal);
|
|
use Shorewall::Rules;
|
|
use Shorewall::Proc;
|
|
|
|
use strict;
|
|
|
|
our @ISA = qw(Exporter);
|
|
our @EXPORT = qw( process_tos
|
|
setup_ecn
|
|
add_common_rules
|
|
setup_mac_lists
|
|
convert_routestopped
|
|
process_stoppedrules
|
|
compile_stop_firewall
|
|
generate_matrix
|
|
);
|
|
our @EXPORT_OK = qw( initialize );
|
|
our $VERSION = 'MODULEVERSION';
|
|
|
|
our $family;
|
|
|
|
#
|
|
# Rather than initializing globals in an INIT block or during declaration,
|
|
# we initialize them in a function. This is done for two reasons:
|
|
#
|
|
# 1. Proper initialization depends on the address family which isn't
|
|
# known until the compiler has started.
|
|
#
|
|
# 2. The compiler can run multiple times in the same process so it has to be
|
|
# able to re-initialize its dependent modules' state.
|
|
#
|
|
sub initialize( $ ) {
|
|
$family = shift;
|
|
}
|
|
|
|
#
|
|
# Warn that the tos file is no longer supported
|
|
#
|
|
sub process_tos() {
|
|
|
|
if ( my $fn = open_file 'tos' ) {
|
|
my $first_entry = 1;
|
|
|
|
my ( $pretosref, $outtosref );
|
|
|
|
first_entry( sub { progress_message2 "$doing $fn...";
|
|
warning_message "Use of the tos file is no longer supported -- use the TOS target in the mangle file instead";
|
|
}
|
|
);
|
|
|
|
while ( read_a_line( NORMAL_READ )) { 1; }
|
|
}
|
|
}
|
|
|
|
#
|
|
# Setup ECN disabling rules
|
|
#
|
|
sub setup_ecn()
|
|
{
|
|
my %interfaces;
|
|
my @hosts;
|
|
my $interfaceref;
|
|
|
|
if ( my $fn = open_file 'ecn' ) {
|
|
|
|
first_entry( sub { progress_message2 "$doing $fn...";
|
|
require_capability 'MANGLE_ENABLED', 'Entries in the ecn file', '';
|
|
warning_message 'ECN will not be applied to forwarded packets' unless have_capability 'MANGLE_FORWARD';
|
|
} );
|
|
|
|
while ( read_a_line( NORMAL_READ ) ) {
|
|
|
|
my ($interface, $hosts ) = split_line1( 'ecn file entry',
|
|
{ interface => 0, host => 1, hosts => 1 },
|
|
{},
|
|
2 );
|
|
|
|
fatal_error 'INTERFACE must be specified' if $interface eq '-';
|
|
fatal_error "Unknown interface ($interface)" unless $interfaceref = known_interface( $interface );
|
|
|
|
if ( $interfaceref->{root} ) {
|
|
$interface = $interfaceref->{name} if $interface eq $interfaceref->{physical};
|
|
} else {
|
|
$interface = $interfaceref->{name};
|
|
}
|
|
|
|
my $lineinfo = shortlineinfo( '' );
|
|
|
|
$interfaces{$interface} ||= $lineinfo;
|
|
|
|
$hosts = ALLIP if $hosts eq '-';
|
|
|
|
for my $host( split_list $hosts, 'address' ) {
|
|
validate_host( $host , 1 );
|
|
push @hosts, [ $interface, $lineinfo, $host ];
|
|
}
|
|
}
|
|
|
|
if ( @hosts ) {
|
|
my @interfaces = ( keys %interfaces );
|
|
|
|
progress_message "$doing ECN control on @interfaces...";
|
|
|
|
for my $interface ( @interfaces ) {
|
|
my $chainref = ensure_chain 'mangle', ecn_chain( $interface );
|
|
|
|
add_ijump_extended $mangle_table->{POSTROUTING} , j => $chainref, $interfaces{$interface}, p => 'tcp', imatch_dest_dev( $interface ) if have_capability 'MANGLE_FORWARD';
|
|
add_ijump_extended $mangle_table->{OUTPUT}, j => $chainref, $interfaces{$interface}, p => 'tcp', imatch_dest_dev( $interface );
|
|
}
|
|
|
|
for my $host ( @hosts ) {
|
|
add_ijump_extended( $mangle_table->{ecn_chain $host->[0]}, j => 'ECN', $host->[1], targetopts => '--ecn-tcp-remove', p => 'tcp', imatch_dest_net( $host->[2] ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#
|
|
# Add a logging rule followed by a jump
|
|
#
|
|
sub add_rule_pair( $$$$$ ) {
|
|
my ($chainref , $predicate , $target , $level, $tag ) = @_;
|
|
|
|
log_rule_limit( $level,
|
|
$chainref,
|
|
$chainref->{name},
|
|
"\U$target",
|
|
$globals{LOGLIMIT},
|
|
$tag,
|
|
'add',
|
|
$predicate ) if supplied $level;
|
|
add_jump( $chainref , $target, 0, $predicate );
|
|
}
|
|
|
|
#
|
|
# Remove instances of 'blacklist' from the passed file.
|
|
#
|
|
sub remove_blacklist( $ ) {
|
|
my $file = shift;
|
|
|
|
my $fn = find_file $file;
|
|
|
|
return 1 unless -f $file;
|
|
|
|
my $oldfile = open_file $fn;
|
|
my $newfile;
|
|
my $changed;
|
|
|
|
open $newfile, '>', "$fn.new" or fatal_error "Unable to open $fn.new for output: $!";
|
|
|
|
while ( read_a_line( EMBEDDED_ENABLED | EXPAND_VARIABLES ) ) {
|
|
my ( $rule, $comment ) = split '#', $currentline, 2;
|
|
|
|
if ( $rule && $rule =~ /blacklist/ ) {
|
|
$changed = 1;
|
|
|
|
if ( $comment ) {
|
|
$comment =~ s/^/ / while $rule =~ s/blacklist,// || $rule =~ s/,blacklist//;
|
|
$rule =~ s/blacklist/ /g;
|
|
$currentline = join( '#', $rule, $comment );
|
|
} else {
|
|
$currentline =~ s/blacklist,//g;
|
|
$currentline =~ s/,blacklist//g;
|
|
$currentline =~ s/blacklist/ /g;
|
|
}
|
|
}
|
|
|
|
print $newfile "$currentline\n";
|
|
}
|
|
|
|
close $newfile;
|
|
|
|
if ( $changed ) {
|
|
rename $fn, "$fn.bak" or fatal_error "Unable to rename $fn to $fn.bak: $!";
|
|
rename "$fn.new", $fn or fatal_error "Unable to rename $fn.new to $fn: $!";
|
|
transfer_permissions( "$fn.bak", $fn );
|
|
progress_message2 "\u$file file $fn saved in $fn.bak"
|
|
}
|
|
}
|
|
|
|
#
|
|
# Convert a pre-4.4.25 blacklist to a 4.4.25 blrules file
|
|
#
|
|
sub convert_blacklist() {
|
|
my $zones = find_zones_by_option 'blacklist', 'in';
|
|
my $zones1 = find_zones_by_option 'blacklist', 'out';
|
|
my ( $level, $disposition ) = @config{'BLACKLIST_LOG_LEVEL', 'BLACKLIST_DISPOSITION' };
|
|
my $tag = $globals{MACLIST_LOG_TAG};
|
|
my $audit = $disposition =~ /^A_/;
|
|
my $target = $disposition;
|
|
my $orig_target = $target;
|
|
my $warnings = 0;
|
|
my @rules;
|
|
|
|
if ( @$zones || @$zones1 ) {
|
|
if ( supplied $level ) {
|
|
$target = supplied $tag ? "$target:$level:$tag":"$target:$level";
|
|
}
|
|
|
|
my $fn = open_file( 'blacklist' );
|
|
|
|
unless ( $fn ) {
|
|
if ( -f ( $fn = find_file( 'blacklist' ) ) ) {
|
|
if ( unlink( $fn ) ) {
|
|
warning_message "Empty blacklist file ($fn) removed";
|
|
} else {
|
|
warning_message "Unable to remove empty blacklist file $fn: $!";
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
directive_callback(
|
|
sub ()
|
|
{
|
|
warning_message "Omitted rules and compiler directives were not translated" unless $warnings++;
|
|
}
|
|
);
|
|
|
|
first_entry "Converting $fn...";
|
|
|
|
while ( read_a_line( NORMAL_READ ) ) {
|
|
my ( $networks, $protocol, $ports, $options ) =
|
|
split_rawline2( 'blacklist file',
|
|
{ networks => 0, proto => 1, port => 2, options => 3 },
|
|
{},
|
|
4,
|
|
);
|
|
|
|
if ( $options eq '-' ) {
|
|
$options = 'src';
|
|
} elsif ( $options eq 'audit' ) {
|
|
$options = 'audit,src';
|
|
}
|
|
|
|
my ( $to, $from, $whitelist, $auditone ) = ( 0, 0, 0, 0 );
|
|
|
|
my @options = split_list $options, 'option';
|
|
|
|
for ( @options ) {
|
|
$whitelist++ if $_ eq 'whitelist';
|
|
$auditone++ if $_ eq 'audit';
|
|
}
|
|
|
|
warning_message "Duplicate 'whitelist' option ignored" if $whitelist > 1;
|
|
|
|
my $tgt = $whitelist ? 'WHITELIST' : $target;
|
|
|
|
if ( $auditone ) {
|
|
fatal_error "'audit' not allowed in whitelist entries" if $whitelist;
|
|
|
|
if ( $audit ) {
|
|
warning_message "Superfluous 'audit' option ignored";
|
|
} else {
|
|
warning_message "Duplicate 'audit' option ignored" if $auditone > 1;
|
|
}
|
|
}
|
|
|
|
for ( @options ) {
|
|
if ( $_ =~ /^(?:src|from)$/ ) {
|
|
if ( $from++ ) {
|
|
warning_message "Duplicate 'src' ignored";
|
|
} else {
|
|
if ( @$zones ) {
|
|
push @rules, [ 'src', $tgt, $networks, $protocol, $ports ];
|
|
} else {
|
|
warning_message '"src" entry ignored because there are no "blacklist in" zones';
|
|
}
|
|
}
|
|
} elsif ( $_ =~ /^(?:dst|to)$/ ) {
|
|
if ( $to++ ) {
|
|
warning_message "Duplicate 'dst' ignored";
|
|
} else {
|
|
if ( @$zones1 ) {
|
|
push @rules, [ 'dst', $tgt, $networks, $protocol, $ports ];
|
|
} else {
|
|
warning_message '"dst" entry ignored because there are no "blacklist out" zones';
|
|
}
|
|
}
|
|
} else {
|
|
fatal_error "Invalid blacklist option($_)" unless $_ eq 'whitelist' || $_ eq 'audit';
|
|
}
|
|
}
|
|
}
|
|
|
|
directive_callback(0);
|
|
|
|
if ( @rules ) {
|
|
my $fn1 = find_writable_file( 'blrules' );
|
|
my $blrules;
|
|
my $date = compiletime;
|
|
|
|
if ( -f $fn1 ) {
|
|
open $blrules, '>>', $fn1 or fatal_error "Unable to open $fn1: $!";
|
|
} else {
|
|
open $blrules, '>', $fn1 or fatal_error "Unable to open $fn1: $!";
|
|
transfer_permissions( $fn, $fn1 );
|
|
print $blrules <<'EOF';
|
|
#
|
|
# Shorewall - Blacklist Rules File
|
|
#
|
|
# For information about entries in this file, type "man shorewall-blrules"
|
|
#
|
|
# Please see http://shorewall.net/blacklisting_support.htm for additional
|
|
# information.
|
|
#
|
|
###################################################################################################################################################################################################
|
|
#ACTION SOURCE DEST PROTO DEST SOURCE ORIGINAL RATE USER/ MARK CONNLIMIT TIME HEADERS SWITCH
|
|
# PORT PORT(S) DEST LIMIT GROUP
|
|
EOF
|
|
}
|
|
|
|
print( $blrules
|
|
"#\n" ,
|
|
"# Rules generated from blacklist file $fn by Shorewall $globals{VERSION} - $date\n" ,
|
|
"#\n" );
|
|
|
|
for ( @rules ) {
|
|
my ( $srcdst, $tgt, $networks, $protocols, $ports ) = @$_;
|
|
|
|
$tgt .= "\t\t";
|
|
|
|
my $list = $srcdst eq 'src' ? $zones : $zones1;
|
|
|
|
for my $zone ( @$list ) {
|
|
my $rule = $tgt;
|
|
|
|
if ( $srcdst eq 'src' ) {
|
|
if ( $networks ne '-' ) {
|
|
$rule .= "$zone:$networks\tall\t\t";
|
|
} else {
|
|
$rule .= "$zone\t\t\tall\t\t";
|
|
}
|
|
} else {
|
|
if ( $networks ne '-' ) {
|
|
$rule .= "all\t\t\t$zone:$networks\t";
|
|
} else {
|
|
$rule .= "all\t\t\t$zone\t\t\t";
|
|
}
|
|
}
|
|
|
|
$rule .= "\t$protocols" if $protocols ne '-';
|
|
$rule .= "\t$ports" if $ports ne '-';
|
|
|
|
print $blrules "$rule\n";
|
|
}
|
|
}
|
|
|
|
close $blrules;
|
|
} else {
|
|
warning_message q(There are interfaces or zones with the 'blacklist' option but the 'blacklist' file is empty or does not exist) unless @rules;
|
|
}
|
|
|
|
if ( -f $fn ) {
|
|
rename $fn, "$fn.bak";
|
|
progress_message2 "Blacklist file $fn saved in $fn.bak";
|
|
}
|
|
|
|
for my $file ( qw(zones interfaces hosts) ) {
|
|
remove_blacklist $file;
|
|
}
|
|
|
|
progress_message2 "Blacklist successfully converted";
|
|
|
|
return 1;
|
|
} else {
|
|
my $fn = find_file 'blacklist';
|
|
if ( -f $fn ) {
|
|
rename $fn, "$fn.bak" or fatal_error "Unable to rename $fn to $fn.bak: $!";
|
|
warning_message "No zones have the blacklist option - the blacklist file was saved in $fn.bak";
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
#
|
|
# Convert a routestopped file into an equivalent stoppedrules file
|
|
#
|
|
sub convert_routestopped() {
|
|
|
|
if ( my $fn = open_file 'routestopped' ) {
|
|
my ( @allhosts, %source, %dest , %notrack, @rule );
|
|
|
|
my $seq = 0;
|
|
my $warnings = 0;
|
|
my $date = compiletime;
|
|
|
|
my ( $stoppedrules, $fn1 );
|
|
|
|
if ( -f ( $fn1 = find_writable_file( 'stoppedrules' ) ) ) {
|
|
open $stoppedrules, '>>', $fn1 or fatal_error "Unable to open $fn1: $!";
|
|
} else {
|
|
open $stoppedrules, '>', $fn1 or fatal_error "Unable to open $fn1: $!";
|
|
transfer_permissions( $fn, $fn1 );
|
|
print $stoppedrules <<'EOF';
|
|
#
|
|
# Shorewall - Stopped Rules File
|
|
#
|
|
# For information about entries in this file, type "man shorewall-stoppedrules"
|
|
#
|
|
# The manpage is also online at
|
|
# http://www.shorewall.net/manpages/shorewall-stoppedrules.html
|
|
#
|
|
# See http://shorewall.net/starting_and_stopping_shorewall.htm for additional
|
|
# information.
|
|
#
|
|
###############################################################################
|
|
#ACTION SOURCE DEST PROTO DEST SOURCE
|
|
# PORT(S) PORT(S)
|
|
EOF
|
|
}
|
|
|
|
directive_callback(
|
|
sub ()
|
|
{
|
|
warning_message "Omitted rules and compiler directives were not translated" unless $warnings++;
|
|
}
|
|
);
|
|
|
|
first_entry(
|
|
sub {
|
|
my $date = compiletime;
|
|
progress_message2 "$doing $fn...";
|
|
print( $stoppedrules
|
|
"#\n" ,
|
|
"# Rules generated from routestopped file $fn by Shorewall $globals{VERSION} - $date\n" ,
|
|
"#\n" );
|
|
}
|
|
);
|
|
|
|
while ( read_a_line ( NORMAL_READ ) ) {
|
|
|
|
my ($interface, $hosts, $options , $proto, $ports, $sports ) =
|
|
split_rawline2( 'routestopped file',
|
|
{ interface => 0, hosts => 1, options => 2, proto => 3, dport => 4, sport => 5 },
|
|
{},
|
|
6,
|
|
0,
|
|
);
|
|
|
|
my $interfaceref;
|
|
|
|
fatal_error 'INTERFACE must be specified' if $interface eq '-';
|
|
$hosts = ALLIP unless $hosts && $hosts ne '-';
|
|
|
|
my $routeback = 0;
|
|
|
|
my @hosts;
|
|
|
|
$seq++;
|
|
|
|
my $rule = "$proto\t$ports\t$sports";
|
|
|
|
$hosts = ALLIP if $hosts eq '-';
|
|
|
|
for my $host ( split /,/, $hosts ) {
|
|
push @hosts, "$interface|$host|$seq";
|
|
push @rule, $rule;
|
|
}
|
|
|
|
|
|
unless ( $options eq '-' ) {
|
|
for my $option (split /,/, $options ) {
|
|
if ( $option eq 'routeback' ) {
|
|
if ( $routeback ) {
|
|
warning_message "Duplicate 'routeback' option ignored";
|
|
} else {
|
|
$routeback = 1;
|
|
}
|
|
} elsif ( $option eq 'source' ) {
|
|
for my $host ( split /,/, $hosts ) {
|
|
$source{"$interface|$host|$seq"} = 1;
|
|
}
|
|
} elsif ( $option eq 'dest' ) {
|
|
for my $host ( split /,/, $hosts ) {
|
|
$dest{"$interface|$host|$seq"} = 1;
|
|
}
|
|
} elsif ( $option eq 'notrack' ) {
|
|
for my $host ( split /,/, $hosts ) {
|
|
$notrack{"$interface|$host|$seq"} = 1;
|
|
}
|
|
} else {
|
|
warning_message "Unknown routestopped option ( $option ) ignored" unless $option eq 'critical';
|
|
warning_message "The 'critical' option is no longer supported (or needed)";
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( $routeback || $interfaceref->{options}{routeback} ) {
|
|
my $chainref = $filter_table->{FORWARD};
|
|
|
|
for my $host ( split /,/, $hosts ) {
|
|
print $stoppedrules "ACCEPT\t$interface:$host\t$interface:$host\n";
|
|
}
|
|
}
|
|
|
|
push @allhosts, @hosts;
|
|
}
|
|
|
|
directive_callback(0);
|
|
|
|
for my $host ( @allhosts ) {
|
|
my ( $interface, $h, $seq ) = split /\|/, $host;
|
|
my $rule = shift @rule;
|
|
|
|
print $stoppedrules "ACCEPT\t$interface:$h\t\$FW\t$rule\n";
|
|
print $stoppedrules "ACCEPT\t\$FW\t$interface:$h\t$rule\n" unless $config{ADMINISABSENTMINDED};
|
|
|
|
my $matched = 0;
|
|
|
|
if ( $source{$host} ) {
|
|
print $stoppedrules "ACCEPT\t$interface:$h\t-\t$rule\n";
|
|
$matched = 1;
|
|
}
|
|
|
|
if ( $dest{$host} ) {
|
|
print $stoppedrules "ACCEPT\t-\t$interface:$h\t$rule\n";
|
|
$matched = 1;
|
|
}
|
|
|
|
if ( $notrack{$host} ) {
|
|
print $stoppedrules "NOTRACK\t$interface:$h\t-\t$rule\n";
|
|
print $stoppedrules "NOTRACK\t\$FW\t$interface:$h\t$rule\n";
|
|
}
|
|
|
|
unless ( $matched ) {
|
|
for my $host1 ( @allhosts ) {
|
|
unless ( $host eq $host1 ) {
|
|
my ( $interface1, $h1 , $seq1 ) = split /\|/, $host1;
|
|
print $stoppedrules "ACCEPT\t$interface:$h\t$interface1:$h1\t$rule\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
rename $fn, "$fn.bak";
|
|
progress_message2 "Routestopped file $fn saved in $fn.bak";
|
|
close $stoppedrules;
|
|
} elsif ( -f ( my $fn1 = find_file( 'routestopped' ) ) ) {
|
|
if ( unlink( $fn1 ) ) {
|
|
warning_message "Empty routestopped file ($fn1) removed";
|
|
} else {
|
|
warning_message "Unable to remove empty routestopped file $fn1: $!";
|
|
}
|
|
}
|
|
}
|
|
|
|
#
|
|
# Process the stoppedrules file. Returns true if the file was non-empty.
|
|
#
|
|
sub process_stoppedrules() {
|
|
my $fw = firewall_zone;
|
|
my $result;
|
|
|
|
if ( my $fn = open_file 'stoppedrules' , 1, 1 ) {
|
|
first_entry sub () {
|
|
progress_message2( "$doing $fn..." );
|
|
unless ( $config{ADMINISABSENTMINDED} ) {
|
|
insert_ijump $filter_table ->{$_}, j => 'ACCEPT', 0, state_imatch 'ESTABLISHED,RELATED' for qw/INPUT FORWARD/;
|
|
}
|
|
};
|
|
|
|
while ( read_a_line( NORMAL_READ ) ) {
|
|
|
|
$result = 1;
|
|
|
|
my ( $target, $source, $dest, $protos, $ports, $sports ) =
|
|
split_line1( 'stoppedrules file',
|
|
{ target => 0, source => 1, dest => 2, proto => 3, dport => 4, sport => 5 } );
|
|
|
|
fatal_error( "Invalid TARGET ($target)" ) unless $target =~ /^(?:ACCEPT|DROP|NOTRACK)$/;
|
|
|
|
my $tableref;
|
|
my $raw;
|
|
my $chainref;
|
|
my $restriction = NO_RESTRICT;
|
|
|
|
if ( $raw = ( $target eq 'NOTRACK' || $target eq 'DROP' ) ) {
|
|
$tableref = $raw_table;
|
|
require_capability 'RAW_TABLE', 'NOTRACK', 's';
|
|
$chainref = $raw_table->{PREROUTING};
|
|
$restriction = PREROUTE_RESTRICT | DESTIFACE_DISALLOW;
|
|
} else {
|
|
$tableref = $filter_table;
|
|
}
|
|
|
|
if ( $source eq $fw ) {
|
|
$chainref = ( $raw ? $raw_table : $filter_table)->{OUTPUT};
|
|
$source = '';
|
|
$restriction = OUTPUT_RESTRICT;
|
|
} elsif ( $source =~ s/^($fw):// ) {
|
|
$chainref = ( $raw ? $raw_table : $filter_table)->{OUTPUT};
|
|
$restriction = OUTPUT_RESTRICT;
|
|
}
|
|
|
|
if ( $dest eq $fw ) {
|
|
fatal_error "\$FW may not be specified as the destination of a NOTRACK or DROP rule" if $raw;
|
|
$chainref = $filter_table->{INPUT};
|
|
$dest = '';
|
|
$restriction = INPUT_RESTRICT;
|
|
} elsif ( $dest =~ s/^($fw):// ) {
|
|
fatal_error "\$FW may not be specified as the destination of a NOTRACK or DROP rule" if $raw;
|
|
$chainref = $filter_table->{INPUT};
|
|
$restriction = INPUT_RESTRICT;
|
|
}
|
|
|
|
$chainref = $tableref->{FORWARD} unless $chainref;
|
|
|
|
my $disposition = $target;
|
|
|
|
$target = 'CT --notrack' if $target eq 'NOTRACK' and have_capability( 'CT_TARGET' );
|
|
|
|
unless ( $restriction == OUTPUT_RESTRICT
|
|
&& $target eq 'ACCEPT'
|
|
&& $config{ADMINISABSENTMINDED} ) {
|
|
for my $proto ( split_list $protos, 'Protocol' ) {
|
|
expand_rule( $chainref ,
|
|
$restriction ,
|
|
'' ,
|
|
do_proto( $proto, $ports, $sports ) ,
|
|
$source ,
|
|
$dest ,
|
|
'' ,
|
|
$target,
|
|
'',
|
|
$disposition,
|
|
do_proto( $proto, '-', '-' ),
|
|
'');
|
|
}
|
|
} else {
|
|
warning_message "Redundant OUTPUT rule ignored because ADMINISABSENTMINDED=Yes";
|
|
}
|
|
}
|
|
}
|
|
|
|
$result;
|
|
}
|
|
|
|
#
|
|
# Generate the rules required when DOCKER=Yes
|
|
#
|
|
sub create_docker_rules() {
|
|
add_commands( $nat_table->{PREROUTING} , '[ -n "$g_docker" ] && echo "-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER" >&3' );
|
|
|
|
my $chainref = $filter_table->{FORWARD};
|
|
|
|
add_commands( $chainref, '[ -n "$g_dockeringress" ] && echo "-A FORWARD -j DOCKER-INGRESS" >&3' );
|
|
add_commands( $chainref, '[ -n "$g_dockeruser" ] && echo "-A FORWARD -j DOCKER-USER" >&3' );
|
|
add_commands( $chainref, '[ -n "$g_dockeriso" ] && echo "-A FORWARD -j DOCKER-ISOLATION" >&3' );
|
|
add_commands( $chainref, '[ -n "$g_dockerisostage" ] && echo "-A FORWARD -j DOCKER-ISOLATION-STAGE-1" >&3' );
|
|
|
|
if ( my $dockerref = known_interface('docker0') ) {
|
|
add_commands( $chainref, 'if [ -n "$g_docker" ]; then' );
|
|
incr_cmd_level( $chainref );
|
|
add_ijump( $chainref, j => 'DOCKER', o => 'docker0' );
|
|
add_ijump( $chainref, j => 'ACCEPT', o => 'docker0', state_imatch 'ESTABLISHED,RELATED' );
|
|
add_ijump( $chainref, j => 'ACCEPT', i => 'docker0', o => '! docker0' );
|
|
add_ijump( $chainref, j => 'ACCEPT', i => 'docker0', o => 'docker0' ) if $dockerref->{options}{routeback};
|
|
decr_cmd_level( $chainref );
|
|
add_commands( $chainref, 'fi' );
|
|
|
|
my $outputref;
|
|
add_commands( $outputref = $filter_table->{OUTPUT}, 'if [ -n "$g_docker" ]; then' );
|
|
incr_cmd_level( $outputref );
|
|
add_ijump( $outputref, j => 'DOCKER' );
|
|
decr_cmd_level( $outputref );
|
|
add_commands( $outputref, 'fi' );
|
|
}
|
|
|
|
add_commands( $chainref, '[ -f ${VARDIR}/.filter_FORWARD ] && cat $VARDIR/.filter_FORWARD >&3', );
|
|
}
|
|
|
|
sub setup_mss();
|
|
|
|
#
|
|
# Add rules generated by .conf options and interface options
|
|
#
|
|
sub add_common_rules ( $ ) {
|
|
my ( $upgrade ) = @_;
|
|
my $interface;
|
|
my $chainref;
|
|
my $target;
|
|
my $target1;
|
|
my $rule;
|
|
my $list;
|
|
my $chain;
|
|
my $dynamicref;
|
|
|
|
my @state = state_imatch( $globals{BLACKLIST_STATES} );
|
|
my $faststate = $config{RELATED_DISPOSITION} eq 'ACCEPT' && $config{RELATED_LOG_LEVEL} eq '' ? 'ESTABLISHED,RELATED' : 'ESTABLISHED';
|
|
my $level = $config{BLACKLIST_LOG_LEVEL};
|
|
my $tag = $globals{BLACKLIST_LOG_TAG};
|
|
my $rejectref = $filter_table->{reject};
|
|
my $dbl_type;
|
|
my $dbl_ipset;
|
|
my $dbl_level;
|
|
my $dbl_tag;
|
|
my $dbl_src_target;
|
|
my $dbl_dst_target;
|
|
|
|
if ( $config{REJECT_ACTION} ) {
|
|
process_reject_action;
|
|
fatal_error( "The REJECT_ACTION ($config{REJECT_ACTION}) is not terminating" ) unless terminating( $rejectref );
|
|
} else {
|
|
if ( have_capability( 'ADDRTYPE' ) ) {
|
|
add_ijump $rejectref , j => 'DROP' , addrtype => '--src-type BROADCAST';
|
|
} else {
|
|
if ( $family == F_IPV4 ) {
|
|
add_commands $rejectref, 'for address in $ALL_BCASTS; do';
|
|
} else {
|
|
add_commands $rejectref, 'for address in $ALL_ACASTS; do';
|
|
}
|
|
|
|
incr_cmd_level $rejectref;
|
|
add_ijump $rejectref, j => 'DROP', d => '$address';
|
|
decr_cmd_level $rejectref;
|
|
add_commands $rejectref, 'done';
|
|
}
|
|
|
|
if ( $family == F_IPV4 ) {
|
|
add_ijump $rejectref , j => 'DROP', s => '224.0.0.0/4';
|
|
} else {
|
|
add_ijump $rejectref , j => 'DROP', s => IPv6_MULTICAST;
|
|
}
|
|
|
|
add_ijump $rejectref , j => 'DROP', p => 2;
|
|
add_ijump $rejectref , j => 'REJECT', targetopts => '--reject-with tcp-reset', p => 6;
|
|
|
|
if ( have_capability( 'ENHANCED_REJECT' ) ) {
|
|
add_ijump $rejectref , j => 'REJECT', p => 17;
|
|
|
|
if ( $family == F_IPV4 ) {
|
|
add_ijump $rejectref, j => 'REJECT --reject-with icmp-host-unreachable', p => 1;
|
|
add_ijump $rejectref, j => 'REJECT --reject-with icmp-host-prohibited';
|
|
} else {
|
|
add_ijump $rejectref, j => 'REJECT --reject-with icmp6-addr-unreachable', p => 58;
|
|
add_ijump $rejectref, j => 'REJECT --reject-with icmp6-adm-prohibited';
|
|
}
|
|
} else {
|
|
add_ijump $rejectref , j => 'REJECT';
|
|
}
|
|
}
|
|
|
|
#
|
|
# Insure that Docker jumps are early in the builtin chains
|
|
#
|
|
create_docker_rules if $config{DOCKER};
|
|
|
|
if ( my $val = $config{DYNAMIC_BLACKLIST} ) {
|
|
( $dbl_type, $dbl_ipset, $dbl_level, $dbl_tag ) = split( ':', $val );
|
|
|
|
unless ( $dbl_type =~ /^ipset-only/ ) {
|
|
add_rule_pair( set_optflags( new_standard_chain( 'logdrop' ) , DONT_OPTIMIZE | DONT_DELETE ), '' , 'DROP' , $level , $tag);
|
|
add_rule_pair( set_optflags( new_standard_chain( 'logreject' ), DONT_OPTIMIZE | DONT_DELETE ), '' , 'reject' , $level , $tag);
|
|
$dynamicref = set_optflags( new_standard_chain( 'dynamic' ) , DONT_OPTIMIZE );
|
|
add_commands( $dynamicref, '[ -f ${VARDIR}/.dynamic ] && cat ${VARDIR}/.dynamic >&3' );
|
|
}
|
|
|
|
if ( $dbl_ipset ) {
|
|
if ( $val = $globals{DBL_TIMEOUT} ) {
|
|
$dbl_src_target = $globals{DBL_OPTIONS} =~ /src-dst/ ? 'dbl_src' : 'dbl_log';
|
|
|
|
my $chainref = set_optflags( new_standard_chain( $dbl_src_target ) , DONT_OPTIMIZE | DONT_DELETE );
|
|
|
|
log_rule_limit( $dbl_level,
|
|
$chainref,
|
|
'dbl_log',
|
|
'DROP',
|
|
$globals{LOGLIMIT},
|
|
$dbl_tag,
|
|
'add',
|
|
'',
|
|
$origin{DYNAMIC_BLACKLIST} ) if $dbl_level;
|
|
add_ijump_extended( $chainref, j => "SET --add-set $dbl_ipset src --exist --timeout $val", $origin{DYNAMIC_BLACKLIST} );
|
|
add_ijump_extended( $chainref, j => 'DROP', $origin{DYNAMIC_BLACKLIST} );
|
|
|
|
if ( $dbl_src_target eq 'dbl_src' ) {
|
|
$chainref = set_optflags( new_standard_chain( $dbl_dst_target = 'dbl_dst' ) , DONT_OPTIMIZE | DONT_DELETE );
|
|
|
|
log_rule_limit( $dbl_level,
|
|
$chainref,
|
|
'dbl_log',
|
|
'DROP',
|
|
$globals{LOGLIMIT},
|
|
$dbl_tag,
|
|
'add',
|
|
'',
|
|
$origin{DYNAMIC_BLACKLIST} ) if $dbl_level;
|
|
add_ijump_extended( $chainref, j => "SET --add-set $dbl_ipset dst --exist --timeout $val", $origin{DYNAMIC_BLACKLIST} );
|
|
add_ijump_extended( $chainref, j => 'DROP', $origin{DYNAMIC_BLACKLIST} );
|
|
} else {
|
|
$dbl_dst_target = $dbl_src_target;
|
|
}
|
|
} elsif ( $dbl_level ) {
|
|
my $chainref = set_optflags( new_standard_chain( $dbl_src_target = $dbl_dst_target = 'dbl_log' ) , DONT_OPTIMIZE | DONT_DELETE );
|
|
|
|
log_rule_limit( $dbl_level,
|
|
$chainref,
|
|
'dbl_log',
|
|
'DROP',
|
|
$globals{LOGLIMIT},
|
|
$dbl_tag,
|
|
'add',
|
|
'',
|
|
$origin{DYNAMIC_BLACKLIST} );
|
|
add_ijump_extended( $chainref, j => 'DROP', $origin{DYNAMIC_BLACKLIST} );
|
|
} else {
|
|
$dbl_src_target = $dbl_dst_target = 'DROP';
|
|
}
|
|
}
|
|
}
|
|
|
|
setup_mss;
|
|
|
|
if ( $config{FASTACCEPT} ) {
|
|
add_ijump_extended( $filter_table->{OUTPUT} , j => 'ACCEPT', $origin{FASTACCEPT}, state_imatch $faststate )
|
|
}
|
|
|
|
my $policy = $config{SFILTER_DISPOSITION};
|
|
$level = $config{SFILTER_LOG_LEVEL};
|
|
$tag = $config{SFILTER_LOG_TAG};
|
|
my $audit = $policy =~ s/^A_//;
|
|
my @ipsec = have_ipsec ? ( policy => '--pol none --dir in' ) : ();
|
|
my $origin = $origin{SFILTER_DISPOSITION};
|
|
|
|
if ( $level || $audit ) {
|
|
#
|
|
# Create a chain to log and/or audit and apply the policy
|
|
#
|
|
$chainref = new_standard_chain 'sfilter';
|
|
|
|
log_rule_limit( $level,
|
|
$chainref,
|
|
$chainref->{name},
|
|
$policy,
|
|
$globals{LOGLIMIT},
|
|
$tag,
|
|
'add',
|
|
'',
|
|
$origin{SFILTER_LOG_LEVEL} ) if $level ne '';
|
|
|
|
add_ijump_extended( $chainref, j => 'AUDIT', $origin, targetopts => '--type ' . lc $policy ) if $audit;
|
|
|
|
add_ijump_extended( $chainref, g => $policy eq 'REJECT' ? 'reject' : $policy, $origin );
|
|
|
|
$target = 'sfilter';
|
|
} else {
|
|
$target = $policy eq 'REJECT' ? 'reject' : $policy;
|
|
}
|
|
|
|
if ( @ipsec ) {
|
|
#
|
|
# sfilter1 will be used in the FORWARD chain where we allow traffic entering the interface
|
|
# to leave the interface encrypted. We need a separate chain because '--dir out' cannot be
|
|
# used in the input chain
|
|
#
|
|
$chainref = new_standard_chain 'sfilter1';
|
|
|
|
add_ijump ( $chainref, j => 'RETURN', policy => '--pol ipsec --dir out' );
|
|
|
|
log_rule_limit( $level,
|
|
$chainref,
|
|
$chainref->{name},
|
|
$policy,
|
|
$globals{LOGLIMIT},
|
|
$tag,
|
|
'add',
|
|
'' ,
|
|
$origin ) if $level ne '';
|
|
|
|
add_ijump_extended( $chainref, j => 'AUDIT', $origin{SFILTER_DISPOSITION}, targetopts => '--type ' . lc $policy ) if $audit;
|
|
|
|
add_ijump_extended( $chainref, g => $policy eq 'REJECT' ? 'reject' : $policy, $origin );
|
|
|
|
$target1 = 'sfilter1';
|
|
} else {
|
|
#
|
|
# No IPSEC -- use the same target in both INPUT and FORWARD
|
|
#
|
|
$target1 = $target;
|
|
}
|
|
|
|
for $interface ( all_real_interfaces ) {
|
|
ensure_chain( 'filter', $_ )->{origin} = interface_origin( $interface )
|
|
for first_chains( $interface ), output_chain( $interface ), option_chains( $interface ), output_option_chain( $interface );
|
|
|
|
my $interfaceref = find_interface $interface;
|
|
|
|
unless ( $interfaceref->{physical} eq loopback_interface ) {
|
|
unless ( $interfaceref->{options}{ignore} & NO_SFILTER || $interfaceref->{options}{rpfilter} ) {
|
|
|
|
my @filters = @{$interfaceref->{filter}};
|
|
my $origin = $interfaceref->{origin};
|
|
|
|
$chainref = $filter_table->{forward_option_chain $interface};
|
|
|
|
if ( @filters ) {
|
|
add_ijump_extended( $chainref , @ipsec ? 'j' : 'g' => $target1, $origin, imatch_source_net( $_ ), @ipsec ), $chainref->{filtered}++ for @filters;
|
|
} elsif ( $interfaceref->{bridge} eq $interface ) {
|
|
add_ijump_extended( $chainref , @ipsec ? 'j' : 'g' => $target1, $origin, imatch_dest_dev( $interface ), @ipsec ), $chainref->{filtered}++
|
|
unless( $config{ROUTE_FILTER} eq 'on' ||
|
|
$interfaceref->{options}{routeback} ||
|
|
$interfaceref->{options}{routefilter} ||
|
|
$interfaceref->{physical} eq '+' );
|
|
}
|
|
|
|
|
|
if ( @filters ) {
|
|
$chainref = $filter_table->{input_option_chain $interface};
|
|
add_ijump_extended( $chainref , g => $target, $origin, imatch_source_net( $_ ), @ipsec ), $chainref->{filtered}++ for @filters;
|
|
}
|
|
}
|
|
|
|
if ( $dbl_ipset && ( ( my $setting = get_interface_option( $interface, 'dbl' ) ) ne '0:0' ) ) {
|
|
|
|
my ( $in, $out ) = split /:/, $setting;
|
|
|
|
if ( $in == 1 ) {
|
|
#
|
|
# src
|
|
#
|
|
add_ijump_extended( $filter_table->{input_option_chain($interface)}, j => $dbl_src_target, $origin{DYNAMIC_BLACKLIST}, @state, set => "--match-set $dbl_ipset src" );
|
|
add_ijump_extended( $filter_table->{forward_option_chain($interface)}, j => $dbl_src_target, $origin{DYNAMIC_BLACKLIST}, @state, set => "--match-set $dbl_ipset src" );
|
|
} elsif ( $in == 2 ) {
|
|
add_ijump_extended( $filter_table->{forward_option_chain($interface)}, j => $dbl_dst_target, $origin{DYNAMIC_BLACKLIST}, @state, set => "--match-set $dbl_ipset dst" );
|
|
}
|
|
|
|
if ( $out == 2 ) {
|
|
#
|
|
# dst
|
|
#
|
|
add_ijump_extended( $filter_table->{output_option_chain($interface)}, j => $dbl_dst_target, $origin{DYNAMIC_BLACKLIST}, @state, set => "--match-set $dbl_ipset dst" );
|
|
}
|
|
}
|
|
|
|
for ( option_chains( $interface ) ) {
|
|
add_ijump_extended( $filter_table->{$_}, j => $dynamicref, $origin{DYNAMIC_BLACKLIST}, @state ) if $dynamicref && ( get_interface_option( $interface, 'dbl' ) ne '0:0' );
|
|
add_ijump_extended( $filter_table->{$_}, j => 'ACCEPT', $origin{FASTACCEPT}, state_imatch $faststate )->{comment} = '' if $config{FASTACCEPT};
|
|
}
|
|
}
|
|
}
|
|
|
|
#
|
|
# Delete 'sfilter' chains unless there are referenced to them
|
|
#
|
|
for ( qw/sfilter sfilter1/ ) {
|
|
if ( $chainref = $filter_table->{$_} ) {
|
|
$chainref->{referenced} = 0 unless keys %{$chainref->{references}};
|
|
}
|
|
}
|
|
|
|
$list = find_interfaces_by_option('rpfilter');
|
|
|
|
if ( @$list ) {
|
|
$policy = $config{RPFILTER_DISPOSITION};
|
|
$level = $config{RPFILTER_LOG_LEVEL};
|
|
$tag = $globals{RPFILTER_LOG_TAG};
|
|
$audit = $policy =~ s/^A_//;
|
|
my $origin
|
|
= $origin{RPFILTER_DISPOSITION};
|
|
|
|
if ( $level || $audit ) {
|
|
#
|
|
# Create a chain to log and/or audit and apply the policy
|
|
#
|
|
$chainref = ensure_mangle_chain 'rplog';
|
|
|
|
log_rule_limit( $level,
|
|
$chainref,
|
|
$chainref->{name},
|
|
$policy,
|
|
$globals{LOGLIMIT},
|
|
$tag,
|
|
'add',
|
|
'',
|
|
$origin{RPFILTER_LOG_LEVEL} );
|
|
|
|
add_ijump_extended( $chainref, j => 'AUDIT', $origin, targetopts => '--type ' . lc $policy ) if $audit;
|
|
|
|
add_ijump_extended( $chainref, g => $policy eq 'REJECT' ? 'reject' : $policy, $origin );
|
|
|
|
$target = 'rplog';
|
|
} else {
|
|
$target = $policy eq 'REJECT' ? 'reject' : $policy;
|
|
}
|
|
|
|
my $rpfilterref = ensure_mangle_chain( 'rpfilter' );
|
|
|
|
if ( $family == F_IPV4 ) {
|
|
for $interface ( @$list ) {
|
|
if ( get_interface_option( $interface, 'dhcp' ) ) {
|
|
add_ijump_extended( $rpfilterref,
|
|
j => 'RETURN',
|
|
get_interface_origin( $interface ),
|
|
s => NILIPv4,
|
|
p => UDP,
|
|
dport => 67,
|
|
sport => 68
|
|
);
|
|
last;
|
|
}
|
|
}
|
|
}
|
|
|
|
add_ijump_extended( $rpfilterref,
|
|
j => $target,
|
|
$origin,
|
|
rpfilter => '--validmark --invert',
|
|
state_imatch 'NEW,RELATED,INVALID',
|
|
@ipsec
|
|
);
|
|
}
|
|
|
|
run_user_exit 'initdone';
|
|
|
|
if ( $upgrade ) {
|
|
convert_blacklist;
|
|
} elsif ( -f ( my $fn = find_file 'blacklist' ) ) {
|
|
warning_message "The blacklist file is no longer supported -- use '$product update' to convert $fn to the equivalent blrules file";
|
|
}
|
|
|
|
$list = find_hosts_by_option 'nosmurfs';
|
|
|
|
if ( @$list ) {
|
|
progress_message2 'Adding Anti-smurf Rules';
|
|
|
|
$chainref = new_standard_chain 'smurfs';
|
|
|
|
my $smurfdest = $config{SMURF_DISPOSITION};
|
|
my $origin = $origin{SMURF_DISPOSITION};
|
|
|
|
if ( supplied $config{SMURF_LOG_LEVEL} ) {
|
|
my $smurfref = new_chain( 'filter', 'smurflog' );
|
|
|
|
log_irule_limit( $config{SMURF_LOG_LEVEL},
|
|
$smurfref,
|
|
'smurfs' ,
|
|
'DROP',
|
|
$globals{LOGILIMIT},
|
|
$globals{SMURF_LOG_TAG},
|
|
'add',
|
|
$origin{SMURF_LOG_LEVEL} );
|
|
|
|
add_ijump_extended( $smurfref, j => 'AUDIT', $origin, targetopts => '--type drop' ) if $smurfdest eq 'A_DROP';
|
|
|
|
add_ijump_extended( $smurfref, j => 'DROP' , $origin );
|
|
|
|
$smurfdest = 'smurflog';
|
|
} else {
|
|
verify_audit( $smurfdest ) if $smurfdest eq 'A_DROP';
|
|
}
|
|
|
|
if ( have_capability( 'ADDRTYPE' ) ) {
|
|
if ( $family == F_IPV4 ) {
|
|
add_ijump $chainref , j => 'RETURN', s => '0.0.0.0'; ;
|
|
} else {
|
|
add_ijump $chainref , j => 'RETURN', s => '::';
|
|
}
|
|
|
|
add_ijump_extended( $chainref, g => $smurfdest, $origin, addrtype => '--src-type BROADCAST' ) ;
|
|
} else {
|
|
if ( $family == F_IPV4 ) {
|
|
add_commands $chainref, 'for address in $ALL_BCASTS; do';
|
|
} else {
|
|
add_commands $chainref, 'for address in $ALL_ACASTS; do';
|
|
}
|
|
|
|
incr_cmd_level $chainref;
|
|
add_ijump_extended( $chainref, g => $smurfdest, $origin, s => '$address' );
|
|
decr_cmd_level $chainref;
|
|
add_commands $chainref, 'done';
|
|
}
|
|
|
|
if ( $family == F_IPV4 ) {
|
|
add_ijump_extended( $chainref, g => $smurfdest, $origin, s => '224.0.0.0/4' );
|
|
} else {
|
|
add_ijump_extended( $chainref, g => $smurfdest, $origin, s => IPv6_MULTICAST );
|
|
}
|
|
|
|
my @state = have_capability( 'RAW_TABLE' ) ? state_imatch 'NEW,INVALID,UNTRACKED' : state_imatch 'NEW,INVALID';
|
|
|
|
for my $hostref ( @$list ) {
|
|
$interface = $hostref->[0];
|
|
my $ipsec = $hostref->[1];
|
|
my $net = $hostref->[2];
|
|
my @policy = $ipsec && have_ipsec ? ( policy => "--pol $ipsec --dir in" ) : ();
|
|
my $target = source_exclusion( $hostref->[3], $chainref );
|
|
my $origin = $hostref->[5];
|
|
|
|
for $chain ( option_chains $interface ) {
|
|
add_ijump_extended( $filter_table->{$chain} , j => $target, $origin, @state, imatch_source_net( $net ), @policy );
|
|
}
|
|
}
|
|
}
|
|
|
|
$list = find_interfaces_by_option 'dhcp';
|
|
|
|
if ( @$list ) {
|
|
progress_message2 'Adding rules for DHCP';
|
|
|
|
my $ports = $family == F_IPV4 ? '67:68' : '546:547';
|
|
|
|
for $interface ( @$list ) {
|
|
my $origin = get_interface_origin($interface);
|
|
set_rule_option( add_ijump_extended( $filter_table->{$_} ,
|
|
j => 'ACCEPT',
|
|
$origin,
|
|
p => "udp --dport $ports" ) ,
|
|
'dhcp',
|
|
1 ) for input_option_chain( $interface ), output_option_chain( $interface );
|
|
|
|
add_ijump_extended( $filter_table->{forward_option_chain $interface} ,
|
|
j => 'ACCEPT',
|
|
$origin,
|
|
p => "udp --dport $ports" ,
|
|
imatch_dest_dev( $interface ) )
|
|
if get_interface_option( $interface, 'bridge' );
|
|
|
|
unless ( $family == F_IPV6 || get_interface_option( $interface, 'allip' ) ) {
|
|
add_ijump_extended( $filter_table->{input_chain( $interface ) } ,
|
|
j => 'ACCEPT' ,
|
|
$origin ,
|
|
p => "udp --dport $ports" ,
|
|
s => NILIPv4 . '/' . VLSMv4 );
|
|
}
|
|
}
|
|
}
|
|
|
|
$list = find_hosts_by_option 'tcpflags';
|
|
|
|
if ( @$list ) {
|
|
my $level = $config{TCP_FLAGS_LOG_LEVEL};
|
|
my $tag = $globals{TCP_FLAGS_LOG_TAG};
|
|
my $disposition = $config{TCP_FLAGS_DISPOSITION};
|
|
my $audit = $disposition =~ /^A_/;
|
|
my $origin = $origin{TCP_FLAGS_DISPOSITION};
|
|
|
|
progress_message2 "$doing TCP Flags filtering...";
|
|
|
|
$chainref = new_standard_chain 'tcpflags';
|
|
|
|
if ( $level ) {
|
|
my $logflagsref = new_standard_chain 'logflags';
|
|
|
|
my $savelogparms = $globals{LOGPARMS};
|
|
|
|
$globals{LOGPARMS} = "$globals{LOGPARMS}--log-ip-options ";
|
|
|
|
log_rule_limit( $level,
|
|
$logflagsref,
|
|
'logflags',
|
|
$disposition,
|
|
$globals{LOGLIMIT},
|
|
$tag,
|
|
'add',
|
|
'' ,
|
|
$origin{TCP_FLAGS_LOG_LEVEL} );
|
|
|
|
$globals{LOGPARMS} = $savelogparms;
|
|
|
|
if ( $audit ) {
|
|
$disposition =~ s/^A_//;
|
|
add_ijump_extended( $logflagsref, j => 'AUDIT', $origin, targetopts => '--type ' . lc $disposition );
|
|
}
|
|
|
|
if ( $disposition eq 'REJECT' ) {
|
|
add_ijump_extended $logflagsref , j => 'REJECT', $origin, targetopts => '--reject-with tcp-reset', p => 6;
|
|
} else {
|
|
add_ijump_extended $logflagsref , j => $disposition, $origin;
|
|
}
|
|
|
|
$disposition = 'logflags';
|
|
} elsif ( $audit ) {
|
|
require_capability( 'AUDIT_TARGET', "TCP_FLAGS_DISPOSITION=$disposition", 's' );
|
|
verify_audit( $disposition );
|
|
}
|
|
|
|
add_ijump $chainref , g => $disposition, p => 'tcp --tcp-flags ALL FIN,URG,PSH';
|
|
add_ijump $chainref , g => $disposition, p => 'tcp --tcp-flags ALL NONE';
|
|
add_ijump $chainref , g => $disposition, p => 'tcp --tcp-flags SYN,RST SYN,RST';
|
|
add_ijump $chainref , g => $disposition, p => 'tcp --tcp-flags FIN,RST FIN,RST';
|
|
add_ijump $chainref , g => $disposition, p => 'tcp --tcp-flags SYN,FIN SYN,FIN';
|
|
add_ijump $chainref , g => $disposition, p => 'tcp --tcp-flags ACK,PSH,FIN PSH,FIN';
|
|
add_ijump $chainref , g => $disposition, p => 'tcp --syn --sport 0';
|
|
|
|
for my $hostref ( @$list ) {
|
|
my $interface = $hostref->[0];
|
|
my $target = source_exclusion( $hostref->[3], $chainref );
|
|
my $ipsec = $hostref->[1];
|
|
my @policy = $ipsec && have_ipsec ? ( policy => "--pol $ipsec --dir in" ) : ();
|
|
my $origin = $hostref->[5];
|
|
|
|
for $chain ( option_chains $interface ) {
|
|
add_ijump_extended( $filter_table->{$chain} , j => $target, $origin, p => 'tcp', imatch_source_net( $hostref->[2] ), @policy );
|
|
}
|
|
}
|
|
}
|
|
|
|
my $announced = 0;
|
|
|
|
$list = find_interfaces_by_option 'upnp';
|
|
|
|
if ( @$list ) {
|
|
progress_message2 "$doing UPnP";
|
|
|
|
$chainref = set_optflags( new_nat_chain( 'UPnP' ), DONT_OPTIMIZE );
|
|
|
|
add_commands( $chainref, '[ -s /${VARDIR}/.UPnP ] && cat ${VARDIR}/.UPnP >&3' );
|
|
|
|
my $chainref1;
|
|
|
|
if ( $config{MINIUPNPD} ) {
|
|
$chainref1 = set_optflags( new_nat_chain( 'MINIUPNPD-POSTROUTING' ), DONT_OPTIMIZE );
|
|
add_commands( $chainref, '[ -s /${VARDIR}/.MINIUPNPD-POSTROUTING ] && cat ${VARDIR}/.MINIUPNPD-POSTROUTING >&3' );
|
|
}
|
|
|
|
$announced = 1;
|
|
|
|
for $interface ( @$list ) {
|
|
add_ijump_extended $nat_table->{PREROUTING} , j => 'UPnP', get_interface_origin($interface), imatch_source_dev ( $interface );
|
|
add_ijump_extended $nat_table->{$globals{POSTROUTING}} , j => 'MINIUPNPD-POSTROUTING' , $origin{MINIUPNPD} , imatch_dest_dev ( $interface ) if $chainref1;
|
|
}
|
|
}
|
|
|
|
$list = find_interfaces_by_option 'upnpclient';
|
|
|
|
if ( @$list ) {
|
|
progress_message2 "$doing UPnP" unless $announced;
|
|
|
|
for $interface ( @$list ) {
|
|
my $chainref = $filter_table->{input_option_chain $interface};
|
|
my $base = uc var_base get_physical $interface;
|
|
my $optional = interface_is_optional( $interface );
|
|
my $variable = get_interface_gateway( $interface, ! $optional );
|
|
my $origin = get_interface_origin( $interface );
|
|
|
|
if ( $optional ) {
|
|
add_commands( $chainref,
|
|
qq(if [ -n "SW_\$${base}_IS_USABLE" -a -n "$variable" ]; then) );
|
|
incr_cmd_level( $chainref );
|
|
add_ijump_extended( $chainref, j => 'ACCEPT', $origin, imatch_source_dev( $interface ), s => $variable, p => 'udp' );
|
|
decr_cmd_level( $chainref );
|
|
add_commands( $chainref, 'fi' );
|
|
} else {
|
|
add_ijump_extended( $chainref, j => 'ACCEPT', $origin, imatch_source_dev( $interface ), s => $variable, p => 'udp' );
|
|
}
|
|
}
|
|
}
|
|
|
|
setup_syn_flood_chains;
|
|
|
|
}
|
|
|
|
my %maclist_targets = ( ACCEPT => { target => 'RETURN' , mangle => 1 } ,
|
|
REJECT => { target => 'reject' , mangle => 0 } ,
|
|
DROP => { target => 'DROP' , mangle => 1 } );
|
|
|
|
#
|
|
# Create rules generated by the 'maclist' option and by entries in the maclist file.
|
|
#
|
|
# The function is called twice. The first call passes '1' and causes the maclist file
|
|
# to be processed. The second call passes '2' and generates the jumps for 'maclist'
|
|
# interfaces.
|
|
#
|
|
sub setup_mac_lists( $ ) {
|
|
|
|
my $phase = $_[0];
|
|
|
|
my %maclist_interfaces;
|
|
|
|
my $table = $config{MACLIST_TABLE};
|
|
|
|
my $maclist_hosts = find_hosts_by_option 'maclist';
|
|
|
|
my $target = $globals{MACLIST_TARGET};
|
|
my $level = $config{MACLIST_LOG_LEVEL};
|
|
my $tag = $globals{MACLIST_LOG_TAG};
|
|
my $disposition = $config{MACLIST_DISPOSITION};
|
|
my $audit = ( $disposition =~ s/^A_// );
|
|
my $ttl = $config{MACLIST_TTL};
|
|
|
|
progress_message2 "$doing MAC Filtration -- Phase $phase...";
|
|
|
|
for my $hostref ( @$maclist_hosts ) {
|
|
$maclist_interfaces{ $hostref->[0] } = 1;
|
|
}
|
|
|
|
my @maclist_interfaces = ( keys %maclist_interfaces );
|
|
|
|
if ( $phase == 1 ) {
|
|
|
|
for my $interface ( @maclist_interfaces ) {
|
|
my $chainref = new_chain $table , mac_chain $interface;
|
|
|
|
if ( $family == F_IPV4 ) {
|
|
add_ijump $chainref , j => 'RETURN', s => '0.0.0.0', d => '255.255.255.255', p => 'udp --dport 67:68'
|
|
if $table eq 'mangle' && get_interface_option( $interface, 'dhcp');
|
|
} else {
|
|
#
|
|
# Accept any packet with a link-level source or destination address
|
|
#
|
|
add_ijump $chainref , j => 'RETURN', s => 'ff80::/10';
|
|
add_ijump $chainref , j => 'RETURN', d => 'ff80::/10';
|
|
#
|
|
# Accept Multicast
|
|
#
|
|
add_ijump $chainref , j => 'RETURN', d => IPv6_MULTICAST;
|
|
}
|
|
|
|
if ( $ttl ) {
|
|
my $chain1ref = new_chain $table, macrecent_target $interface;
|
|
|
|
my $chain = $chainref->{name};
|
|
|
|
add_ijump $chainref, j => 'RETURN', recent => "--rcheck --seconds $ttl --name $chain";
|
|
add_ijump $chainref, j => $chain1ref;
|
|
add_ijump $chainref, j => 'RETURN', recent => "--update --name $chain";
|
|
add_irule $chainref, recent => "--set --name $chain";
|
|
}
|
|
}
|
|
|
|
if ( my $fn = open_file 'maclist', 1, 1 ) {
|
|
|
|
first_entry "$doing $fn...";
|
|
|
|
while ( read_a_line( NORMAL_READ ) ) {
|
|
|
|
my ( $original_disposition, $interface, $mac, $addresses ) =
|
|
split_line1( 'maclist file',
|
|
{ disposition => 0, interface => 1, mac => 2, addresses => 3 } );
|
|
|
|
my ( $disposition, $level, $remainder) = split( /:/, $original_disposition, 3 );
|
|
|
|
fatal_error "Invalid DISPOSITION ($original_disposition)" if defined $remainder || ! $disposition;
|
|
|
|
my $targetref = $maclist_targets{$disposition};
|
|
|
|
fatal_error "Invalid DISPOSITION ($original_disposition)" if ! $targetref || ( ( $table eq 'mangle' ) && ! $targetref->{mangle} );
|
|
fatal_error "Unknown Interface ($interface)" unless known_interface( $interface );
|
|
fatal_error "No hosts on $interface have the maclist option specified" unless $maclist_interfaces{$interface};
|
|
|
|
my $chainref = $chain_table{$table}{( $ttl ? macrecent_target $interface : mac_chain $interface )};
|
|
|
|
$mac = '' unless $mac && ( $mac ne '-' );
|
|
$addresses = '' unless defined $addresses && ( $addresses ne '-' );
|
|
|
|
fatal_error "You must specify a MAC address or an IP address" unless $mac || $addresses;
|
|
|
|
$mac = do_mac $mac if $mac;
|
|
|
|
if ( $addresses ) {
|
|
for my $address ( split ',', $addresses ) {
|
|
my $source = match_source_net $address;
|
|
log_rule_limit $level, $chainref , mac_chain( $interface) , $disposition, '', '', 'add' , "${mac}${source}"
|
|
if supplied $level;
|
|
|
|
add_ijump( $chainref , j => 'AUDIT', targetopts => '--type ' . lc $disposition ) if $audit && $disposition ne 'ACCEPT';
|
|
add_jump( $chainref , $targetref->{target}, 0, "${mac}${source}" );
|
|
}
|
|
} else {
|
|
log_rule_limit $level, $chainref , mac_chain( $interface) , $disposition, '', '', 'add' , $mac
|
|
if supplied $level;
|
|
|
|
add_ijump( $chainref , j => 'AUDIT', targetopts => '--type ' . lc $disposition ) if $audit && $disposition ne 'ACCEPT';
|
|
add_jump ( $chainref , $targetref->{target}, 0, "$mac" );
|
|
}
|
|
|
|
progress_message " Maclist entry \"$currentline\" $done";
|
|
}
|
|
}
|
|
#
|
|
# Generate jumps from the input and forward chains
|
|
#
|
|
for my $hostref ( @$maclist_hosts ) {
|
|
my $interface = $hostref->[0];
|
|
my $ipsec = $hostref->[1];
|
|
my @policy = $ipsec && have_ipsec ? ( policy => "--pol $ipsec --dir in" ) : ();
|
|
my @source = imatch_source_net $hostref->[2];
|
|
my $origin = $hostref->[5];
|
|
|
|
my @state = have_capability( 'RAW_TABLE' ) ? state_imatch 'NEW,UNTRACKED' : state_imatch 'NEW';
|
|
|
|
if ( $table eq 'filter' ) {
|
|
my $chainref = source_exclusion( $hostref->[3], $filter_table->{mac_chain $interface} );
|
|
|
|
for my $chain ( option_chains $interface ) {
|
|
add_ijump_extended $filter_table->{$chain} , j => $chainref, $origin, @source, @state, @policy;
|
|
}
|
|
} else {
|
|
my $chainref = source_exclusion( $hostref->[3], $mangle_table->{mac_chain $interface} );
|
|
add_ijump_extended $mangle_table->{PREROUTING}, j => $chainref, $origin, imatch_source_dev( $interface ), @source, @state, @policy;
|
|
}
|
|
}
|
|
} else {
|
|
#
|
|
# Phase II
|
|
#
|
|
ensure_audit_chain( $target, $disposition, undef, $table ) if $audit;
|
|
|
|
for my $interface ( @maclist_interfaces ) {
|
|
my $chainref = $chain_table{$table}{( $ttl ? macrecent_target $interface : mac_chain $interface )};
|
|
my $chain = $chainref->{name};
|
|
|
|
if ( $family == F_IPV4 ) {
|
|
if ( $level ne '' || $disposition ne 'ACCEPT' ) {
|
|
my $variable = get_interface_addresses source_port_to_bridge( $interface );
|
|
|
|
if ( have_capability( 'ADDRTYPE' ) ) {
|
|
add_commands( $chainref, "for address in $variable; do" );
|
|
incr_cmd_level( $chainref );
|
|
add_ijump( $chainref, j => 'RETURN', s => '$address', addrtype => '--dst-type BROADCAST' );
|
|
add_ijump( $chainref, j => 'RETURN', s => '$address', d => '224.0.0.0/4' );
|
|
decr_cmd_level( $chainref );
|
|
add_commands( $chainref, 'done' );
|
|
} else {
|
|
my $bridge = source_port_to_bridge( $interface );
|
|
my $bridgeref = find_interface( $bridge );
|
|
|
|
add_commands( $chainref,
|
|
"for address in $variable; do" );
|
|
incr_cmd_level( $chainref );
|
|
|
|
if ( $bridgeref->{broadcasts} ) {
|
|
for my $address ( @{$bridgeref->{broadcasts}}, '255.255.255.255' ) {
|
|
add_ijump( $chainref, j => 'RETURN', s => '$address', d => $address );
|
|
}
|
|
} else {
|
|
my $variable1 = get_interface_bcasts $bridge;
|
|
|
|
add_commands( $chainref,
|
|
" for address1 in $variable1; do" );
|
|
incr_cmd_level( $chainref );
|
|
add_ijump( $chainref, j => 'RETURN', s => '$address', d => '$address1' );
|
|
decr_cmd_level( $chainref );
|
|
add_commands( $chainref, 'done' );
|
|
}
|
|
|
|
add_ijump( $chainref, j => 'RETURN', s => '$address', d => '224.0.0.0/4' );
|
|
decr_cmd_level( $chainref );
|
|
add_commands( $chainref, 'done' );
|
|
}
|
|
}
|
|
}
|
|
|
|
log_irule_limit $level, $chainref , $chain , $disposition, [], $tag, 'add', '' if $level ne '';
|
|
add_ijump $chainref, j => $target;
|
|
}
|
|
}
|
|
}
|
|
|
|
#
|
|
# Helper functions for generate_matrix()
|
|
#-----------------------------------------
|
|
#
|
|
# Return the target for rules from $zone to $zone1.
|
|
#
|
|
sub rules_target( $$ ) {
|
|
my ( $zone, $zone1 ) = @_;
|
|
my $chain = rules_chain( ${zone}, ${zone1} );
|
|
my $chainref = $filter_table->{$chain};
|
|
|
|
return $chain if $chainref && $chainref->{referenced};
|
|
return 'ACCEPT' if $zone eq $zone1;
|
|
|
|
assert( $chainref );
|
|
|
|
if ( $chainref->{policy} ne 'CONTINUE' ) {
|
|
my $policyref = $filter_table->{$chainref->{policychain}};
|
|
assert( $policyref );
|
|
return $policyref->{name} if $policyref ne $chainref;
|
|
return $chainref->{policy} eq 'REJECT' ? 'reject' : $chainref->{policy};
|
|
}
|
|
|
|
''; # CONTINUE policy
|
|
}
|
|
|
|
#
|
|
# Generate rules for one destination zone
|
|
#
|
|
sub generate_dest_rules( $$$$;@ ) {
|
|
my ( $chainref, $chain, $z2, $origin, @matches ) = @_;
|
|
|
|
my $z2ref = find_zone( $z2 );
|
|
my $type2 = $z2ref->{type};
|
|
|
|
if ( $type2 & VSERVER ) {
|
|
for my $hostref ( @{$z2ref->{hosts}{ip}{'%vserver%'}} ) {
|
|
my $exclusion = dest_exclusion( $hostref->{exclusions}, $chain);
|
|
my $origin = $hostref->{origin};
|
|
|
|
for my $net ( @{$hostref->{hosts}} ) {
|
|
add_ijump_extended( $chainref,
|
|
j => $exclusion ,
|
|
$origin,
|
|
imatch_dest_net ( $net ),
|
|
@matches );
|
|
}
|
|
}
|
|
} else {
|
|
add_ijump_extended( $chainref, j => $chain, $origin, @matches );
|
|
}
|
|
}
|
|
|
|
#
|
|
# Generate rules for one vserver source zone
|
|
#
|
|
sub generate_source_rules( $$$;@ ) {
|
|
my ( $outchainref, $z1, $z2, @matches ) = @_;
|
|
my $chain = rules_target ( $z1, $z2 );
|
|
|
|
if ( $chain && $chain ne 'NONE' ) {
|
|
#
|
|
# Not a CONTINUE policy with no rules
|
|
#
|
|
for my $hostref ( @{defined_zone( $z1 )->{hosts}{ip}{'%vserver%'}} ) {
|
|
my @ipsec_match = match_ipsec_in $z1 , $hostref;
|
|
my $exclusion = source_exclusion( $hostref->{exclusions}, $chain);
|
|
my $origin = $hostref->{origin};
|
|
|
|
for my $net ( @{$hostref->{hosts}} ) {
|
|
generate_dest_rules( $outchainref,
|
|
$exclusion,
|
|
$z2,
|
|
$origin,
|
|
imatch_source_net( $net ),
|
|
@matches ,
|
|
@ipsec_match
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#
|
|
# Loopback traffic -- this is where we assemble the intra-firewall chains
|
|
#
|
|
sub handle_loopback_traffic() {
|
|
my @zones = ( vserver_zones, firewall_zone );
|
|
my $natout = $nat_table->{OUTPUT};
|
|
my $rawout = $raw_table->{OUTPUT};
|
|
my $rulenum = 0;
|
|
my $loopback = loopback_zones;
|
|
my $loref = known_interface(loopback_interface);
|
|
|
|
my $unmanaged;
|
|
my $outchainref;
|
|
my @rule;
|
|
|
|
if ( @zones > 1 ) {
|
|
#
|
|
# We have a vserver zone -- route output through a separate chain
|
|
#
|
|
$outchainref = new_standard_chain 'loopback';
|
|
|
|
if ( have_capability 'IFACE_MATCH' ) {
|
|
add_ijump $filter_table->{OUTPUT}, j => $outchainref, iface => '--dev-out --loopback';
|
|
} else {
|
|
add_ijump $filter_table->{OUTPUT}, j => $outchainref, o => loopback_interface;
|
|
}
|
|
} else {
|
|
#
|
|
# Only the firewall -- just use the OUTPUT chain
|
|
#
|
|
if ( $unmanaged = $loref && $loref->{options}{unmanaged} ) {
|
|
if ( have_capability 'IFACE_MATCH' ) {
|
|
add_ijump( $filter_table->{OUTPUT}, j => 'ACCEPT', iface => '--dev-out --loopback' );
|
|
} else {
|
|
add_ijump( $filter_table->{OUTPUT}, j => 'ACCEPT', o => loopback_interface );
|
|
}
|
|
} else {
|
|
$outchainref = $filter_table->{OUTPUT};
|
|
if ( have_capability 'IFACE_MATCH' ) {
|
|
@rule = ( iface => '--dev-out --loopback' );
|
|
} else {
|
|
@rule = ( o => loopback_interface );
|
|
}
|
|
}
|
|
}
|
|
|
|
for my $z1 ( @zones ) {
|
|
my $z1ref = find_zone( $z1 );
|
|
my $type1 = $z1ref->{type};
|
|
my $natref = $nat_table->{dnat_chain $z1};
|
|
my $notrackref = $raw_table->{notrack_chain( $z1 )};
|
|
#
|
|
# Add jumps in the 'output' chain to the rules chains
|
|
#
|
|
if ( $type1 == FIREWALL ) {
|
|
for my $z2 ( @zones ) {
|
|
next if $z1 eq $z2 && ( $loopback || $unmanaged );
|
|
|
|
my $chain = rules_target( $z1, $z2 );
|
|
generate_dest_rules( $outchainref, $chain, $z2, '', @rule ) if $chain;
|
|
}
|
|
#
|
|
# Handle conntrack
|
|
#
|
|
if ( $notrackref ) {
|
|
add_ijump $rawout, j => $notrackref if $notrackref->{referenced};
|
|
}
|
|
} else {
|
|
for my $z2 ( @zones ) {
|
|
generate_source_rules( $outchainref, $z1, $z2, @rule );
|
|
}
|
|
#
|
|
# Handle conntrack rules
|
|
#
|
|
if ( $notrackref->{referenced} ) {
|
|
for my $hostref ( @{defined_zone( $z1 )->{hosts}{ip}{'%vserver%'}} ) {
|
|
my $exclusion = source_exclusion( $hostref->{exclusions}, $notrackref);
|
|
my @ipsec_match = match_ipsec_in $z1 , $hostref;
|
|
|
|
for my $net ( @{$hostref->{hosts}} ) {
|
|
insert_ijump( $rawout,
|
|
j => $exclusion ,
|
|
$rawout->{insert}++,
|
|
imatch_source_net $net,
|
|
@ipsec_match );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( $natref && $natref->{referenced} ) {
|
|
#
|
|
# There are DNAT rules with this zone as the source -- add jumps from the nat OUTPUT chain
|
|
#
|
|
my $source_hosts_ref = defined_zone( $z1 )->{hosts};
|
|
|
|
for my $typeref ( values %{$source_hosts_ref} ) {
|
|
for my $hostref ( @{$typeref->{'%vserver%'}} ) {
|
|
my $exclusion = source_exclusion( $hostref->{exclusions}, $natref);
|
|
|
|
for my $net ( @{$hostref->{hosts}} ) {
|
|
insert_ijump( $natout,
|
|
j => $exclusion,
|
|
$rulenum++,
|
|
imatch_source_net( $net , 0, ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#
|
|
# Add jumps from the builtin chains to the interface-chains that are used by this configuration
|
|
#
|
|
sub add_interface_jumps {
|
|
our %input_jump_added;
|
|
our %output_jump_added;
|
|
our %forward_jump_added;
|
|
my @interfaces = grep $_ ne '%vserver%', @_;
|
|
my $dummy;
|
|
my $lo_jump_added = interface_zone( loopback_interface ) && ! get_interface_option( loopback_interface, 'destonly' );
|
|
#
|
|
# Add Nat jumps
|
|
#
|
|
for my $interface ( @_ ) {
|
|
addnatjump $globals{POSTROUTING} , snat_chain( $interface ), imatch_dest_dev( $interface );
|
|
}
|
|
|
|
addnatjump( 'POSTROUTING', 'SHOREWALL' ) if $config{DOCKER};
|
|
|
|
for my $interface ( @interfaces ) {
|
|
addnatjump 'PREROUTING' , input_chain( $interface ) , imatch_source_dev( $interface );
|
|
addnatjump $globals{POSTROUTING} , output_chain( $interface ) , imatch_dest_dev( $interface );
|
|
addnatjump $globals{POSTROUTING} , masq_chain( $interface ) , imatch_dest_dev( $interface );
|
|
|
|
add_ijump( $mangle_table->{PREROUTING}, j => 'rpfilter' , imatch_source_dev( $interface ) ) if interface_has_option( $interface, 'rpfilter', $dummy );
|
|
}
|
|
#
|
|
# Add the jumps to the interface chains from filter FORWARD, INPUT, OUTPUT
|
|
#
|
|
for my $interface ( @interfaces ) {
|
|
my $forwardref = $filter_table->{forward_chain $interface};
|
|
my $inputref = $filter_table->{input_chain $interface};
|
|
my $outputref = $filter_table->{output_chain $interface};
|
|
my $interfaceref = find_interface($interface);
|
|
|
|
if ( $interfaceref->{physical} eq '+' && ! $lo_jump_added++ ) {
|
|
if ( have_capability 'IFACE_MATCH' ) {
|
|
add_ijump $filter_table->{INPUT} , j => 'ACCEPT', iface => '--dev-in --loopback';
|
|
} else {
|
|
add_ijump $filter_table->{INPUT} , j => 'ACCEPT', i => loopback_interface;
|
|
}
|
|
}
|
|
|
|
if ( $interfaceref->{options}{port} ) {
|
|
my $bridge = $interfaceref->{bridge};
|
|
|
|
add_ijump ( $filter_table->{forward_chain $bridge},
|
|
j => 'ACCEPT',
|
|
imatch_source_dev( $interface, 1),
|
|
imatch_dest_dev( $interface, 1)
|
|
) unless $interfaceref->{nets};
|
|
|
|
add_ijump( $filter_table->{forward_chain $bridge} ,
|
|
j => $forwardref ,
|
|
imatch_source_dev( $interface, 1 )
|
|
) unless $forward_jump_added{$interface} || ! use_forward_chain $interface, $forwardref;
|
|
|
|
add_ijump( $filter_table->{input_chain $bridge },
|
|
j => $inputref ,
|
|
imatch_source_dev( $interface, 1 )
|
|
) unless $input_jump_added{$interface} || ! use_interface_chain( $interface, 'use_input_chain' );
|
|
|
|
unless ( $output_jump_added{$interface} || ! use_interface_chain( $interface, 'use_output_chain') ) {
|
|
add_ijump( $filter_table->{output_chain $bridge} ,
|
|
j => $outputref ,
|
|
imatch_dest_dev( $interface, 1 ) )
|
|
unless get_interface_option( $interface, 'port' );
|
|
}
|
|
} else {
|
|
add_ijump ( $filter_table->{FORWARD}, j => 'ACCEPT', imatch_source_dev( $interface) , imatch_dest_dev( $interface) ) unless $interfaceref->{nets} || ! $interfaceref->{options}{bridge};
|
|
|
|
add_ijump( $filter_table->{FORWARD} , j => $forwardref , imatch_source_dev( $interface ) ) if use_forward_chain( $interface, $forwardref ) && ! $forward_jump_added{$interface}++;
|
|
add_ijump( $filter_table->{INPUT} , j => $inputref , imatch_source_dev( $interface ) ) if use_interface_chain( $interface, 'use_input_chain' ) && ! $input_jump_added{$interface}++;
|
|
|
|
if ( use_interface_chain( $interface, 'use_output_chain' ) ) {
|
|
add_ijump $filter_table->{OUTPUT} , j => $outputref , imatch_dest_dev( $interface ) unless get_interface_option( $interface, 'port' ) || $output_jump_added{$interface}++;
|
|
}
|
|
}
|
|
}
|
|
|
|
unless ( $lo_jump_added++ ) {
|
|
if ( have_capability 'IFACE_MATCH' ) {
|
|
add_ijump $filter_table->{INPUT} , j => 'ACCEPT', iface => '--dev-in --loopback';
|
|
} else {
|
|
add_ijump $filter_table->{INPUT} , j => 'ACCEPT', i => loopback_interface;
|
|
}
|
|
}
|
|
|
|
handle_loopback_traffic;
|
|
}
|
|
|
|
#
|
|
# Do the initial matrix processing for a complex zone
|
|
#
|
|
sub handle_complex_zone( $$ ) {
|
|
my ( $zone, $zoneref ) = @_;
|
|
|
|
our %input_jump_added;
|
|
our %output_jump_added;
|
|
our %forward_jump_added;
|
|
our %ipsec_jump_added;
|
|
#
|
|
# Complex zone or we have more than two off-firewall zones -- Shorewall::Rules::classic_blacklist created a zone forwarding chain
|
|
#
|
|
my $frwd_ref = $filter_table->{zone_forward_chain( $zone )};
|
|
|
|
assert( $frwd_ref, $zone );
|
|
#
|
|
# Add Zone mark if any
|
|
#
|
|
add_ijump( $frwd_ref , j => 'MARK --set-mark ' . in_hex( $zoneref->{mark} ) . '/' . in_hex( $globals{ZONE_MASK} ) ) if $zoneref->{mark};
|
|
|
|
if ( have_ipsec ) {
|
|
#
|
|
# In general, policy match can only match an 'in' or an 'out' policy (but not both), so we place the
|
|
# '--pol ipsec --dir in' rules at the front of the (interface) forwarding chains. Otherwise, decrypted packets
|
|
# can match '--pol none --dir out' rules and send the packets down the wrong rules chain.
|
|
#
|
|
my $type = $zoneref->{type};
|
|
my $source_ref = ( $zoneref->{hosts}{ipsec} ) || {};
|
|
|
|
for my $interface ( keys %$source_ref ) {
|
|
my $sourcechainref = $filter_table->{forward_chain $interface};
|
|
my @interfacematch;
|
|
my $interfaceref = find_interface $interface;
|
|
|
|
next if $interfaceref->{options}{destonly};
|
|
|
|
if ( use_forward_chain( $interface, $sourcechainref ) ) {
|
|
#
|
|
# Use the interface forward chain
|
|
#
|
|
if ( $interfaceref->{ports} && $interfaceref->{options}{bridge} ) {
|
|
#
|
|
# This is a bridge with ports
|
|
#
|
|
@interfacematch = imatch_source_dev $interface;
|
|
#
|
|
# Copy the rules from the interface forward chain to the zone forward chain unless they have already been copied
|
|
#
|
|
copy_rules( $sourcechainref, $frwd_ref, 1 ) unless $ipsec_jump_added{$zone}++;
|
|
#
|
|
# Jump directly from FORWARD to the zone forward chain
|
|
#
|
|
$sourcechainref = $filter_table->{FORWARD};
|
|
} elsif ( $interfaceref->{options}{port} ) {
|
|
#
|
|
# The forwarding chain for a bridge with ports is always used -- use physdev match for this interface
|
|
#
|
|
add_ijump( $filter_table->{ forward_chain $interfaceref->{bridge} } ,
|
|
j => $sourcechainref ,
|
|
imatch_source_dev( $interface , 1 ) )
|
|
unless $forward_jump_added{$interface}++;
|
|
} else {
|
|
#
|
|
# Add jump from FORWARD to the intrface forward chain
|
|
#
|
|
add_ijump $filter_table->{FORWARD} , j => $sourcechainref, imatch_source_dev( $interface ) unless $forward_jump_added{$interface}++;
|
|
}
|
|
} else {
|
|
if ( $interfaceref->{options}{port} ) {
|
|
#
|
|
# The forwarding chain for a bridge with ports is always used -- use physdev match
|
|
#
|
|
$sourcechainref = $filter_table->{ forward_chain $interfaceref->{bridge} };
|
|
@interfacematch = imatch_source_dev $interface, 1;
|
|
} else {
|
|
$sourcechainref = $filter_table->{FORWARD};
|
|
@interfacematch = imatch_source_dev $interface;
|
|
}
|
|
#
|
|
# copy any rules from the interface forward chain to the zone forward chain
|
|
#
|
|
move_rules( $filter_table->{forward_chain $interface} , $frwd_ref );
|
|
}
|
|
|
|
my $arrayref = $source_ref->{$interface};
|
|
#
|
|
# Now add the jumps from the source chain (interface forward or FORWARD) to the zone forward chain
|
|
#
|
|
for my $hostref ( @{$arrayref} ) {
|
|
my @ipsec_match = match_ipsec_in $zone , $hostref;
|
|
my $origin = $hostref->{origin};
|
|
for my $net ( @{$hostref->{hosts}} ) {
|
|
add_ijump_extended(
|
|
$sourcechainref,
|
|
@{$zoneref->{parents}} ? 'j' : 'g' => source_exclusion( $hostref->{exclusions}, $frwd_ref ),
|
|
$origin,
|
|
@interfacematch ,
|
|
imatch_source_net( $net ),
|
|
@ipsec_match
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#
|
|
# The passed zone is a sub-zone. We need to determine if
|
|
#
|
|
# a) A parent zone defines DNAT/REDIRECT or notrack rules; and
|
|
# b) The current zone has a CONTINUE policy to some other zone.
|
|
#
|
|
# If a) but not b), then we must avoid sending packets from this
|
|
# zone through the DNAT/REDIRECT or notrack chain for the parent.
|
|
#
|
|
sub handle_nested_zone( $$ ) {
|
|
my ( $zone, $zoneref ) = @_;
|
|
#
|
|
# Function returns this 3-tuple
|
|
#
|
|
my ( $nested, $parenthasnat, $parenthasnotrack ) = ( 1, 0, 0 );
|
|
|
|
for my $parent ( @{$zoneref->{parents}} ) {
|
|
my $ref1 = $nat_table->{dnat_chain $parent} || {};
|
|
my $ref2 = $raw_table->{notrack_chain $parent} || {};
|
|
$parenthasnat = 1 if $ref1->{referenced};
|
|
$parenthasnotrack = 1 if $ref2->{referenced};
|
|
last if $parenthasnat && $parenthasnotrack;
|
|
}
|
|
|
|
if ( $parenthasnat || $parenthasnotrack ) {
|
|
for my $zone1 ( all_zones ) {
|
|
if ( $filter_table->{rules_chain( ${zone}, ${zone1} )}->{policy} eq 'CONTINUE' ) {
|
|
#
|
|
# This zone has a continue policy to another zone. We must
|
|
# send packets from this zone through the parent's DNAT/REDIRECT/NOTRACK chain.
|
|
#
|
|
$nested = 0;
|
|
last;
|
|
}
|
|
}
|
|
} else {
|
|
#
|
|
# No parent has DNAT or notrack so there is nothing to worry about. Don't bother to generate needless RETURN rules in the 'dnat' or 'notrack' chain.
|
|
#
|
|
$nested = 0;
|
|
}
|
|
|
|
( $nested, $parenthasnat, $parenthasnotrack );
|
|
}
|
|
|
|
#
|
|
# Add output jump to the passed zone:interface:hostref:net
|
|
#
|
|
sub add_output_jumps( $$$$$$$$ ) {
|
|
my ( $zone, $interface, $hostref, $net, $exclusions, $isport, $bridge, $origin ) = @_;
|
|
|
|
our @vservers;
|
|
our %output_jump_added;
|
|
|
|
my $chain1 = rules_target( firewall_zone , $zone );
|
|
my $chain1ref = $filter_table->{$chain1};
|
|
my $nextchain = dest_exclusion( $exclusions, $chain1 );
|
|
my $outputref;
|
|
my $interfacechainref = $filter_table->{output_chain $interface};
|
|
my @interfacematch;
|
|
my $use_output = 0;
|
|
my @dest = imatch_dest_net $net;
|
|
my @ipsec_out_match = match_ipsec_out $zone , $hostref;
|
|
my @zone_interfaces = keys %{zone_interfaces( $zone )};
|
|
|
|
if ( @vservers || use_interface_chain( $interface, 'use_output_chain' ) || ( @{$interfacechainref->{rules}} && ! $chain1ref ) || @zone_interfaces > 1 ) {
|
|
#
|
|
# - There are vserver zones (so OUTPUT will have multiple source; or
|
|
# - We must use the interface output chain; or
|
|
# - There are rules in the interface chain and none in the rules chain
|
|
# - The zone has multiple interfaces
|
|
#
|
|
# In any of these cases use the inteface output chain
|
|
#
|
|
$outputref = $interfacechainref;
|
|
|
|
if ( $isport ) {
|
|
#
|
|
# It is a bridge port zone -- use the bridges output chain and match the physdev
|
|
#
|
|
add_ijump_extended( $filter_table->{ output_chain $bridge },
|
|
j => $outputref ,
|
|
$origin ,
|
|
imatch_dest_dev( $interface, 1 ) )
|
|
unless $output_jump_added{$interface}++;
|
|
} else {
|
|
#
|
|
# Not a bridge -- match the output interface
|
|
#
|
|
add_ijump_extended $filter_table->{OUTPUT}, j => $outputref, $origin, imatch_dest_dev( $interface ) unless $output_jump_added{$interface}++;
|
|
}
|
|
|
|
$use_output = 1;
|
|
|
|
unless ( lc $net eq IPv6_LINKLOCAL ) {
|
|
#
|
|
# Generate output rules for the vservers
|
|
#
|
|
for my $vzone ( @vservers ) {
|
|
generate_source_rules ( $outputref, $vzone, $zone, @dest );
|
|
}
|
|
}
|
|
} elsif ( $isport ) {
|
|
#
|
|
# It is a bridge port zone -- use the bridges output chain and match the physdev
|
|
#
|
|
$outputref = $filter_table->{ output_chain $bridge };
|
|
@interfacematch = imatch_dest_dev $interface, 1;
|
|
} else {
|
|
#
|
|
# Just put the jump in the OUTPUT chain
|
|
#
|
|
$outputref = $filter_table->{OUTPUT};
|
|
@interfacematch = imatch_dest_dev $interface;
|
|
}
|
|
#
|
|
# Add the jump
|
|
#
|
|
add_ijump_extended $outputref , j => $nextchain, $origin, @interfacematch, @dest, @ipsec_out_match;
|
|
#
|
|
# Add jump for broadcast
|
|
#
|
|
add_ijump_extended( $outputref , j => $nextchain, get_interface_origin( $interface ), @interfacematch, d => '255.255.255.255' , @ipsec_out_match )
|
|
if $family == F_IPV4 && $hostref->{options}{broadcast};
|
|
#
|
|
# Move the rules from the interface output chain if we didn't use it
|
|
#
|
|
move_rules( $interfacechainref , $chain1ref ) unless $use_output;
|
|
}
|
|
|
|
#
|
|
# Add prerouting jumps from the passed zone:interface:hostref:net
|
|
#
|
|
sub add_prerouting_jumps( $$$$$$$$$ ) {
|
|
my ( $zone, $interface, $hostref, $net, $exclusions, $nested, $parenthasnat, $parenthasnotrack , $origin ) = @_;
|
|
|
|
my $dnatref = $nat_table->{dnat_chain( $zone )};
|
|
my $preroutingref = $nat_table->{PREROUTING};
|
|
my $rawref = $raw_table->{PREROUTING};
|
|
my $notrackref = ensure_chain 'raw' , notrack_chain( $zone );
|
|
my @ipsec_in_match = match_ipsec_in $zone , $hostref;
|
|
|
|
my @source = imatch_source_net $net;
|
|
|
|
if ( $dnatref->{referenced} ) {
|
|
#
|
|
# There are DNAT/REDIRECT rules with this zone as the source.
|
|
# Add a jump from this source network to this zone's DNAT/REDIRECT chain
|
|
#
|
|
add_ijump_extended( $preroutingref,
|
|
j => source_exclusion( $exclusions, $dnatref),
|
|
$origin,
|
|
imatch_source_dev( $interface),
|
|
@source,
|
|
@ipsec_in_match );
|
|
|
|
check_optimization( $dnatref ) if @source;
|
|
}
|
|
|
|
if ( $notrackref->{referenced} ) {
|
|
#
|
|
# There are notrack rules with this zone as the source.
|
|
# Add a jump from this source network to this zone's notrack chain
|
|
#
|
|
insert_ijump $rawref, j => source_exclusion( $exclusions, $notrackref), $rawref->{insert}++, imatch_source_dev( $interface), @source, @ipsec_in_match;
|
|
}
|
|
#
|
|
# If this zone has parents with DNAT/REDIRECT or notrack rules and there are no CONTINUE polcies with this zone as the source
|
|
# then add a RETURN jump for this source network.
|
|
#
|
|
if ( $nested ) {
|
|
if ( $parenthasnat ) {
|
|
add_ijump_extended $preroutingref, j => 'RETURN', $origin, imatch_source_dev( $interface), @source, @ipsec_in_match;
|
|
}
|
|
if ( $parenthasnotrack ) {
|
|
my $rawref = $raw_table->{PREROUTING};
|
|
insert_ijump $rawref, j => 'RETURN', $rawref->{insert}++, imatch_source_dev( $interface), @source, @ipsec_in_match;
|
|
}
|
|
}
|
|
}
|
|
|
|
#
|
|
# Add input jump from the passed zone:interface:hostref:net
|
|
#
|
|
sub add_input_jumps( $$$$$$$$$ ) {
|
|
my ( $zone, $interface, $hostref, $net, $exclusions, $frwd_ref, $isport, $bridge, $origin ) = @_;
|
|
|
|
our @vservers;
|
|
our %input_jump_added;
|
|
|
|
my $chain2 = rules_target $zone, firewall_zone;
|
|
my $chain2ref = $filter_table->{$chain2};
|
|
my $inputchainref;
|
|
my $interfacechainref = $filter_table->{input_chain $interface};
|
|
my @interfacematch;
|
|
my $use_input;
|
|
my @source = imatch_source_net $net;
|
|
my @ipsec_in_match = match_ipsec_in $zone , $hostref;
|
|
|
|
if ( @vservers || use_interface_chain( $interface, 'use_input_chain' ) || ! $chain2 || ( @{$interfacechainref->{rules}} && ! $chain2ref ) ) {
|
|
#
|
|
# - There are vserver zones (so INPUT will have multiple destinations; or
|
|
# - We must use the interface input chain; or
|
|
# - The zone->firewall policy is CONTINUE; or
|
|
# - There are rules in the interface chain and none in the rules chain
|
|
#
|
|
# In any of these cases use the inteface input chain
|
|
#
|
|
$inputchainref = $interfacechainref;
|
|
|
|
if ( $isport ) {
|
|
#
|
|
# It is a bridge port zone -- use the bridges input chain and match the physdev
|
|
#
|
|
add_ijump_extended( $filter_table->{ input_chain $bridge },
|
|
j => $inputchainref ,
|
|
$origin ,
|
|
imatch_source_dev($interface, 1) )
|
|
unless $input_jump_added{$interface}++;
|
|
} else {
|
|
#
|
|
# Not a bridge -- match the input interface
|
|
#
|
|
add_ijump_extended $filter_table->{INPUT}, j => $inputchainref, $origin, imatch_source_dev($interface) unless $input_jump_added{$interface}++;
|
|
}
|
|
|
|
$use_input = 1;
|
|
|
|
unless ( lc $net eq IPv6_LINKLOCAL ) {
|
|
#
|
|
# Generate input rules for the vservers
|
|
#
|
|
for my $vzone ( @vservers ) {
|
|
my $target = rules_target( $zone, $vzone );
|
|
generate_dest_rules( $inputchainref, $target, $vzone, $origin, @source, @ipsec_in_match ) if $target;
|
|
}
|
|
}
|
|
} elsif ( $isport ) {
|
|
#
|
|
# It is a bridge port zone -- use the bridges input chain and match the physdev
|
|
#
|
|
$inputchainref = $filter_table->{ input_chain $bridge };
|
|
@interfacematch = imatch_source_dev $interface, 1;
|
|
} else {
|
|
#
|
|
# Just put the jump in the INPUT chain
|
|
#
|
|
$inputchainref = $filter_table->{INPUT};
|
|
@interfacematch = imatch_source_dev $interface;
|
|
}
|
|
|
|
if ( $chain2 ) {
|
|
#
|
|
# Add the jump from the input chain to the rules chain
|
|
#
|
|
add_ijump_extended $inputchainref, j => source_exclusion( $exclusions, $chain2 ), $origin, @interfacematch, @source, @ipsec_in_match;
|
|
move_rules( $interfacechainref , $chain2ref ) unless $use_input;
|
|
}
|
|
}
|
|
|
|
#
|
|
# This function is called when there is forwarding and this net isn't IPSEC protected. It adds the jump for this net to the zone forwarding chain.
|
|
#
|
|
sub add_forward_jump( $$$$$$$$$ ) {
|
|
my ( $zone, $interface, $hostref, $net, $exclusions, $frwd_ref, $isport, $bridge, $origin ) = @_;
|
|
|
|
our %forward_jump_added;
|
|
|
|
my @source = imatch_source_net $net;
|
|
my @ipsec_in_match = match_ipsec_in $zone , $hostref;
|
|
|
|
my $ref = source_exclusion( $exclusions, $frwd_ref );
|
|
my $forwardref = $filter_table->{forward_chain $interface};
|
|
|
|
if ( use_forward_chain $interface, $forwardref ) {
|
|
#
|
|
# We must use the interface forwarding chain -- add the jump from the interface forward chain to the zone forward chain.
|
|
#
|
|
add_ijump_extended $forwardref , j => $ref, $origin, @source, @ipsec_in_match;
|
|
|
|
if ( $isport ) {
|
|
#
|
|
# It is a bridge port zone -- use the bridges input chain and match the physdev
|
|
#
|
|
add_ijump_extended( $filter_table->{ forward_chain $bridge } ,
|
|
j => $forwardref ,
|
|
$origin ,
|
|
imatch_source_dev( $interface , 1 ) )
|
|
unless $forward_jump_added{$interface}++;
|
|
} else {
|
|
#
|
|
# Not a bridge -- match the input interface
|
|
#
|
|
add_ijump_extended $filter_table->{FORWARD} , j => $forwardref, $origin, imatch_source_dev( $interface ) unless $forward_jump_added{$interface}++;
|
|
}
|
|
} else {
|
|
if ( $isport ) {
|
|
#
|
|
# It is a bridge port zone -- use the bridges input chain and match the physdev
|
|
#
|
|
add_ijump_extended( $filter_table->{ forward_chain $bridge } ,
|
|
j => $ref ,
|
|
$origin ,
|
|
imatch_source_dev( $interface, 1 ) ,
|
|
@source,
|
|
@ipsec_in_match );
|
|
} else {
|
|
#
|
|
# Not a bridge -- match the input interface
|
|
#
|
|
add_ijump_extended $filter_table->{FORWARD} , j => $ref, $origin, imatch_source_dev( $interface ) , @source, @ipsec_in_match;
|
|
}
|
|
|
|
move_rules ( $forwardref , $frwd_ref );
|
|
}
|
|
}
|
|
|
|
#
|
|
# Generate the list of destination zones from the passed source zone when optimization level 1 is selected
|
|
#
|
|
# - Drop zones where the policy to that zone is 'NONE'
|
|
# - Drop this zone if it has only one interface without 'routeback'
|
|
# - Drop BPORT zones that are not on the same bridge
|
|
# - Eliminate duplicate zones that have the same '2all' (-all) rules chain.
|
|
#
|
|
sub optimize1_zones( $$@ ) {
|
|
my $zone = shift;
|
|
my $zoneref = shift;
|
|
my $last_chain = '';
|
|
my @dest_zones;
|
|
my @temp_zones;
|
|
|
|
for my $zone1 ( @_ ) {
|
|
my $zone1ref = find_zone( $zone1 );
|
|
my $policy = $filter_table->{rules_chain( ${zone}, ${zone1} )}->{policy};
|
|
|
|
next if $policy eq 'NONE';
|
|
|
|
my $chain = rules_target $zone, $zone1;
|
|
|
|
next unless $chain;
|
|
|
|
if ( $zone eq $zone1 ) {
|
|
next if ( scalar ( keys( %{ $zoneref->{interfaces}} ) ) < 2 ) && ! $zoneref->{options}{in_out}{routeback};
|
|
}
|
|
|
|
if ( $zone1ref->{type} & BPORT ) {
|
|
next unless $zoneref->{bridge} eq $zone1ref->{bridge};
|
|
}
|
|
|
|
if ( $chain =~ /(2all|-all)$/ ) {
|
|
if ( $chain ne $last_chain ) {
|
|
$last_chain = $chain;
|
|
push @dest_zones, @temp_zones;
|
|
@temp_zones = ( $zone1 );
|
|
} elsif ( $policy eq 'ACCEPT' ) {
|
|
push @temp_zones , $zone1;
|
|
} else {
|
|
$last_chain = $chain;
|
|
@temp_zones = ( $zone1 );
|
|
}
|
|
} else {
|
|
push @dest_zones, @temp_zones, $zone1;
|
|
@temp_zones = ();
|
|
$last_chain = '';
|
|
}
|
|
}
|
|
|
|
if ( $last_chain && @temp_zones == 1 ) {
|
|
push @dest_zones, @temp_zones;
|
|
$last_chain = '';
|
|
}
|
|
|
|
( $last_chain, @dest_zones );
|
|
}
|
|
|
|
# Generate the rules matrix.
|
|
#
|
|
# Stealing a comment from the Burroughs B6700 MCP Operating System source, "generate_matrix makes a sow's ear out of a silk purse".
|
|
#
|
|
# The biggest disadvantage of the zone-policy-rule model used by Shorewall is that it doesn't scale well as the number of zones increases (Order N**2 where N = number of zones).
|
|
# A major goal of the rewrite of the compiler in Perl was to restrict those scaling effects to this function and the rules that it generates.
|
|
#
|
|
# The function traverses the full "source-zone by destination-zone" matrix and generates the rules necessary to direct traffic through the right set of filter-table, raw-table and
|
|
# nat-table rules.
|
|
#
|
|
sub generate_matrix() {
|
|
my @interfaces = ( managed_interfaces );
|
|
my @zones = off_firewall_zones;
|
|
|
|
our @vservers = vserver_zones;
|
|
|
|
my $interface_jumps_added = 0;
|
|
|
|
our %input_jump_added = ();
|
|
our %output_jump_added = ();
|
|
our %forward_jump_added = ();
|
|
our %ipsec_jump_added = ();
|
|
|
|
progress_message2 'Generating Rule Matrix...';
|
|
progress_message ' Handling complex zones...';
|
|
#
|
|
# Special processing for configurations with more than 2 off-firewall zones or with other special considerations like IPSEC.
|
|
# Don't be tempted to move this logic into the zone loop below -- it won't work.
|
|
#
|
|
for my $zone ( @zones ) {
|
|
my $zoneref = find_zone( $zone );
|
|
if ( @zones > 2 || $zoneref->{complex} ) {
|
|
handle_complex_zone( $zone, $zoneref );
|
|
} else {
|
|
new_standard_chain zone_forward_chain( $zone ) if @zones > 1;
|
|
}
|
|
}
|
|
#
|
|
# Main source-zone matrix-generation loop
|
|
#
|
|
progress_message ' Entering main matrix-generation loop...';
|
|
|
|
for my $zone ( @zones ) {
|
|
my $zoneref = find_zone( $zone );
|
|
my $source_hosts_ref = $zoneref->{hosts};
|
|
my $frwd_ref = $filter_table->{zone_forward_chain $zone};
|
|
my $nested = @{$zoneref->{parents}};
|
|
my $parenthasnat = 0;
|
|
my $parenthasnotrack = 0;
|
|
my $type = $zoneref->{type};
|
|
#
|
|
# Create the zone's dnat chain
|
|
#
|
|
ensure_chain 'nat', dnat_chain( $zone );
|
|
|
|
( $nested, $parenthasnat, $parenthasnotrack) = handle_nested_zone( $zone, $zoneref ) if $nested;
|
|
#
|
|
# Take care of PREROUTING, INPUT and OUTPUT jumps
|
|
#
|
|
for my $type ( keys %$source_hosts_ref ) {
|
|
my $typeref = $source_hosts_ref->{$type};
|
|
for my $interface ( keys %$typeref ) {
|
|
if ( get_physical( $interface ) eq '+' ) {
|
|
#
|
|
# Insert the interface-specific jumps before this one which is not interface-specific
|
|
#
|
|
add_interface_jumps(@interfaces) unless $interface_jumps_added++;
|
|
}
|
|
|
|
my $interfaceref = find_interface $interface;
|
|
my $isport = $interfaceref->{options}{port};
|
|
my $bridge = $interfaceref->{bridge};
|
|
|
|
for my $hostref ( @{$typeref->{$interface}} ) {
|
|
my $exclusions = $hostref->{exclusions};
|
|
my $origin = $hostref->{origin};
|
|
|
|
for my $net ( @{$hostref->{hosts}} ) {
|
|
#
|
|
# OUTPUT
|
|
#
|
|
if ( rules_target( firewall_zone, $zone ) && ! ( zone_type( $zone) & BPORT ) ) {
|
|
#
|
|
# Policy from the firewall to this zone is not 'CONTINUE' and this isn't a bport zone
|
|
#
|
|
add_output_jumps( $zone, $interface, $hostref, $net, $exclusions, $isport, $bridge, $origin );
|
|
}
|
|
|
|
clearrule;
|
|
|
|
unless( $hostref->{options}{destonly} ) {
|
|
#
|
|
# PREROUTING
|
|
#
|
|
add_prerouting_jumps( $zone, $interface, $hostref, $net, $exclusions, $nested, $parenthasnat, $parenthasnotrack , $origin );
|
|
#
|
|
# INPUT
|
|
#
|
|
add_input_jumps( $zone, $interface, $hostref, $net, $exclusions, $frwd_ref, $isport, $bridge , $origin );
|
|
#
|
|
# FORWARDING Jump for non-IPSEC host group
|
|
#
|
|
add_forward_jump( $zone, $interface, $hostref, $net, $exclusions, $frwd_ref, $isport, $bridge, $origin ) if $frwd_ref && $hostref->{ipsec} ne 'ipsec';
|
|
}
|
|
} # Subnet Loop
|
|
} # Hostref Loop
|
|
} # Interface Loop
|
|
} #Type Loop
|
|
|
|
if ( $frwd_ref ) {
|
|
#
|
|
# F O R W A R D I N G
|
|
#
|
|
my @dest_zones;
|
|
my $last_chain = '';
|
|
|
|
if ( $config{OPTIMIZE} & 1 ) {
|
|
( $last_chain , @dest_zones ) = optimize1_zones($zone, $zoneref, @zones );
|
|
} else {
|
|
@dest_zones = @zones ;
|
|
}
|
|
#
|
|
# We now loop through the destination zones creating jumps to the rules chain for each source/dest combination.
|
|
# @dest_zones is the list of destination zones that we need to handle from this source zone
|
|
#
|
|
for my $zone1 ( @dest_zones ) {
|
|
my $zone1ref = find_zone( $zone1 );
|
|
my $type1 = $zone1ref->{type};
|
|
|
|
next if $filter_table->{rules_chain( ${zone}, ${zone1} )}->{policy} eq 'NONE';
|
|
|
|
my $chain = rules_target $zone, $zone1;
|
|
|
|
next unless $chain; # CONTINUE policy with no rules
|
|
my $num_ifaces = 0;
|
|
|
|
if ( $zone eq $zone1 ) {
|
|
next if ( $num_ifaces = scalar( keys ( %{$zoneref->{interfaces}} ) ) ) < 2 && ! $zoneref->{options}{in_out}{routeback};
|
|
}
|
|
|
|
if ( $type1 & BPORT ) {
|
|
next unless $zoneref->{bridge} eq $zone1ref->{bridge};
|
|
}
|
|
|
|
my $chainref = $filter_table->{$chain}; #Will be null if $chain is a Netfilter Built-in target like ACCEPT
|
|
|
|
for my $type ( keys %{$zone1ref->{hosts}} ) {
|
|
my $typeref = $zone1ref->{hosts}{$type};
|
|
for my $interface ( keys %$typeref ) {
|
|
for my $hostref ( @{$typeref->{$interface}} ) {
|
|
next if $hostref->{options}{sourceonly};
|
|
if ( $zone ne $zone1 || $num_ifaces > 1 || $hostref->{options}{routeback} ) {
|
|
my @ipsec_out_match = match_ipsec_out $zone1 , $hostref;
|
|
my $dest_exclusion = dest_exclusion( $hostref->{exclusions}, $chain);
|
|
my $origin = $hostref->{origin};
|
|
for my $net ( @{$hostref->{hosts}} ) {
|
|
add_ijump_extended $frwd_ref, j => $dest_exclusion, $origin, imatch_dest_dev( $interface) , imatch_dest_net($net), @ipsec_out_match;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#
|
|
# E N D F O R W A R D I N G
|
|
#
|
|
# Now add an unconditional jump to the last unique policy-only chain determined above, if any
|
|
#
|
|
add_ijump $frwd_ref , g => $last_chain if $frwd_ref && $last_chain;
|
|
} # Forwarding required
|
|
} # Source Zone Loop
|
|
|
|
progress_message ' Finishing matrix...';
|
|
#
|
|
# Make sure that the 1:1 NAT jumps are last in PREROUTING
|
|
#
|
|
addnatjump 'PREROUTING' , 'nat_in';
|
|
addnatjump $globals{POSTROUTING} , 'nat_out';
|
|
|
|
add_interface_jumps @interfaces unless $interface_jumps_added;
|
|
|
|
unless ( $config{COMPLETE} ) {
|
|
for ( unmanaged_interfaces ) {
|
|
my $physical = get_physical $_;
|
|
add_ijump( $filter_table->{INPUT}, j => 'ACCEPT', i => $physical );
|
|
add_ijump( $filter_table->{OUTPUT}, j => 'ACCEPT', o => $physical );
|
|
}
|
|
|
|
complete_standard_chain $filter_table->{INPUT} , 'all' , firewall_zone , 'DROP';
|
|
complete_standard_chain $filter_table->{OUTPUT} , firewall_zone , 'all', 'REJECT';
|
|
complete_standard_chain $filter_table->{FORWARD} , 'all' , 'all', 'REJECT';
|
|
}
|
|
|
|
if ( $config{LOGALLNEW} ) {
|
|
my %builtins = ( mangle => [ qw/PREROUTING INPUT FORWARD POSTROUTING/ ] ,
|
|
nat=> [ qw/PREROUTING OUTPUT POSTROUTING/ ] ,
|
|
filter=> [ qw/INPUT FORWARD OUTPUT/ ] );
|
|
|
|
my $origin = $origin{LOGALLNEW};
|
|
|
|
for my $table ( qw/mangle nat filter/ ) {
|
|
for my $chain ( @{$builtins{$table}} ) {
|
|
log_rule_limit( $config{LOGALLNEW} ,
|
|
$chain_table{$table}{$chain} ,
|
|
$table ,
|
|
$chain ,
|
|
'' ,
|
|
'' ,
|
|
'insert' ,
|
|
state_match('NEW') ,
|
|
$origin );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#
|
|
# Generate MSS rules
|
|
#
|
|
sub setup_mss( ) {
|
|
my $clampmss = $config{CLAMPMSS};
|
|
my $option;
|
|
my @match;
|
|
my $chainref = $mangle_table->{FORWARD};
|
|
|
|
if ( $clampmss ) {
|
|
if ( "\L$clampmss" eq 'yes' ) {
|
|
$option = '--clamp-mss-to-pmtu';
|
|
} else {
|
|
@match = ( tcpmss => "--mss $clampmss:" ) if have_capability( 'TCPMSS_MATCH' );
|
|
$option = "--set-mss $clampmss";
|
|
}
|
|
|
|
push @match, ( policy => '--pol none --dir out' ) if have_ipsec;
|
|
}
|
|
|
|
my $interfaces = find_interfaces_by_option( 'mss' );
|
|
|
|
if ( @$interfaces ) {
|
|
#
|
|
# Since we will need multiple rules, we create a separate chain
|
|
#
|
|
$chainref = new_chain 'filter', 'settcpmss';
|
|
#
|
|
# Send all forwarded SYN packets to the 'settcpmss' chain
|
|
#
|
|
add_ijump $filter_table->{FORWARD} , j => $chainref, p => 'tcp --tcp-flags SYN,RST SYN';
|
|
|
|
my @in_match = ();
|
|
my @out_match = ();
|
|
|
|
if ( have_ipsec ) {
|
|
@in_match = ( policy => '--pol none --dir in' );
|
|
@out_match = ( policy => '--pol none --dir out' );
|
|
}
|
|
|
|
for ( @$interfaces ) {
|
|
my $mss = get_interface_option( $_, 'mss' );
|
|
my @mssmatch = have_capability( 'TCPMSS_MATCH' ) ? ( tcpmss => "--mss $mss:" ) : ();
|
|
my @source = imatch_source_dev $_;
|
|
my @dest = imatch_dest_dev $_;
|
|
add_ijump $chainref, j => 'TCPMSS', targetopts => "--set-mss $mss", @dest, p => 'tcp --tcp-flags SYN,RST SYN', @mssmatch, @out_match;
|
|
add_ijump $chainref, j => 'RETURN', @dest if $clampmss;
|
|
add_ijump $chainref, j => 'TCPMSS', targetopts => "--set-mss $mss", @source, p => 'tcp --tcp-flags SYN,RST SYN', @mssmatch, @in_match;
|
|
add_ijump $chainref, j => 'RETURN', @source if $clampmss;
|
|
}
|
|
}
|
|
|
|
add_ijump $chainref , j => 'TCPMSS', targetopts => $option, p => 'tcp --tcp-flags SYN,RST SYN', @match if $clampmss;
|
|
}
|
|
|
|
#
|
|
# Compile the stop_firewall() function
|
|
#
|
|
sub compile_stop_firewall( $$$$ ) {
|
|
my ( $test, $export, $have_arptables, $convert ) = @_;
|
|
|
|
my $input = $filter_table->{INPUT};
|
|
my $output = $filter_table->{OUTPUT};
|
|
my $forward = $filter_table->{FORWARD};
|
|
|
|
emit <<'EOF';
|
|
#
|
|
# Stop/restore the firewall after an error or because of a 'stop' or 'clear' command
|
|
#
|
|
stop_firewall() {
|
|
EOF
|
|
$output->{policy} = 'ACCEPT' if $config{ADMINISABSENTMINDED};
|
|
|
|
if ( $family == F_IPV4 ) {
|
|
emit <<'EOF';
|
|
deletechain() {
|
|
qt $IPTABLES -L $1 -n && qt $IPTABLES -F $1 && qt $IPTABLES -X $1
|
|
}
|
|
|
|
case $COMMAND in
|
|
stop|clear|restore)
|
|
if chain_exists dynamic; then
|
|
${IPTABLES}-save -t filter | grep '^-A dynamic' | fgrep -v -- '-j ACCEPT' > ${VARDIR}/.dynamic
|
|
fi
|
|
;;
|
|
*)
|
|
set +x
|
|
EOF
|
|
} else {
|
|
emit <<'EOF';
|
|
deletechain() {
|
|
qt $IPTABLES -L $1 -n && qt $IPTABLES -F $1 && qt $IPTABLES -X $1
|
|
}
|
|
|
|
case $COMMAND in
|
|
stop|clear|restore)
|
|
if chain_exists dynamic; then
|
|
${IP6TABLES}-save -t filter | grep '^-A dynamic' | fgrep -v -- '-j ACCEPT' > ${VARDIR}/.dynamic
|
|
fi
|
|
;;
|
|
*)
|
|
set +x
|
|
EOF
|
|
}
|
|
|
|
emit <<'EOF';
|
|
case $COMMAND in
|
|
start)
|
|
mylogger kern.err "ERROR:$g_product start failed"
|
|
;;
|
|
reload)
|
|
mylogger kern.err "ERROR:$g_product reload failed"
|
|
;;
|
|
enable)
|
|
mylogger kern.err "ERROR:$g_product 'enable $g_interface' failed"
|
|
;;
|
|
esac
|
|
|
|
if [ "$RESTOREFILE" = NONE ]; then
|
|
COMMAND=clear
|
|
clear_firewall
|
|
echo "$g_product Cleared"
|
|
|
|
kill $$
|
|
exit 2
|
|
else
|
|
g_restorepath=${VARDIR}/$RESTOREFILE
|
|
|
|
if [ -x $g_restorepath ]; then
|
|
echo Restoring ${g_product:=Shorewall}...
|
|
|
|
g_recovering=Yes
|
|
|
|
if run_it $g_restorepath restore; then
|
|
echo "$g_product restored from $g_restorepath"
|
|
set_state "Restored from $g_restorepath"
|
|
else
|
|
set_state "Unknown"
|
|
fi
|
|
|
|
kill $$
|
|
exit 2
|
|
fi
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
if [ -n "$g_stopping" ]; then
|
|
kill $$
|
|
exit 1
|
|
fi
|
|
|
|
set_state "Stopping"
|
|
|
|
g_stopping="Yes"
|
|
|
|
deletechain shorewall
|
|
|
|
run_stop_exit
|
|
|
|
#
|
|
# Enable automatic helper association on kernel 3.5.0 and later
|
|
#
|
|
if [ $COMMAND = clear -a -f /proc/sys/net/netfilter/nf_conntrack_helper ]; then
|
|
echo 1 > /proc/sys/net/netfilter/nf_conntrack_helper
|
|
fi
|
|
EOF
|
|
|
|
if ( $config{DOCKER} ) {
|
|
push_indent;
|
|
emit( 'if [ $COMMAND = stop ]; then' );
|
|
push_indent;
|
|
save_docker_rules( $family == F_IPV4 ? '${IPTABLES}' : '${IP6TABLES}');
|
|
pop_indent;
|
|
emit( "fi\n");
|
|
pop_indent;
|
|
}
|
|
|
|
if ( have_capability( 'NAT_ENABLED' ) ) {
|
|
emit<<'EOF';
|
|
if [ -f ${VARDIR}/nat ]; then
|
|
while read external interface; do
|
|
del_ip_addr $external $interface
|
|
done < ${VARDIR}/nat
|
|
|
|
rm -f ${VARDIR}/nat
|
|
fi
|
|
EOF
|
|
}
|
|
|
|
if ( $family == F_IPV4 ) {
|
|
emit <<'EOF';
|
|
if [ -f ${VARDIR}/proxyarp ]; then
|
|
while read address interface external haveroute; do
|
|
qtnoin $IP -4 neigh del proxy $address dev $external
|
|
[ -z "${haveroute}${g_noroutes}" ] && qtnoin $IP -4 route del $address/32 dev $interface
|
|
f=/proc/sys/net/ipv4/conf/$interface/proxy_arp
|
|
[ -f $f ] && echo 0 > $f
|
|
done < ${VARDIR}/proxyarp
|
|
|
|
rm -f ${VARDIR}/proxyarp
|
|
fi
|
|
EOF
|
|
} else {
|
|
emit <<'EOF';
|
|
if [ -f ${VARDIR}/proxyndp ]; then
|
|
while read address interface external haveroute; do
|
|
qtnoin $IP -6 neigh del proxy $address dev $external
|
|
[ -z "${haveroute}${g_noroutes}" ] && qtnoin $IP -6 route del $address/128 dev $interface
|
|
f=/proc/sys/net/ipv4/conf/$interface/proxy_ndp
|
|
[ -f $f ] && echo 0 > $f
|
|
done < ${VARDIR}/proxyndp
|
|
|
|
rm -f ${VARDIR}/proxyndp
|
|
fi
|
|
EOF
|
|
}
|
|
|
|
push_indent;
|
|
|
|
emit 'delete_tc1' if $config{CLEAR_TC};
|
|
|
|
emit( 'undo_routing',
|
|
"restore_default_route $config{USE_DEFAULT_RT}"
|
|
);
|
|
#
|
|
# Insure that Docker jumps are early in the builtin chains
|
|
#
|
|
create_docker_rules if $config{DOCKER};
|
|
|
|
if ( $config{ADMINISABSENTMINDED} ) {
|
|
add_ijump $filter_table ->{$_}, j => 'ACCEPT', state_imatch 'ESTABLISHED,RELATED' for qw/INPUT FORWARD/;
|
|
}
|
|
|
|
if ( $family == F_IPV6 ) {
|
|
add_ijump $input, j => 'ACCEPT', s => IPv6_LINKLOCAL;
|
|
add_ijump $input, j => 'ACCEPT', d => IPv6_LINKLOCAL;
|
|
add_ijump $input, j => 'ACCEPT', d => IPv6_MULTICAST;
|
|
|
|
unless ( $config{ADMINISABSENTMINDED} ) {
|
|
add_ijump $output, j => 'ACCEPT', d => IPv6_LINKLOCAL;
|
|
add_ijump $output, j => 'ACCEPT', d => IPv6_MULTICAST;
|
|
}
|
|
}
|
|
|
|
if ( $convert ) {
|
|
convert_routestopped;
|
|
} elsif ( -f ( my $fn = find_file 'routestopped' ) ) {
|
|
warning_message "The routestopped file is no longer supported - use '$product update' to convert $fn to an equivalent 'stoppedrules' file";
|
|
}
|
|
|
|
process_stoppedrules;
|
|
|
|
if ( have_capability 'IFACE_MATCH' ) {
|
|
add_ijump $input, j => 'ACCEPT', iface => '--dev-in --loopback';
|
|
add_ijump $output, j => 'ACCEPT', iface => '--dev-out --loopback' unless $config{ADMINISABSENTMINDED};
|
|
} else {
|
|
add_ijump $input, j => 'ACCEPT', i => loopback_interface;
|
|
add_ijump $output, j => 'ACCEPT', o => loopback_interface unless $config{ADMINISABSENTMINDED};
|
|
}
|
|
|
|
my $interfaces = find_interfaces_by_option 'dhcp';
|
|
|
|
if ( @$interfaces ) {
|
|
my $ports = $family == F_IPV4 ? '67:68' : '546:547';
|
|
|
|
for my $interface ( @$interfaces ) {
|
|
add_ijump $input, j => 'ACCEPT', p => "udp --dport $ports", imatch_source_dev( $interface );
|
|
add_ijump $output, j => 'ACCEPT', p => "udp --dport $ports", imatch_dest_dev( $interface ) unless $config{ADMINISABSENTMINDED};
|
|
#
|
|
# This might be a bridge
|
|
#
|
|
add_ijump $forward, j => 'ACCEPT', p => "udp --dport $ports", imatch_source_dev( $interface ), imatch_dest_dev( $interface );
|
|
}
|
|
}
|
|
|
|
emit '';
|
|
|
|
create_stop_load $test;
|
|
|
|
if ( $family == F_IPV4 ) {
|
|
emit( '$ARPTABLES -F',
|
|
'' ) if $have_arptables;
|
|
if ( $config{IP_FORWARDING} eq 'on' ) {
|
|
emit( 'echo 1 > /proc/sys/net/ipv4/ip_forward',
|
|
'progress_message2 IPv4 Forwarding Enabled' );
|
|
} elsif ( $config{IP_FORWARDING} eq 'off' ) {
|
|
emit( 'echo 0 > /proc/sys/net/ipv4/ip_forward',
|
|
'progress_message2 IPv4 Forwarding Disabled!'
|
|
);
|
|
}
|
|
} else {
|
|
for my $interface ( all_bridges ) {
|
|
emit "do_iptables -A FORWARD -p 58 " . match_source_dev( $interface ) . match_dest_dev( $interface ) . "-j ACCEPT";
|
|
}
|
|
|
|
if ( $config{IP_FORWARDING} eq 'on' ) {
|
|
emit( 'echo 1 > /proc/sys/net/ipv6/conf/all/forwarding',
|
|
'progress_message2 IPv6 Forwarding Enabled' );
|
|
} elsif ( $config{IP_FORWARDING} eq 'off' ) {
|
|
emit( 'echo 0 > /proc/sys/net/ipv6/conf/all/forwarding',
|
|
'progress_message2 IPv6 Forwarding Disabled!'
|
|
);
|
|
}
|
|
}
|
|
|
|
pop_indent;
|
|
|
|
emit '
|
|
rm -f ${VARDIR}/*.address
|
|
rm -f ${VARDIR}/*.gateway
|
|
|
|
run_stopped_exit';
|
|
|
|
my @ipsets = all_ipsets;
|
|
|
|
if ( @ipsets || @{$globals{SAVED_IPSETS}} || ( $config{SAVE_IPSETS} && have_ipset_rules ) ) {
|
|
emit( '',
|
|
' save_ipsets ${VARDIR}/ipsets.save' );
|
|
}
|
|
|
|
emit '
|
|
|
|
set_state "Stopped"
|
|
mylogger kern.info "$g_product Stopped"
|
|
|
|
case $COMMAND in
|
|
stop|clear)
|
|
;;
|
|
*)
|
|
#
|
|
# The firewall is being stopped when we were trying to do something
|
|
# else. Kill the shell in case we\'re running in a subshell
|
|
#
|
|
kill $$
|
|
;;
|
|
esac
|
|
}
|
|
';
|
|
|
|
}
|
|
|
|
1;
|