diff --git a/nftables/index.cgi b/nftables/index.cgi
index 15ccf82ba..c2842e20a 100755
--- a/nftables/index.cgi
+++ b/nftables/index.cgi
@@ -263,6 +263,7 @@ if (@tables) {
print ui_buttons_start();
print ui_buttons_row("apply.cgi", $text{'index_apply'}, $text{'index_applydesc'});
print ui_buttons_row("active.cgi", $text{'index_active'}, $text{'index_activedesc'});
+ print ui_buttons_row("setup.cgi", $text{'index_setup'}, $text{'index_setupdesc'});
print ui_buttons_end();
}
diff --git a/nftables/lang/en b/nftables/lang/en
index 5fb0b0ddd..ce3652ead 100644
--- a/nftables/lang/en
+++ b/nftables/lang/en
@@ -69,18 +69,62 @@ save_err=Failed to save rule
apply_err=Failed to apply configuration
apply_enone=No saved nftables tables were found to apply.
apply_eexternal=Cannot apply configuration because table $1 is currently marked as externally managed.
-setup_title=Setup Default Ruleset
-setup_header=Create Default Ruleset
-setup_desc=This page allows you to create a default nftables ruleset. Select one of the options below and click 'Create'.
-setup_deny_note=Deny options will still allow SSH (port 22), Webmin (port $1), and localhost (loopback).
-setup_allow_all=Allow all traffic
-setup_deny_incoming=Deny all incoming traffic (except SSH and Webmin), allow all outgoing
-setup_deny_all=Deny all traffic (except SSH and Webmin)
+setup_title=Create Ruleset Profile
+setup_header=Ruleset profile
+setup_err=Failed to create ruleset profile
+setup_table_name=Table name
+setup_profile=Profile
+setup_profile_allow_all=Allow all traffic
+setup_profile_allow_all_desc=Open input, forward and output policy
+setup_profile_management=Management only
+setup_profile_management_desc=Allow SSH and this Webmin service, with outgoing traffic allowed
+setup_profile_web=Web server
+setup_profile_web_desc=Management access plus HTTP and HTTPS
+setup_profile_mail=Mail server
+setup_profile_mail_desc=Management access plus SMTP, submission, SMTPS, POP3, POP3S, IMAP and IMAPS
+setup_profile_dns=DNS server
+setup_profile_dns_desc=Management access plus DNS, DNS-over-TLS, DHCPv6 client and mDNS
+setup_profile_virtualmin=Virtualmin hosting server
+setup_profile_virtualmin_desc=Management, web, mail, DNS, FTP, Usermin and passive FTP ports
+setup_profile_locked=Locked-down server
+setup_profile_locked_desc=Drop input, forward and output traffic except management and replies
+setup_profile_custom=Custom selected services
+setup_profile_custom_desc=Use only the services and ports selected below
+setup_services=Allowed services and ports
+setup_service_col=Service or port
+setup_type_col=Type
+setup_port_col=Port
+setup_proto_col=Protocol
+setup_type_service=Service
+setup_type_port=Port
+setup_svc_ssh=SSH
+setup_svc_webmin=Webmin
+setup_svc_dhcpv6=DHCPv6 client
+setup_svc_dns=DNS
+setup_svc_dot=DNS-over-TLS
+setup_svc_ftp=FTP
+setup_svc_http=HTTP
+setup_svc_https=HTTPS
+setup_svc_imap=IMAP
+setup_svc_imaps=IMAPS
+setup_svc_mdns=mDNS
+setup_svc_pop3=POP3
+setup_svc_pop3s=POP3S
+setup_svc_smtp=SMTP
+setup_svc_submission=SMTP submission
+setup_svc_smtps=SMTPS
+setup_port_ftp_data=FTP data
+setup_port_ssh_alt=SFTP
+setup_port_webmin_range=Webmin RPC
+setup_port_usermin=Usermin
+setup_port_passive_ftp=FTP passive range
setup_create=Create
setup_invalid_type=Invalid ruleset type selected.
-setup_failed=Failed to create default ruleset:
$1
-index_setup=Create Default Ruleset
-index_setupdesc=Create a default set of rules, for example to allow all traffic.
+setup_edup=Table $1 already exists in Webmin's saved nftables configuration.
+setup_eservice=Invalid service selected: $1
+setup_failed=Failed to create ruleset profile: $1
+index_setup=Create Ruleset Profile
+index_setupdesc=Create a managed nftables table from a predefined profile.
index_table_create=Create Table
index_table_createdesc=Add a new nftables table.
index_table_delete=Delete Table
diff --git a/nftables/nftables-lib.pl b/nftables/nftables-lib.pl
index 7de4e04c4..cc4d145ef 100644
--- a/nftables/nftables-lib.pl
+++ b/nftables/nftables-lib.pl
@@ -1389,4 +1389,19 @@ if (get_miniserv_config(\%miniserv) && $miniserv{'port'} =~ /^\d+$/) {
return 10000;
}
+# get_usermin_port()
+# Returns the configured Usermin port, or 20000 if unknown
+sub get_usermin_port
+{
+my %miniserv;
+if (foreign_installed("usermin")) {
+ foreign_require("usermin", "usermin-lib.pl");
+ usermin::get_usermin_miniserv_config(\%miniserv);
+ if ($miniserv{'port'} =~ /^\d+$/) {
+ return $miniserv{'port'};
+ }
+ }
+return 20000;
+}
+
1;
diff --git a/nftables/setup.cgi b/nftables/setup.cgi
index c7809f7de..595eb291d 100644
--- a/nftables/setup.cgi
+++ b/nftables/setup.cgi
@@ -1,197 +1,399 @@
#!/usr/bin/perl
# setup.cgi
-# Create a default nftables ruleset
+# Create a Webmin-managed nftables profile table
require './nftables-lib.pl'; ## no critic
use strict;
use warnings;
our (%in, %text);
ReadParse();
+error_setup($text{'setup_err'});
if ($in{'action'} eq 'create') {
- my $type = $in{'type'};
- my @tables;
- if ($type eq 'allow_all') {
- @tables = create_allow_all_ruleset();
- }
- elsif ($type eq 'deny_incoming') {
- @tables = create_deny_incoming_ruleset();
- }
- elsif ($type eq 'deny_all') {
- @tables = create_deny_all_ruleset();
- }
- else {
- error($text{'setup_invalid_type'});
- }
+ my $profile = $in{'profile'} || 'virtualmin';
+ my $table_name = $in{'table_name'} || default_profile_table_name();
+ $table_name =~ /^\w[\w-]*$/ || error($text{'create_ename'});
- my $error = save_configuration(@tables);
- if ($error) {
- error(text('setup_failed', $error));
- }
- $error = apply_restore();
- if ($error) {
- error(text('setup_failed', $error));
- }
- webmin_log("setup", "create", $type);
- redirect("index.cgi");
-}
+ my @tables = get_nftables_save();
+ foreach my $t (@tables) {
+ if ($t->{'family'} eq 'inet' && $t->{'name'} eq $table_name) {
+ error(text('setup_edup', nft_table_spec($t)));
+ }
+ }
+ my ($active, $active_err) = get_active_nftables_save();
+ if (!$active_err) {
+ foreach my $t (@$active) {
+ if ($t->{'family'} eq 'inet' && $t->{'name'} eq $table_name &&
+ table_is_externally_managed($t)) {
+ error(text('create_eexternal', nft_table_spec($t)));
+ }
+ }
+ }
+
+ my @allow = grep { $_ ne '' } split(/\0/, $in{'allow'} || '');
+ my $table = create_profile_ruleset($profile, $table_name, \@allow);
+ push(@tables, $table);
+
+ my $error = save_configuration(@tables);
+ if ($error) {
+ error(text('setup_failed', $error));
+ }
+ $error = apply_restore();
+ if ($error) {
+ error(text('setup_failed', $error));
+ }
+ webmin_log("setup", "create", $profile,
+ { 'family' => 'inet', 'table' => $table_name });
+ redirect("index.cgi?table_family=inet&table_name=".urlize($table_name));
+ }
ui_print_header(undef, $text{'setup_title'}, "", "intro", 1, 1);
-print "$text{'setup_header'}
";
-my $webmin_port = get_webmin_port();
-print "$text{'setup_desc'}
";
-print "",text('setup_deny_note', $webmin_port),"
";
-
print ui_form_start("setup.cgi");
print ui_hidden("action", "create");
-my @type_opts = (
- [ 'allow_all', $text{'setup_allow_all'} . "
" ],
- [ 'deny_incoming', $text{'setup_deny_incoming'} . "
" ],
- [ 'deny_all', $text{'setup_deny_all'} ],
-);
-print ui_radio("type", "allow_all", \@type_opts);
+my @profiles = setup_profiles();
+my $profile = $in{'profile'} || 'virtualmin';
+my %profile_map = map { $_->{'id'} => $_ } @profiles;
+$profile = 'virtualmin' if (!$profile_map{$profile});
+my %checked = map { $_ => 1 } @{$profile_map{$profile}->{'services'} || [ ]};
+my @profile_opts = map { [ $_->{'id'}, $_->{'name'} ] } @profiles;
+
+print ui_table_start($text{'setup_header'}, "width=100%", 2);
+print ui_table_row($text{'setup_table_name'},
+ ui_textbox("table_name", $in{'table_name'} || profile_table_name($profile), 24));
+print ui_table_row($text{'setup_profile'},
+ ui_select("profile", $profile, \@profile_opts, 1, 0, 0, 0).
+ ui_tag('div', ui_note($profile_map{$profile}->{'desc'}, 0),
+ { 'id' => 'nftables_profile_note',
+ 'style' => 'margin-top: 0.35em; margin-left: 0.15em;' }));
+print ui_table_end();
+
+my @services = setup_services();
+my @links = ( select_all_link("allow", 0),
+ select_invert_link("allow", 0) );
+print ui_hr();
+print ui_links_row(\@links);
+my @tds = ( "width=5" );
+print ui_columns_start(
+ [ "", $text{'setup_service_col'}, $text{'setup_type_col'},
+ $text{'setup_port_col'}, $text{'setup_proto_col'} ], 100, 0, \@tds,
+ $text{'setup_services'});
+foreach my $svc (sort { lc($a->{'label'}) cmp lc($b->{'label'}) } @services) {
+ print ui_checked_columns_row([
+ $svc->{'label'},
+ $svc->{'type'},
+ $svc->{'port'},
+ $svc->{'proto'},
+ ], \@tds, "allow", $svc->{'id'}, $checked{$svc->{'id'}});
+ }
+print ui_columns_end();
+print profile_javascript(@profiles);
print ui_form_end([ [ undef, $text{'setup_create'} ] ]);
+ui_print_footer("index.cgi", $text{'index_return'});
-sub create_allow_all_ruleset
+sub setup_profiles
{
- my @tables;
- my $table = {
- 'name' => 'inet_filter',
- 'family' => 'inet',
- 'rules' => [],
- 'sets' => {},
- 'chains' => {
- 'input' => {
- 'type' => 'filter',
- 'hook' => 'input',
- 'priority' => 0,
- 'policy' => 'accept'
- },
- 'forward' => {
- 'type' => 'filter',
- 'hook' => 'forward',
- 'priority' => 0,
- 'policy' => 'accept'
- },
- 'output' => {
- 'type' => 'filter',
- 'hook' => 'output',
- 'priority' => 0,
- 'policy' => 'accept'
- }
- }
- };
- push(@tables, $table);
- return @tables;
+return (
+ { 'id' => 'allow_all',
+ 'name' => $text{'setup_profile_allow_all'},
+ 'desc' => $text{'setup_profile_allow_all_desc'},
+ 'input' => 'accept',
+ 'forward' => 'accept',
+ 'output' => 'accept',
+ 'services' => [ ] },
+ { 'id' => 'management',
+ 'name' => $text{'setup_profile_management'},
+ 'desc' => $text{'setup_profile_management_desc'},
+ 'input' => 'drop',
+ 'forward' => 'drop',
+ 'output' => 'accept',
+ 'services' => [ qw(ssh webmin) ] },
+ { 'id' => 'web',
+ 'name' => $text{'setup_profile_web'},
+ 'desc' => $text{'setup_profile_web_desc'},
+ 'input' => 'drop',
+ 'forward' => 'drop',
+ 'output' => 'accept',
+ 'services' => [ qw(ssh webmin http https) ] },
+ { 'id' => 'mail',
+ 'name' => $text{'setup_profile_mail'},
+ 'desc' => $text{'setup_profile_mail_desc'},
+ 'input' => 'drop',
+ 'forward' => 'drop',
+ 'output' => 'accept',
+ 'services' => [ qw(ssh webmin smtp submission smtps pop3 pop3s imap imaps) ] },
+ { 'id' => 'dns',
+ 'name' => $text{'setup_profile_dns'},
+ 'desc' => $text{'setup_profile_dns_desc'},
+ 'input' => 'drop',
+ 'forward' => 'drop',
+ 'output' => 'accept',
+ 'services' => [ qw(ssh webmin dhcpv6 dns dot mdns) ] },
+ { 'id' => 'virtualmin',
+ 'name' => $text{'setup_profile_virtualmin'},
+ 'desc' => $text{'setup_profile_virtualmin_desc'},
+ 'input' => 'drop',
+ 'forward' => 'drop',
+ 'output' => 'accept',
+ 'services' => [ qw(ssh webmin dhcpv6 dns dot ftp http https imap imaps
+ mdns pop3 pop3s smtp submission smtps ftp_data
+ ssh_alt webmin_range usermin passive_ftp) ] },
+ { 'id' => 'locked',
+ 'name' => $text{'setup_profile_locked'},
+ 'desc' => $text{'setup_profile_locked_desc'},
+ 'input' => 'drop',
+ 'forward' => 'drop',
+ 'output' => 'drop',
+ 'services' => [ qw(ssh webmin) ] },
+ { 'id' => 'custom',
+ 'name' => $text{'setup_profile_custom'},
+ 'desc' => $text{'setup_profile_custom_desc'},
+ 'input' => 'drop',
+ 'forward' => 'drop',
+ 'output' => 'accept',
+ 'services' => [ ] },
+ );
}
-sub create_deny_incoming_ruleset
+sub setup_services
{
- my @tables;
- my $webmin_port = get_webmin_port();
- my $table = {
- 'name' => 'inet_filter',
- 'family' => 'inet',
- 'rules' => [
- {
- 'text' => 'ct state established,related accept',
- 'chain' => 'input'
- },
- {
- 'text' => 'iif "lo" accept',
- 'chain' => 'input'
- },
- {
- 'text' => 'tcp dport 22 accept',
- 'chain' => 'input'
- },
- {
- 'text' => "tcp dport $webmin_port accept",
- 'chain' => 'input'
- }
- ],
- 'sets' => {},
- 'chains' => {
- 'input' => {
- 'type' => 'filter',
- 'hook' => 'input',
- 'priority' => 0,
- 'policy' => 'drop'
- },
- 'forward' => {
- 'type' => 'filter',
- 'hook' => 'forward',
- 'priority' => 0,
- 'policy' => 'accept'
- },
- 'output' => {
- 'type' => 'filter',
- 'hook' => 'output',
- 'priority' => 0,
- 'policy' => 'accept'
- }
- }
- };
- push(@tables, $table);
- return @tables;
+my $webmin_port = get_webmin_port();
+my $usermin_port = get_usermin_port();
+return (
+ { 'id' => 'ssh', 'label' => $text{'setup_svc_ssh'},
+ 'type' => $text{'setup_type_service'}, 'port' => '22',
+ 'proto' => 'TCP', 'rules' => [ 'tcp dport 22 accept' ] },
+ { 'id' => 'webmin', 'label' => text('setup_svc_webmin', $webmin_port),
+ 'type' => $text{'setup_type_service'}, 'port' => $webmin_port,
+ 'proto' => 'TCP', 'rules' => [ "tcp dport $webmin_port accept" ] },
+ { 'id' => 'dhcpv6', 'label' => $text{'setup_svc_dhcpv6'},
+ 'type' => $text{'setup_type_service'}, 'port' => '546',
+ 'proto' => 'UDP',
+ 'rules' => [ 'ip6 daddr fe80::/64 udp dport 546 accept' ] },
+ { 'id' => 'dns', 'label' => $text{'setup_svc_dns'},
+ 'type' => $text{'setup_type_service'}, 'port' => '53',
+ 'proto' => 'TCP/UDP',
+ 'rules' => [ 'tcp dport 53 accept', 'udp dport 53 accept' ] },
+ { 'id' => 'dot', 'label' => $text{'setup_svc_dot'},
+ 'type' => $text{'setup_type_service'}, 'port' => '853',
+ 'proto' => 'TCP', 'rules' => [ 'tcp dport 853 accept' ] },
+ { 'id' => 'ftp', 'label' => $text{'setup_svc_ftp'},
+ 'type' => $text{'setup_type_service'}, 'port' => '21',
+ 'proto' => 'TCP', 'rules' => [ 'tcp dport 21 accept' ] },
+ { 'id' => 'http', 'label' => $text{'setup_svc_http'},
+ 'type' => $text{'setup_type_service'}, 'port' => '80',
+ 'proto' => 'TCP', 'rules' => [ 'tcp dport 80 accept' ] },
+ { 'id' => 'https', 'label' => $text{'setup_svc_https'},
+ 'type' => $text{'setup_type_service'}, 'port' => '443',
+ 'proto' => 'TCP', 'rules' => [ 'tcp dport 443 accept' ] },
+ { 'id' => 'imap', 'label' => $text{'setup_svc_imap'},
+ 'type' => $text{'setup_type_service'}, 'port' => '143',
+ 'proto' => 'TCP', 'rules' => [ 'tcp dport 143 accept' ] },
+ { 'id' => 'imaps', 'label' => $text{'setup_svc_imaps'},
+ 'type' => $text{'setup_type_service'}, 'port' => '993',
+ 'proto' => 'TCP', 'rules' => [ 'tcp dport 993 accept' ] },
+ { 'id' => 'mdns', 'label' => $text{'setup_svc_mdns'},
+ 'type' => $text{'setup_type_service'}, 'port' => '5353',
+ 'proto' => 'UDP',
+ 'rules' => [ 'ip daddr 224.0.0.251 udp dport 5353 accept',
+ 'ip6 daddr ff02::fb udp dport 5353 accept' ] },
+ { 'id' => 'pop3', 'label' => $text{'setup_svc_pop3'},
+ 'type' => $text{'setup_type_service'}, 'port' => '110',
+ 'proto' => 'TCP', 'rules' => [ 'tcp dport 110 accept' ] },
+ { 'id' => 'pop3s', 'label' => $text{'setup_svc_pop3s'},
+ 'type' => $text{'setup_type_service'}, 'port' => '995',
+ 'proto' => 'TCP', 'rules' => [ 'tcp dport 995 accept' ] },
+ { 'id' => 'smtp', 'label' => $text{'setup_svc_smtp'},
+ 'type' => $text{'setup_type_service'}, 'port' => '25',
+ 'proto' => 'TCP', 'rules' => [ 'tcp dport 25 accept' ] },
+ { 'id' => 'submission', 'label' => $text{'setup_svc_submission'},
+ 'type' => $text{'setup_type_service'}, 'port' => '587',
+ 'proto' => 'TCP', 'rules' => [ 'tcp dport 587 accept' ] },
+ { 'id' => 'smtps', 'label' => $text{'setup_svc_smtps'},
+ 'type' => $text{'setup_type_service'}, 'port' => '465',
+ 'proto' => 'TCP', 'rules' => [ 'tcp dport 465 accept' ] },
+ { 'id' => 'ftp_data', 'label' => $text{'setup_port_ftp_data'},
+ 'type' => $text{'setup_type_port'}, 'port' => '20',
+ 'proto' => 'TCP', 'rules' => [ 'tcp dport 20 accept' ] },
+ { 'id' => 'ssh_alt', 'label' => $text{'setup_port_ssh_alt'},
+ 'type' => $text{'setup_type_port'}, 'port' => '2222',
+ 'proto' => 'TCP', 'rules' => [ 'tcp dport 2222 accept' ] },
+ { 'id' => 'webmin_range', 'label' => $text{'setup_port_webmin_range'},
+ 'type' => $text{'setup_type_port'}, 'port' => '10000-10100',
+ 'proto' => 'TCP', 'rules' => [ 'tcp dport 10000-10100 accept' ] },
+ { 'id' => 'usermin', 'label' => $text{'setup_port_usermin'},
+ 'type' => $text{'setup_type_port'}, 'port' => $usermin_port,
+ 'proto' => 'TCP', 'rules' => [ "tcp dport $usermin_port accept" ] },
+ { 'id' => 'passive_ftp', 'label' => $text{'setup_port_passive_ftp'},
+ 'type' => $text{'setup_type_port'}, 'port' => '49152-65535',
+ 'proto' => 'TCP', 'rules' => [ 'tcp dport 49152-65535 accept' ] },
+ );
}
-sub create_deny_all_ruleset
+sub create_profile_ruleset
{
- my @tables;
- my $webmin_port = get_webmin_port();
- my $table = {
- 'name' => 'inet_filter',
- 'family' => 'inet',
- 'rules' => [],
- 'sets' => {},
- 'chains' => {
- 'input' => {
- 'type' => 'filter',
- 'hook' => 'input',
- 'priority' => 0,
- 'policy' => 'drop'
- },
- 'forward' => {
- 'type' => 'filter',
- 'hook' => 'forward',
- 'priority' => 0,
- 'policy' => 'drop'
- },
- 'output' => {
- 'type' => 'filter',
- 'hook' => 'output',
- 'priority' => 0,
- 'policy' => 'drop'
- }
- }
- };
- $table->{'rules'} = [
- {
- 'text' => 'ct state established,related accept',
- 'chain' => 'output'
- },
- {
- 'text' => 'iif "lo" accept',
- 'chain' => 'input'
- },
- {
- 'text' => 'oif "lo" accept',
- 'chain' => 'output'
- },
- {
- 'text' => 'tcp dport 22 accept',
- 'chain' => 'input'
- },
- {
- 'text' => "tcp dport $webmin_port accept",
- 'chain' => 'input'
- }
- ];
- push(@tables, $table);
- return @tables;
+my ($profile_id, $table_name, $allow_ids) = @_;
+my %profiles = map { $_->{'id'} => $_ } setup_profiles();
+my $profile = $profiles{$profile_id} || error($text{'setup_invalid_type'});
+my @services = setup_services();
+my %services = map { $_->{'id'} => $_ } @services;
+my %allow;
+foreach my $id (@$allow_ids) {
+ $services{$id} || error(text('setup_eservice', $id));
+ $allow{$id} = 1;
+ }
+
+my $table = {
+ 'name' => $table_name,
+ 'family' => 'inet',
+ 'rules' => [ ],
+ 'sets' => { },
+ 'chains' => {
+ 'input' => {
+ 'type' => 'filter',
+ 'hook' => 'input',
+ 'priority' => 0,
+ 'policy' => $profile->{'input'}
+ },
+ 'forward' => {
+ 'type' => 'filter',
+ 'hook' => 'forward',
+ 'priority' => 0,
+ 'policy' => $profile->{'forward'}
+ },
+ 'output' => {
+ 'type' => 'filter',
+ 'hook' => 'output',
+ 'priority' => 0,
+ 'policy' => $profile->{'output'}
+ }
+ }
+ };
+return $table if ($profile_id eq 'allow_all');
+
+add_profile_rule($table, 'input', 'ct state established,related accept');
+add_profile_rule($table, 'input', 'iif "lo" accept');
+add_profile_rule($table, 'input', 'meta l4proto { icmp, ipv6-icmp } accept');
+if ($profile->{'output'} eq 'drop') {
+ add_profile_rule($table, 'output', 'ct state established,related accept');
+ add_profile_rule($table, 'output', 'oif "lo" accept');
+ add_profile_rule($table, 'output', 'meta l4proto { icmp, ipv6-icmp } accept');
+ }
+
+my %seen;
+foreach my $id (map { $_->{'id'} } @services) {
+ next if (!$allow{$id});
+ foreach my $rule (@{$services{$id}->{'rules'}}) {
+ next if ($seen{$rule}++);
+ add_profile_rule($table, 'input', $rule);
+ }
+ }
+return $table;
}
-ui_print_footer("/", $text{'index'});
+sub profile_javascript
+{
+my (@profiles) = @_;
+my %profile_services = map {
+ $_->{'id'} => $_->{'services'}
+ } @profiles;
+my %profile_tables = map {
+ $_->{'id'} => profile_table_name($_->{'id'})
+ } @profiles;
+my %profile_notes = map {
+ $_->{'id'} => ui_note($_->{'desc'}, 0)
+ } @profiles;
+my $json = convert_to_json(\%profile_services);
+my $table_json = convert_to_json(\%profile_tables);
+my $note_json = convert_to_json(\%profile_notes);
+return <
+(function() {
+ var profileServices = $json;
+ var profileTables = $table_json;
+ var profileNotes = $note_json;
+ var tableInput = document.querySelector('input[name="table_name"]');
+ var profileSelect = document.querySelector('select[name="profile"]');
+ var profileNote = document.getElementById('nftables_profile_note');
+ var tableNameTouched = false;
+ if (tableInput) {
+ tableInput.addEventListener('input', function() {
+ tableNameTouched = true;
+ });
+ }
+ function applyProfileServices(profile) {
+ var selected = {};
+ (profileServices[profile] || []).forEach(function(id) {
+ selected[id] = true;
+ });
+ document.querySelectorAll('input[name="allow"]').forEach(function(input) {
+ var checked = !!selected[input.value];
+ if (input.checked != checked) {
+ input.click();
+ }
+ });
+ }
+ function applyProfileTable(profile) {
+ if (!tableInput || tableNameTouched || !profileTables[profile]) {
+ return;
+ }
+ tableInput.value = profileTables[profile];
+ }
+ function applyProfileNote(profile) {
+ if (profileNote && profileNotes[profile]) {
+ profileNote.innerHTML = profileNotes[profile];
+ }
+ }
+ if (profileSelect) {
+ profileSelect.addEventListener('change', function() {
+ applyProfileServices(this.value);
+ applyProfileTable(this.value);
+ applyProfileNote(this.value);
+ });
+ }
+})();
+
+EOF
+}
+
+sub add_profile_rule
+{
+my ($table, $chain, $text) = @_;
+push(@{$table->{'rules'}}, {
+ 'text' => $text,
+ 'chain' => $chain,
+ 'index' => scalar(@{$table->{'rules'}}),
+ });
+return;
+}
+
+sub profile_table_name
+{
+my ($profile) = @_;
+my %names = (
+ 'allow_all' => 'profile_allow_all',
+ 'management' => 'profile_management',
+ 'web' => 'profile_web',
+ 'mail' => 'profile_mail',
+ 'dns' => 'profile_dns',
+ 'virtualmin' => 'profile_hosting',
+ 'locked' => 'profile_locked',
+ 'custom' => 'profile_custom',
+ );
+my $base = $names{$profile} || 'profile_custom';
+my @tables = get_nftables_save();
+my %used = map { $_->{'family'} eq 'inet' ? ($_->{'name'} => 1) : ( ) }
+ @tables;
+my $name = $base;
+my $i = 1;
+while ($used{$name}) {
+ $name = $base."_".$i++;
+ }
+return $name;
+}
+
+sub default_profile_table_name
+{
+return profile_table_name('virtualmin');
+}