Fix to factor nftables profile ruleset generation into library

This commit is contained in:
Ilia Ross
2026-05-03 18:41:57 +02:00
parent a6e6b1d21c
commit 83eb3eeffc
3 changed files with 337 additions and 311 deletions

View File

@@ -1331,6 +1331,325 @@ my ($bb) = $b =~ /^(\d+)/;
return ($aa || 0) <=> ($bb || 0) || $a cmp $b;
}
# setup_profiles()
# Returns available ruleset profiles and their default policies/services
sub setup_profiles
{
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 usermin 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' => [ ] },
{ 'id' => 'custom',
'name' => text('setup_profile_custom'),
'desc' => text('setup_profile_custom_desc'),
'input' => 'drop',
'forward' => 'drop',
'output' => 'accept',
'services' => [ ] },
);
}
# setup_services()
# Returns selectable services and ports used by ruleset profiles
sub setup_services
{
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'),
'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' ] },
);
}
# create_profile_ruleset(table-name, profile-id, allowed-service-ids|'*')
# Builds an inet table for a selected profile and service list
sub create_profile_ruleset
{
my ($table_name, $profile_id, $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_ids;
if (!defined($allow_ids) || $allow_ids eq '*') {
@allow_ids = @{$profile->{'services'} || [ ]};
}
elsif (ref($allow_ids) eq 'ARRAY') {
@allow_ids = @$allow_ids;
}
else {
@allow_ids = grep { $_ ne '' } split(/\s*,\s*|\s+/, $allow_ids);
}
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;
my %ports;
my @special_rules;
foreach my $id (map { $_->{'id'} } @services) {
next if (!$allow{$id});
foreach my $rule (@{$services{$id}->{'rules'}}) {
next if ($seen{$rule}++);
if ($rule =~ /^(tcp|udp)\s+dport\s+(\S+)\s+accept$/) {
$ports{$1}->{$2} = 1;
}
else {
push(@special_rules, $rule);
}
}
}
add_profile_port_set($table, $profile_id, \%ports);
foreach my $rule (@special_rules) {
add_profile_rule($table, 'input', $rule);
}
return $table;
}
# add_profile_port_set(&table, profile-id, &proto-ports)
# Adds profile service port sets and their input accept rules
sub add_profile_port_set
{
my ($table, $profile_id, $ports) = @_;
# Keep TCP and UDP ports in separate sets when they differ, otherwise a UDP
# accept rule would also allow TCP-only service ports.
my @protos = grep { keys %{$ports->{$_}} } sort keys %$ports;
return if (!@protos);
foreach my $proto (@protos) {
next if (!keys %{$ports->{$proto}});
my $set_name = profile_port_set_name($profile_id, $proto, scalar(@protos));
my @elements = normalize_port_set_elements(keys %{$ports->{$proto}});
$table->{'sets'}->{$set_name} = {
'name' => $set_name,
'type' => 'inet_service',
'flags' => (grep { /-/ } @elements) ? 'interval' : undef,
'elements' => \@elements,
'raw_lines' => [ ],
};
add_profile_rule($table, 'input', "$proto dport \@$set_name accept");
}
return;
}
# add_profile_rule(&table, chain, rule-text)
# Appends a generated rule to a profile table
sub add_profile_rule
{
my ($table, $chain, $text) = @_;
push(@{$table->{'rules'}}, {
'text' => $text,
'chain' => $chain,
'index' => scalar(@{$table->{'rules'}}),
});
return;
}
# profile_table_name(profile-id)
# Returns an unused default table name for a profile
sub profile_table_name
{
my ($profile) = @_;
my $base = profile_base_table_name($profile);
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;
}
# profile_base_table_name(profile-id)
# Returns the base table name for a profile before uniquifying
sub profile_base_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',
);
return $names{$profile} || 'profile_custom';
}
# profile_port_set_name(profile, proto, proto-count)
# Returns the set name used for profile-generated service ports
sub profile_port_set_name
{
my ($profile, $proto, $proto_count) = @_;
my $name = profile_base_table_name($profile);
$name .= "_".$proto if ($proto_count && $proto_count > 1);
$name .= "_ports";
$name =~ s/[^\w-]/_/g;
return $name;
}
# default_profile_table_name()
# Returns the default table name for the default profile
sub default_profile_table_name
{
return profile_table_name('virtualmin');
}
# set_type_kind(type)
# Returns addr, port or undef for a set type
sub set_type_kind

View File

@@ -31,7 +31,7 @@ if ($in{'action'} eq 'create') {
}
my @allow = grep { $_ ne '' } split(/\0/, $in{'allow'} || '');
my $table = create_profile_ruleset($profile, $table_name, \@allow);
my $table = create_profile_ruleset($table_name, $profile, \@allow);
assert_table_acl($table);
push(@tables, $table);
@@ -49,7 +49,7 @@ if ($in{'action'} eq 'create') {
return;
}
ui_print_header(undef, $text{'setup_title'}, "", "intro", 1, 1);
ui_print_header(undef, $text{'index_profile_setup'}, "", "intro", 1, 1);
print ui_form_start("setup.cgi");
print ui_hidden("action", "create");
@@ -95,248 +95,6 @@ print profile_javascript(@profiles);
print ui_form_end([ [ undef, $text{'setup_create'} ] ]);
ui_print_footer("index.cgi", $text{'index_return'});
# setup_profiles()
# Returns available ruleset profiles and their default policies/services
sub setup_profiles
{
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 usermin 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' => [ ] },
{ 'id' => 'custom',
'name' => $text{'setup_profile_custom'},
'desc' => $text{'setup_profile_custom_desc'},
'input' => 'drop',
'forward' => 'drop',
'output' => 'accept',
'services' => [ ] },
);
}
# setup_services()
# Returns selectable services and ports used by ruleset profiles
sub setup_services
{
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' ] },
);
}
# create_profile_ruleset(profile-id, table-name, &allowed-service-ids)
# Builds an inet table for a selected profile and service list
sub create_profile_ruleset
{
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;
my %ports;
my @special_rules;
foreach my $id (map { $_->{'id'} } @services) {
next if (!$allow{$id});
foreach my $rule (@{$services{$id}->{'rules'}}) {
next if ($seen{$rule}++);
if ($rule =~ /^(tcp|udp)\s+dport\s+(\S+)\s+accept$/) {
$ports{$1}->{$2} = 1;
}
else {
push(@special_rules, $rule);
}
}
}
add_profile_port_set($table, $profile_id, \%ports);
foreach my $rule (@special_rules) {
add_profile_rule($table, 'input', $rule);
}
return $table;
}
# add_profile_port_set(&table, profile-id, &proto-ports)
# Adds profile service port sets and their input accept rules
sub add_profile_port_set
{
my ($table, $profile_id, $ports) = @_;
# Keep TCP and UDP ports in separate sets when they differ, otherwise a UDP
# accept rule would also allow TCP-only service ports.
my @protos = grep { keys %{$ports->{$_}} } sort keys %$ports;
return if (!@protos);
foreach my $proto (@protos) {
next if (!keys %{$ports->{$proto}});
my $set_name = profile_port_set_name($profile_id, $proto, scalar(@protos));
my @elements = normalize_port_set_elements(keys %{$ports->{$proto}});
$table->{'sets'}->{$set_name} = {
'name' => $set_name,
'type' => 'inet_service',
'flags' => (grep { /-/ } @elements) ? 'interval' : undef,
'elements' => \@elements,
'raw_lines' => [ ],
};
add_profile_rule($table, 'input', "$proto dport \@$set_name accept");
}
return;
}
# profile_javascript(@profiles)
# Returns JavaScript for profile-driven table names, notes and service checks
sub profile_javascript
@@ -403,70 +161,3 @@ return <<EOF;
</script>
EOF
}
# add_profile_rule(&table, chain, rule-text)
# Appends a generated rule to a profile table
sub add_profile_rule
{
my ($table, $chain, $text) = @_;
push(@{$table->{'rules'}}, {
'text' => $text,
'chain' => $chain,
'index' => scalar(@{$table->{'rules'}}),
});
return;
}
# profile_table_name(profile-id)
# Returns an unused default table name for a profile
sub profile_table_name
{
my ($profile) = @_;
my $base = profile_base_table_name($profile);
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;
}
# profile_base_table_name(profile-id)
# Returns the base table name for a profile before uniquifying
sub profile_base_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',
);
return $names{$profile} || 'profile_custom';
}
# profile_port_set_name(profile, proto, proto-count)
# Returns the set name used for profile-generated service ports
sub profile_port_set_name
{
my ($profile, $proto, $proto_count) = @_;
my $name = profile_base_table_name($profile);
$name .= "_".$proto if ($proto_count && $proto_count > 1);
$name .= "_ports";
$name =~ s/[^\w-]/_/g;
return $name;
}
# default_profile_table_name()
# Returns the default table name for the default profile
sub default_profile_table_name
{
return profile_table_name('virtualmin');
}

View File

@@ -235,4 +235,20 @@ my $quick_ip_table = {
like(add_quick_ip_rule($quick_ip_table, '2001:db8::1/64', 'allow'),
qr/cannot contain/, 'wrong address family rejected');
my $profile_table = create_profile_ruleset('profile_virtualmin', 'virtualmin', '*');
is($profile_table->{family}, 'inet', 'profile helper family');
is($profile_table->{name}, 'profile_virtualmin', 'profile helper table name');
ok($profile_table->{sets}->{profile_hosting_tcp_ports},
'profile helper tcp port set');
is($profile_table->{sets}->{profile_hosting_tcp_ports}->{flags}, 'interval',
'profile helper tcp port set interval flag');
is_deeply($profile_table->{sets}->{profile_hosting_udp_ports}->{elements},
[ '53' ], 'profile helper udp port set elements');
ok(scalar(grep { $_->{text} eq 'tcp dport @profile_hosting_tcp_ports accept' }
@{$profile_table->{rules}}),
'profile helper tcp set rule');
ok(scalar(grep { $_->{text} eq 'ip6 daddr fe80::/64 udp dport 546 accept' }
@{$profile_table->{rules}}),
'profile helper special dhcpv6 rule');
done_testing();