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'); +}