456 lines
13 KiB
Perl
456 lines
13 KiB
Perl
package SrvMngr::Controller::Fail2ban;
|
|
|
|
#----------------------------------------------------------------------
|
|
# heading : Network
|
|
# description : Fail2Ban
|
|
# navigation : 6000 800
|
|
|
|
# name : fail2ban, method : get, url : /fail2ban, ctlact : fail2ban#main
|
|
# name : fail2banu, method : post, url : /fail2ban, ctlact : fail2ban#do_action
|
|
# name : fail2banr, method : get, url : /fail2ban2, ctlact : fail2ban#do_action_get
|
|
#
|
|
# routes : end
|
|
#----------------------------------------------------------------------
|
|
|
|
use strict;
|
|
use warnings;
|
|
use Mojo::Base 'Mojolicious::Controller';
|
|
|
|
use Locale::gettext;
|
|
use SrvMngr::I18N;
|
|
|
|
use Data::Validate::IP;
|
|
|
|
#use esmith::FormMagick::Panel::fail2ban;
|
|
# qw( get_value get_prop change_settings RemoveIP );
|
|
|
|
use SrvMngr qw( theme_list init_session ip_number );
|
|
|
|
our $cdb = esmith::ConfigDB->open() or die "Couldn't open ConfigDB\n";
|
|
|
|
my %defaultval=('FilterLocalNetworks'=> "enabled",
|
|
'FilterValidRemoteHosts'=> "enabled",
|
|
"Mail" => "enabled",
|
|
"BanTime" => '1800',
|
|
"FindTime" => '900',
|
|
"MaxRetry" => '3',
|
|
"sshd" => 'enabled',
|
|
"qpsmtpd" => 'enabled',
|
|
"dovecot" => 'enabled',
|
|
"httpd-e-smith" => 'enabled',
|
|
"ftp" => 'enabled',
|
|
"lemonldap" => 'enabled',
|
|
"ejabberd" => 'enabled',
|
|
"sogod" => 'disabled',
|
|
"wordpress" => 'disabled',
|
|
"smanager" => 'enabled',
|
|
);
|
|
|
|
|
|
sub main {
|
|
|
|
my $c = shift;
|
|
$c->app->log->info($c->log_req);
|
|
|
|
my %f2b_datas = ();
|
|
my $title = $c->l('f2b_FORM_TITLE');
|
|
|
|
$f2b_datas{'status'} = get_prop('fail2ban', 'status');
|
|
$f2b_datas{'filterlocalnetworks'} = get_prop('fail2ban', 'FilterLocalNetworks');
|
|
$f2b_datas{'filtervalidremotehosts'} = get_prop('fail2ban', 'FilterValidRemoteHosts');
|
|
$f2b_datas{'mail'} = get_prop('fail2ban', 'Mail');
|
|
$f2b_datas{'bantime'} = get_prop('fail2ban', 'BanTime');
|
|
$f2b_datas{'findtime'} = get_prop('fail2ban', 'FindTime');
|
|
$f2b_datas{'maxretry'} = get_prop('fail2ban', 'MaxRetry');
|
|
$f2b_datas{'wordpress'} = get_prop('fail2ban', 'wordpress');
|
|
|
|
$f2b_datas{'sshd'} = get_prop('sshd', 'Fail2Ban');
|
|
$f2b_datas{'qpsmtpd'} = get_prop('qpsmtpd', 'Fail2Ban');
|
|
$f2b_datas{'dovecot'} = get_prop('dovecot', 'Fail2Ban');
|
|
$f2b_datas{'httpd-e-smith'} = get_prop('httpd-e-smith', 'Fail2Ban');
|
|
$f2b_datas{'ftp'} = get_prop('sshd', 'Fail2Ban');
|
|
$f2b_datas{'lemonldap'} = get_prop('lemonldap', 'Fail2Ban');
|
|
$f2b_datas{'ejabberd'} = get_prop('ejabberd', 'Fail2Ban');
|
|
$f2b_datas{'sogod'} = get_prop('sogod', 'Fail2Ban');
|
|
$f2b_datas{'smanager'} = get_prop('smanager', 'Fail2Ban');
|
|
|
|
$c->stash( title => $title, f2b_datas => \%f2b_datas);
|
|
$c->render('fail2ban');
|
|
};
|
|
|
|
|
|
sub do_action {
|
|
|
|
my $c = shift;
|
|
$c->app->log->info($c->log_req);
|
|
|
|
my $rt = $c->current_route;
|
|
|
|
my %f2b_datas = ();
|
|
my $title = $c->l('f2b_FORM_TITLE');
|
|
|
|
my ($res, $result) = '';
|
|
|
|
$f2b_datas{status} = $c->param('Status');
|
|
my $action = ( $c->param('action') || '' );
|
|
$f2b_datas{ip} = $c->param('Ip');
|
|
$f2b_datas{bits} = $c->param('Bits');
|
|
|
|
# controls
|
|
$res = ip_number_or_blank( $c, $f2b_datas{ip} );
|
|
$result .= $res . " <br>" if ( $res ne 'OK' );
|
|
|
|
$res = subnet_mask_bit( $c, $f2b_datas{bit} );
|
|
$result .= $res . " <br>" if ( $res ne 'OK' );
|
|
|
|
$res = validate_network_and_mask( $c, $f2b_datas{ip}, $f2b_datas{bits} );
|
|
$result .= $res . " <br>" if ( $res ne 'OK' );
|
|
|
|
#$result .= 'Blocked for testing d_a ! No updates for now '; # if $action;
|
|
|
|
$res = '';
|
|
if ( ! $result ) {
|
|
$res = $c->do_changes();
|
|
$result .= $res unless $res eq 'OK';
|
|
if ( ! $result ) {
|
|
$result = $c->l('f2b_SUCCESS');
|
|
}
|
|
}
|
|
|
|
$c->stash( title => $title, f2b_datas => \%f2b_datas );
|
|
if ($res ne 'OK') {
|
|
$c->stash( error => $result );
|
|
return $c->render('fail2ban');
|
|
}
|
|
|
|
my $message = 'fail2ban updates DONE';
|
|
$c->app->log->info($message);
|
|
$c->flash( success => $result );
|
|
#$c->flash( error => " No changes applied !!" );
|
|
|
|
#return to 'fail2ban' route !!!
|
|
$c->redirect_to('/fail2ban');
|
|
|
|
};
|
|
|
|
|
|
sub do_action_get {
|
|
|
|
my $c = shift;
|
|
$c->app->log->info($c->log_req);
|
|
|
|
my ($res, $result) = '';
|
|
|
|
# controls
|
|
|
|
my $action = ($c->param('action') || '');
|
|
$result .= $c->l('f2b_ERROR_UPDATING') . " action: $action <br>"
|
|
unless ($action eq 'RemoveIP');
|
|
|
|
my $ip = ($c->param('IP') || '');
|
|
my $whitelist = ($c->param('Whitelist'))? 'true' : 'false';
|
|
|
|
#check ip
|
|
my $validator=Data::Validate::IP->new;
|
|
$result .= $c->l('f2b_ERROR_STOPPING') . " IP: $ip <br>"
|
|
unless ($validator->is_ipv4($ip));
|
|
$ip = $validator->is_ipv4($ip);
|
|
|
|
# validate and untaint jail
|
|
my $jail = ($c->param('Jail') || '');
|
|
# could be [a-zA-Z0-9_\-]
|
|
$jail = $jail =~ /([a-zA-Z0-9_\-]+)/ ? $1 : undef;
|
|
$result .= $c->l('f2b_ERROR_UPDATING') . " jail: $jail <br>"
|
|
unless $jail;
|
|
|
|
#$result .= 'Blocked for testing d_a_g ! No updates for now '; # if $action;
|
|
|
|
$res = '';
|
|
if ( ! $result ) {
|
|
$res = $c->RemoveIP( $ip, $whitelist, $jail );
|
|
$result .= $res unless $res eq 'OK';
|
|
if ( ! $result ) {
|
|
if ($whitelist eq "true" ) {
|
|
$result = $c->l('f2b_SUCCESS_IP_WHITE')." : $ip";
|
|
} else {
|
|
$result = $c->l('f2b_SUCCESS_IP')." : $ip";
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($res ne 'OK') {
|
|
$c->flash( error => $result );
|
|
} else {
|
|
my $message = "fail2ban removeip $ip DONE";
|
|
$c->app->log->info($message);
|
|
$c->flash( success => $result );
|
|
}
|
|
|
|
$c->redirect_to('/fail2ban');
|
|
|
|
};
|
|
|
|
|
|
sub do_changes {
|
|
|
|
my $c = shift;
|
|
my %conf;
|
|
|
|
# Don't process the form unless we clicked the Save button. The event is
|
|
# called even if we chose the Remove link or the Add link.
|
|
|
|
my $ip = ($c->param ('Ip') || '');
|
|
my $status = ($c->param ('Status') || 'status');
|
|
my $FilterLocalNetworks = ($c->param ('FilterLocalNetworks') || "enabled");
|
|
my $FilterValidRemoteHosts= ($c->param ('FilterValidRemoteHosts') || "enabled");
|
|
my $Mail= ($c->param ("Mail") || "enabled");
|
|
my $BanTime= ($c->param ("BanTime") || '1800');
|
|
my $FindTime= ($c->param ("FindTime") || '900');
|
|
my $MaxRetry= ($c->param ("MaxRetry") || '3');
|
|
|
|
# those are stored in a different key dedicated to the service
|
|
my %services;
|
|
$services{'sshd'}= ($c->param ("Sshd") ||'enabled');
|
|
$services{'qpsmtpd'}= ($c->param ("Qpsmtpd") ||'enabled');
|
|
$services{'dovecot'}= ($c->param ("Dovecot") ||'enabled');
|
|
$services{'httpd-e-smith'}= ($c->param ("Httpd-e-smith") ||'enabled');
|
|
$services{'ftp'}= ($c->param ("Ftp") ||'enabled');
|
|
$services{'lemonldap'}= ($c->param ("Lemonldap") ||'enabled');
|
|
$services{'ejabberd'}= ($c->param ("Ejabberd" ) ||'enabled');
|
|
$services{'sogod'}= ($c->param ("Sogod" ) ||'enabled');
|
|
$services{'wordpress'}= ($c->param ("Wordpress") ||'enabled');
|
|
$services{'smanager'}= ($c->param ("Smanager") ||'enabled');
|
|
|
|
|
|
#------------------------------------------------------------
|
|
# Looks good; go ahead and change the access.
|
|
#------------------------------------------------------------
|
|
|
|
my $rec = $cdb->get('fail2ban');
|
|
if ($rec) {
|
|
$rec->set_prop('status', $status);
|
|
# unless prop empty and value eq default
|
|
$rec->set_prop('FilterLocalNetworks', $FilterLocalNetworks)
|
|
unless ( ! $cdb->get_prop('fail2ban','FilterLocalNetworks')
|
|
&& $FilterLocalNetworks eq $defaultval{'FilterLocalNetworks'} );
|
|
$rec->set_prop('FilterValidRemoteHosts', $FilterValidRemoteHosts)
|
|
unless ( ! $cdb->get_prop('fail2ban','FilterValidRemoteHosts')
|
|
&& $FilterValidRemoteHosts eq $defaultval{'FilterValidRemoteHosts'} );
|
|
$rec->set_prop('Mail', $Mail)
|
|
unless ( ! $cdb->get_prop('fail2ban','Mail') && $Mail eq $defaultval{'Mail'} );
|
|
$rec->set_prop('BanTime', $BanTime)
|
|
unless ( ! $cdb->get_prop('fail2ban','BanTime') && $BanTime eq $defaultval{'BanTime'} );
|
|
$rec->set_prop('FindTime', $FindTime)
|
|
unless ( ! $cdb->get_prop('fail2ban','FindTime') && $FindTime eq $defaultval{'FindTime'} );
|
|
$rec->set_prop('MaxRetry', $MaxRetry)
|
|
unless ( ! $cdb->get_prop('fail2ban','MaxRetry') && $MaxRetry eq $defaultval{'MaxRetry'} );
|
|
}
|
|
# for the 9 services update unless key does not exist and property does not exist and value eq default
|
|
foreach my $key (keys %services) {
|
|
if ($key eq "wordpress") {
|
|
$rec = $cdb->get('fail2ban');
|
|
my $getprop = $cdb->get_prop('fail2ban',$key) || "";
|
|
$rec->set_prop($key, $services{$key} )
|
|
unless ( ! $rec || (! $cdb->get_prop('fail2ban', $key) && $services{$key} eq $defaultval{$key} ) );
|
|
} else {
|
|
$rec = $cdb->get($key);
|
|
my $getprop = $cdb->get_prop($key,'Fail2Ban') || "";
|
|
$rec->set_prop('Fail2Ban', $services{$key} )
|
|
unless ( ! $rec || (! $cdb->get_prop($key,'Fail2Ban') && $services{$key} eq $defaultval{$key} ) );
|
|
}
|
|
}
|
|
|
|
# ?? this seems to prevent reload of service if we update something and remove or add an ip... ??
|
|
$c->add_new_valid_from;
|
|
$c->remove_valid_from;
|
|
|
|
unless ( system( "/sbin/e-smith/signal-event", "fail2ban-update" ) == 0 ) {
|
|
return $c->l('f2b_ERROR_UPDATING');
|
|
}
|
|
|
|
unless ( system( "/sbin/e-smith/signal-event", "fail2ban-conf" ) == 0 ) {
|
|
return $c->l('f2b_ERROR_UPDATING');
|
|
}
|
|
|
|
if ( $rec->prop('status') eq 'disabled' ) {
|
|
unless ( `/etc/init.d/fail2ban stop` ) {
|
|
return $c->l('f2b_ERROR_STOPPING');
|
|
}
|
|
}
|
|
|
|
return 'OK';
|
|
}
|
|
|
|
|
|
# RemoveIP after validation
|
|
sub RemoveIP {
|
|
|
|
my ( $c, $ip, $whitelist, $jail ) = @_;
|
|
|
|
unless ( system( "/usr/bin/fail2ban-client set $jail unbanip $ip ".' >/dev/null 2>&1' ) == 0 ) {
|
|
return $c->l('f2b_ERROR_UPDATING');
|
|
}
|
|
|
|
if ($whitelist eq 'true' ) {
|
|
# add $ip to whitelist for the current $jail
|
|
warn "/sbin/e-smith/db configuration setprop fail2ban IgnoreIP `/sbin/e-smith/db configuration getprop fail2ban IgnoreIP`,$ip/32";
|
|
unless ( system( "/sbin/e-smith/db configuration setprop fail2ban IgnoreIP `/sbin/e-smith/db configuration getprop fail2ban IgnoreIP`,$ip/32 ".' >/dev/null 2>&1' ) == 0
|
|
&& system( "/usr/bin/fail2ban-client reload ".' >/dev/null 2>&1' ) == 0
|
|
) {
|
|
return $c->l('f2b_ERROR_UPDATING_WHITE');
|
|
}
|
|
}
|
|
|
|
return 'OK';
|
|
|
|
}
|
|
|
|
|
|
sub add_new_valid_from {
|
|
|
|
my $c = shift;
|
|
|
|
my $ip = $c->param('Ip');
|
|
my $bits = $c->param('Bits');
|
|
|
|
# do nothing if no ip was added
|
|
return 1 unless ($ip);
|
|
|
|
my $rec = $cdb->get('fail2ban');
|
|
return $c->l('f2b_ERR_NO_RECORD') unless $rec;
|
|
|
|
my $prop = $rec->prop('IgnoreIP') || '';
|
|
|
|
my @vals = split /,/, $prop;
|
|
return '' if (grep /^$ip\/$bits$/, @vals); # already have this entry
|
|
|
|
if ($prop ne '') {
|
|
$prop .= ",$ip/$bits";
|
|
} else {
|
|
$prop = "$ip/$bits";
|
|
}
|
|
|
|
$rec->set_prop('IgnoreIP', $prop);
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
sub remove_valid_from {
|
|
|
|
my $c = shift;
|
|
|
|
my @remove = @{$c->every_param('ValidFromRemove')};
|
|
return 1 unless @remove;
|
|
|
|
my @vals = @{$c->get_valid_from()};
|
|
unless (@vals) {
|
|
print STDERR "ERROR: unable to load IgnoreIP property from conf db\n";
|
|
return undef;
|
|
}
|
|
|
|
#$c->app->log->debug("remo: " . $c->dumper(\@remove) .' vals: '. $c->dumper(\@vals));
|
|
|
|
foreach my $entry (@remove) {
|
|
@vals = (grep { $entry ne $_ } @vals);
|
|
}
|
|
|
|
my $prop = '';
|
|
$prop = join(',', @vals) if @vals;
|
|
|
|
$cdb->get('fail2ban')->set_prop('IgnoreIP', $prop);
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
sub ip_number_or_blank {
|
|
|
|
my $c = shift;
|
|
my $ip = shift;
|
|
|
|
if (!defined($ip) || $ip eq "") {
|
|
return 'OK';
|
|
}
|
|
$c->ip_number( $ip );
|
|
}
|
|
|
|
|
|
sub subnet_mask_bit {
|
|
|
|
my ($c, $mask) = @_;
|
|
|
|
my @allowed = (8,9,12,14,16,17,20,22,24,25,28,30,32);
|
|
|
|
if ( !defined($mask) || $mask eq "" || grep( /^$mask$/, @allowed ) ) {
|
|
return "OK";
|
|
}
|
|
return $c->l('f2b_INVALID_SUBNET_MASK');
|
|
}
|
|
|
|
|
|
sub validate_network_and_mask {
|
|
|
|
my $c = shift;
|
|
my $net = shift || "";
|
|
my $mask = shift || "";
|
|
|
|
# my $net = $c->param('Ip') || "";
|
|
if ($net xor $mask) {
|
|
return $c->l('f2b_ERR_INVALID_PARAMS');
|
|
}
|
|
|
|
return 'OK';
|
|
}
|
|
|
|
|
|
sub get_prop {
|
|
|
|
# my $c = shift;
|
|
my $item = shift;
|
|
my $prop = shift;
|
|
my $value = $cdb->get_prop($item, $prop) || '';
|
|
if ( $value eq "" && exists($defaultval{$prop}) && $item eq "fail2ban") {
|
|
$value=$defaultval{$prop};
|
|
} elsif ( $value eq "" && exists($defaultval{$item}) && $prop eq "Fail2Ban" && $item ne "fail2ban" ) {
|
|
$value=$defaultval{$item};
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
|
|
sub get_valid_from {
|
|
|
|
my $c = shift;
|
|
my @vals_sorted = ();
|
|
|
|
my $rec = $cdb->get('fail2ban');
|
|
if ( $rec ) {
|
|
my @vals = (split ',', $rec->prop('IgnoreIP'));
|
|
@vals_sorted = sort ip_sort @vals if @vals;
|
|
# @vals_sorted = @vals;
|
|
}
|
|
|
|
return \@vals_sorted;
|
|
}
|
|
|
|
|
|
sub get_current_deny {
|
|
|
|
my $c = shift;
|
|
|
|
my @cdeny = `/usr/bin/sfail2ban`;
|
|
|
|
return \@cdeny
|
|
}
|
|
|
|
|
|
sub ip_sort(@) {
|
|
return esmith::util::IPquadToAddr($a) <=> esmith::util::IPquadToAddr($b);
|
|
}
|
|
|
|
|
|
1;
|
|
|