diff --git a/fail2ban/lang/en b/fail2ban/lang/en
index d42794642..4b8099417 100644
--- a/fail2ban/lang/en
+++ b/fail2ban/lang/en
@@ -178,9 +178,16 @@ status_head_total_failed=Total failed
status_head_currently_banned=Currently banned
status_head_total_banned=Total banned
status_head_banned_ip_list=Banned IP list
+status_jail_unblock_ip=Remove $1 from banned list
+status_jail_permblock_ip=Permanently block this IP
status_jail_unblock=Unblock All IPs for Selected Jails
status_jail_block=Permanently Block All IPs in Banned List for Selected Jails
status_jail_noactive=There are no active jails enabled yet.
+status_err_set=Failed set action
+status_err_unblock=Failed to unblock action
+status_err_nojail=No jails has been selected
+status_err_unban=Cannot un-ban $1 IP address
+status_err_ban=Cannot ban $1 IP address
syslog_logtarget=Fail2Ban action log
diff --git a/fail2ban/list_status.cgi b/fail2ban/list_status.cgi
index 4fe53a3b6..173d18d5b 100755
--- a/fail2ban/list_status.cgi
+++ b/fail2ban/list_status.cgi
@@ -16,12 +16,14 @@ if (@jails) {
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 ".&html_escape($jail).""));
+ my $jips;
&open_execute_command($fh, $jcmd, 1);
while(<$fh>) {
if (/-\s+(.*):\s*(.*)/) {
@@ -32,8 +34,17 @@ if (@jails) {
if ($col !~ /journal_matches/) {
push(@head, $text{"status_head_$col"});
if ($col =~ /banned_ip_list/) {
+ $jips = $val;
my @ips = split(/\s+/, $val);
- $val =~ s/\s+/
/g;
+ @ips = map { &ui_link("unblock_jail.cgi?unblock=1&jips-@{[&urlize($jail)]}=@{[&urlize($_)]}&jail=@{[&urlize($jail)]}", $_, undef,
+ "title=\"@{[&text('status_jail_unblock_ip', "e_escape($_))]}\" onmouseover=\"this.style.textDecoration='line-through'\" onmouseout=\"this.style.textDecoration='none'\""
+ ) . " " .
+ &ui_link("unblock_jail.cgi?permblock=1&jips-@{[&urlize($jail)]}=@{[&urlize($_)]}&jail=@{[&urlize($jail)]}", "∅", undef,
+ "title=\"@{[&text('status_jail_permblock_ip', "e_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%; filter: grayscale(100%); opacity: .25\""
+ ) } @ips;
+ $val = "
" if ($val);
+ $val .= join('
', @ips);
+ $val .= "
" if ($val);
}
push(@body, $val);
}
@@ -46,10 +57,14 @@ if (@jails) {
print &ui_columns_start(\@head);
}
print &ui_checked_columns_row(\@body, ['width=5', undef, $tdc, $tdc, $tdc, $tdc], "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'} ],
[ 'permblock', $text{'status_jail_block'} ] ]);
};
diff --git a/fail2ban/unblock_jail.cgi b/fail2ban/unblock_jail.cgi
new file mode 100755
index 000000000..7037d1828
--- /dev/null
+++ b/fail2ban/unblock_jail.cgi
@@ -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 {'action'})
+ if ($opts->{'action'} &&
+ $opts->{'action'} =~ /^accept|reject|drop|mark$/);
+}
+
+# Default zone
+if (!$zone) {
+ my @zones = &list_firewalld_zones();
+ ($zone) = grep { $_->{'default'} } @zones;
+ }
+my $zone_name = $zone->{'name'};
+$zone_name =~ tr/A-Za-z0-9\-\_//cd;
+
+# Validate action
+$action eq 'add' || $action eq 'remove' || &error($text{'richrule_actionerr'});
+
+# Validate IP
+&$ip_validate($ip) || &error($text{'richrule_iperr'});
+
+# Set family
+my $family = $ip =~ /:/ ? 'ipv6' : 'ipv4';
+
+# Apply block (you cannot quotemeta IP address and
+# other params, i.e. must be validated manually)
+my $get_cmd = sub {
+ my ($rtype) = @_;
+ my $type;
+ $type = " --permanent" if ($rtype eq 'permanent');
+ return "$config{'firewall_cmd'} --zone=".$zone_name."$type --$action-rich-rule=\"rule family='$family' source address='$ip' $action_type\"";
+ };
+my $out = &backquote_logged(&$get_cmd()." 2>&1 &1