mirror of
https://github.com/webmin/webmin.git
synced 2026-06-10 14:50:33 +01:00
*Note: Removes Kea-specific ACL wrapper helpers and switches ACL editor/runtime checks to standard Webmin handling with direct supplied ACL values and get_module_acl checks.
260 lines
9.8 KiB
Perl
Executable File
260 lines
9.8 KiB
Perl
Executable File
#!/usr/local/bin/perl
|
|
# Edit or create a Kea subnet.
|
|
|
|
use strict;
|
|
use warnings;
|
|
require './kea-dhcp-lib.pl'; ## no critic
|
|
&ReadParse();
|
|
our (%in, %text);
|
|
&error_setup($text{'eacl_aviol'});
|
|
|
|
my $ver = $in{'version'} == 6 ? 6 : 4;
|
|
my %access = &get_module_acl();
|
|
&error("$text{'eacl_np'} $text{'eacl_pedit'.$ver}")
|
|
if (!$access{'edit'.$ver});
|
|
my ($c, $root, $data, $err) = &kea_read_dhcp_config($ver);
|
|
&error($err) if ($err);
|
|
my $sidx = defined($in{'sidx'}) ? $in{'sidx'} : "";
|
|
&error($text{'subnet_enone'}) if (!&kea_valid_subnet_parent($root, $sidx));
|
|
&error($text{'subnet_enone'})
|
|
if (!$in{'new'} && (!defined($in{'idx'}) || $in{'idx'} !~ /^\d+$/));
|
|
my $sub = $in{'new'} ? { 'id' => &kea_next_subnet_id($root, $ver) }
|
|
: &kea_get_subnet($root, $ver, $sidx, $in{'idx'});
|
|
&error($text{'subnet_enone'}) if (!$sub);
|
|
|
|
# Main request flow: render the tabbed subnet editor, then delegate repeated
|
|
# row-heavy controls to helpers below.
|
|
my $title = $in{'new'} ? $text{'subnet_create'} : $text{'subnet_edit'};
|
|
&ui_print_header(undef, $title, "", undef, 1, 1);
|
|
print &kea_comment_loss_warning($c);
|
|
print &ui_form_start("save_subnet.cgi", "post");
|
|
print &ui_hidden("version", $ver);
|
|
print &ui_hidden("new", 1) if ($in{'new'});
|
|
print &ui_hidden("idx", $in{'idx'}) if (!$in{'new'});
|
|
print &ui_hidden("sidx", $sidx) if ($sidx ne '');
|
|
|
|
my @tabs = (
|
|
[ 'general', $text{'tab_general'} ],
|
|
[ 'pools', $text{'tab_pools'} ],
|
|
[ 'reservations', $text{'tab_reservations'} ],
|
|
[ 'options', $text{'tab_options'} ],
|
|
[ 'advanced', $text{'tab_advanced'} ],
|
|
);
|
|
print &ui_tabs_start(\@tabs, "mode", $in{'mode'} || "general", 1);
|
|
|
|
# General owns the required subnet identity plus the parent shared-network
|
|
# pointer, which determines where the subnet is stored in Kea JSON.
|
|
print &ui_tabs_start_tab("mode", "general");
|
|
print &ui_div($text{'subnet_general_desc'});
|
|
print &ui_table_start($text{'subnet_general'}, "width=100%", 4);
|
|
print &ui_table_row(&kea_field_hlink('subnet-id', $text{'subnet_id'}),
|
|
&ui_textbox("id", $sub->{'id'} || "", 8));
|
|
print &ui_table_row(&kea_field_hlink('subnet-prefix',
|
|
$text{'subnet_prefix'}),
|
|
&ui_textbox("subnet", $sub->{'subnet'} || "", 40));
|
|
print &ui_table_row(&kea_field_hlink('calculated-subnet-mask',
|
|
$text{'subnet_mask_auto'}),
|
|
&ui_tag('tt', &html_escape(&kea_ipv4_mask_from_subnet($sub->{'subnet'} || "")
|
|
|| $text{'index_empty'})))
|
|
if ($ver == 4);
|
|
print &ui_table_row(&kea_field_hlink('description', $text{'subnet_desc'}),
|
|
&ui_textbox("desc", &kea_get_comment($sub) || "", 60));
|
|
|
|
# A subnet may be top-level or nested under a shared network. Kea stores those
|
|
# in different arrays, so the selected parent is carried through saves.
|
|
my @shared_opts = ([ "", "<$text{'shared_none'}>" ]);
|
|
my $shareds = &kea_shared_networks($root);
|
|
for(my $i=0; $i<@$shareds; $i++) {
|
|
push(@shared_opts, [ $i, &kea_scope_name($shareds->[$i], &text('index_shared_num', $i+1)) ]);
|
|
}
|
|
print &ui_table_row(&kea_field_hlink('shared-network',
|
|
$text{'subnet_shared'}),
|
|
&ui_select("parent", $sidx ne '' ? $sidx : "", \@shared_opts));
|
|
print &ui_table_end();
|
|
print &ui_tabs_end_tab("mode", "general");
|
|
|
|
# Pools are row editors: DHCPv4/DHCPv6 address pools are common, while prefix
|
|
# delegation is rendered only for DHCPv6.
|
|
print &ui_tabs_start_tab("mode", "pools");
|
|
print &ui_div($text{'subnet_pools_desc'});
|
|
&pool_rows($sub->{'pools'});
|
|
if ($ver == 6) {
|
|
print &ui_subheading($text{'pd_pools'});
|
|
&pd_pool_rows($sub->{'pd-pools'});
|
|
}
|
|
print &ui_tabs_end_tab("mode", "pools");
|
|
|
|
# Reservations stay compact here, but the parser preserves advanced fields that
|
|
# the UI does not expose.
|
|
print &ui_tabs_start_tab("mode", "reservations");
|
|
print &ui_div($text{'subnet_reservations_desc'});
|
|
&reservation_rows($sub->{'reservations'}, $ver);
|
|
print &ui_tabs_end_tab("mode", "reservations");
|
|
|
|
# Option editing is split between named common fields and generic option-data
|
|
# rows so uncommon options can still round-trip.
|
|
print &ui_tabs_start_tab("mode", "options");
|
|
print &ui_div($text{'subnet_options_desc'});
|
|
&kea_common_option_rows($sub->{'option-data'}, $ver, "common_");
|
|
&kea_option_data_section($sub->{'option-data'}, "opt_", $ver);
|
|
print &ui_tabs_end_tab("mode", "options");
|
|
|
|
# Advanced values affect subnet selection, relay matching, lease timing, and
|
|
# DHCPv4 boot fields. They are top-level subnet keys, not normal options.
|
|
print &ui_tabs_start_tab("mode", "advanced");
|
|
print &ui_div($text{'subnet_advanced_desc'});
|
|
print &ui_table_start($text{'subnet_advanced'}, "width=100%", 4);
|
|
print &ui_table_row(&kea_field_hlink('interface'),
|
|
&ui_textbox("interface", &text_value($sub->{'interface'}), 30));
|
|
print &ui_table_row(&kea_field_hlink('relay_ip_addresses'),
|
|
&ui_textbox("relay_ip_addresses",
|
|
join(" ", &kea_relay_addresses($sub)), 50));
|
|
if ($ver == 4) {
|
|
print &ui_table_row(&kea_field_hlink('authoritative'),
|
|
&ui_select("authoritative", &kea_bool_value($sub->{'authoritative'}),
|
|
[ [ "", $text{'inherit_default'} ],
|
|
[ "true", $text{'yes'} ],
|
|
[ "false", $text{'no'} ] ]));
|
|
}
|
|
foreach my $k ('renew-timer', 'rebind-timer', 'valid-lifetime',
|
|
'min-valid-lifetime', 'max-valid-lifetime') {
|
|
print &ui_table_row(&kea_field_hlink($k),
|
|
&ui_textbox($k, &text_value($sub->{$k}), 12));
|
|
}
|
|
print &ui_table_row(&kea_field_hlink('preferred-lifetime'),
|
|
&ui_textbox("preferred-lifetime", &text_value($sub->{'preferred-lifetime'}), 12))
|
|
if ($ver == 6);
|
|
foreach my $k ('next-server', 'server-hostname', 'boot-file-name') {
|
|
print &ui_table_row(&kea_field_hlink($k),
|
|
&ui_textbox($k, &text_value($sub->{$k}), 40))
|
|
if ($ver == 4);
|
|
}
|
|
&kea_advanced_option_rows($sub->{'option-data'}, $ver, "adv_");
|
|
print &ui_table_end();
|
|
print &ui_tabs_end_tab("mode", "advanced");
|
|
|
|
print &ui_tabs_end();
|
|
|
|
my @buttons = $in{'new'} ? ([ "save", $text{'create'} ]) :
|
|
([ "save", $text{'save'} ],
|
|
[ "delete", $text{'delete'} ]);
|
|
print &ui_form_end(\@buttons);
|
|
&ui_print_footer("", $text{'index_return'});
|
|
|
|
# text_value(value)
|
|
# Returns a defined scalar for textboxes without hiding valid zero values.
|
|
sub text_value
|
|
{
|
|
my ($v) = @_;
|
|
return defined($v) ? $v : "";
|
|
}
|
|
|
|
# pool_rows(&pools)
|
|
# Renders address pools with one extra empty row for adding a pool.
|
|
sub pool_rows
|
|
{
|
|
my ($pools) = @_;
|
|
$pools = [ ] if (ref($pools) ne 'ARRAY');
|
|
print &ui_table_start($text{'tab_pools'}, "width=100%", 2);
|
|
|
|
# Always include one empty row so adding a pool does not need a separate
|
|
# client-side table mutation.
|
|
for(my $i=0; $i<=$#$pools+1; $i++) {
|
|
my $p = $pools->[$i] || { };
|
|
print &ui_table_row(&kea_field_hlink('address-pool',
|
|
$text{'pool_range'}),
|
|
&ui_textbox("pool_pool_$i", $p->{'pool'} || "", 60));
|
|
}
|
|
print &ui_table_end();
|
|
}
|
|
|
|
# pd_pool_rows(&pd-pools)
|
|
# Renders DHCPv6 prefix delegation pools with room for one new entry.
|
|
sub pd_pool_rows
|
|
{
|
|
my ($pools) = @_;
|
|
$pools = [ ] if (ref($pools) ne 'ARRAY');
|
|
|
|
# Prefix delegation rows are wider than standard form rows, so keep them in the
|
|
# same table wrapper used by generic option-data editors.
|
|
print &ui_tag_start('div', { 'class' => 'option-data-table' });
|
|
print &ui_columns_start([
|
|
&kea_field_hlink('pd-prefix', $text{'pd_prefix'}),
|
|
&kea_field_hlink('pd-prefix-len', $text{'pd_prefix_len'}),
|
|
&kea_field_hlink('pd-delegated-len', $text{'pd_delegated_len'}),
|
|
&kea_field_hlink('pd-excluded-prefix', $text{'pd_excluded_prefix'}),
|
|
&kea_field_hlink('pd-excluded-prefix-len',
|
|
$text{'pd_excluded_prefix_len'}) ], 100);
|
|
for(my $i=0; $i<=$#$pools+1; $i++) {
|
|
my $p = $pools->[$i] || { };
|
|
print &ui_columns_row([
|
|
&ui_textbox("pd_prefix_$i", $p->{'prefix'} || "", 26),
|
|
&ui_textbox("pd_prefix_len_$i", $p->{'prefix-len'} || "", 5),
|
|
&ui_textbox("pd_delegated_len_$i", $p->{'delegated-len'} || "", 5),
|
|
&ui_textbox("pd_excluded_prefix_$i", $p->{'excluded-prefix'} || "", 26),
|
|
&ui_textbox("pd_excluded_prefix_len_$i", $p->{'excluded-prefix-len'} || "", 5),
|
|
]);
|
|
}
|
|
print &ui_columns_end();
|
|
print &ui_tag_end('div');
|
|
}
|
|
|
|
# reservation_rows(&reservations, version)
|
|
# Renders host reservations without trying to flatten every advanced Kea field.
|
|
sub reservation_rows
|
|
{
|
|
my ($reservations, $ver) = @_;
|
|
$reservations = [ ] if (ref($reservations) ne 'ARRAY');
|
|
|
|
# Kea accepts different reservation identifiers per protocol; the dropdown is
|
|
# limited to identifiers that the selected daemon can actually use.
|
|
my @types = $ver == 6 ?
|
|
([ 'duid', 'DUID' ], [ 'hw-address', $text{'res_hw'} ],
|
|
[ 'flex-id', 'Flex ID' ]) :
|
|
([ 'hw-address', $text{'res_hw'} ], [ 'client-id', $text{'res_client'} ],
|
|
[ 'duid', 'DUID' ], [ 'circuit-id', $text{'res_circuit'} ],
|
|
[ 'flex-id', 'Flex ID' ]);
|
|
my @heads = ( &kea_field_hlink('reservation-identifier-type',
|
|
$text{'res_type'}),
|
|
&kea_field_hlink('reservation-identifier',
|
|
$text{'res_identifier'}),
|
|
$ver == 6 ?
|
|
&kea_field_hlink('reservation-addresses',
|
|
$text{'res_addresses'}) :
|
|
&kea_field_hlink('reservation-address',
|
|
$text{'res_address'}),
|
|
&kea_field_hlink('reservation-hostname',
|
|
$text{'res_hostname'}) );
|
|
push(@heads, &kea_field_hlink('reservation-prefixes',
|
|
$text{'res_prefixes'})) if ($ver == 6);
|
|
print &ui_tag_start('div', { 'class' => 'option-data-table' });
|
|
print &ui_columns_start(\@heads, 100);
|
|
|
|
# Pick the first identifier field already present, otherwise default to the
|
|
# common identifier for the protocol.
|
|
for(my $i=0; $i<=$#$reservations+1; $i++) {
|
|
my $r = $reservations->[$i] || { };
|
|
my $rtype = "";
|
|
foreach my $k (map { $_->[0] } @types) {
|
|
if (defined($r->{$k})) {
|
|
$rtype = $k;
|
|
last;
|
|
}
|
|
}
|
|
$rtype ||= $types[0]->[0];
|
|
my $addr = $ver == 6 ? join(" ", @{$r->{'ip-addresses'} || [ ]}) :
|
|
$r->{'ip-address'};
|
|
my @cols = (
|
|
&ui_select("res_type_$i", $rtype, \@types),
|
|
&ui_textbox("res_identifier_$i", $r->{$rtype} || "", 28),
|
|
&ui_textbox("res_address_$i", $addr || "", 32),
|
|
&ui_textbox("res_hostname_$i", $r->{'hostname'} || "", 22),
|
|
);
|
|
push(@cols, &ui_textbox("res_prefixes_$i",
|
|
join(" ", @{$r->{'prefixes'} || [ ]}), 30)) if ($ver == 6);
|
|
print &ui_columns_row(\@cols);
|
|
}
|
|
print &ui_columns_end();
|
|
print &ui_tag_end('div');
|
|
}
|