Merge pull request #1624 from webmin/dev/fail2ban-jails-status-and-actions

Add jails status and actions 1/2 #1623
This commit is contained in:
Jamie Cameron
2022-04-11 21:38:42 -07:00
committed by GitHub
8 changed files with 642 additions and 123 deletions

BIN
fail2ban/images/status.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -28,14 +28,14 @@ if ($err) {
# Show category icons
my @links = ( "list_filters.cgi", "list_actions.cgi",
"list_jails.cgi", "edit_config.cgi",
"edit_manual.cgi", );
"list_jails.cgi", "list_status.cgi",
"edit_config.cgi", "edit_manual.cgi", );
my @titles = ( $text{'filters_title'}, $text{'actions_title'},
$text{'jails_title'}, $text{'config_title'},
$text{'manual_title'}, );
$text{'jails_title'}, $text{'status_title'},
$text{'config_title'}, $text{'manual_title'}, );
my @icons = ( "images/filters.gif", "images/actions.gif",
"images/jails.gif", "images/config.gif",
"images/manual.gif", );
"images/jails.gif", "images/status.gif",
"images/config.gif", "images/manual.gif", );
&icons_table(\@links, \@titles, \@icons, 5);
# Show start / stop buttons

102
fail2ban/list_status.cgi Executable file
View File

@@ -0,0 +1,102 @@
#!/usr/local/bin/perl
# Show a list of all defined actions
use strict;
use warnings;
require './fail2ban-lib.pl';
our (%in, %text, %config);
&ui_print_header(undef, $text{'status_title2'}, "");
# Check if firewalld is used
&foreign_require('firewalld', 'install_check.pl');
my $is_firewalld = &firewalld::is_installed();
my $out = &backquote_logged("$config{'client_cmd'} status 2>&1 </dev/null");
my ($jail_list) = $out =~ /jail\s+list:\s*(.*)/im;
my @jails = split(/,\s*/, $jail_list);
if (@jails) {
my $tdc = "style=\"text-align: center\"";
my @links = ( &select_all_link("jail"),
&select_invert_link("jail") );
my $head;
my @jipsall;
foreach my $jail (@jails) {
my $fh = 'cmdjail';
my $cmd = "$config{'client_cmd'} status ".quotemeta($jail);
my $jcmd = "$cmd 2>&1 </dev/null";
my @head = (undef, $text{"status_head_jail_name"});
my @body = (&ui_link("edit_jail.cgi?name=".urlize($jail), "&nbsp;".&html_escape($jail)));
my $br = '<br>';
my $nbsp = '&nbsp;';
my $ipslimit = sub {
my ($ips, $limit) = @_;
$limit ||= 15;
# Limit sanity check
$limit = 1 if ($limit < 1);
my $ipscount = () = $ips =~ /$br/g;
if ($ipscount > $limit) {
my @ips = split($br, $ips);
@ips = @ips[0 .. $limit];
$ips = join($br, @ips);
$ips .= "<small>$br$nbsp".&text('list_rules_plus_more', $ipscount-$limit)."</small>";
}
return $ips;
};
my $jips;
my $noval;
&open_execute_command($fh, $jcmd, 1);
while(<$fh>) {
if (/-\s+(.*):\s*(.*)/) {
my $col = $1;
my $val = $2;
$col = lc($col);
$col =~ s/\s/_/g;
if ($col !~ /journal_matches/) {
push(@head, "<div $tdc>".$text{"status_head_$col"}."</div>");
if ($col =~ /banned_ip_list/) {
$jips = $val;
my @ips = split(/\s+/, $val);
@ips = map { "<label style=\"white-space: nowrap\">" .
&ui_link("unblock_jail.cgi?unblock=1&jips-@{[&urlize($jail)]}=@{[&urlize($_)]}&jail=@{[&urlize($jail)]}", $_, undef,
"title=\"@{[&text('status_jail_unblock_ip', &quote_escape($_))]}\" onmouseover=\"this.style.textDecoration='line-through'\" onmouseout=\"this.style.textDecoration='none'\""
) .
($is_firewalld ? "&nbsp; &nbsp; " .
&ui_link("unblock_jail.cgi?permblock=1&jips-@{[&urlize($jail)]}=@{[&urlize($_)]}&jail=@{[&urlize($jail)]}", "&empty;", undef,
"title=\"@{[&text('status_jail_permblock_ip', &quote_escape($_))]}\" onmouseover=\"this.style.opacity='1';this.style.filter='grayscale(0)'\" onmouseout=\"this.style.opacity='0.25';this.style.filter='grayscale(100%)'\" style=\"font-size: 125%; margin-right:10px; filter: grayscale(100%); opacity: .25\""
) : undef) . "</label>" } @ips;
$val = "<br>" if ($val);
$val .= join('<br>', @ips);
$val = &$ipslimit($val);
$val .= "<br><br>" if ($val);
$val .= "&ndash;", $noval++ if (!$val);
}
push(@body, $val);
}
}
}
close($fh);
if (!$head++) {
print &ui_form_start("unblock_jail.cgi", "post");
print &ui_links_row(\@links);
print &ui_columns_start(\@head);
}
print &ui_checked_columns_row(\@body, [ 'width=5', undef, $tdc, $tdc, $tdc, $tdc, $noval ? $tdc : undef ], "jail", $jail);
push(@jipsall, ["$jail" => $jips]);
}
if ($head) {
print &ui_columns_end();
print &ui_links_row(\@links);
foreach my $j (@jipsall) {
print &ui_hidden("jips-$j->[0]", "$j->[1]");
}
print &ui_form_end([ [ 'unblock', $text{'status_jail_unblock'} ],
$is_firewalld ?
[ 'permblock', $text{'status_jail_block'} ] : undef ]);
}
}
else {
print $text{'status_jail_noactive'};
}
&ui_print_footer("", $text{'index_return'});

54
fail2ban/unblock_jail.cgi Executable file
View File

@@ -0,0 +1,54 @@
#!/usr/local/bin/perl
# Create, update or delete a action
use strict;
use warnings;
require './fail2ban-lib.pl';
our (%in, %text, %config);
&ReadParse();
&error_setup($text{'status_err_set'});
my @jails = split(/\0/, $in{'jail'});
my $action = $in{'permblock'} ? 'block' : $in{'unblock'} ? 'unblock' : undef;
# Error checks
!$action || $in{'jail'} || &error($text{'status_err_nojail'});
# Unblock given IP in given jail
my $unblock_jailed_ip = sub {
my ($jail, $ip) = @_;
my $cmd = "$config{'client_cmd'} set ".quotemeta($jail)." unbanip ".quotemeta($ip)." 2>&1 </dev/null";
my $out = &backquote_logged($cmd);
if ($?) {
&error(&text('status_err_unban', &html_escape($ip)) . " : $out");
}
};
# Processes jails actions
foreach my $jail (@jails) {
my @jailips = split(/\s+/, $in{"jips-$jail"});
if (@jailips) {
foreach my $ip (@jailips) {
# Blocking permanently IP from given jail
if ($action eq 'block') {
# Add permanent block first
&foreign_require('firewalld');
my $out = &firewalld::add_ip_ban($ip);
if ($out) {
&error(&text('status_err_ban', &html_escape($ip)) . " : $out");
}
# Remove from fail2ban now
&$unblock_jailed_ip($jail, $ip);
}
# Unblocking IP from given jail
elsif ($action eq 'unblock') {
# Just unblock
&$unblock_jailed_ip($jail, $ip);
}
}
}
}
# Log and redirect
&webmin_log('update', 'jail', join(", ", @jails));
&redirect("list_status.cgi");

View File

@@ -230,8 +230,9 @@ return ($w{'port'}, $w{'proto'}, $w{'toport'}, $w{'toaddr'});
# Make the current saved config active
sub apply_firewalld
{
my $out = &backquote_logged("$config{'firewall_cmd'} --reload 2>&1");
return $? ? $out : undef;
&foreign_require("init");
my ($ok, $err) = &init::restart_action($config{'init_name'});
return $ok ? undef : $err;
}
# stop_firewalld()
@@ -353,4 +354,147 @@ else {
}
}
# get_default_zone
# Returns default zone
sub get_default_zone
{
my @zones = &list_firewalld_zones();
my ($zone) = grep { $_->{'default'} } @zones;
return $zone;
}
# add_ip_ban(ip, [zone])
# Ban given IP address in given or default zone
sub add_ip_ban
{
my ($ip, $zone) = @_;
return create_rich_rule('add', $ip, $zone);
}
# remove_ip_ban(ip, [zone])
# Un-ban given IP address in given or default zone
sub remove_ip_ban
{
my ($ip, $zone) = @_;
return create_rich_rule('remove', $ip, $zone);
}
# create_rich_rule(action, ip, [\zone], [opts])
# Add or remove rich rule for given IP in given or default zone
sub create_rich_rule
{
my ($action, $ip, $zone, $opts) = @_;
my $ip_validate = sub {
return &check_ipaddress($_[0]) || &check_ip6address($_[0]);
};
# Default action for permanent ban is 'drop'
my $action_type = "drop";
# Override defaults
if (ref($opts)) {
# Override default action
$action_type = lc($opts->{'action'})
if ($opts->{'action'} &&
$opts->{'action'} =~ /^accept|reject|drop|mark$/);
}
# Zone name
if (!$zone) {
($zone) = get_default_zone();
}
$zone = &sanitize_zone_name($zone->{'name'});
# Validate action
$action eq 'add' || $action eq 'remove' || &error($text{'list_rule_actionerr'});
# Validate IP
&$ip_validate($ip) || &error($text{'list_rule_iperr'});
# Set family
my $family = $ip =~ /:/ ? 'ipv6' : 'ipv4';
# Apply block
# (quotemeta doesn't work for params)
my $get_cmd = sub {
my ($rtype) = @_;
my $type;
$type = " --permanent" if ($rtype eq 'permanent');
return "$config{'firewall_cmd'} --zone=".$zone."$type --$action-rich-rule=\"rule family='$family' source address='$ip' $action_type\"";
};
my $out = &backquote_logged(&$get_cmd()." 2>&1 </dev/null");
return $out if ($?);
$out = &backquote_logged(&$get_cmd('permanent')." 2>&1 </dev/null");
return $? ? $out : undef;
}
# remove_rich_rule(rule, [\zone])
# Remove rich rule in given or default zone
sub remove_rich_rule
{
my ($rule, $zone) = @_;
# Zone name
if (!$zone) {
($zone) = get_default_zone();
}
$zone = &sanitize_zone_name($zone->{'name'});
# Sanitize rule
$rule = &sanitize_rule_name($rule);
# Remove rule command
# (quotemeta doesn't work for params)
my $get_cmd = sub {
my ($rtype) = @_;
my $type;
$type = " --permanent" if ($rtype eq 'permanent');
return "$config{'firewall_cmd'} --zone=${zone}${type} --remove-rich-rule '${rule}'";
};
my $out = &backquote_logged(&$get_cmd()." 2>&1 </dev/null");
return $out if ($?);
$out = &backquote_logged(&$get_cmd('permanent')." 2>&1 </dev/null");
return $? ? $out : undef;
}
# remove_direct_rule(rule)
# Remove given direct rule
sub remove_direct_rule
{
my ($rule) = @_;
# Sanitize rule
$rule = &sanitize_rule_name($rule);
# Remove rule command
# (quotemeta doesn't work for params)
my $get_cmd = sub {
my ($rtype) = @_;
my $type;
$type = " --permanent" if ($rtype eq 'permanent');
return "$config{'firewall_cmd'}${type} --direct --remove-rule $rule";
};
my $out = &backquote_logged(&$get_cmd()." 2>&1 </dev/null");
return $out if ($?);
$out = &backquote_logged(&$get_cmd('permanent')." 2>&1 </dev/null");
return $? ? $out : undef;
}
sub sanitize_zone_name
{
my ($zone) = @_;
$zone =~ tr/A-Za-z0-9\-\_//cd;
return $zone;
}
sub sanitize_rule_name
{
my ($rule) = @_;
$rule =~ tr/A-Za-z0-9\-\_\=\"\:\.\,\/ //cd;
return $rule;
}
1;

View File

@@ -30,129 +30,140 @@ if ($err) {
return;
}
# Get rules and zones
my @zones = &list_firewalld_zones();
@zones || &error($text{'index_ezones'});
my @zones;
my $zone;
if ($in{'zone'}) {
($zone) = grep { $_->{'name'} eq $in{'zone'} } @zones;
}
else {
($zone) = grep { $_->{'default'} } @zones;
}
$zone ||= $zones[0];
my ($azone);
eval {
local $main::error_must_die = 1;
my @azones = &list_firewalld_zones(1);
($azone) = grep { $_->{'name'} eq $zone->{'name'} } @azones;
};
# Show zone selector
print &ui_form_start("index.cgi");
print "$text{'index_zone'}&nbsp; ",
&ui_select("zone", $zone->{'name'},
[ map { [ $_->{'name'},
$_->{'name'}.($_->{'default'} ? ' (default)' : '') ]}
@zones ], 1, 0, 0, 0,
"onChange='form.submit()'")," ",
&ui_submit($text{'index_zonedef'}, "defzone")," ",
&ui_submit($text{'index_zonedel'}, "delzone")," &nbsp; ",
&ui_submit($text{'index_zoneadd'}, "addzone")," ",
"<p>\n";
print &ui_form_end();
# Show allowed ports and services in this zone
my @links = ( &ui_link("edit_port.cgi?new=1&zone=".&urlize($zone->{'name'}),
$text{'index_padd'}),
&ui_link("edit_serv.cgi?new=1&zone=".&urlize($zone->{'name'}),
$text{'index_sadd'}),
&ui_link("edit_forward.cgi?new=1&zone=".&urlize($zone->{'name'}),
$text{'index_fadd'}),
);
if (@{$zone->{'services'}} || @{$zone->{'ports'}}) {
my @tds = ( "width=5" );
unshift(@links, &select_all_link("d", 1),
&select_invert_link("d", 1));
print &ui_form_start("delete_rules.cgi", "post");
print &ui_hidden("zone", $zone->{'name'});
print &ui_links_row(\@links);
print &ui_columns_start([ "", $text{'index_type'}, $text{'index_port'},
$text{'index_proto'} ], 100, 0, \@tds);
foreach my $s (@{$zone->{'services'}}) {
my $url = "edit_serv.cgi?id=".&urlize($s).
"&zone=".&urlize($zone->{'name'});
my $sportsprotos = &list_firewalld_service_desc($s);
my $sport = $sportsprotos->{'ports'};
my $sprotocols = $sportsprotos->{'protocols'};
$sport = " ($sport)" if ($sport);
print &ui_checked_columns_row([
&ui_link($url, $text{'index_tservice'}),
&ui_link($url, "$s$sport"),
$sprotocols || "",
], \@tds, "d", "service/".$s);
}
foreach my $p (@{$zone->{'ports'}}) {
my $url = "edit_port.cgi?id=".&urlize($p).
"&zone=".&urlize($zone->{'name'});
my ($port, $proto) = split(/\//, $p);
print &ui_checked_columns_row([
&ui_link($url, $text{'index_tport'}),
&ui_link($url, $port),
uc($proto),
], \@tds, "d", "port/".$p);
}
foreach my $f (@{$zone->{'forward-ports'}}) {
my ($port, $proto, $dstport, $dstaddr) =
&parse_firewalld_forward($f);
my $p = join("/", $port, $proto, $dstport, $dstaddr);
my $url = "edit_forward.cgi?id=".&urlize($p).
"&zone=".&urlize($zone->{'name'});
print &ui_checked_columns_row([
&ui_link($url, $text{'index_tforward'}),
&ui_link($url, $port),
&ui_link($url, uc($proto)),
], \@tds, "d", "forward/".$p);
}
print &ui_columns_end();
print &ui_links_row(\@links);
print &ui_form_end([ [ undef, $text{'index_delete'} ] ]);
}
else {
print "$text{'index_none'}<p>\n";
print &ui_links_row(\@links);
}
if ($azone) {
# Show interfaces for this zone
print &ui_form_start("save_ifaces.cgi");
print &ui_hidden("zone", $zone->{'name'});
print "<br>$text{'index_ifaces'}&nbsp;\n";
my %zifcs = map { $_, 1 } &unique(@{$azone->{'interfaces'}},
@{$zone->{'interfaces'}});
print &ui_radio("iface_def", %zifcs ? 0 : 1,
[ [ 1, $text{'index_ifaces_def'} ],
[ 0, $text{'index_ifaces_sel'} ] ]),"\n";
foreach my $i (&list_system_interfaces()) {
print &ui_checkbox("iface", $i, $i, $zifcs{$i}),"\n";
}
print &ui_submit($text{'save'});
print &ui_form_end();
}
# Show start/apply buttons
print &ui_hr();
print &ui_buttons_start();
# Is FirewallD running if not, show start button
my $ok = &is_firewalld_running();
if ($ok) {
print &ui_buttons_row("restart.cgi", $text{'index_restart'},
$text{'index_restartdesc'},
# Get rules and zones
@zones = &list_firewalld_zones();
@zones || &error($text{'index_ezones'});
if ($in{'zone'}) {
($zone) = grep { $_->{'name'} eq $in{'zone'} } @zones;
}
else {
($zone) = grep { $_->{'default'} } @zones;
}
$zone ||= $zones[0];
my ($azone);
eval {
local $main::error_must_die = 1;
my @azones = &list_firewalld_zones(1);
($azone) = grep { $_->{'name'} eq $zone->{'name'} } @azones;
};
# Show zone selector
print &ui_form_start("index.cgi");
print "$text{'index_zone'}&nbsp; ",
&ui_select("zone", $zone->{'name'},
[ map { [ $_->{'name'},
$_->{'name'}.($_->{'default'} ? ' (default)' : '') ]}
@zones ], 1, 0, 0, 0,
"onChange='form.submit()'")," ",
&ui_submit($text{'index_zonedef'}, "defzone")," ",
&ui_submit($text{'index_zonedel'}, "delzone")," &nbsp; ",
&ui_submit($text{'index_zoneadd'}, "addzone")," ",
"<p>\n";
print &ui_form_end();
# Show allowed ports and services in this zone
my @links = ( &ui_link("edit_port.cgi?new=1&zone=".&urlize($zone->{'name'}),
$text{'index_padd'}),
&ui_link("edit_serv.cgi?new=1&zone=".&urlize($zone->{'name'}),
$text{'index_sadd'}),
&ui_link("edit_forward.cgi?new=1&zone=".&urlize($zone->{'name'}),
$text{'index_fadd'}),
);
if (@{$zone->{'services'}} || @{$zone->{'ports'}}) {
my @tds = ( "width=5" );
unshift(@links, &select_all_link("d", 1),
&select_invert_link("d", 1));
print &ui_form_start("delete_rules.cgi", "post");
print &ui_hidden("zone", $zone->{'name'});
print &ui_links_row(\@links);
print &ui_columns_start([ "", $text{'index_type'}, $text{'index_port'},
$text{'index_proto'} ], 100, 0, \@tds);
foreach my $s (@{$zone->{'services'}}) {
my $url = "edit_serv.cgi?id=".&urlize($s).
"&zone=".&urlize($zone->{'name'});
my $sportsprotos = &list_firewalld_service_desc($s);
my $sport = $sportsprotos->{'ports'};
my $sprotocols = $sportsprotos->{'protocols'};
$sport = " ($sport)" if ($sport);
print &ui_checked_columns_row([
&ui_link($url, $text{'index_tservice'}),
&ui_link($url, "$s$sport"),
$sprotocols || "",
], \@tds, "d", "service/".$s);
}
foreach my $p (@{$zone->{'ports'}}) {
my $url = "edit_port.cgi?id=".&urlize($p).
"&zone=".&urlize($zone->{'name'});
my ($port, $proto) = split(/\//, $p);
print &ui_checked_columns_row([
&ui_link($url, $text{'index_tport'}),
&ui_link($url, $port),
uc($proto),
], \@tds, "d", "port/".$p);
}
foreach my $f (@{$zone->{'forward-ports'}}) {
my ($port, $proto, $dstport, $dstaddr) =
&parse_firewalld_forward($f);
my $p = join("/", $port, $proto, $dstport, $dstaddr);
my $url = "edit_forward.cgi?id=".&urlize($p).
"&zone=".&urlize($zone->{'name'});
print &ui_checked_columns_row([
&ui_link($url, $text{'index_tforward'}),
&ui_link($url, $port),
&ui_link($url, uc($proto)),
], \@tds, "d", "forward/".$p);
}
print &ui_columns_end();
print &ui_links_row(\@links);
print &ui_form_end([ [ undef, $text{'index_delete'} ] ]);
}
else {
print "$text{'index_none'}<p>\n";
print &ui_links_row(\@links);
}
if ($azone) {
# Show interfaces for this zone
print &ui_form_start("save_ifaces.cgi");
print &ui_hidden("zone", $zone->{'name'});
print "<br>$text{'index_ifaces'}&nbsp;\n";
my %zifcs = map { $_, 1 } &unique(@{$azone->{'interfaces'}},
@{$zone->{'interfaces'}});
print &ui_radio("iface_def", %zifcs ? 0 : 1,
[ [ 1, $text{'index_ifaces_def'} ],
[ 0, $text{'index_ifaces_sel'} ] ]),"\n";
foreach my $i (&list_system_interfaces()) {
print &ui_checkbox("iface", $i, $i, $zifcs{$i}),"\n";
}
print &ui_submit($text{'index_ifaces_apply'});
print &ui_form_end();
}
print &ui_hr();
# Show start/apply buttons
print &ui_buttons_start();
print &ui_buttons_row("list_rules.cgi", $text{'index_listrules'},
&text("index_listrules_restartdesc",
"<tt>".$zone->{'name'}."</tt>"),
[ [ "zone", $zone->{'name'} ] ]);
print &ui_buttons_row("restart.cgi", $text{'index_restart_firewalld'},
$text{'index_restart_firewallddesc'},
[ [ "zone", $zone->{'name'} ] ]);
print &ui_buttons_row("stop.cgi", $text{'index_stop'},
$text{'index_stopdesc'},
[ [ "zone", $zone->{'name'} ] ]);
}
else {
}
# Show Start and disable/enabled at boot button
if (!$ok) {
print &ui_buttons_start();
print &ui_buttons_row("start.cgi", $text{'index_start'},
$text{'index_startdesc'},
[ [ "zone", $zone->{'name'} ] ]);

182
firewalld/list_rules.cgi Executable file
View File

@@ -0,0 +1,182 @@
#!/usr/local/bin/perl
# List FirewallD rich and direct rules
use strict;
use warnings;
require './firewalld-lib.pl';
our (%in, %text, %config);
&ReadParse();
my $dzone = $in{'zone'};
if (!$dzone) {
my $zone = &get_default_zone();
$dzone = $zone->{'name'};
}
&ui_print_header(&text('list_rules_title_sub', "<tt>".&html_escape($dzone)."</tt>"), $text{'list_rules_title'}, "");
my $head;
my @head = (undef, $text{'list_rules_type'});
my $tdc = "style=\"text-align: center\"";
my @links = ( &select_all_link("rules"),
&select_invert_link("rules") );
# Check rich rules first
my $fh = 'rrules';
my $rcmd = "$config{'firewall_cmd'} --list-rich-rules --zone=$dzone";
&open_execute_command($fh, "$rcmd 2>&1 </dev/null", 1);
while(<$fh>) {
my @body;
if ($_ =~ /\S+/) {
push(@body, $text{'list_rules_type_rich'});
# Get protocol
if (/family=["'](ipv\d)["']/) {
push(@head, $text{'list_rules_protocol'});
push(@body, $1 =~ /ipv6/i ? "IPv6" : "IPv4");
}
# Get address
if (/address=["'](.*?)["']/) {
push(@head, $text{'list_rules_ip'});
push(@body, "$1&nbsp;&nbsp;");
}
# Get origin
if (/\s+(source|destination)\s+/) {
push(@head, $text{'list_rules_origin'});
push(@body, $1 eq 'source' ? 'Input' : 'Output');
}
# Get action
if (/(accept|reject|drop|mark$)/i) {
push(@head, $text{'list_rules_action'});
push(@body, ucfirst($1));
}
# Add full rule
push(@head, $text{'list_rules_rule'});
push(@body, "<tt>$_</tt>");
# Print start
if (!$head++) {
print &ui_form_start("save_rules.cgi", "post");
print &ui_hidden("zone", $dzone);
print &ui_links_row(\@links);
print &ui_columns_start(\@head);
}
print &ui_checked_columns_row(\@body, [ 'width=5', $tdc, $tdc, undef, $tdc, $tdc, undef ], "rules", $_);
}
}
close($fh);
# Check direct rules
my $fh2 = 'drules';
my $dcmd = "$config{'firewall_cmd'} --direct --get-all-rules";
&open_execute_command($fh2, "$dcmd 2>&1 </dev/null", 1);
while(<$fh2>) {
my @body;
if ($_ =~ /\S+/) {
my $ndash = "&ndash;";
my $br = "<br>";
my $nbsp = "&nbsp;";
my $ips = $ndash;
my $candelete = 1;
my $ipslimit = sub {
my ($ips, $limit) = @_;
$limit ||= 15;
# Limit sanity check and adjustment
$limit = 1 if ($limit < 1);
$limit -= 1;
my $ipscount = () = $ips =~ /$br/g;
if ($ipscount > $limit) {
my @ips = split($br, $ips);
@ips = @ips[0 .. $limit];
$ips = join($br, @ips);
$ips .= "<small>$br$nbsp".&text('list_rules_plus_more', $ipscount-$limit)."</small>";
}
return $ips;
};
# Extract IPs from match sets
if (/set\s+\-\-match-set\s+(.*?)\s+/) {
my $ipset_name = $1;
my $ipset_cmd = &has_command($config{'firewall_ipset'} || 'ipset');
my $ipset_cmd_out = &backquote_logged("$ipset_cmd list ".quotemeta($ipset_name)." 2>&1 </dev/null");
if (!$?) {
if ($ipset_cmd_out =~ /number\s+of\s+entries:\s+(\d)+/i) {
if ($1 > 0) {
my @ipset_cmd_out_lines = split(/\n/, $ipset_cmd_out);
my @ips = map { $_ =~ /^([0-9\.\:a-f\/]+)/i } @ipset_cmd_out_lines;
$ips = join("$nbsp$nbsp$br", @ips);
}
}
}
# Rules with match sets must not be controlled here
$candelete = 0;
}
# Standard direct rules
else {
# Extract IPs from the rule,
# considering comma separated
my @ips = ($_ =~ /-[sd]\s+([0-9\.\:a-f,\/]+)/gi);
$ips = join("$nbsp$nbsp$br", @ips);
$ips =~ s/\s*,\s*/$nbsp$nbsp$br/g;
$ips ||= $ndash;
}
# Trim the number of IPs to allow at max 10
$ips = &$ipslimit($ips);
# Add type name
push(@body, $text{'list_rules_type_direct'});
# Get protocol
if (/(ipv\d)/) {
push(@head, $text{'list_rules_protocol'});
push(@body, $1 =~ /ipv6/i ? "IPv6" : "IPv4");
}
# Get address
if (/address=["'](.*?)["']/) {
}
push(@head, $text{'list_rules_ip'});
push(@body, $ips);
# Get origin
if (/(INPUT|OUTPUT)/) {
push(@head, $text{'list_rules_origin'});
push(@body, ucfirst(lc($1)));
}
# Get action
if (/(ACCEPT|REJECT|DROP|MARK$)/) {
push(@head, $text{'list_rules_action'});
push(@body, ucfirst(lc($1)));
}
# Add full rule
push(@head, $text{'list_rules_rule'});
push(@body, "<tt>$_</tt>");
# Print start
if (!$head++) {
print &ui_form_start("save_rules.cgi", "post");
print &ui_hidden("zone", $dzone);
print &ui_links_row(\@links);
print &ui_columns_start(\@head);
}
print &ui_checked_columns_row(\@body, [ 'width=5', $tdc, $tdc, undef, $tdc, $tdc, undef ], "rules", $_, undef, !$candelete);
}
}
close($fh2);
if ($head) {
print &ui_columns_end();
print &ui_links_row(\@links);
print &ui_form_end([ [ 'remove', $text{'list_rules_delete'} ] ] );
}
else {
print "There are no existing direct or rich firewall rules to display."
}
&ui_print_footer("index.cgi?zone=".&urlize($dzone), $text{'index_return'});

26
firewalld/save_rules.cgi Executable file
View File

@@ -0,0 +1,26 @@
#!/usr/local/bin/perl
# Delete multiple ports or services
use strict;
use warnings;
require './firewalld-lib.pl';
our (%in, %text);
&error_setup($text{'delete_err'});
&ReadParse();
my @rules = split(/\0/, $in{'rules'});
@rules || &error($text{'delete_enone'});
my @zones = &list_firewalld_zones();
my ($zone) = grep { $_->{'name'} eq $in{'zone'} } @zones;
$zone || &error($text{'port_ezone'});
if ($in{'remove'}) {
foreach my $rule (@rules) {
my $rrfunc = \&{"remove_" . ($rule =~ /^(ipv4|ipv6|eb)/ ? 'direct' : 'rich') . "_rule"};
my $rmerr = &$rrfunc($rule, $zone);
&error(&text('delete_edel', $rule, $rmerr)) if ($rmerr);
}
}
&webmin_log("save", "rules", scalar(@rules));
&redirect("list_rules.cgi?zone=".&urlize($zone->{'name'}));