"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "src/ferm" between
ferm-2.5.1.tar.xz and ferm-2.6.tar.xz

About: ferm is a tool to maintain and setup complicated firewall rules.

ferm  (ferm-2.5.1.tar.xz):ferm  (ferm-2.6.tar.xz)
#!/usr/bin/perl #!/usr/bin/perl
# #
# ferm, a firewall setup program that makes firewall rules easy! # ferm, a firewall setup program that makes firewall rules easy!
# #
# Copyright 2001-2017 Max Kellermann, Auke Kok # Copyright 2001-2021 Max Kellermann, Auke Kok
# #
# Bug reports and patches for this program may be sent to the GitHub # Bug reports and patches for this program may be sent to the GitHub
# repository: L<https://github.com/MaxKellermann/ferm> # repository: L<https://github.com/MaxKellermann/ferm>
# #
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or # the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version. # (at your option) any later version.
skipping to change at line 53 skipping to change at line 53
} }
eval { require Getopt::Long; import Getopt::Long; }; eval { require Getopt::Long; import Getopt::Long; };
$has_getopt = not $@; $has_getopt = not $@;
} }
use vars qw($has_strict $has_getopt); use vars qw($has_strict $has_getopt);
use vars qw($VERSION); use vars qw($VERSION);
$VERSION = '2.5.1'; $VERSION = '2.6';
#$VERSION .= '~git'; #$VERSION .= '~git';
## interface variables ## interface variables
# %option = command line and other options # %option = command line and other options
use vars qw(%option); use vars qw(%option);
## hooks ## hooks
use vars qw(@pre_hooks @post_hooks @flush_hooks); use vars qw(@pre_hooks @post_hooks @flush_hooks);
## parser variables ## parser variables
skipping to change at line 76 skipping to change at line 76
# $auto_chain = index for the next auto-generated chain # $auto_chain = index for the next auto-generated chain
use vars qw($script @stack $auto_chain); use vars qw($script @stack $auto_chain);
## netfilter variables ## netfilter variables
# %domains = state information about all domains ("ip" and "ip6") # %domains = state information about all domains ("ip" and "ip6")
# - initialized: domain initialization is done # - initialized: domain initialization is done
# - tools: hash providing the paths of the domain's tools # - tools: hash providing the paths of the domain's tools
# - previous: save file of the previous ruleset, for rollback # - previous: save file of the previous ruleset, for rollback
# - tables{$name}: ferm state information about tables # - tables{$name}: ferm state information about tables
# - has_builtin: whether built-in chains have been determined in this table # - has_builtin: whether built-in chains have been determined in this table
# - preserve_regexes: regular expressions for dynamically preserved chains
# - chains{$chain}: ferm state information about the chains # - chains{$chain}: ferm state information about the chains
# - builtin: whether this is a built-in chain # - builtin: whether this is a built-in chain
use vars qw(%domains); use vars qw(%domains);
## constants ## constants
use vars qw(%deprecated_keywords); use vars qw(%deprecated_keywords);
# keywords from ferm 1.1 which are deprecated, and the new one; these # keywords from ferm 1.1 which are deprecated, and the new one; these
# are automatically replaced, and a warning is printed # are automatically replaced, and a warning is printed
%deprecated_keywords = ( realgoto => 'goto', %deprecated_keywords = ( realgoto => 'goto',
); );
# these hashes provide the Netfilter module definitions # these hashes provide the Netfilter module definitions
use vars qw(%proto_defs %match_defs %target_defs); use vars qw(%proto_defs %match_defs %target_defs);
# these are ebtables tables that we manage
use vars qw(@eb_tables);
@eb_tables = qw(filter nat broute);
# #
# This subsubsystem allows you to support (most) new netfilter modules # This subsubsystem allows you to support (most) new netfilter modules
# in ferm. Add a call to one of the "add_XY_def()" functions below. # in ferm. Add a call to one of the "add_XY_def()" functions below.
# #
# Ok, now about the cryptic syntax: the function "add_XY_def()" # Ok, now about the cryptic syntax: the function "add_XY_def()"
# registers a new module. There are three kinds of modules: protocol # registers a new module. There are three kinds of modules: protocol
# module (e.g. TCP, ICMP), match modules (e.g. state, physdev) and # module (e.g. TCP, ICMP), match modules (e.g. state, physdev) and
# target modules (e.g. DNAT, MARK). # target modules (e.g. DNAT, MARK).
# #
# The first parameter is always the module name which is passed to # The first parameter is always the module name which is passed to
skipping to change at line 319 skipping to change at line 324
add_match_def 'u32', qw(!u32=m); add_match_def 'u32', qw(!u32=m);
add_target_def 'AUDIT', qw(type); add_target_def 'AUDIT', qw(type);
add_target_def 'BALANCE', qw(to-destination to:=to-destination); add_target_def 'BALANCE', qw(to-destination to:=to-destination);
add_target_def 'CHECKSUM', qw(checksum-fill*0); add_target_def 'CHECKSUM', qw(checksum-fill*0);
add_target_def 'CLASSIFY', qw(set-class); add_target_def 'CLASSIFY', qw(set-class);
add_target_def 'CLUSTERIP', qw(new*0 hashmode clustermac total-nodes local-node hash-init); add_target_def 'CLUSTERIP', qw(new*0 hashmode clustermac total-nodes local-node hash-init);
add_target_def 'CONNMARK', qw(set-xmark save-mark*0 restore-mark*0 nfmask ctmask ), add_target_def 'CONNMARK', qw(set-xmark save-mark*0 restore-mark*0 nfmask ctmask ),
qw(and-mark or-mark xor-mark set-mark mask); qw(and-mark or-mark xor-mark set-mark mask);
add_target_def 'CONNSECMARK', qw(save*0 restore*0); add_target_def 'CONNSECMARK', qw(save*0 restore*0);
add_target_def 'CT', qw(notrack*0 helper ctevents=c expevents=c zone timeout); add_target_def 'CT', qw(notrack*0 helper ctevents=c expevents=c zone-orig zone-r eply zone timeout);
add_target_def 'DNAT', qw(to-destination=m to:=to-destination persistent*0 rando m*0); add_target_def 'DNAT', qw(to-destination=m to:=to-destination persistent*0 rando m*0);
add_target_def 'DNPT', qw(src-pfx dst-pfx); add_target_def 'DNPT', qw(src-pfx dst-pfx);
add_target_def 'DSCP', qw(set-dscp set-dscp-class); add_target_def 'DSCP', qw(set-dscp set-dscp-class);
add_target_def 'ECN', qw(ecn-tcp-remove*0); add_target_def 'ECN', qw(ecn-tcp-remove*0);
add_target_def 'HL', qw(hl-set hl-dec hl-inc); add_target_def 'HL', qw(hl-set hl-dec hl-inc);
add_target_def 'HMARK', qw(hmark-tuple hmark-mod hmark-offset), add_target_def 'HMARK', qw(hmark-tuple hmark-mod hmark-offset),
qw(hmark-src-prefix hmark-dst-prefix hmark-sport-mask), qw(hmark-src-prefix hmark-dst-prefix hmark-sport-mask),
qw(hmark-dport-mask hmark-spi-mask hmark-proto-mask hmark-rnd); qw(hmark-dport-mask hmark-spi-mask hmark-proto-mask hmark-rnd);
add_target_def 'IDLETIMER', qw(timeout label); add_target_def 'IDLETIMER', qw(timeout label);
add_target_def 'IPV4OPTSSTRIP'; add_target_def 'IPV4OPTSSTRIP';
add_target_def 'JOOL', qw(instance);
add_target_def 'JOOL_SIIT', qw(instance);
add_target_def 'LED', qw(led-trigger-id led-delay led-always-blink*0); add_target_def 'LED', qw(led-trigger-id led-delay led-always-blink*0);
add_target_def 'LOG', qw(log-level log-prefix), add_target_def 'LOG', qw(log-level log-prefix),
qw(log-tcp-sequence*0 log-tcp-options*0 log-ip-options*0 log-uid*0); qw(log-tcp-sequence*0 log-tcp-options*0 log-ip-options*0 log-uid*0);
add_target_def 'MARK', qw(set-mark set-xmark and-mark or-mark xor-mark); add_target_def 'MARK', qw(set-mark set-xmark and-mark or-mark xor-mark);
add_target_def 'MASQUERADE', qw(to-ports random*0); add_target_def 'MASQUERADE', qw(to-ports random*0);
add_target_def 'MIRROR'; add_target_def 'MIRROR';
add_target_def 'NETMAP', qw(to); add_target_def 'NETMAP', qw(to);
add_target_def 'NFLOG', qw(nflog-group nflog-prefix nflog-range nflog-threshold) ; add_target_def 'NFLOG', qw(nflog-group nflog-prefix nflog-range nflog-threshold) ;
add_target_def 'NFQUEUE', qw(queue-num queue-balance queue-bypass*0 queue-cpu-fa nout*0); add_target_def 'NFQUEUE', qw(queue-num queue-balance queue-bypass*0 queue-cpu-fa nout*0);
add_target_def 'NOTRACK'; add_target_def 'NOTRACK';
skipping to change at line 551 skipping to change at line 558
} }
sub address_magic { sub address_magic {
my $rule = shift; my $rule = shift;
my $domain = $rule->{domain}; my $domain = $rule->{domain};
my $value = getvalues(undef, allow_negation => 1); my $value = getvalues(undef, allow_negation => 1);
my @ips; my @ips;
my $negated = 0; my $negated = 0;
if (ref $value and ref $value eq 'ARRAY') { if (ref $value and ref $value eq 'ARRAY') {
foreach my $inside_value (@$value) { @ips = realize_deferred($domain, @$value);
# realize deferred values even within arrays } elsif (ref $value and ref $value eq 'deferred') {
if (ref $inside_value and ref $inside_value eq 'deferred') { @ips = realize_deferred($domain, $value);
my @args = @$inside_value;
my $function = shift @args;
push @ips, &$function($domain, @args);
} else {
push @ips, $inside_value;
}
}
} elsif (ref $value and ref $value eq 'negated') { } elsif (ref $value and ref $value eq 'negated') {
@ips = @$value; @ips = realize_deferred($domain, @$value);
$negated = 1; $negated = 1;
} elsif (ref $value and ref $value eq 'deferred') {
my @args = @$value;
my $function = shift @args;
@ips = &$function($domain, @args);
} elsif (ref $value) { } elsif (ref $value) {
die; die;
} else { } else {
@ips = ($value); @ips = ($value);
} }
# only do magic on domain (ip ip6); do not process on a single-stack rule # only do magic on domain (ip ip6); do not process on a single-stack rule
# as to let admins spot their errors instead of silently ignoring them # as to let admins spot their errors instead of silently ignoring them
@ips = ipfilter($domain, \@ips) if defined $rule->{domain_both}; @ips = ipfilter($domain, \@ips) if defined $rule->{domain_both};
skipping to change at line 628 skipping to change at line 624
# initialize stack: command line definitions # initialize stack: command line definitions
unshift @stack, {}; unshift @stack, {};
# Get command line stuff # Get command line stuff
if ($has_getopt) { if ($has_getopt) {
my ($opt_noexec, $opt_flush, $opt_noflush, $opt_lines, $opt_interactive, my ($opt_noexec, $opt_flush, $opt_noflush, $opt_lines, $opt_interactive,
$opt_timeout, $opt_help, $opt_timeout, $opt_help,
$opt_version, $opt_test, $opt_fast, $opt_slow, $opt_shell, $opt_version, $opt_test, $opt_fast, $opt_slow, $opt_shell,
$opt_domain); $opt_domain);
my @opt_test_mock_previous;
Getopt::Long::Configure('bundling', 'auto_help', 'no_ignore_case', Getopt::Long::Configure('bundling', 'auto_help', 'no_ignore_case',
'no_auto_abbrev'); 'no_auto_abbrev');
sub opt_def { sub opt_def {
my ($opt, $value) = @_; my ($opt, $value) = @_;
die 'Invalid --def specification' die 'Invalid --def specification'
unless $value =~ /^\$?(\w+)=(.*)$/s; unless $value =~ /^\$?(\w+)=(.*)$/s;
my ($name, $unparsed_value) = ($1, $2); my ($name, $unparsed_value) = ($1, $2);
my $tokens = tokenize_string($unparsed_value); my $tokens = tokenize_string($unparsed_value);
skipping to change at line 654 skipping to change at line 651
local $SIG{__WARN__} = sub { die $_[0]; }; local $SIG{__WARN__} = sub { die $_[0]; };
GetOptions('noexec|n' => \$opt_noexec, GetOptions('noexec|n' => \$opt_noexec,
'flush|F' => \$opt_flush, 'flush|F' => \$opt_flush,
'noflush' => \$opt_noflush, 'noflush' => \$opt_noflush,
'lines|l' => \$opt_lines, 'lines|l' => \$opt_lines,
'interactive|i' => \$opt_interactive, 'interactive|i' => \$opt_interactive,
'timeout|t=s' => \$opt_timeout, 'timeout|t=s' => \$opt_timeout,
'help|h' => \$opt_help, 'help|h' => \$opt_help,
'version|V' => \$opt_version, 'version|V' => \$opt_version,
test => \$opt_test, test => \$opt_test,
# to mock the previous ruleset (for unit tests)
'test-mock-previous=s' => \@opt_test_mock_previous,
remote => \$opt_test, remote => \$opt_test,
fast => \$opt_fast, fast => \$opt_fast,
slow => \$opt_slow, slow => \$opt_slow,
shell => \$opt_shell, shell => \$opt_shell,
'domain=s' => \$opt_domain, 'domain=s' => \$opt_domain,
'def=s' => \&opt_def, 'def=s' => \&opt_def,
); );
if (defined $opt_help) { if (defined $opt_help) {
require Pod::Usage; require Pod::Usage;
skipping to change at line 682 skipping to change at line 683
$option{noexec} = $opt_noexec || $opt_test; $option{noexec} = $opt_noexec || $opt_test;
$option{flush} = $opt_flush; $option{flush} = $opt_flush;
$option{noflush} = $opt_noflush; $option{noflush} = $opt_noflush;
$option{lines} = $opt_lines || $opt_test || $opt_shell; $option{lines} = $opt_lines || $opt_test || $opt_shell;
$option{interactive} = $opt_interactive && !$opt_noexec; $option{interactive} = $opt_interactive && !$opt_noexec;
$option{timeout} = defined $opt_timeout ? $opt_timeout : "30"; $option{timeout} = defined $opt_timeout ? $opt_timeout : "30";
$option{test} = $opt_test; $option{test} = $opt_test;
$option{fast} = !$opt_slow; $option{fast} = !$opt_slow;
$option{shell} = $opt_shell; $option{shell} = $opt_shell;
foreach (@opt_test_mock_previous) {
die unless /^(\w+)=(.+)$/;
$option{mock_previous} ||= {};
$option{mock_previous}{$1} = $2;
}
die("ferm interactive mode not possible: /dev/stdin is not a tty\n") die("ferm interactive mode not possible: /dev/stdin is not a tty\n")
if $option{interactive} and not -t STDIN; if $option{interactive} and not -t STDIN;
die("ferm interactive mode not possible: /dev/stderr is not a tty\n") die("ferm interactive mode not possible: /dev/stderr is not a tty\n")
if $option{interactive} and not -t STDERR; if $option{interactive} and not -t STDERR;
die("ferm timeout has no sense without interactive mode") die("ferm timeout has no sense without interactive mode")
if not $opt_interactive and defined $opt_timeout; if not $opt_interactive and defined $opt_timeout;
die("invalid timeout. must be an integer") die("invalid timeout. must be an integer")
if defined $opt_timeout and not $opt_timeout =~ /^[+-]?\d+$/; if defined $opt_timeout and not $opt_timeout =~ /^[+-]?\d+$/;
$option{domain} = $opt_domain if defined $opt_domain; $option{domain} = $opt_domain if defined $opt_domain;
skipping to change at line 816 skipping to change at line 823
} }
exit 0; exit 0;
# end of program execution! # end of program execution!
# funcs # funcs
sub printversion { sub printversion {
print "ferm $VERSION\n"; print "ferm $VERSION\n";
print "Copyright 2001-2017 Max Kellermann, Auke Kok\n"; print "Copyright 2001-2021 Max Kellermann, Auke Kok\n";
print "This program is free software released under GPLv2.\n"; print "This program is free software released under GPLv2.\n";
print "See the included COPYING file for license details.\n"; print "See the included COPYING file for license details.\n";
} }
sub error { sub error {
# returns a nice formatted error message, showing the # returns a nice formatted error message, showing the
# location of the error. # location of the error.
my $tabs = 0; my $tabs = 0;
my @lines; my @lines;
my $l = 0; my $l = 0;
skipping to change at line 890 skipping to change at line 897
} }
} }
foreach my $path (@path) { foreach my $path (@path) {
my $ret = "$path/$name"; my $ret = "$path/$name";
return $ret if -x $ret; return $ret if -x $ret;
} }
die "$name not found in PATH\n"; die "$name not found in PATH\n";
} }
sub read_previous($$) {
my ($fh, $domain_info) = @_;
my $save = '';
my $table_info;
while (<$fh>) {
$save .= $_;
if (/^\*(\w+)/) {
my $table = $1;
$table_info = $domain_info->{tables}{$table} ||= {};
} elsif (defined $table_info and /^:(\w+)\s+(\S+)/
and $2 ne '-') {
$table_info->{chains}{$1}{builtin} = 1;
$table_info->{has_builtin} = 1;
}
}
return $save;
}
sub initialize_domain { sub initialize_domain {
my $domain = shift; my $domain = shift;
my $domain_info = $domains{$domain} ||= {}; my $domain_info = $domains{$domain} ||= {};
return if exists $domain_info->{initialized}; return if exists $domain_info->{initialized};
die "Invalid domain '$domain'\n" unless $domain =~ /^(?:ip6?|arp|eb)$/; die "Invalid domain '$domain'\n" unless $domain =~ /^(?:ip6?|arp|eb)$/;
my @tools = qw(tables); my @tools = qw(tables);
push @tools, qw(tables-save tables-restore) push @tools, qw(tables-save tables-restore)
if $domain =~ /^ip6?$/; if $domain =~ /^ip6?$/;
# determine the location of this domain's tools # determine the location of this domain's tools
my %tools = map { $_ => find_tool($domain . $_) } @tools; my %tools = map { $_ => find_tool($domain . $_) } @tools;
$domain_info->{tools} = \%tools; $domain_info->{tools} = \%tools;
# make tables-save tell us about the state of this domain # make tables-save tell us about the state of this domain
# (which tables and chains do exist?), also remember the old # (which tables and chains do exist?), also remember the old
# save data which may be used later by the rollback function # save data which may be used later by the rollback function
local *SAVE; local *SAVE;
if (!$option{test} && if ($option{test}) {
exists $tools{'tables-save'} && if (exists $option{mock_previous} and
open(SAVE, "$tools{'tables-save'}|")) { exists $option{mock_previous}{$domain}) {
my $save = ''; open SAVE, $option{mock_previous}{$domain} or die $!;
$domain_info->{previous} = read_previous(\*SAVE, $domain_info);
my $table_info;
while (<SAVE>) {
$save .= $_;
if (/^\*(\w+)/) {
my $table = $1;
$table_info = $domain_info->{tables}{$table} ||= {};
} elsif (defined $table_info and /^:(\w+)\s+(\S+)/
and $2 ne '-') {
$table_info->{chains}{$1}{builtin} = 1;
$table_info->{has_builtin} = 1;
}
} }
} elsif (exists $tools{'tables-save'} &&
open(SAVE, "$tools{'tables-save'}|")) {
# for rollback # for rollback
$domain_info->{previous} = $save; $domain_info->{previous} = read_previous(\*SAVE, $domain_info);
} }
if ($option{shell} && $option{interactive} && if ($option{shell} && $option{interactive} &&
exists $tools{'tables-save'}) { exists $tools{'tables-save'}) {
print LINES "${domain}_tmp=\$(mktemp ferm.XXXXXXXXXX)\n"; print LINES "${domain}_tmp=\$(mktemp ferm.XXXXXXXXXX)\n";
print LINES "$tools{'tables-save'} >\$${domain}_tmp\n"; print LINES "$tools{'tables-save'} >\$${domain}_tmp\n";
} }
if ($domain eq 'eb') { if ($domain eq 'eb') {
my $tempfile = File::Temp->new(TEMPLATE => 'ferm.XXXXXXXXXX', TMPDIR =>
1, OPEN => 0, UNLINK => 1);
my $filename = $tempfile->filename;
my $domain_cmd = $domain_info->{tools}{tables}; my $domain_cmd = $domain_info->{tools}{tables};
execute_command("$domain_cmd --atomic-file $filename --atomic-save"); foreach my $eb_table (@eb_tables) {
$domain_info->{ebt_previous} = $tempfile; my $tempfile = File::Temp->new(TEMPLATE => 'ferm.XXXXXXXXXX', TMPDIR
=> 1, OPEN => 0, UNLINK => 1);
my $filename = $tempfile->filename;
execute_command("$domain_cmd -t $eb_table --atomic-file $filename --
atomic-save");
$domain_info->{ebt_previous}->{$eb_table} = $tempfile;
}
} }
$domain_info->{initialized} = 1; $domain_info->{initialized} = 1;
} }
sub check_domain($) { sub check_domain($) {
my $domain = shift; my $domain = shift;
my @result; my @result;
return if exists $option{domain} return if exists $option{domain}
skipping to change at line 1280 skipping to change at line 1300
if $@; if $@;
$resolver = new Net::DNS::Resolver::Mock; $resolver = new Net::DNS::Resolver::Mock;
my $parent_dir = $script->{filename} =~ m,^(.*/), my $parent_dir = $script->{filename} =~ m,^(.*/),
? $1 : './'; ? $1 : './';
$resolver->zonefile_read($parent_dir . 'zonefile'); $resolver->zonefile_read($parent_dir . 'zonefile');
} }
return $resolver; return $resolver;
} }
sub identify_numeric_address($) {
$_ = shift;
s,(?:/\d+)$,,; # allow netmask prefix
return 'A' if /^\d+\.\d+\.\d+\.\d+$/;
return 'AAAA' if /^[0-9a-fA-F]*:[0-9a-fA-F:]*:[0-9a-fA-F:]*$/;
return;
}
sub resolve($@) { sub resolve($@) {
my ($domain, $names, $type) = @_; my ($domain, $names, $type) = @_;
my @names = to_array($names); my @names = to_array($names);
error('String expected') if ref $type; error('String expected') if ref $type;
my $resolver = pick_resolver(); my $resolver = pick_resolver();
$type = ($domain eq 'ip6') ? 'AAAA' : 'A' $type = ($domain eq 'ip6') ? 'AAAA' : 'A'
unless $type; unless $type;
my @result; my @result;
foreach my $hostname (@names) { foreach my $hostname (@names) {
if (($type eq 'A' and $hostname =~ /^\d+\.\d+\.\d+\.\d+$/) or my $numeric_type = identify_numeric_address($hostname);
(($type eq 'AAAA' and if (defined $numeric_type) {
$hostname =~ /^[0-9a-fA-F:]*:[0-9a-fA-F:]*$/))) { # It's a numeric IP address, we don't need to resolve it;
push @result, $hostname; # only filter out IP addresses for the wrong domain (no
# IPv4 in ip6tables and no IPv6 in iptables).
push @result, $hostname if $numeric_type eq $type;
next; next;
} }
my $query = $resolver->search($hostname, $type); my $query = $resolver->search($hostname, $type);
unless ($query) { unless ($query) {
if (!$resolver->errorstring || if (!$resolver->errorstring ||
$resolver->errorstring eq 'NOERROR' || $resolver->errorstring eq 'NOERROR' ||
$resolver->errorstring eq 'NXDOMAIN') { $resolver->errorstring eq 'NXDOMAIN') {
# skip NOERROR/NXDOMAINs, i.e. don't error out but return nothin g # skip NOERROR/NXDOMAINs, i.e. don't error out but return nothin g
next; next;
skipping to change at line 1681 skipping to change at line 1711
die if @_; die if @_;
unless (ref $value) { unless (ref $value) {
return $value; return $value;
} elsif (ref $value eq 'ARRAY') { } elsif (ref $value eq 'ARRAY') {
return @$value > 0; return @$value > 0;
} else { } else {
die; die;
} }
} }
sub realize_deferred {
my $domain = shift;
my @values;
foreach my $inside_value (@_) {
# realize deferred values even within arrays
if (ref $inside_value and ref $inside_value eq 'deferred') {
my @args = @$inside_value;
my $function = shift @args;
push @values, &$function($domain, @args);
} else {
push @values, $inside_value;
}
}
return @values;
}
sub is_netfilter_core_target($) { sub is_netfilter_core_target($) {
my $target = shift; my $target = shift;
die unless defined $target and length $target; die unless defined $target and length $target;
return grep { $_ eq $target } qw(ACCEPT DROP RETURN QUEUE); return grep { $_ eq $target } qw(ACCEPT DROP RETURN QUEUE);
} }
sub is_netfilter_module_target($$) { sub is_netfilter_module_target($$) {
my ($domain_family, $target) = @_; my ($domain_family, $target) = @_;
die unless defined $target and length $target; die unless defined $target and length $target;
skipping to change at line 1876 skipping to change at line 1922
if ($$negated_ref && exists $def->{pre_negation}) { if ($$negated_ref && exists $def->{pre_negation}) {
$negated = 1; $negated = 1;
undef $$negated_ref; undef $$negated_ref;
} }
unless (defined $params) { unless (defined $params) {
undef $value; undef $value;
} elsif (ref $params && ref $params eq 'CODE') { } elsif (ref $params && ref $params eq 'CODE') {
$value = &$params($rule); $value = &$params($rule);
} elsif ($params eq 'm') { } elsif ($params eq 'm') {
$value = bless [ to_array getvalues() ], 'multi'; my $domain = $stack[0]{auto}{DOMAIN};
$value = bless [ realize_deferred($domain, to_array getvalues()) ], 'mul
ti';
} elsif ($params =~ /^[a-z]/) { } elsif ($params =~ /^[a-z]/) {
if (exists $def->{negation} and not $negated) { if (exists $def->{negation} and not $negated) {
my $token = peek_token(); my $token = peek_token();
if ($token eq '!') { if ($token eq '!') {
require_next_token(); require_next_token();
$negated = 1; $negated = 1;
} }
} }
my @params; my @params;
skipping to change at line 2328 skipping to change at line 2375
unless $option{test} or exists $domain_info->{previous}; unless $option{test} or exists $domain_info->{previous};
my $chains = $rule{chain}; my $chains = $rule{chain};
foreach my $table (to_array $rule{table}) { foreach my $table (to_array $rule{table}) {
my $table_info = $domain_info->{tables}{$table}; my $table_info = $domain_info->{tables}{$table};
foreach my $chain (to_array $chains) { foreach my $chain (to_array $chains) {
my $chain_info = $table_info->{chains}{$chain}; my $chain_info = $table_info->{chains}{$chain};
error("Cannot \@preserve chain $chain because it is not empty") error("Cannot \@preserve chain $chain because it is not empty")
if exists $chain_info->{rules} and @{$chain_info->{rul es}}; if exists $chain_info->{rules} and @{$chain_info->{rul es}};
$chain_info->{preserve} = 1; # Does this chain look like a regex? If yes, remember
# it and drop it from the chains hash.
if ($chain =~ m,^/(.+)/$,) {
push @{$table_info->{preserve_regexes}}, $1;
delete $table_info->{chains}{$chain};
} else {
$chain_info->{preserve} = 1;
}
} }
} }
new_level(%rule, $prev); new_level(%rule, $prev);
next; next;
} }
# this rule has something which isn't inherited by its # this rule has something which isn't inherited by its
# parent closure. This variable is used in a lot of # parent closure. This variable is used in a lot of
# syntax checks. # syntax checks.
skipping to change at line 2833 skipping to change at line 2887
return; return;
} }
sub execute_slow($$) { sub execute_slow($$) {
my $domain_info = shift; my $domain_info = shift;
my $domain = shift; my $domain = shift;
my $domain_cmd = $domain_info->{tools}{tables}; my $domain_cmd = $domain_info->{tools}{tables};
if ($domain eq 'eb') { if ($domain eq 'eb') {
my $tempfile = File::Temp->new(TEMPLATE => 'ferm.XXXXXXXXXX', TMPDIR => foreach my $eb_table (@eb_tables) {
1, OPEN => 0, UNLINK => 1); my $tempfile = File::Temp->new(TEMPLATE => 'ferm.XXXXXXXXXX', TMPDIR
my $filename = $tempfile->filename; => 1, OPEN => 0, UNLINK => 1);
$domain_info->{ebt_current} = $tempfile; my $filename = $tempfile->filename;
$domain_cmd .= " --atomic-file $filename"; $domain_info->{ebt_current}->{$eb_table} = $tempfile;
execute_command("$domain_cmd --atomic-init"); execute_command("$domain_cmd -t $eb_table --atomic-file $filename --
atomic-init");
execute_command("$domain_cmd -t $eb_table --atomic-file $filename --
init-table");
}
} }
my $status; my $status;
while (my ($table, $table_info) = each %{$domain_info->{tables}}) { while (my ($table, $table_info) = each %{$domain_info->{tables}}) {
my $table_cmd = "$domain_cmd -t $table"; my $table_cmd = "$domain_cmd -t $table";
if ($domain eq 'eb') {
my $tablefile = $domain_info->{ebt_current}->{$table}->filename;
$table_cmd = "$domain_cmd -t $table --atomic-file $tablefile"
}
# reset chain policies # reset chain policies
while (my ($chain, $chain_info) = each %{$table_info->{chains}}) { while (my ($chain, $chain_info) = each %{$table_info->{chains}}) {
next unless $chain_info->{builtin} or next unless $chain_info->{builtin} or
(not $table_info->{has_builtin} and (not $table_info->{has_builtin} and
is_netfilter_builtin_chain($table, $chain)); is_netfilter_builtin_chain($table, $chain));
$status ||= execute_command("$table_cmd -P $chain ACCEPT") $status ||= execute_command("$table_cmd -P $chain ACCEPT")
unless $option{noflush}; unless $option{noflush};
} }
# clear # clear
skipping to change at line 2888 skipping to change at line 2949
# dump rules # dump rules
while (my ($chain, $chain_info) = each %{$table_info->{chains}}) { while (my ($chain, $chain_info) = each %{$table_info->{chains}}) {
my $chain_cmd = "$table_cmd -A $chain"; my $chain_cmd = "$table_cmd -A $chain";
foreach my $rule (@{$chain_info->{rules}}) { foreach my $rule (@{$chain_info->{rules}}) {
$status ||= execute_command($chain_cmd . $rule->{rule}); $status ||= execute_command($chain_cmd . $rule->{rule});
} }
} }
} }
if ($domain eq 'eb') { if ($domain eq 'eb') {
execute_command("$domain_cmd --atomic-commit"); foreach my $eb_table (@eb_tables) {
my $tablefile = $domain_info->{ebt_current}->{$eb_table}->filename;
execute_command("$domain_cmd -t $eb_table --atomic-file $tablefile -
-atomic-commit");
}
} }
return $status; return $status;
} }
sub table_to_save($$) { sub table_to_save($$) {
my ($result_r, $table_info) = @_; my ($result_r, $table_info) = @_;
foreach my $chain (sort keys %{$table_info->{chains}}) { foreach my $chain (sort keys %{$table_info->{chains}}) {
my $chain_info = $table_info->{chains}{$chain}; my $chain_info = $table_info->{chains}{$chain};
skipping to change at line 2925 skipping to change at line 2989
: ''; : '';
} }
sub extract_chain_from_table_save($$) { sub extract_chain_from_table_save($$) {
my ($table_save, $chain) = @_; my ($table_save, $chain) = @_;
my $result = ''; my $result = '';
$result .= $& while $table_save =~ /^-A \Q${chain}\E .*\n/gm; $result .= $& while $table_save =~ /^-A \Q${chain}\E .*\n/gm;
return $result; return $result;
} }
# Find all chains in table_save matching any of table_info->{preserve_regexes}
# and include them into table_info->{chains} with preserve=1 unless existing.
sub resolve_dynamic_preserve($$) {
my ($table_info, $table_save) = @_;
while ($table_save =~ /^:([^ ]+) .*/mg) {
my $chain = $1;
next if exists $table_info->{chains}{$chain};
foreach my $re (@{$table_info->{preserve_regexes}}) {
next unless $chain =~ /${re}/;
$table_info->{chains}{$chain} = {
preserve => 1,
};
}
}
}
sub rules_to_save($) { sub rules_to_save($) {
my ($domain_info) = @_; my ($domain_info) = @_;
# convert this into an iptables-save text # convert this into an iptables-save text
my $tool = $domain_info->{tools}{'tables-save'}; my $tool = $domain_info->{tools}{'tables-save'};
$tool =~ s,.*/,,; # remove path $tool =~ s,.*/,,; # remove path
my $result = "# Generated by ferm $VERSION ($tool) on " . localtime() . "\n" ; my $result = "# Generated by ferm $VERSION ($tool) on " . localtime() . "\n" ;
foreach my $table (sort keys %{$domain_info->{tables}}) { foreach my $table (sort keys %{$domain_info->{tables}}) {
my $table_info = $domain_info->{tables}{$table}; my $table_info = $domain_info->{tables}{$table};
if (exists $table_info->{preserve_regexes}) {
my $table_save = extract_table_from_save($domain_info->{previous}, $
table);
resolve_dynamic_preserve($table_info, $table_save)
}
# select table # select table
$result .= '*' . $table . "\n"; $result .= '*' . $table . "\n";
# create chains / set policy # create chains / set policy
foreach my $chain (sort keys %{$table_info->{chains}}) { foreach my $chain (sort keys %{$table_info->{chains}}) {
my $chain_info = $table_info->{chains}{$chain}; my $chain_info = $table_info->{chains}{$chain};
if (exists $chain_info->{preserve}) { if (exists $chain_info->{preserve}) {
my $table_save = my $table_save =
extract_table_from_save($domain_info->{previous}, $table); extract_table_from_save($domain_info->{previous}, $table);
skipping to change at line 3026 skipping to change at line 3113
} }
return; return;
} }
sub rollback() { sub rollback() {
my $error; my $error;
while (my ($domain, $domain_info) = each %domains) { while (my ($domain, $domain_info) = each %domains) {
next unless $domain_info->{enabled}; next unless $domain_info->{enabled};
if ($domain eq 'eb') { if ($domain eq 'eb') {
my $previous_rules = $domain_info->{ebt_previous}->filename; foreach my $eb_table (@eb_tables) {
my $domain_cmd = $domain_info->{tools}{tables}; my $previous_rules = $domain_info->{ebt_previous}->{$eb_table}->
execute_command("$domain_cmd --atomic-file $previous_rules --atomic- filename;
commit"); my $domain_cmd = $domain_info->{tools}{tables};
execute_command("$domain_cmd -t $eb_table --atomic-file $previou
s_rules --atomic-commit");
}
next; next;
} }
unless (defined $domain_info->{tools}{'tables-restore'}) { unless (defined $domain_info->{tools}{'tables-restore'}) {
print STDERR "Cannot rollback domain '$domain' because there is no $ {domain}tables-restore\n"; print STDERR "Cannot rollback domain '$domain' because there is no $ {domain}tables-restore\n";
next; next;
} }
my $reset = ''; my $reset = '';
while (my ($table, $table_info) = each %{$domain_info->{tables}}) { while (my ($table, $table_info) = each %{$domain_info->{tables}}) {
my $reset_chain = ''; my $reset_chain = '';
 End of changes. 30 change blocks. 
60 lines changed or deleted 156 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)