mirror of
https://github.com/webmin/webmin.git
synced 2026-06-05 04:40:24 +01:00
Merge branch 'master' of github.com:webmin/webmin
This commit is contained in:
@@ -36,10 +36,13 @@ print ui_table_row(
|
||||
|
||||
foreach my $a (
|
||||
qw(view active create setup chains sets rules raw delete
|
||||
apply bootup import clear quick manual)
|
||||
apply bootup import clear quick quick_ip quick_port
|
||||
quick_service quick_forward manual)
|
||||
)
|
||||
{
|
||||
print ui_table_row($text{'acl_'.$a}, ui_yesno_radio($a, $o->{$a}));
|
||||
my $enabled = $o->{$a};
|
||||
$enabled = $o->{'quick'} if ($a =~ /^quick_/ && !defined($enabled));
|
||||
print ui_table_row($text{'acl_'.$a}, ui_yesno_radio($a, $enabled));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +61,8 @@ else {
|
||||
}
|
||||
foreach my $a (
|
||||
qw(view active create setup chains sets rules raw delete
|
||||
apply bootup import clear quick manual)
|
||||
apply bootup import clear quick quick_ip quick_port
|
||||
quick_service quick_forward manual)
|
||||
)
|
||||
{
|
||||
$_[0]->{$a} = $in{$a} || 0;
|
||||
|
||||
@@ -13,4 +13,8 @@ bootup=1
|
||||
import=1
|
||||
clear=1
|
||||
quick=1
|
||||
quick_ip=1
|
||||
quick_port=1
|
||||
quick_service=1
|
||||
quick_forward=1
|
||||
manual=1
|
||||
|
||||
@@ -32,6 +32,8 @@ my $saddr_val;
|
||||
my $daddr_val;
|
||||
my $sport_val;
|
||||
my $dport_val;
|
||||
my $nat_addr_val;
|
||||
my $nat_port_val;
|
||||
my @addr_set_opts;
|
||||
my @port_set_opts;
|
||||
my %set_families;
|
||||
@@ -99,6 +101,8 @@ $saddr_val = $saddr_set ? "" : $rule->{'saddr'};
|
||||
$daddr_val = $daddr_set ? "" : $rule->{'daddr'};
|
||||
$sport_val = $sport_set ? "" : $rule->{'sport'};
|
||||
$dport_val = $dport_set ? "" : $rule->{'dport'};
|
||||
$nat_addr_val = $rule->{'nat_addr'};
|
||||
$nat_port_val = $rule->{'nat_port'};
|
||||
|
||||
@addr_set_opts = (["", $text{'edit_set_none'}]);
|
||||
@port_set_opts = (["", $text{'edit_set_none'}]);
|
||||
@@ -198,20 +202,39 @@ print ui_table_row(
|
||||
);
|
||||
|
||||
# Action
|
||||
my $show_nat_actions =
|
||||
($chain_def && (($chain_def->{'type'} || '') eq 'nat')) ||
|
||||
($action_sel && $action_sel =~ /^(redirect|dnat)$/);
|
||||
my @action_opts = (
|
||||
["accept", $text{'index_accept'}],
|
||||
["drop", $text{'index_drop'}],
|
||||
["reject", $text{'index_reject'}],
|
||||
["return", $text{'edit_return'}],
|
||||
);
|
||||
push(@action_opts,
|
||||
["redirect", $text{'edit_redirect_action'}],
|
||||
["dnat", $text{'edit_dnat_action'}])
|
||||
if ($show_nat_actions);
|
||||
push(@action_opts,
|
||||
["jump", $text{'edit_jump_action'}],
|
||||
["goto", $text{'edit_goto_action'}]);
|
||||
print ui_table_row(
|
||||
hlink($text{'edit_action'}, "action"),
|
||||
ui_select(
|
||||
"action",
|
||||
$action_sel,
|
||||
[
|
||||
["accept", $text{'index_accept'}],
|
||||
["drop", $text{'index_drop'}],
|
||||
["reject", $text{'index_reject'}],
|
||||
["return", $text{'edit_return'}],
|
||||
["jump", $text{'edit_jump_action'}],
|
||||
["goto", $text{'edit_goto_action'}],
|
||||
]
|
||||
)
|
||||
ui_select("action", $action_sel, \@action_opts)
|
||||
);
|
||||
|
||||
my $nat_show = $action_sel && $action_sel =~ /^(redirect|dnat)$/;
|
||||
my $nat_addr_style = $action_sel && $action_sel eq 'dnat' ? "" : " style='display:none'";
|
||||
my $nat_port_style = $nat_show ? "" : " style='display:none'";
|
||||
print ui_table_row(
|
||||
hlink($text{'edit_nat_addr'}, "nat_addr"),
|
||||
ui_textbox("nat_addr", $nat_addr_val, 30),
|
||||
undef, undef, ["id='nftables_nat_addr_row'".$nat_addr_style]
|
||||
);
|
||||
print ui_table_row(
|
||||
hlink($text{'edit_nat_port'}, "nat_port"),
|
||||
ui_textbox("nat_port", $nat_port_val, 10),
|
||||
undef, undef, ["id='nftables_nat_port_row'".$nat_port_style]
|
||||
);
|
||||
|
||||
# Addresses
|
||||
@@ -400,6 +423,9 @@ my $icmpv6_js = js_array(@icmpv6_types);
|
||||
my $icmp_any = $text{'edit_proto_any'};
|
||||
$icmp_any =~ s/\\/\\\\/g;
|
||||
$icmp_any =~ s/"/\\"/g;
|
||||
my $table_family = $table->{'family'} || '';
|
||||
$table_family =~ s/\\/\\\\/g;
|
||||
$table_family =~ s/"/\\"/g;
|
||||
my $set_fam_js = js_object(%set_families);
|
||||
|
||||
print "<script>\n";
|
||||
@@ -407,6 +433,7 @@ print "(function() {\n";
|
||||
print " var icmpTypes = $icmp_js;\n";
|
||||
print " var icmpv6Types = $icmpv6_js;\n";
|
||||
print " var icmpAnyLabel = \"$icmp_any\";\n";
|
||||
print " var tableFamily = \"$table_family\";\n";
|
||||
print " var setFamilies = $set_fam_js;\n";
|
||||
print <<'EOF';
|
||||
function byName(name) {
|
||||
@@ -454,6 +481,13 @@ print <<'EOF';
|
||||
function guessFamily(addr) {
|
||||
return addr.indexOf(":") >= 0 ? "ip6" : "ip";
|
||||
}
|
||||
function natTarget(addr, port) {
|
||||
if (addr) {
|
||||
if (port) return addr.indexOf(":") >= 0 ? "[" + addr + "]:" + port : addr + ":" + port;
|
||||
return addr;
|
||||
}
|
||||
return port ? ":" + port : "";
|
||||
}
|
||||
function familyForSet(name) {
|
||||
return setFamilies && setFamilies[name] ? setFamilies[name] : "";
|
||||
}
|
||||
@@ -468,6 +502,7 @@ print <<'EOF';
|
||||
function buildRule() {
|
||||
var direct = byName("edit_direct");
|
||||
if (direct && direct.checked) return;
|
||||
toggleNatFields();
|
||||
var parts = [];
|
||||
|
||||
var iif = ifaceVal("iif");
|
||||
@@ -573,6 +608,19 @@ print <<'EOF';
|
||||
var go = val("goto");
|
||||
if (action === "jump" && jump) parts.push("jump " + jump);
|
||||
else if (action === "goto" && go) parts.push("goto " + go);
|
||||
else if (action === "redirect") {
|
||||
var redirectTarget = natTarget("", val("nat_port"));
|
||||
parts.push(redirectTarget ? "redirect to " + redirectTarget : "redirect");
|
||||
}
|
||||
else if (action === "dnat") {
|
||||
var natAddr = val("nat_addr");
|
||||
var natPort = val("nat_port");
|
||||
var dnatTarget = natTarget(natAddr, natPort);
|
||||
var dnatExpr = "dnat";
|
||||
if (natAddr && tableFamily === "inet") dnatExpr += " " + guessFamily(natAddr);
|
||||
if (dnatTarget) dnatExpr += " to " + dnatTarget;
|
||||
parts.push(dnatExpr);
|
||||
}
|
||||
else if (action && action !== "jump" && action !== "goto") parts.push(action);
|
||||
|
||||
var comment = val("comment");
|
||||
@@ -603,6 +651,17 @@ print <<'EOF';
|
||||
if (!on) buildRule();
|
||||
}
|
||||
|
||||
function setRowVisible(id, visible) {
|
||||
var el = document.getElementById(id);
|
||||
if (el) el.style.display = visible ? "" : "none";
|
||||
}
|
||||
|
||||
function toggleNatFields() {
|
||||
var action = val("action");
|
||||
setRowVisible("nftables_nat_addr_row", action === "dnat");
|
||||
setRowVisible("nftables_nat_port_row", action === "redirect" || action === "dnat");
|
||||
}
|
||||
|
||||
function uniqList(list) {
|
||||
var seen = {};
|
||||
var out = [];
|
||||
@@ -683,6 +742,7 @@ print <<'EOF';
|
||||
el.addEventListener("input", buildRule);
|
||||
el.addEventListener("change", buildRule);
|
||||
}
|
||||
toggleNatFields();
|
||||
updateIcmpTypes();
|
||||
toggleDirect();
|
||||
buildRule();
|
||||
|
||||
@@ -18,10 +18,301 @@ if (!$can_view_saved &&
|
||||
}
|
||||
my $partial = $in{'partial'};
|
||||
if (!$partial) {
|
||||
ui_print_header(nft_version_text(), $text{'index_title'}, "", "intro", 1, 1,
|
||||
undef, restart_button());
|
||||
ui_print_header(nft_version_text() || "",
|
||||
$text{'index_title'}, "", "intro", 1, 1,
|
||||
undef, restart_button());
|
||||
}
|
||||
|
||||
# quick_hidden_fields(table-index, &table, selected-view)
|
||||
# Returns hidden table selectors for quick action forms
|
||||
sub quick_hidden_fields
|
||||
{
|
||||
my ($idx, $table, $view) = @_;
|
||||
return ui_hidden("table", $idx).
|
||||
ui_hidden("table_family", $table->{'family'}).
|
||||
ui_hidden("table_name", $table->{'name'}).
|
||||
ui_hidden("view", $view);
|
||||
}
|
||||
|
||||
# quick_service_autocomplete()
|
||||
# Returns the quick service textbox and JavaScript-backed matcher
|
||||
sub quick_service_autocomplete
|
||||
{
|
||||
my $placeholder = quote_escape($text{'quick_service_placeholder'});
|
||||
my $results_style = "display: none; position: absolute; z-index: 1000; ".
|
||||
"left: 0; right: auto; width: 100%; min-width: 0; ".
|
||||
"max-height: 18em; overflow: auto; ".
|
||||
"border: 1px solid var(--border-color-input-results, ".
|
||||
"var(--border-color-input, #3f4855)); ".
|
||||
"border-radius: var(--border-radius-input, 3px); ".
|
||||
"background-color: var(--bg-color-input, #fff); ".
|
||||
"color: var(--text-color, inherit);";
|
||||
my $results = ui_tag('div', undef, {
|
||||
'id' => 'nftables_quick_service_results',
|
||||
'role' => 'listbox',
|
||||
'style' => $results_style,
|
||||
});
|
||||
my $input = ui_textbox(
|
||||
"service_text",
|
||||
"",
|
||||
32,
|
||||
undef,
|
||||
undef,
|
||||
"autocomplete='off' placeholder='".$placeholder."'"
|
||||
);
|
||||
my $wrap = ui_tag('span', $input.$results, {
|
||||
'id' => 'nftables_quick_service_wrap',
|
||||
'style' => 'position: relative; display: inline-block; max-width: 100%;',
|
||||
});
|
||||
return ui_hidden("service", "").
|
||||
$wrap.
|
||||
quick_service_autocomplete_javascript();
|
||||
}
|
||||
|
||||
# quick_service_autocomplete_javascript()
|
||||
# Returns JavaScript for the quick service autocomplete widget
|
||||
sub quick_service_autocomplete_javascript
|
||||
{
|
||||
my $labels = convert_to_json({
|
||||
'no_matches' => $text{'quick_service_nomatch'},
|
||||
'failed' => $text{'quick_service_searchfail'},
|
||||
});
|
||||
my $js = <<EOF;
|
||||
(function() {
|
||||
var labels = $labels;
|
||||
if (!window.fetch) {
|
||||
return;
|
||||
}
|
||||
var mode = document.querySelector('form[action="manage_port.cgi"] input[name="mode"][value="service"]');
|
||||
if (!mode || !mode.form) {
|
||||
return;
|
||||
}
|
||||
var form = mode.form;
|
||||
var input = form.querySelector('input[name="service_text"]');
|
||||
var hidden = form.querySelector('input[name="service"]');
|
||||
var box = document.getElementById('nftables_quick_service_results');
|
||||
if (!input || !hidden || !box) {
|
||||
return;
|
||||
}
|
||||
var timer = null;
|
||||
var serial = 0;
|
||||
var currentQuery = "";
|
||||
var results = [];
|
||||
var active = -1;
|
||||
|
||||
function trim(value) {
|
||||
return (value || "").replace(/^\\s+|\\s+\$/g, "");
|
||||
}
|
||||
|
||||
function showBox() {
|
||||
placeBox();
|
||||
box.style.display = "block";
|
||||
}
|
||||
|
||||
function hideBox() {
|
||||
box.style.display = "none";
|
||||
box.textContent = "";
|
||||
results = [];
|
||||
active = -1;
|
||||
}
|
||||
|
||||
function placeBox() {
|
||||
var rect = input.getBoundingClientRect();
|
||||
var below = window.innerHeight - rect.bottom;
|
||||
var above = rect.top;
|
||||
box.style.width = input.offsetWidth + "px";
|
||||
var preferred = Math.min(box.scrollHeight || 288, 288);
|
||||
if (below < preferred && above >= preferred) {
|
||||
box.style.top = "auto";
|
||||
box.style.bottom = (input.offsetHeight + 2) + "px";
|
||||
}
|
||||
else {
|
||||
box.style.top = (input.offsetHeight + 2) + "px";
|
||||
box.style.bottom = "auto";
|
||||
}
|
||||
}
|
||||
|
||||
function styleRow(row, selected) {
|
||||
row.style.padding = "0.25em 0.45em";
|
||||
row.style.cursor = "pointer";
|
||||
row.style.whiteSpace = "nowrap";
|
||||
row.style.overflow = "hidden";
|
||||
row.style.textOverflow = "ellipsis";
|
||||
row.style.borderTop = "1px solid var(--border-color-input-results, #3f4855)";
|
||||
row.style.backgroundColor = selected ?
|
||||
"var(--bg-color-input-results-hover, rgba(127,127,127,0.16))" :
|
||||
"";
|
||||
}
|
||||
|
||||
function setActive(index) {
|
||||
active = index;
|
||||
var rows = box.querySelectorAll('[data-service-id]');
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
styleRow(rows[i], i === active);
|
||||
}
|
||||
var row = rows[active];
|
||||
if (row) {
|
||||
var top = row.offsetTop;
|
||||
var bottom = top + row.offsetHeight;
|
||||
if (top < box.scrollTop) {
|
||||
box.scrollTop = top;
|
||||
}
|
||||
else if (bottom > box.scrollTop + box.clientHeight) {
|
||||
box.scrollTop = bottom - box.clientHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function choose(item) {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
hidden.value = item.id || "";
|
||||
input.value = item.label || item.id || "";
|
||||
hideBox();
|
||||
}
|
||||
|
||||
function message(text) {
|
||||
box.textContent = "";
|
||||
var row = document.createElement("div");
|
||||
row.textContent = text;
|
||||
row.style.padding = "0.25em 0.45em";
|
||||
row.style.fontStyle = "italic";
|
||||
box.appendChild(row);
|
||||
results = [];
|
||||
active = -1;
|
||||
showBox();
|
||||
}
|
||||
|
||||
function draw(items, query) {
|
||||
box.textContent = "";
|
||||
results = items || [];
|
||||
if (!results.length) {
|
||||
if (query) {
|
||||
message(labels.no_matches);
|
||||
}
|
||||
else {
|
||||
hideBox();
|
||||
}
|
||||
return;
|
||||
}
|
||||
results.forEach(function(item, index) {
|
||||
var row = document.createElement("div");
|
||||
row.setAttribute("role", "option");
|
||||
row.setAttribute("data-service-id", item.id || "");
|
||||
row.textContent = item.label || item.id || "";
|
||||
styleRow(row, false);
|
||||
row.addEventListener("mousedown", function(event) {
|
||||
event.preventDefault();
|
||||
choose(item);
|
||||
});
|
||||
row.addEventListener("mousemove", function() {
|
||||
setActive(index);
|
||||
});
|
||||
box.appendChild(row);
|
||||
});
|
||||
showBox();
|
||||
setActive(0);
|
||||
}
|
||||
|
||||
function search() {
|
||||
var query = trim(input.value);
|
||||
currentQuery = query;
|
||||
if (!query) {
|
||||
hideBox();
|
||||
return;
|
||||
}
|
||||
var mySerial = ++serial;
|
||||
fetch("search_services.cgi?q=" + encodeURIComponent(query) + "&limit=20", {
|
||||
credentials: "same-origin"
|
||||
}).then(function(response) {
|
||||
if (!response.ok) {
|
||||
throw new Error("service search failed");
|
||||
}
|
||||
return response.json();
|
||||
}).then(function(items) {
|
||||
if (mySerial !== serial || query !== currentQuery) {
|
||||
return;
|
||||
}
|
||||
draw(items, query);
|
||||
}).catch(function() {
|
||||
if (mySerial === serial) {
|
||||
message(labels.failed);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
input.addEventListener("input", function() {
|
||||
hidden.value = "";
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
timer = setTimeout(search, 200);
|
||||
});
|
||||
|
||||
input.addEventListener("focus", function() {
|
||||
if (trim(input.value) && !hidden.value) {
|
||||
search();
|
||||
}
|
||||
});
|
||||
|
||||
input.addEventListener("keydown", function(event) {
|
||||
var open = box.style.display !== "none";
|
||||
if (!open) {
|
||||
return;
|
||||
}
|
||||
if (event.key === "ArrowDown") {
|
||||
event.preventDefault();
|
||||
if (results.length) {
|
||||
setActive((active + 1) % results.length);
|
||||
}
|
||||
}
|
||||
else if (event.key === "ArrowUp") {
|
||||
event.preventDefault();
|
||||
if (results.length) {
|
||||
setActive((active + results.length - 1) % results.length);
|
||||
}
|
||||
}
|
||||
else if (event.key === "Enter" && active >= 0 && results[active]) {
|
||||
event.preventDefault();
|
||||
choose(results[active]);
|
||||
}
|
||||
else if (event.key === "Escape") {
|
||||
hideBox();
|
||||
}
|
||||
});
|
||||
|
||||
form.addEventListener("submit", function() {
|
||||
if (!hidden.value) {
|
||||
hidden.value = trim(input.value);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("mousedown", function(event) {
|
||||
var wrap = document.getElementById("nftables_quick_service_wrap");
|
||||
if (wrap && !wrap.contains(event.target)) {
|
||||
hideBox();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("resize", function() {
|
||||
if (box.style.display !== "none") {
|
||||
placeBox();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("scroll", function() {
|
||||
if (box.style.display !== "none") {
|
||||
placeBox();
|
||||
}
|
||||
}, true);
|
||||
})();
|
||||
EOF
|
||||
return ui_tag('script', $js, {
|
||||
'type' => 'text/javascript',
|
||||
});
|
||||
}
|
||||
|
||||
# Check for nft command
|
||||
my $cmd = get_nft_command();
|
||||
if (!$cmd) {
|
||||
@@ -443,29 +734,103 @@ else {
|
||||
}
|
||||
$rules_html .= ui_tabs_end(1);
|
||||
|
||||
if (check_acl('quick') && find_input_chain($curr)) {
|
||||
my $ip_placeholder =
|
||||
text('quick_ip_placeholder', '1.2.3.4', '2001:db8::1/64');
|
||||
foreach my $action (
|
||||
['allow', $text{'index_allowip_go'}],
|
||||
['block', $text{'index_blockip_go'}],
|
||||
)
|
||||
{
|
||||
if (check_quick_acl() && !table_supports_quick_l4($curr)) {
|
||||
my @proto_opts = (
|
||||
['tcp', 'TCP'],
|
||||
['udp', 'UDP'],
|
||||
);
|
||||
my $has_input_chain = find_input_chain($curr) ? 1 : 0;
|
||||
if ($has_input_chain) {
|
||||
my $ip_placeholder =
|
||||
text('quick_ip_placeholder', '1.2.3.4', '2001:db8::1/64');
|
||||
if (check_quick_acl('ip')) {
|
||||
foreach my $action (
|
||||
['allow', $text{'index_allowip_go'}],
|
||||
['block', $text{'index_blockip_go'}],
|
||||
)
|
||||
{
|
||||
$rules_html .=
|
||||
"<br>".ui_form_start("manage_ip.cgi", "post");
|
||||
$rules_html .= quick_hidden_fields($in{'table'}, $curr, $tab);
|
||||
$rules_html .= ui_submit($action->[1], $action->[0]).
|
||||
ui_textbox(
|
||||
"ip",
|
||||
undef,
|
||||
22,
|
||||
undef,
|
||||
undef,
|
||||
"placeholder='".
|
||||
quote_escape($ip_placeholder)."'"
|
||||
);
|
||||
$rules_html .= ui_form_end();
|
||||
}
|
||||
}
|
||||
if (check_quick_acl('port')) {
|
||||
$rules_html .=
|
||||
"<br>".ui_form_start("manage_port.cgi", "post");
|
||||
$rules_html .= quick_hidden_fields($in{'table'}, $curr, $tab);
|
||||
$rules_html .= ui_hidden("mode", "port");
|
||||
$rules_html .= ui_submit($text{'index_allowport_go'}, "allow_port").
|
||||
ui_textbox(
|
||||
"port",
|
||||
undef,
|
||||
14,
|
||||
undef,
|
||||
undef,
|
||||
"placeholder='".
|
||||
quote_escape($text{'quick_port_placeholder'})."'"
|
||||
).
|
||||
" ".
|
||||
ui_select("proto", "tcp", \@proto_opts, 1, 0, 1);
|
||||
$rules_html .= ui_form_end();
|
||||
}
|
||||
|
||||
if (check_quick_acl('service')) {
|
||||
$rules_html .=
|
||||
"<br>".ui_form_start("manage_port.cgi", "post");
|
||||
$rules_html .= quick_hidden_fields($in{'table'}, $curr, $tab);
|
||||
$rules_html .= ui_hidden("mode", "service");
|
||||
$rules_html .=
|
||||
ui_submit($text{'index_allowservice_go'},
|
||||
"allow_service").
|
||||
quick_service_autocomplete();
|
||||
$rules_html .= ui_form_end();
|
||||
}
|
||||
}
|
||||
if (check_quick_acl('forward')) {
|
||||
$rules_html .=
|
||||
"<br>".ui_form_start("manage_ip.cgi", "post");
|
||||
$rules_html .= ui_hidden("table", $in{'table'});
|
||||
$rules_html .=
|
||||
ui_hidden("table_family", $curr->{'family'});
|
||||
$rules_html .= ui_hidden("table_name", $curr->{'name'});
|
||||
$rules_html .= ui_submit($action->[1], $action->[0]).
|
||||
"<br>".ui_form_start("manage_forward.cgi", "post");
|
||||
$rules_html .= quick_hidden_fields($in{'table'}, $curr, $tab);
|
||||
$rules_html .= ui_submit($text{'index_forward_go'}, "forward").
|
||||
ui_textbox(
|
||||
"ip",
|
||||
"src_port",
|
||||
undef,
|
||||
22,
|
||||
10,
|
||||
undef,
|
||||
undef,
|
||||
"placeholder='".
|
||||
quote_escape($ip_placeholder)."'"
|
||||
"placeholder='".quote_escape($text{'quick_forward_src'})."'"
|
||||
).
|
||||
" ".
|
||||
ui_select("proto", "tcp", \@proto_opts, 1, 0, 1).
|
||||
" ".
|
||||
$text{'quick_forward_to'}.
|
||||
" ".
|
||||
ui_textbox(
|
||||
"dst_port",
|
||||
undef,
|
||||
10,
|
||||
undef,
|
||||
undef,
|
||||
"placeholder='".quote_escape($text{'quick_forward_dst'})."'"
|
||||
).
|
||||
" ".
|
||||
ui_textbox(
|
||||
"dst_addr",
|
||||
undef,
|
||||
32,
|
||||
undef,
|
||||
undef,
|
||||
"placeholder='".quote_escape($text{'quick_forward_addr'})."'"
|
||||
);
|
||||
$rules_html .= ui_form_end();
|
||||
}
|
||||
|
||||
@@ -58,14 +58,37 @@ index_edit_manual=Edit Config Files
|
||||
index_edit_manualdesc=Edit saved nftables configuration files manually.
|
||||
index_allowip_go=Allow IP/CIDR
|
||||
index_blockip_go=Block IP/CIDR
|
||||
index_allowport_go=Add allowed port
|
||||
index_allowservice_go=Add allowed service
|
||||
index_forward_go=Add port forward
|
||||
quick_ip_placeholder=$1 or $2
|
||||
quick_port_placeholder=80 or 8000-8080
|
||||
quick_service_placeholder=ssh, http or dns
|
||||
quick_service_nomatch=No matching services
|
||||
quick_service_searchfail=Service lookup failed
|
||||
quick_forward_src=from port
|
||||
quick_forward_to=to
|
||||
quick_forward_dst=port
|
||||
quick_forward_addr=destination IP, blank for this system
|
||||
quick_source_ports=source
|
||||
quick_allow_err=Failed to allow IP/CIDR
|
||||
quick_block_err=Failed to block IP/CIDR
|
||||
quick_port_err=Failed to add allowed port
|
||||
quick_service_err=Failed to add allowed service
|
||||
quick_forward_err=Failed to add port forward
|
||||
quick_eaction=No quick action selected.
|
||||
quick_etable=No such table selected.
|
||||
quick_echain=Table $1 has no input chain to add this rule to.
|
||||
quick_efamily=Table $1 cannot contain IP source address rules.
|
||||
quick_efamily=Table $1 cannot contain these quick firewall rules.
|
||||
quick_eip=Enter a valid IPv4 or IPv6 address, with an optional CIDR prefix.
|
||||
quick_eport=Enter a valid TCP or UDP port number, or a range like 8000-8080.
|
||||
quick_eportrange=Port ranges must start with a lower port number.
|
||||
quick_eproto=Select TCP or UDP as the network protocol.
|
||||
quick_eservice=Enter or select a valid service to allow.
|
||||
quick_eservice_empty=Service $1 has no usable TCP, UDP or protocol definitions for nftables.
|
||||
quick_eforward_addr=Enter a valid IPv4 or IPv6 destination address, without a CIDR prefix.
|
||||
quick_eforward_family=Table $1 cannot forward to that destination address family.
|
||||
quick_eforward_target=Enter a destination port and/or destination address for the forward.
|
||||
quick_edup=An equivalent quick rule for $1 already exists.
|
||||
quick_failed=Failed to save and apply quick rule: $1
|
||||
index_unapply=Revert Configuration
|
||||
@@ -172,15 +195,23 @@ index_accept=Accept
|
||||
index_drop=Drop
|
||||
index_reject=Reject
|
||||
index_return_action=Return
|
||||
index_redirect=Redirect
|
||||
index_dnat=DNAT
|
||||
index_redirect_to=Redirect to $1
|
||||
index_dnat_to=DNAT to $1
|
||||
edit_title_new=Create Rule
|
||||
edit_title_edit=Edit Rule
|
||||
edit_comment=Comment
|
||||
edit_action=Action
|
||||
edit_return=Return
|
||||
edit_redirect_action=Redirect
|
||||
edit_dnat_action=DNAT
|
||||
edit_jump_action=Jump
|
||||
edit_goto_action=Goto
|
||||
edit_jump=Jump target chain
|
||||
edit_goto=Goto target chain
|
||||
edit_nat_addr=Forward to address
|
||||
edit_nat_port=Forward to port
|
||||
edit_proto=Protocol
|
||||
edit_proto_any=Any
|
||||
edit_saddr=Source address
|
||||
@@ -361,5 +392,9 @@ acl_apply=Apply saved configuration
|
||||
acl_bootup=Enable firewall at boot
|
||||
acl_import=Import active tables
|
||||
acl_clear=Clear active tables
|
||||
acl_quick=Use quick allow/block controls
|
||||
acl_quick=Use quick controls
|
||||
acl_quick_ip=Use quick IP allow/block controls
|
||||
acl_quick_port=Use quick allowed port controls
|
||||
acl_quick_service=Use quick allowed service controls
|
||||
acl_quick_forward=Use quick port forward controls
|
||||
acl_manual=Edit config files manually
|
||||
|
||||
61
nftables/manage_forward.cgi
Executable file
61
nftables/manage_forward.cgi
Executable file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/perl
|
||||
# manage_forward.cgi
|
||||
# Quickly add a simple port forward in the selected table
|
||||
|
||||
require './nftables-lib.pl'; ## no critic
|
||||
use strict;
|
||||
use warnings;
|
||||
our (%in, %text);
|
||||
ReadParse();
|
||||
assert_quick_acl('forward');
|
||||
error_setup($text{'quick_forward_err'});
|
||||
|
||||
my @tables = get_nftables_save();
|
||||
my $table_idx = $in{'table'};
|
||||
my $table;
|
||||
if (defined($in{'table_family'}) && defined($in{'table_name'})) {
|
||||
for (my $i = 0 ; $i <= $#tables ; $i++) {
|
||||
if ($tables[$i]->{'family'} eq $in{'table_family'} &&
|
||||
$tables[$i]->{'name'} eq $in{'table_name'})
|
||||
{
|
||||
$table_idx = $i;
|
||||
$table = $tables[$i];
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$table = $tables[$table_idx];
|
||||
}
|
||||
$table || error($text{'quick_etable'});
|
||||
assert_table_acl($table);
|
||||
|
||||
my $err = add_quick_forward_rule(
|
||||
$table,
|
||||
$in{'src_port'},
|
||||
$in{'proto'},
|
||||
$in{'dst_port'},
|
||||
$in{'dst_addr'}
|
||||
);
|
||||
error($err) if ($err);
|
||||
|
||||
$err = save_table_configuration($table, @tables);
|
||||
error(text('quick_failed', $err)) if ($err);
|
||||
|
||||
# Quick forwarding is expected to affect the live firewall immediately.
|
||||
$err = apply_restore();
|
||||
error(text('quick_failed', $err)) if ($err);
|
||||
|
||||
webmin_log(
|
||||
"create",
|
||||
"forward",
|
||||
$in{'src_port'},
|
||||
{'table' => $table->{'name'}, 'family' => $table->{'family'}}
|
||||
);
|
||||
my $redir = "index.cgi?table_family=".
|
||||
urlize($table->{'family'}).
|
||||
"&table_name=".
|
||||
urlize($table->{'name'});
|
||||
$redir .= "&view=".urlize($in{'view'})
|
||||
if (($in{'view'} || '') =~ /^(chains|sets)$/);
|
||||
redirect($redir);
|
||||
@@ -7,7 +7,7 @@ use strict;
|
||||
use warnings;
|
||||
our (%in, %text);
|
||||
ReadParse();
|
||||
assert_acl('quick');
|
||||
assert_quick_acl('ip');
|
||||
|
||||
my $action = $in{'allow'} ? 'allow' : $in{'block'} ? 'block' : '';
|
||||
error_setup(
|
||||
@@ -48,7 +48,10 @@ error(text('quick_failed', $err)) if ($err);
|
||||
|
||||
webmin_log($action, "ip", $in{'ip'},
|
||||
{'table' => $table->{'name'}, 'family' => $table->{'family'}});
|
||||
redirect("index.cgi?table_family=".
|
||||
my $redir = "index.cgi?table_family=".
|
||||
urlize($table->{'family'}).
|
||||
"&table_name=".
|
||||
urlize($table->{'name'}));
|
||||
urlize($table->{'name'});
|
||||
$redir .= "&view=".urlize($in{'view'})
|
||||
if (($in{'view'} || '') =~ /^(chains|sets)$/);
|
||||
redirect($redir);
|
||||
|
||||
69
nftables/manage_port.cgi
Executable file
69
nftables/manage_port.cgi
Executable file
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/perl
|
||||
# manage_port.cgi
|
||||
# Quickly allow a port or service in the selected table
|
||||
|
||||
require './nftables-lib.pl'; ## no critic
|
||||
use strict;
|
||||
use warnings;
|
||||
our (%in, %text);
|
||||
ReadParse();
|
||||
|
||||
my $mode = $in{'mode'} || '';
|
||||
assert_quick_acl($mode eq 'service' ? 'service' : 'port');
|
||||
error_setup(
|
||||
$mode eq 'service' ? $text{'quick_service_err'} : $text{'quick_port_err'}
|
||||
);
|
||||
|
||||
my @tables = get_nftables_save();
|
||||
my $table_idx = $in{'table'};
|
||||
my $table;
|
||||
if (defined($in{'table_family'}) && defined($in{'table_name'})) {
|
||||
for (my $i = 0 ; $i <= $#tables ; $i++) {
|
||||
if ($tables[$i]->{'family'} eq $in{'table_family'} &&
|
||||
$tables[$i]->{'name'} eq $in{'table_name'})
|
||||
{
|
||||
$table_idx = $i;
|
||||
$table = $tables[$i];
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$table = $tables[$table_idx];
|
||||
}
|
||||
$table || error($text{'quick_etable'});
|
||||
assert_table_acl($table);
|
||||
|
||||
my $err;
|
||||
my $service = $in{'service'};
|
||||
if (!defined($service) || $service eq '') {
|
||||
$service = $in{'service_text'};
|
||||
}
|
||||
if ($mode eq 'service') {
|
||||
$err = add_quick_service_rule($table, $service);
|
||||
}
|
||||
else {
|
||||
$err = add_quick_port_rule($table, $in{'port'}, $in{'proto'});
|
||||
}
|
||||
error($err) if ($err);
|
||||
|
||||
$err = save_table_configuration($table, @tables);
|
||||
error(text('quick_failed', $err)) if ($err);
|
||||
|
||||
# Quick allow actions are expected to affect the live firewall immediately.
|
||||
$err = apply_restore();
|
||||
error(text('quick_failed', $err)) if ($err);
|
||||
|
||||
webmin_log(
|
||||
"allow",
|
||||
$mode eq 'service' ? "service" : "port",
|
||||
$mode eq 'service' ? $service : $in{'port'},
|
||||
{'table' => $table->{'name'}, 'family' => $table->{'family'}}
|
||||
);
|
||||
my $redir = "index.cgi?table_family=".
|
||||
urlize($table->{'family'}).
|
||||
"&table_name=".
|
||||
urlize($table->{'name'});
|
||||
$redir .= "&view=".urlize($in{'view'})
|
||||
if (($in{'view'} || '') =~ /^(chains|sets)$/);
|
||||
redirect($redir);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -81,6 +81,9 @@ else {
|
||||
$rule->{'action'} = undef;
|
||||
$rule->{'jump'} = undef;
|
||||
$rule->{'goto'} = undef;
|
||||
$rule->{'nat_addr'} = undef;
|
||||
$rule->{'nat_port'} = undef;
|
||||
$rule->{'nat_family'} = undef;
|
||||
if ($action eq 'jump') {
|
||||
$rule->{'jump'} = $in{'jump'};
|
||||
}
|
||||
@@ -90,6 +93,29 @@ else {
|
||||
else {
|
||||
$rule->{'action'} = $action;
|
||||
}
|
||||
if ($action eq 'redirect') {
|
||||
my $nat_port = $in{'nat_port'};
|
||||
$nat_port =~ s/^\s+// if (defined($nat_port));
|
||||
$nat_port =~ s/\s+$// if (defined($nat_port));
|
||||
$rule->{'nat_port'} =
|
||||
(defined($nat_port) && $nat_port ne '') ? $nat_port : undef;
|
||||
}
|
||||
elsif ($action eq 'dnat') {
|
||||
my $nat_addr = $in{'nat_addr'};
|
||||
my $nat_port = $in{'nat_port'};
|
||||
$nat_addr =~ s/^\s+// if (defined($nat_addr));
|
||||
$nat_addr =~ s/\s+$// if (defined($nat_addr));
|
||||
$nat_port =~ s/^\s+// if (defined($nat_port));
|
||||
$nat_port =~ s/\s+$// if (defined($nat_port));
|
||||
$rule->{'nat_addr'} =
|
||||
(defined($nat_addr) && $nat_addr ne '') ? $nat_addr : undef;
|
||||
$rule->{'nat_port'} =
|
||||
(defined($nat_port) && $nat_port ne '') ? $nat_port : undef;
|
||||
$rule->{'nat_family'} =
|
||||
$rule->{'nat_addr'} &&
|
||||
(($table->{'family'} || '') eq 'inet') ?
|
||||
guess_addr_family($rule->{'nat_addr'}) : undef;
|
||||
}
|
||||
|
||||
my $saddr = $in{'saddr'};
|
||||
my $daddr = $in{'daddr'};
|
||||
|
||||
22
nftables/search_services.cgi
Executable file
22
nftables/search_services.cgi
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/perl
|
||||
# search_services.cgi
|
||||
# Return matching quick service definitions for the autocomplete widget
|
||||
|
||||
require './nftables-lib.pl'; ## no critic
|
||||
use strict;
|
||||
use warnings;
|
||||
our (%in);
|
||||
ReadParse();
|
||||
assert_quick_acl('service');
|
||||
|
||||
my $limit = $in{'limit'} || 20;
|
||||
$limit = 20 if ($limit !~ /^\d+$/);
|
||||
my @services = search_quick_services($in{'q'}, $limit);
|
||||
my @results = map {
|
||||
{
|
||||
'id' => $_->{'id'},
|
||||
'label' => $_->{'label'} || $_->{'id'},
|
||||
}
|
||||
} @services;
|
||||
|
||||
print_json(\@results);
|
||||
@@ -1,8 +0,0 @@
|
||||
table inet filter {
|
||||
chain input {
|
||||
type filter hook input priority 0; policy drop;
|
||||
iif "lo" accept
|
||||
ip saddr 192.168.1.0/24 tcp dport 22 accept comment "ssh"
|
||||
ct state established,related accept
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
table inet firewalld {
|
||||
flags owner,persist
|
||||
|
||||
chain filter_INPUT {
|
||||
type filter hook input priority filter + 10; policy accept;
|
||||
ct state { established, related } accept
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
table inet filter {
|
||||
set trusted_v4 {
|
||||
type ipv4_addr;
|
||||
flags interval;
|
||||
elements = { 192.168.1.0/24, 10.0.0.1 }
|
||||
}
|
||||
set web_ports {
|
||||
type inet_service;
|
||||
elements = {
|
||||
80,
|
||||
443
|
||||
}
|
||||
}
|
||||
chain input {
|
||||
type filter hook input priority 0; policy drop;
|
||||
ip saddr @trusted_v4 tcp dport @web_ports accept
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,18 @@ $ENV{'FOREIGN_ROOT_DIRECTORY'} = $rootdir;
|
||||
chdir("$bindir/..") or die "chdir: $!";
|
||||
|
||||
require "$bindir/../nftables-lib.pl";
|
||||
our %access;
|
||||
|
||||
{
|
||||
local %access = (quick => 1);
|
||||
ok(check_quick_acl('forward'), 'quick sub-acl defaults to allowed');
|
||||
$access{quick_forward} = 0;
|
||||
ok(!check_quick_acl('forward'), 'quick sub-acl can deny one action');
|
||||
$access{quick_forward} = 1;
|
||||
ok(check_quick_acl('forward'), 'quick sub-acl can allow one action');
|
||||
$access{quick} = 0;
|
||||
ok(!check_quick_acl('forward'), 'quick master acl denies sub-actions');
|
||||
}
|
||||
|
||||
my $services_file = "$confdir/services";
|
||||
open(my $sfh, ">", $services_file) or die "services: $!";
|
||||
@@ -71,6 +83,16 @@ sub check_fields
|
||||
}
|
||||
}
|
||||
|
||||
sub write_ruleset
|
||||
{
|
||||
my ($dir, $name, $content) = @_;
|
||||
my $file = "$dir/$name";
|
||||
open(my $fh, ">", $file) or die "$name: $!";
|
||||
print $fh $content;
|
||||
close($fh);
|
||||
return $file;
|
||||
}
|
||||
|
||||
my @cases = (
|
||||
{
|
||||
name => 'tcp dport accept',
|
||||
@@ -117,6 +139,30 @@ my @cases = (
|
||||
expect => { proto => 'tcp', dport => '22', action => 'accept' },
|
||||
preserve => 'meta skgid 1000',
|
||||
},
|
||||
{
|
||||
name => 'redirect target',
|
||||
line => 'tcp dport 2023 redirect to :20022 comment "Webmin quick forward"',
|
||||
expect => {
|
||||
proto => 'tcp',
|
||||
dport => '2023',
|
||||
action => 'redirect',
|
||||
nat_port => '20022',
|
||||
comment => 'Webmin quick forward',
|
||||
},
|
||||
},
|
||||
{
|
||||
name => 'dnat target',
|
||||
line => 'tcp dport 8080 dnat ip to 192.0.2.10:80 comment "Webmin quick forward"',
|
||||
expect => {
|
||||
proto => 'tcp',
|
||||
dport => '8080',
|
||||
action => 'dnat',
|
||||
nat_family => 'ip',
|
||||
nat_addr => '192.0.2.10',
|
||||
nat_port => '80',
|
||||
comment => 'Webmin quick forward',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
foreach my $c (@cases) {
|
||||
@@ -134,7 +180,28 @@ foreach my $c (@cases) {
|
||||
check_fields($c->{name}.' roundtrip', $r2, $c->{expect});
|
||||
}
|
||||
|
||||
my $ruleset = "$bindir/rulesets/basic.nft";
|
||||
my $redirect_desc = describe_rule(parse_rule_text(
|
||||
'tcp dport 2026 redirect to :20026 comment "Webmin quick forward"'));
|
||||
like($redirect_desc, qr/Redirect.*:20026.*Destination port 2026/,
|
||||
'redirect rule summary includes target port');
|
||||
my $dnat_desc = describe_rule(parse_rule_text(
|
||||
'tcp dport 2024 dnat ip to 10.211.55.21:20024 comment "Webmin quick forward"'));
|
||||
like($dnat_desc, qr/DNAT.*10\.211\.55\.21:20024.*Destination port 2024/,
|
||||
'dnat rule summary includes target address and port');
|
||||
is(format_forward_target({ family => 'ip' }, '10.211.55.21', 'ip', '20024'),
|
||||
'dnat to 10.211.55.21:20024',
|
||||
'ip quick forward omits inet-only dnat family');
|
||||
|
||||
my $ruleset = write_ruleset($confdir, "basic.nft", <<'EOF');
|
||||
table inet filter {
|
||||
chain input {
|
||||
type filter hook input priority 0; policy drop;
|
||||
iif "lo" accept
|
||||
ip saddr 192.168.1.0/24 tcp dport 22 accept comment "ssh"
|
||||
ct state established,related accept
|
||||
}
|
||||
}
|
||||
EOF
|
||||
my @tables = get_nftables_save($ruleset);
|
||||
ok(@tables == 1, 'ruleset table count');
|
||||
my $t = $tables[0];
|
||||
@@ -147,32 +214,63 @@ is($chain->{hook}, 'input', 'chain hook');
|
||||
is($chain->{priority}, '0', 'chain priority');
|
||||
is($chain->{policy}, 'drop', 'chain policy');
|
||||
|
||||
my $ruleset_prio = "$bindir/rulesets/firewalld-priority.nft";
|
||||
my $ruleset_prio = write_ruleset($confdir, "externally-managed-priority.nft", <<'EOF');
|
||||
table inet externally_managed {
|
||||
flags owner,persist
|
||||
|
||||
chain managed_INPUT {
|
||||
type filter hook input priority filter + 10; policy accept;
|
||||
ct state { established, related } accept
|
||||
}
|
||||
}
|
||||
EOF
|
||||
my @tables_prio = get_nftables_save($ruleset_prio);
|
||||
ok(@tables_prio == 1, 'firewalld priority table count');
|
||||
is($tables_prio[0]->{flags}, 'owner,persist', 'firewalld table flags');
|
||||
ok(table_is_externally_managed($tables_prio[0]), 'firewalld table is externally managed');
|
||||
ok(@tables_prio == 1, 'externally managed priority table count');
|
||||
is($tables_prio[0]->{flags}, 'owner,persist', 'externally managed table flags');
|
||||
ok(table_is_externally_managed($tables_prio[0]),
|
||||
'table with owner,persist flags is externally managed');
|
||||
is(active_table_status($tables_prio[0], []), 'external',
|
||||
'external active table status');
|
||||
is(active_table_status({ family => 'inet', name => 'filter' }, [ $t ]), 'webmin',
|
||||
'saved active table status');
|
||||
is(active_table_status({ family => 'inet', name => 'loose' }, []), 'unclaimed',
|
||||
'unclaimed active table status');
|
||||
my $fw_chain = $tables_prio[0]->{chains}->{filter_INPUT};
|
||||
ok($fw_chain, 'firewalld priority chain present');
|
||||
is($fw_chain->{type}, 'filter', 'firewalld priority chain type');
|
||||
is($fw_chain->{hook}, 'input', 'firewalld priority chain hook');
|
||||
is($fw_chain->{priority}, 'filter + 10', 'firewalld priority chain priority');
|
||||
is($fw_chain->{policy}, 'accept', 'firewalld priority chain policy');
|
||||
my $managed_chain = $tables_prio[0]->{chains}->{managed_INPUT};
|
||||
ok($managed_chain, 'externally managed priority chain present');
|
||||
is($managed_chain->{type}, 'filter', 'externally managed priority chain type');
|
||||
is($managed_chain->{hook}, 'input', 'externally managed priority chain hook');
|
||||
is($managed_chain->{priority}, 'filter + 10',
|
||||
'externally managed symbolic priority preserved');
|
||||
is($managed_chain->{policy}, 'accept',
|
||||
'externally managed priority chain policy');
|
||||
is(scalar @{$tables_prio[0]->{rules}}, 1,
|
||||
'firewalld priority chain definition is not parsed as a rule');
|
||||
'externally managed chain definition is not parsed as a rule');
|
||||
|
||||
my @rules = @{$t->{rules}};
|
||||
check_fields('ruleset r1', $rules[0], { iif => 'lo', action => 'accept' });
|
||||
check_fields('ruleset r2', $rules[1], { saddr => '192.168.1.0/24', proto => 'tcp', dport => '22', action => 'accept', comment => 'ssh' });
|
||||
check_fields('ruleset r3', $rules[2], { ct_state => 'established,related', action => 'accept' });
|
||||
|
||||
my $ruleset_sets = "$bindir/rulesets/sets.nft";
|
||||
my $ruleset_sets = write_ruleset($confdir, "sets.nft", <<'EOF');
|
||||
table inet filter {
|
||||
set trusted_v4 {
|
||||
type ipv4_addr;
|
||||
flags interval;
|
||||
elements = { 192.168.1.0/24, 10.0.0.1 }
|
||||
}
|
||||
set web_ports {
|
||||
type inet_service;
|
||||
elements = {
|
||||
80,
|
||||
443
|
||||
}
|
||||
}
|
||||
chain input {
|
||||
type filter hook input priority 0; policy drop;
|
||||
ip saddr @trusted_v4 tcp dport @web_ports accept
|
||||
}
|
||||
}
|
||||
EOF
|
||||
my @tables_sets = get_nftables_save($ruleset_sets);
|
||||
ok(@tables_sets == 1, 'sets ruleset table count');
|
||||
my $ts = $tables_sets[0];
|
||||
@@ -257,6 +355,112 @@ 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 $quick_port_table = {
|
||||
family => 'inet',
|
||||
name => 'quickports',
|
||||
chains => {
|
||||
input => { hook => 'input' },
|
||||
},
|
||||
sets => {
|
||||
allowed_tcp => {
|
||||
name => 'allowed_tcp',
|
||||
type => 'inet_service',
|
||||
elements => [ '22' ],
|
||||
raw_lines => [ ],
|
||||
},
|
||||
},
|
||||
rules => [
|
||||
{
|
||||
chain => 'input',
|
||||
index => 0,
|
||||
proto => 'tcp',
|
||||
dport => '@allowed_tcp',
|
||||
action => 'accept',
|
||||
text => 'tcp dport @allowed_tcp accept',
|
||||
},
|
||||
],
|
||||
};
|
||||
is(add_quick_port_rule($quick_port_table, '8443', 'tcp'), undef,
|
||||
'quick port added to accepted set');
|
||||
is_deeply($quick_port_table->{sets}->{allowed_tcp}->{elements},
|
||||
[ '22', '8443' ], 'quick port set extended');
|
||||
like(add_quick_port_rule($quick_port_table, '8443', 'tcp'), qr/exists/,
|
||||
'duplicate quick port rejected');
|
||||
|
||||
my ($customsvc) = grep { $_->{id} eq 'customsvc' }
|
||||
read_etc_service_defs($services_file);
|
||||
ok($customsvc, '/etc/services service parsed');
|
||||
is($customsvc->{id}, 'customsvc', '/etc/services service id');
|
||||
like($customsvc->{label}, qr/customsvc \(4242 TCP; 4243 UDP\)/,
|
||||
'/etc/services service label includes ports and protocol');
|
||||
is_deeply([ sort { $a cmp $b } quick_service_rules($customsvc) ],
|
||||
[ 'tcp dport 4242 accept',
|
||||
'udp dport 4243 accept' ],
|
||||
'/etc/services service rules generated');
|
||||
my @service_matches = search_quick_services('customsvc', 5, $services_file);
|
||||
ok(@service_matches, 'quick service search returns matches');
|
||||
is($service_matches[0]->{id}, 'customsvc',
|
||||
'quick service search ranks exact service IDs first');
|
||||
my @alias_service_matches = search_quick_services('custom-alias', 5, $services_file);
|
||||
is($alias_service_matches[0]->{id}, 'customsvc',
|
||||
'quick service search matches /etc/services aliases');
|
||||
is(quick_service_by_id('custom-alias', $services_file)->{id}, 'customsvc',
|
||||
'quick service lookup accepts /etc/services aliases');
|
||||
my @empty_service_matches = search_quick_services('', 5, $services_file);
|
||||
is(scalar(@empty_service_matches), 0,
|
||||
'empty quick service search returns no matches');
|
||||
|
||||
my $forward_table = {
|
||||
family => 'inet',
|
||||
name => 'forward',
|
||||
chains => {
|
||||
input => { type => 'filter', hook => 'input', priority => 0, policy => 'drop' },
|
||||
forward => { type => 'filter', hook => 'forward', priority => 0, policy => 'drop' },
|
||||
},
|
||||
sets => {},
|
||||
rules => [],
|
||||
};
|
||||
is(add_quick_forward_rule($forward_table, '8080', 'tcp', '80', '192.0.2.10'), undef,
|
||||
'quick forward added');
|
||||
ok($forward_table->{chains}->{prerouting}, 'quick forward created prerouting chain');
|
||||
is($forward_table->{chains}->{prerouting}->{type}, 'nat',
|
||||
'quick forward prerouting chain is nat');
|
||||
ok(scalar(grep {
|
||||
$_->{chain} eq 'prerouting' &&
|
||||
$_->{text} eq 'tcp dport 8080 dnat ip to 192.0.2.10:80 comment "Webmin quick forward"'
|
||||
} @{$forward_table->{rules}}),
|
||||
'quick forward DNAT rule added');
|
||||
ok(scalar(grep {
|
||||
$_->{chain} eq 'forward' &&
|
||||
$_->{text} eq 'ct state established,related accept comment "Webmin quick forward"'
|
||||
} @{$forward_table->{rules}}),
|
||||
'quick forward established rule added');
|
||||
ok(scalar(grep {
|
||||
$_->{chain} eq 'forward' &&
|
||||
$_->{text} eq 'ip daddr 192.0.2.10 tcp dport 80 accept comment "Webmin quick forward"'
|
||||
} @{$forward_table->{rules}}),
|
||||
'quick forward destination accept added');
|
||||
|
||||
my $redirect_table = {
|
||||
family => 'inet',
|
||||
name => 'redirect',
|
||||
chains => {
|
||||
input => { type => 'filter', hook => 'input', priority => 0, policy => 'drop' },
|
||||
forward => { type => 'filter', hook => 'forward', priority => 0, policy => 'drop' },
|
||||
},
|
||||
sets => {},
|
||||
rules => [],
|
||||
};
|
||||
is(add_quick_forward_rule($redirect_table, '2023', 'tcp', '20022', ''), undef,
|
||||
'quick local redirect added');
|
||||
my ($redirect_rule) = grep {
|
||||
$_->{chain} eq 'prerouting' &&
|
||||
$_->{text} eq 'tcp dport 2023 redirect to :20022 comment "Webmin quick forward"'
|
||||
} @{$redirect_table->{rules}};
|
||||
ok($redirect_rule, 'quick local redirect rule added');
|
||||
check_fields('quick local redirect rule', $redirect_rule,
|
||||
{ proto => 'tcp', dport => '2023', action => 'redirect', nat_port => '20022' });
|
||||
|
||||
my %setup_services = map { $_->{id} => $_ } setup_services();
|
||||
is($setup_services{ssh}->{port}, '2022, 2200, 2223',
|
||||
'ssh service uses configured sshd ports');
|
||||
|
||||
Reference in New Issue
Block a user