#!/usr/bin/perl # setup.cgi # 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'}); assert_acl('setup'); if ($in{'action'} eq 'create') { my $profile = $in{'profile'} || 'virtualmin'; my $table_name = $in{'table_name'} || default_profile_table_name(); $table_name =~ /^\w[\w-]*$/ || error($text{'create_ename'}); 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($table_name, $profile, \@allow); assert_table_acl($table); 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)); return; } ui_print_header(undef, $text{'index_profile_setup'}, ""); print ui_form_start("setup.cgi"); print ui_hidden("action", "create"); 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'}); # profile_javascript(@profiles) # Returns JavaScript for profile-driven table names, notes and service checks 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 }