diff --git a/delete_chain.cgi b/delete_chain.cgi new file mode 100644 index 000000000..09793bc86 --- /dev/null +++ b/delete_chain.cgi @@ -0,0 +1,54 @@ +#!/usr/bin/perl +# delete_chain.cgi +# Delete an existing nftables chain + +require './nftables-lib.pl'; +use strict; +use warnings; +our (%in, %text); +&ReadParse(); +&error_setup($text{'delete_chain_err'}); + +my @tables = &get_nftables_save(); +my $table = $tables[$in{'table'}]; +$table || &error($text{'chain_notable'}); + +my $chain = $table->{'chains'}->{$in{'chain'}}; +$chain || &error($text{'chain_nochain'}); + +my @refs = grep { + ($_->{'jump'} && $_->{'jump'} eq $in{'chain'}) || + ($_->{'goto'} && $_->{'goto'} eq $in{'chain'}) +} @{$table->{'rules'}}; + +if ($in{'confirm'}) { + @refs && &error(&text('delete_chain_inuse', $in{'chain'}, scalar(@refs))); + + @{$table->{'rules'}} = grep { $_->{'chain'} ne $in{'chain'} } @{$table->{'rules'}}; + delete($table->{'chains'}->{$in{'chain'}}); + + my $err = &save_configuration(@tables); + &error(&text('delete_chain_failed', $err)) if ($err); + &webmin_log("delete", "chain", $in{'chain'}, + { 'table' => $table->{'name'}, 'family' => $table->{'family'} }); + &redirect("index.cgi?table=$in{'table'}"); +} + +&ui_print_header(undef, $text{'delete_chain_title'}, "", "intro", 1, 1); +print &ui_form_start("delete_chain.cgi"); +print &ui_hidden("table", $in{'table'}); +print &ui_hidden("chain", $in{'chain'}); +print "
\n"; +print &ui_submit($text{'delete'}, "confirm"); +print "
Base chain hook point, such as prerouting, input, forward, output, postrouting, or ingress.
+ diff --git a/help/chain_name.html b/help/chain_name.html new file mode 100644 index 000000000..bcc7e69e2 --- /dev/null +++ b/help/chain_name.html @@ -0,0 +1,3 @@ +Unique name for the chain within this table. Use letters, numbers, underscores, and dashes.
+ diff --git a/help/chain_policy.html b/help/chain_policy.html new file mode 100644 index 000000000..c0397985d --- /dev/null +++ b/help/chain_policy.html @@ -0,0 +1,3 @@ +Default action for this base chain, such as accept, drop, reject, queue, or continue.
+ diff --git a/help/chain_priority.html b/help/chain_priority.html new file mode 100644 index 000000000..1b09c3730 --- /dev/null +++ b/help/chain_priority.html @@ -0,0 +1,3 @@ +Priority for this base chain. Lower values run earlier. Common values include -300, -150, 0, or 100.
+ diff --git a/help/chain_type.html b/help/chain_type.html new file mode 100644 index 000000000..0279d9871 --- /dev/null +++ b/help/chain_type.html @@ -0,0 +1,3 @@ +Base chain type, such as filter, nat, or route. Leave blank to create a regular chain with no hook.
+ diff --git a/index.cgi b/index.cgi index f7d60d507..605973ffc 100755 --- a/index.cgi +++ b/index.cgi @@ -76,7 +76,8 @@ if (!@tables) { $rules_html .= &ui_columns_start( [ $text{'index_chain_col'}, $text{'index_type'}, $text{'index_hook'}, $text{'index_priority'}, - $text{'index_policy_col'}, $text{'index_rules'} ], 100); + $text{'index_policy_col'}, $text{'index_rules'}, + $text{'index_actions'} ], 100); foreach my $c (sort keys %{$curr->{'chains'}}) { my $chain_def = $curr->{'chains'}->{$c} || { }; @@ -102,18 +103,30 @@ if (!@tables) { &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=". + &urlize($c), $text{'index_cedit'})."$1+chain_ename=Chain name is invalid +chain_edup=A chain with that name already exists +chain_notable=No such table selected +chain_nochain=No such chain selected +chain_ebase=Base chains require type, hook, priority, and policy. +delete_chain_title=Delete chain +delete_chain_err=Failed to delete chain +delete_chain_failed=Failed to delete chain:
$1+delete_chain_confirm=Are you sure you want to delete chain $1 from table $2? +delete_chain_inuse=Chain $1 is referenced by $2 rule(s) via jump/goto. Remove those rules first. +rename_chain_title=Rename chain +rename_chain_header=Rename chain +rename_chain_old=Current name +rename_chain_new=New name +rename_chain_ok=Rename +rename_chain_failed=Failed to rename chain:
$1create_title=Create table create_header=Create a new table create_family=Table family diff --git a/nftables-lib.pl b/nftables-lib.pl index 8af072a05..bc3d1a352 100644 --- a/nftables-lib.pl +++ b/nftables-lib.pl @@ -181,6 +181,16 @@ return "ip6" if (defined($addr) && $addr =~ /:/); return "ip"; } +sub validate_chain_base +{ +my ($type, $hook, $priority, $policy) = @_; +if (defined($type) || defined($hook) || defined($priority) || defined($policy)) { + return 0 if (!defined($type) || !defined($hook) || + !defined($priority) || !defined($policy)); +} +return 1; +} + sub format_addr_expr { my ($dir, $rule) = @_; diff --git a/rename_chain.cgi b/rename_chain.cgi new file mode 100644 index 000000000..30ff58496 --- /dev/null +++ b/rename_chain.cgi @@ -0,0 +1,33 @@ +#!/usr/bin/perl +# rename_chain.cgi +# Rename an existing chain + +require './nftables-lib.pl'; +use strict; +use warnings; +our (%in, %text); +&ReadParse(); + +my @tables = &get_nftables_save(); +my $table = $tables[$in{'table'}]; +$table || &error($text{'chain_notable'}); + +my $chain = $table->{'chains'}->{$in{'chain'}}; +$chain || &error($text{'chain_nochain'}); + +&ui_print_header(undef, $text{'rename_chain_title'}, "", "intro", 1, 1); +print &ui_form_start("save_chain.cgi"); +print &ui_hidden("table", $in{'table'}); +print &ui_hidden("rename", 1); +print &ui_hidden("chain_old", $in{'chain'}); + +print &ui_table_start($text{'rename_chain_header'}, "width=100%", 2); +print &ui_table_row($text{'rename_chain_old'}, + "".&html_escape($in{'chain'}).""); +print &ui_table_row(hlink($text{'rename_chain_new'}, "chain_name"), + &ui_textbox("chain_name", $in{'chain'}, 20)); +print &ui_table_end(); + +print &ui_form_end([ [ undef, $text{'rename_chain_ok'} ] ]); +&ui_print_footer("index.cgi?table=$in{'table'}", $text{'index_return'}); + diff --git a/save_chain.cgi b/save_chain.cgi new file mode 100644 index 000000000..ff865eca6 --- /dev/null +++ b/save_chain.cgi @@ -0,0 +1,99 @@ +#!/usr/bin/perl +# save_chain.cgi +# Save a new or existing chain + +require './nftables-lib.pl'; +use strict; +use warnings; +our (%in, %text); +&ReadParse(); +&error_setup($text{'chain_err'}); + +my @tables = &get_nftables_save(); +my $table = $tables[$in{'table'}]; +$table || &error($text{'chain_notable'}); + +my $is_new = $in{'new'} ? 1 : 0; +my $is_rename = $in{'rename'} ? 1 : 0; +my $name = $in{'chain_name'}; +$name =~ s/^\s+// if (defined($name)); +$name =~ s/\s+$// if (defined($name)); +$name =~ /^\w[\w-]*$/ || &error($text{'chain_ename'}); + +my $old = $is_rename ? $in{'chain_old'} : $name; +$old =~ s/^\s+// if (defined($old)); +$old =~ s/\s+$// if (defined($old)); + +if ($is_new) { + $table->{'chains'}->{$name} && &error($text{'chain_edup'}); +} elsif ($is_rename) { + $table->{'chains'}->{$old} || &error($text{'chain_nochain'}); + if ($name ne $old && $table->{'chains'}->{$name}) { + &error($text{'chain_edup'}); + } +} else { + $table->{'chains'}->{$name} || &error($text{'chain_nochain'}); +} + +if ($is_rename) { + if ($name eq $old) { + &redirect("index.cgi?table=$in{'table'}"); + } + if ($name ne $old) { + $table->{'chains'}->{$name} = $table->{'chains'}->{$old}; + delete($table->{'chains'}->{$old}); + + foreach my $r (@{$table->{'rules'}}) { + $r->{'chain'} = $name if ($r->{'chain'} && $r->{'chain'} eq $old); + my $changed = 0; + if ($r->{'jump'} && $r->{'jump'} eq $old) { + $r->{'jump'} = $name; + $changed = 1; + } + if ($r->{'goto'} && $r->{'goto'} eq $old) { + $r->{'goto'} = $name; + $changed = 1; + } + $r->{'text'} = &format_rule_text($r) if ($changed); + } + } + + my $err = &save_configuration(@tables); + &error(&text('rename_chain_failed', $err)) if ($err); + &webmin_log("rename", "chain", $old, + { 'new' => $name, + 'table' => $table->{'name'}, + 'family' => $table->{'family'} }); + &redirect("index.cgi?table=$in{'table'}"); +} + +my $type = $in{'chain_type'}; +my $hook = $in{'chain_hook'}; +my $priority = $in{'chain_priority'}; +my $policy = $in{'chain_policy'}; + +for my $v (\$type, \$hook, \$priority, \$policy) { + $$v =~ s/^\s+// if (defined($$v)); + $$v =~ s/\s+$// if (defined($$v)); +} +$type = undef if (!defined($type) || $type eq ''); +$hook = undef if (!defined($hook) || $hook eq ''); +$priority = undef if (!defined($priority) || $priority eq ''); +$policy = undef if (!defined($policy) || $policy eq ''); + +&validate_chain_base($type, $hook, $priority, $policy) || + &error($text{'chain_ebase'}); + +my $chain = $table->{'chains'}->{$name} || { }; +$chain->{'type'} = $type; +$chain->{'hook'} = $hook; +$chain->{'priority'} = $priority; +$chain->{'policy'} = $policy; +$table->{'chains'}->{$name} = $chain; + +my $err = &save_configuration(@tables); +&error(&text('chain_failed', $err)) if ($err); + +&webmin_log($is_new ? "create" : "modify", "chain", $name, + { 'table' => $table->{'name'}, 'family' => $table->{'family'} }); +&redirect("index.cgi?table=$in{'table'}"); diff --git a/t/run-tests.t b/t/run-tests.t index a7c4634f4..2e79c8b12 100755 --- a/t/run-tests.t +++ b/t/run-tests.t @@ -128,4 +128,11 @@ check_fields('ruleset r1', $rules[0], { iif => 'lo', action => 'accept' }); check_fields('ruleset r2', $rules[1], { saddr => '192.168.1.0/24', proto => 'tcp', dport => '22', action => 'accept', comment => 'ssh' }); check_fields('ruleset r3', $rules[2], { ct_state => 'established,related', action => 'accept' }); +ok(&validate_chain_base('filter', 'input', '0', 'accept'), + 'chain base allows zero priority'); +ok(!&validate_chain_base('filter', 'input', undef, 'accept'), + 'chain base missing priority invalid'); +ok(&validate_chain_base(undef, undef, undef, undef), + 'chain base none set valid'); + done_testing();