diff --git a/images/icon.gif b/images/icon.gif new file mode 100644 index 000000000..650a15379 Binary files /dev/null and b/images/icon.gif differ diff --git a/index.cgi b/index.cgi index 605973ffc..af3797db3 100755 --- a/index.cgi +++ b/index.cgi @@ -87,21 +87,36 @@ if (!@tables) { my @rules = grep { $_->{'chain'} eq $c } @{$curr->{'rules'}}; my $rules_html_row; if (@rules) { - my @rows; + my $ri = 0; + $rules_html_row = "\n"; foreach my $r (@rules) { my $desc = &describe_rule($r); - push(@rows, &ui_link( + my $rule_link = &ui_link( "edit_rule.cgi?table=$in{'table'}&chain=". &urlize($c)."&idx=$r->{'index'}", - $desc)); + $desc); + my $move = &ui_up_down_arrows( + "move_rule.cgi?table=$in{'table'}&chain=". + &urlize($c)."&idx=$r->{'index'}&dir=up", + "move_rule.cgi?table=$in{'table'}&chain=". + &urlize($c)."&idx=$r->{'index'}&dir=down", + $ri > 0, + $ri < $#rules); + $rules_html_row .= "". + "\n"; + $ri++; } - $rules_html_row = join("
", @rows); + $rules_html_row .= "\n"; + $rules_html_row .= "
$rule_link$move
". + &ui_link("edit_rule.cgi?table=$in{'table'}&chain=". + &urlize($c)."&new=1", $text{'index_radd'}). + "
"; } else { $rules_html_row = "$text{'index_rules_none'}"; + $rules_html_row .= "
". + &ui_link("edit_rule.cgi?table=$in{'table'}&chain=". + &urlize($c)."&new=1", $text{'index_radd'}); } - $rules_html_row .= "
". - &ui_link("edit_rule.cgi?table=$in{'table'}&chain=". - &urlize($c)."&new=1", $text{'index_radd'}); my $actions_html = &ui_link("edit_chain.cgi?table=$in{'table'}&chain=". diff --git a/lang/en b/lang/en index 838a0a52d..4ad0b702f 100644 --- a/lang/en +++ b/lang/en @@ -177,3 +177,8 @@ save_failed=Failed to save rule:
$1
save_raw_empty=Raw rule cannot be empty. save_raw_multiline=Raw rule must be a single line. save_invalid_rule=Raw rule is invalid: $1 +move_err=Failed to move rule +move_failed=Failed to move rule:
$1
+move_notable=No such table selected +move_nochain=No such chain selected +move_norule=No such rule selected diff --git a/move_rule.cgi b/move_rule.cgi new file mode 100755 index 000000000..fef1ededb --- /dev/null +++ b/move_rule.cgi @@ -0,0 +1,40 @@ +#!/usr/bin/perl +# move_rule.cgi +# Move a rule up or down within a chain + +require './nftables-lib.pl'; +use strict; +use warnings; +our (%in, %text); +&ReadParse(); +&error_setup($text{'move_err'}); + +my @tables = &get_nftables_save(); +my $table = $tables[$in{'table'}]; +$table || &error($text{'move_notable'}); + +my $chain = $in{'chain'}; +$chain || &error($text{'move_nochain'}); + +my $dir = $in{'dir'}; +$dir = '' if (!defined($dir)); + +my $idx = $in{'idx'}; +$idx =~ /^\d+$/ || &error($text{'move_norule'}); + +my $rv = &move_rule_in_chain($table, $chain, $idx, $dir); +if (!defined($rv)) { + &error($text{'move_norule'}); +} + +if ($rv) { + my $err = &save_configuration(@tables); + &error(&text('move_failed', $err)) if ($err); + &webmin_log("move", "rule", undef, + { 'table' => $table->{'name'}, + 'family' => $table->{'family'}, + 'chain' => $chain, + 'dir' => $dir }); +} + +&redirect("index.cgi?table=$in{'table'}"); diff --git a/nftables-lib.pl b/nftables-lib.pl index bc3d1a352..7b567ced7 100644 --- a/nftables-lib.pl +++ b/nftables-lib.pl @@ -191,6 +191,56 @@ if (defined($type) || defined($hook) || defined($priority) || defined($policy)) return 1; } +sub move_rule_in_chain +{ +my ($table, $chain, $idx, $dir) = @_; +return undef if (!defined($table) || ref($table) ne 'HASH'); +return undef if (!defined($idx) || $idx !~ /^\d+$/); +return undef if (!defined($chain) || $chain eq ''); +return undef if (!$table->{'rules'} || ref($table->{'rules'}) ne 'ARRAY'); +return undef if ($idx > $#{$table->{'rules'}}); +my $rule = $table->{'rules'}->[$idx]; +return undef if (!$rule || $rule->{'chain'} ne $chain); + +my @chain_idxs; +for (my $i = 0; $i < @{$table->{'rules'}}; $i++) { + my $r = $table->{'rules'}->[$i]; + next if (!$r || ref($r) ne 'HASH'); + push(@chain_idxs, $i) if ($r->{'chain'} && $r->{'chain'} eq $chain); +} +my $pos; +for (my $i = 0; $i <= $#chain_idxs; $i++) { + if ($chain_idxs[$i] == $idx) { + $pos = $i; + last; + } +} +return undef if (!defined($pos)); + +my $swap; +if ($dir eq 'up') { + return 0 if ($pos == 0); + $swap = $chain_idxs[$pos-1]; +} +elsif ($dir eq 'down') { + return 0 if ($pos == $#chain_idxs); + $swap = $chain_idxs[$pos+1]; +} +else { + return undef; +} + +($table->{'rules'}->[$idx], $table->{'rules'}->[$swap]) = + ($table->{'rules'}->[$swap], $table->{'rules'}->[$idx]); + +for (my $i = 0; $i < @{$table->{'rules'}}; $i++) { + my $r = $table->{'rules'}->[$i]; + $r->{'index'} = $i if ($r && ref($r) eq 'HASH'); +} + +return 1; +} + sub format_addr_expr { my ($dir, $rule) = @_; diff --git a/t/run-tests.t b/t/run-tests.t index 2e79c8b12..813a128ab 100755 --- a/t/run-tests.t +++ b/t/run-tests.t @@ -135,4 +135,28 @@ ok(!&validate_chain_base('filter', 'input', undef, 'accept'), ok(&validate_chain_base(undef, undef, undef, undef), 'chain base none set valid'); +my $table_move = { + rules => [ + { chain => 'input', index => 0, text => 'r0' }, + { chain => 'input', index => 1, text => 'r1' }, + { chain => 'forward', index => 2, text => 'r2' }, + { chain => 'input', index => 3, text => 'r3' }, + ], +}; +ok(&move_rule_in_chain($table_move, 'input', 1, 'down'), + 'move rule down returns true'); +is($table_move->{rules}->[1]->{text}, 'r3', 'rule moved down in array'); +is($table_move->{rules}->[3]->{text}, 'r1', 'rule swapped down in array'); +is($table_move->{rules}->[1]->{index}, 1, 'moved rule index updated'); +is($table_move->{rules}->[3]->{index}, 3, 'swapped rule index updated'); + +my $table_move2 = { + rules => [ + { chain => 'input', index => 0, text => 'r0' }, + { chain => 'input', index => 1, text => 'r1' }, + ], +}; +is(&move_rule_in_chain($table_move2, 'input', 0, 'up'), 0, + 'top rule cannot move up'); + done_testing();