Compare commits
312
Commits
debian/python3
...
0.9.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acc4c2d104 | ||
|
|
840fea9f71 | ||
|
|
c0e1333fe6 | ||
|
|
ced4852383 | ||
|
|
7ae0ef2408 | ||
|
|
4aafb2d4ac | ||
|
|
8f792f52fb | ||
|
|
0f75ed5e2a | ||
|
|
1784205ff8 | ||
|
|
1fb867b839 | ||
|
|
ca849b93dc | ||
|
|
23d9e22477 | ||
|
|
b530d88eca | ||
|
|
878cbd008e | ||
|
|
f8c7247f42 | ||
|
|
5f2807b41f | ||
|
|
6ba389c70c | ||
|
|
7a1f1c6b0c | ||
|
|
b9a09af914 | ||
|
|
8825a5f31b | ||
|
|
13d56af981 | ||
|
|
aa8113c128 | ||
|
|
c926af1fce | ||
|
|
b2c1673d28 | ||
|
|
eb05cd7bd5 | ||
|
|
6da0c4ad48 | ||
|
|
c5ba76aab8 | ||
|
|
72f4bcfbff | ||
|
|
d28880fdca | ||
|
|
54f111429d | ||
|
|
9339293413 | ||
|
|
4a83741397 | ||
|
|
56aacf872c | ||
|
|
02836b599c | ||
|
|
320a28a4a4 | ||
|
|
938e6343ea | ||
|
|
d5d8bc8134 | ||
|
|
382e7f02ca | ||
|
|
782629c256 | ||
|
|
3f3ddaceae | ||
|
|
dd9b5f75af | ||
|
|
31d107d181 | ||
|
|
daa2a9e5d8 | ||
|
|
954075449d | ||
|
|
e52790073d | ||
|
|
0254cbf7fb | ||
|
|
5ab30c88c2 | ||
|
|
2bfe22aa66 | ||
|
|
6c788a32ee | ||
|
|
50cbd4e9eb | ||
|
|
82d5e6b840 | ||
|
|
83805ee5dc | ||
|
|
e788e3823e | ||
|
|
b4f1f613bb | ||
|
|
0fac7e40b6 | ||
|
|
54e182e017 | ||
|
|
ae2af0d51b | ||
|
|
07b0ab07ad | ||
|
|
be75519402 | ||
|
|
74c6f6ac4b | ||
|
|
d5e68abf95 | ||
|
|
ae1451b29f | ||
|
|
3fb2becddb | ||
|
|
6268eb32be | ||
|
|
b46ac0b58e | ||
|
|
549ab24e70 | ||
|
|
119a7bbb16 | ||
|
|
4c88a00c28 | ||
|
|
d676a9fd4f | ||
|
|
445fd7367f | ||
|
|
40068f5f31 | ||
|
|
eaca33e227 | ||
|
|
ed71a7cd22 | ||
|
|
b28fea4d41 | ||
|
|
9bd25f51c1 | ||
|
|
642e425f75 | ||
|
|
5b3aa42c42 | ||
|
|
43a30f0ad6 | ||
|
|
eb0d086ed0 | ||
|
|
4091fdde27 | ||
|
|
1c6d2074fb | ||
|
|
991096e599 | ||
|
|
51333bb0a6 | ||
|
|
50d18f68df | ||
|
|
c755138672 | ||
|
|
8233f21fc2 | ||
|
|
aa848a5f9e | ||
|
|
e7ff7e90b7 | ||
|
|
fb0f463eac | ||
|
|
705718be52 | ||
|
|
18778d9174 | ||
|
|
3186df55e3 | ||
|
|
646c799231 | ||
|
|
1fbafa46c9 | ||
|
|
73af02ffc6 | ||
|
|
df581fe6e2 | ||
|
|
7f2d1a7269 | ||
|
|
7ada96b4e9 | ||
|
|
f8fe165cd2 | ||
|
|
767ac4ecac | ||
|
|
8f6d9c6a5a | ||
|
|
96ae041132 | ||
|
|
fc0ac45f55 | ||
|
|
2d266d96d5 | ||
|
|
b725db04d8 | ||
|
|
eb17b68641 | ||
|
|
fef031b3cd | ||
|
|
4cfbb840ad | ||
|
|
0463518d28 | ||
|
|
d0986148f0 | ||
|
|
841c476045 | ||
|
|
15b65c7ad2 | ||
|
|
7e94ba6f0c | ||
|
|
854915920f | ||
|
|
00961d5281 | ||
|
|
38415d2cf1 | ||
|
|
af078532ac | ||
|
|
0430e0dacc | ||
|
|
1619ab3145 | ||
|
|
cac0848d2c | ||
|
|
ec6a30efcf | ||
|
|
c8e82f18b6 | ||
|
|
b4776a1ba0 | ||
|
|
ee5c5b34d6 | ||
|
|
3bc92610f7 | ||
|
|
c96415da27 | ||
|
|
6bdfe756cf | ||
|
|
f4341999cd | ||
|
|
076b103f97 | ||
|
|
79b5a2617f | ||
|
|
64feb0fd16 | ||
|
|
43732acae1 | ||
|
|
887fa2a3a0 | ||
|
|
297a32e6bb | ||
|
|
767fc0d4d4 | ||
|
|
486214585e | ||
|
|
e0f11ae722 | ||
|
|
085d0f72ed | ||
|
|
65980a70fc | ||
|
|
4f28bc4fb0 | ||
|
|
eb76dcd5a0 | ||
|
|
dfe4d02f65 | ||
|
|
735c51adae | ||
|
|
a0debea56a | ||
|
|
07a47179a7 | ||
|
|
ef3c942808 | ||
|
|
405f363fe8 | ||
|
|
ba69969057 | ||
|
|
60ac0a1a17 | ||
|
|
13b2aea48a | ||
|
|
03db257c43 | ||
|
|
ce0fe8b907 | ||
|
|
256c871121 | ||
|
|
8fd9d6a770 | ||
|
|
a80ff26e15 | ||
|
|
a8f057eab7 | ||
|
|
7d0700b6f8 | ||
|
|
cfaa76a355 | ||
|
|
625bcef8a1 | ||
|
|
d01099f347 | ||
|
|
40eb7a40f7 | ||
|
|
17b3df004e | ||
|
|
d0b932aaca | ||
|
|
6b42878b8c | ||
|
|
33e9e2174a | ||
|
|
607af36ad3 | ||
|
|
b04a51246f | ||
|
|
9720e0971e | ||
|
|
12e3cca3f2 | ||
|
|
fdd93d1475 | ||
|
|
083031524d | ||
|
|
d7b7f4bc91 | ||
|
|
c7edd9e67f | ||
|
|
995b1d18df | ||
|
|
77677e43df | ||
|
|
a8f26cd2b2 | ||
|
|
4714028c69 | ||
|
|
bda8dc1926 | ||
|
|
2f360ce447 | ||
|
|
38641e741a | ||
|
|
541a747d79 | ||
|
|
b26725f654 | ||
|
|
d0bcacd97b | ||
|
|
74c3d5d96c | ||
|
|
948eec6425 | ||
|
|
7eed55266b | ||
|
|
9f91cb2fd8 | ||
|
|
a47001ea0e | ||
|
|
1dfd9941df | ||
|
|
50e5fd9ed7 | ||
|
|
591e444753 | ||
|
|
c2bb3253ad | ||
|
|
8fc32bb33f | ||
|
|
acfa83229b | ||
|
|
0f48cf4284 | ||
|
|
fe72a5585c | ||
|
|
37edb075e9 | ||
|
|
e6ffa2e4a1 | ||
|
|
2d7429c47c | ||
|
|
6c34cf2e1e | ||
|
|
d65c4f8f5d | ||
|
|
bcfcefa203 | ||
|
|
35766e720d | ||
|
|
10f68f4946 | ||
|
|
971c3d93fe | ||
|
|
cc89649d04 | ||
|
|
a9b6a3754b | ||
|
|
190f55b06e | ||
|
|
81b3dbde1d | ||
|
|
fb2b52af14 | ||
|
|
a71a64733a | ||
|
|
20d212dcd8 | ||
|
|
ccc986b7d8 | ||
|
|
7d4f071d4b | ||
|
|
d62b046704 | ||
|
|
c5b5695eb9 | ||
|
|
80fb48c5b0 | ||
|
|
a170afcb76 | ||
|
|
effdb450fc | ||
|
|
9bab6d0009 | ||
|
|
4a620aa2ba | ||
|
|
d8867807f5 | ||
|
|
00c2ac4b03 | ||
|
|
d63b125877 | ||
|
|
1439152121 | ||
|
|
cad09d2df3 | ||
|
|
72370d2574 | ||
|
|
a6a2dc868b | ||
|
|
9269664350 | ||
|
|
b379f5d51b | ||
|
|
6dfddbcdf6 | ||
|
|
2a3790f8e8 | ||
|
|
a44cfba9ae | ||
|
|
21be983620 | ||
|
|
967485c2d0 | ||
|
|
efbf5064a1 | ||
|
|
6d345a13f0 | ||
|
|
01b2673e34 | ||
|
|
92ba5ae09c | ||
|
|
46a8899f20 | ||
|
|
36abb5ed96 | ||
|
|
8dbc04aa06 | ||
|
|
518cc92ccc | ||
|
|
7acddcbe4a | ||
|
|
a0115ee458 | ||
|
|
d0a5fe620f | ||
|
|
564eb3389b | ||
|
|
987356d6c0 | ||
|
|
9e8e4dde69 | ||
|
|
fc145eb795 | ||
|
|
8a453018a9 | ||
|
|
b60e2bf42f | ||
|
|
e3a037ee3f | ||
|
|
3dac765598 | ||
|
|
0b5413ec0b | ||
|
|
3dabd5fc83 | ||
|
|
6293e44889 | ||
|
|
b2b5d0b41c | ||
|
|
e1a5decc00 | ||
|
|
caa6006a31 | ||
|
|
07d4badfd0 | ||
|
|
bef0502e6b | ||
|
|
0b0ea41f87 | ||
|
|
d4015d6566 | ||
|
|
e2f49b7334 | ||
|
|
78e1a13fad | ||
|
|
5ac496d030 | ||
|
|
47441d1383 | ||
|
|
86a5f42f73 | ||
|
|
98dc0844ce | ||
|
|
7d3e6e9935 | ||
|
|
73a06d55a8 | ||
|
|
7f5d4aa7a6 | ||
|
|
95bdcdecaa | ||
|
|
02a46d0901 | ||
|
|
e0eb4f2358 | ||
|
|
c35b4b24d2 | ||
|
|
37952ab75f | ||
|
|
f67053c2ec | ||
|
|
f6723a12ff | ||
|
|
b62ce14ccd | ||
|
|
0c5f11079c | ||
|
|
f31607ded1 | ||
|
|
51cae63bf0 | ||
|
|
4244c87802 | ||
|
|
2a54e61238 | ||
|
|
af4b48e841 | ||
|
|
ce4f2d1c88 | ||
|
|
37acc6b832 | ||
|
|
d00af327c5 | ||
|
|
05fcb1f104 | ||
|
|
270ea363d3 | ||
|
|
b912d61ccb | ||
|
|
5d526bbeb1 | ||
|
|
7b5dc9f24f | ||
|
|
fc5f729f01 | ||
|
|
4f636eb0e3 | ||
|
|
bfaf33b6ba | ||
|
|
2c158fe168 | ||
|
|
caa851e5c8 | ||
|
|
8f521b8551 | ||
|
|
0e1f8f7f39 | ||
|
|
96c20c8379 | ||
|
|
c58c4de9bc | ||
|
|
ba44ff312b | ||
|
|
249e169d8e | ||
|
|
544cfaff2c | ||
|
|
b1c04f5fa2 | ||
|
|
decea64cf9 | ||
|
|
8b62353ab0 | ||
|
|
3bd36ba40a | ||
|
|
b0f26fa391 |
@@ -9,3 +9,4 @@ htmlcov
|
||||
*.bak
|
||||
__pycache__
|
||||
.vagrant/
|
||||
.idea/
|
||||
|
||||
+11
-4
@@ -8,14 +8,21 @@ python:
|
||||
- "3.3"
|
||||
- "3.4"
|
||||
- "pypy"
|
||||
- "pypy3"
|
||||
before_install:
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then sudo apt-get update -qq; fi
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then travis_retry sudo apt-get update -qq; fi
|
||||
install:
|
||||
- pip install pyinotify
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then sudo apt-get install -qq python-gamin; cp /usr/share/pyshared/gamin.py /usr/lib/pyshared/python2.7/_gamin.so $VIRTUAL_ENV/lib/python2.7/site-packages/; fi
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then cd ..; pip install -q coveralls; cd -; fi
|
||||
- travis_retry pip install pyinotify
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == 2* || $TRAVIS_PYTHON_VERSION == 'pypy' ]]; then travis_retry pip install dnspython; fi
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == 3* || $TRAVIS_PYTHON_VERSION == 'pypy3' ]]; then travis_retry pip install dnspython3; fi
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then travis_retry sudo apt-get install -qq python-gamin; cp /usr/share/pyshared/gamin.py /usr/lib/pyshared/python2.7/_gamin.so $VIRTUAL_ENV/lib/python2.7/site-packages/; fi
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then cd ..; travis_retry pip install -q coveralls; cd -; fi
|
||||
# overcome buggy pypy
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == pypy ]] ; then dpkg --compare-versions $(pypy --version 2>&1 | awk '/PyPy/{print $2;}') ge 2.5.1 || { d=$PWD; cd /tmp; wget http://buildbot.pypy.org/nightly/trunk/pypy-c-jit-latest-linux64.tar.bz2; tar -xjvf pypy*bz2; cd pypy-*/bin/; export PATH=$PWD:$PATH; cd $d; } ; fi
|
||||
script:
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then coverage run --rcfile=.travis_coveragerc setup.py test; else python setup.py test; fi
|
||||
# test installation
|
||||
- sudo python setup.py install
|
||||
after_success:
|
||||
# Coverage config file must be .coveragerc for coveralls
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then cp -v .travis_coveragerc .coveragerc; fi
|
||||
|
||||
Vendored
-181
@@ -1,181 +0,0 @@
|
||||
#!/usr/bin/perl
|
||||
##########################################################################
|
||||
# $Id: fail2ban 150 2013-06-18 22:19:38Z mtremaine $
|
||||
##########################################################################
|
||||
# $Log: fail2ban,v $
|
||||
# Revision 1.5 2008/08/18 16:07:46 mike
|
||||
# Patches from Paul Gear <paul at libertysys.com> -mgt
|
||||
#
|
||||
# Revision 1.4 2008/06/30 23:07:51 kirk
|
||||
# fixed copyright holders for files where I know who they should be
|
||||
#
|
||||
# Revision 1.3 2008/03/24 23:31:26 kirk
|
||||
# added copyright/license notice to each script
|
||||
#
|
||||
# Revision 1.2 2006/12/15 04:53:59 bjorn
|
||||
# Additional filtering, by Willi Mann.
|
||||
#
|
||||
# Revision 1.1 2006/05/30 19:04:26 bjorn
|
||||
# Added fail2ban service, written by Yaroslav Halchenko.
|
||||
#
|
||||
# Written by Yaroslav Halchenko <debian@onerussian.com> for fail2ban
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
########################################################
|
||||
## Copyright (c) 2008 Yaroslav Halchenko
|
||||
## Covered under the included MIT/X-Consortium License:
|
||||
## http://www.opensource.org/licenses/mit-license.php
|
||||
## All modifications and contributions by other persons to
|
||||
## this script are assumed to have been donated to the
|
||||
## Logwatch project and thus assume the above copyright
|
||||
## and licensing terms. If you want to make contributions
|
||||
## under your own copyright or a different license this
|
||||
## must be explicitly stated in the contribution an the
|
||||
## Logwatch project reserves the right to not accept such
|
||||
## contributions. If you have made significant
|
||||
## contributions to this script and want to claim
|
||||
## copyright please contact logwatch-devel@lists.sourceforge.net.
|
||||
#########################################################
|
||||
|
||||
use strict;
|
||||
use Logwatch ':all';
|
||||
|
||||
my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0;
|
||||
my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0;
|
||||
my $IgnoreHost = $ENV{'sshd_ignore_host'} || "";
|
||||
my $DebugCounter = 0;
|
||||
my $ReInitializations = 0;
|
||||
my @IptablesErrors = ();
|
||||
my @ActionErrors = ();
|
||||
my $NotValidIP = 0; # reported invalid IPs number
|
||||
my @OtherList = ();
|
||||
|
||||
my %ServicesBans = ();
|
||||
|
||||
if ( $Debug >= 5 ) {
|
||||
print STDERR "\n\nDEBUG: Inside Fail2Ban Filter \n\n";
|
||||
$DebugCounter = 1;
|
||||
}
|
||||
|
||||
while (defined(my $ThisLine = <STDIN>)) {
|
||||
if ( $Debug >= 5 ) {
|
||||
print STDERR "DEBUG($DebugCounter): $ThisLine";
|
||||
$DebugCounter++;
|
||||
}
|
||||
chomp($ThisLine);
|
||||
if ( ($ThisLine =~ /..,... DEBUG: /) or
|
||||
($ThisLine =~ /..,... \S*\s*: DEBUG /) or # syntax of 0.7.? fail2ban
|
||||
($ThisLine =~ /..,... INFO: (Fail2Ban v.* is running|Exiting|Enabled sections:)/) or
|
||||
($ThisLine =~ /INFO\s+Log rotation detected for/) or
|
||||
($ThisLine =~ /INFO\s+Jail.+(?:stopped|started|uses poller)/) or
|
||||
($ThisLine =~ /INFO\s+Changed logging target to/) or
|
||||
($ThisLine =~ /INFO\s+Creating new jail/) or
|
||||
($ThisLine =~ /..,... \S+\s*: INFO\s+(Set |Socket|Exiting|Gamin|Created|Added|Using)/) or # syntax of 0.7.? fail2ban
|
||||
($ThisLine =~ /..,... WARNING: Verbose level is /) or
|
||||
($ThisLine =~ /..,... WARNING: Restoring firewall rules/)
|
||||
)
|
||||
{
|
||||
if ( $Debug >= 6 ) {
|
||||
print STDERR "DEBUG($DebugCounter): line ignored\n";
|
||||
}
|
||||
} elsif ( my ($Service,$Action,$Host) = ($ThisLine =~ m/WARNING:?\s\[?(.*?)[]:]?\s(Ban|Unban)[^\.]* (\S+)/)) {
|
||||
if ( $Debug >= 6 ) {
|
||||
print STDERR "DEBUG($DebugCounter): Found $Action for $Service from $Host\n";
|
||||
}
|
||||
$ServicesBans{$Service}{$Host}{$Action}++;
|
||||
$ServicesBans{$Service}{"(all)"}{$Action}++;
|
||||
} elsif ( my ($Service,$Host,$NumFailures) = ($ThisLine =~ m/INFO: (\S+): (.+) has (\d+) login failure\(s\). Banned./)) {
|
||||
if ($Debug >= 4) {
|
||||
print STDERR "DEBUG: Found host $Host trying to access $Service - failed $NumFailures times\n";
|
||||
}
|
||||
push @{$ServicesBans{$Service}{$Host}{'Failures'}}, $NumFailures;
|
||||
} elsif ( my ($Service,$Host) = ($ThisLine =~ m/ ERROR:\s(.*):\s(\S+)\salready in ban list/)) {
|
||||
$ServicesBans{$Service}{$Host}{'AlreadyInTheList'}++;
|
||||
} elsif ( my ($Service,$Host) = ($ThisLine =~ m/WARNING\s*\[(.*)\]\s*(\S+)\s*already banned/)) {
|
||||
$ServicesBans{$Service}{$Host}{'AlreadyInTheList'}++;
|
||||
} elsif ( my ($Service,$Host) = ($ThisLine =~ m/ WARNING:\s(.*):\sReBan (\S+)/)) {
|
||||
$ServicesBans{$Service}{$Host}{'ReBan'}++;
|
||||
} elsif ($ThisLine =~ / ERROR:?\s*(Execution of command )?\'?iptables/) {
|
||||
push @IptablesErrors, "$ThisLine\n";
|
||||
} elsif ($ThisLine =~ /ERROR.*returned \d+$/) {
|
||||
push @ActionErrors, "$ThisLine\n";
|
||||
} elsif (($ThisLine =~ /..,... WARNING: \#\S+ reinitialization of firewalls/) or
|
||||
($ThisLine =~ / ERROR\s*Invariant check failed. Trying to restore a sane environment/)) {
|
||||
$ReInitializations++;
|
||||
} elsif ($ThisLine =~ /..,... WARNING: is not a valid IP address/) {
|
||||
# just ignore - this will be fixed within fail2ban and is harmless warning
|
||||
}
|
||||
else
|
||||
{
|
||||
# Report any unmatched entries...
|
||||
push @OtherList, "$ThisLine\n";
|
||||
}
|
||||
}
|
||||
|
||||
###########################################################
|
||||
|
||||
|
||||
if (keys %ServicesBans) {
|
||||
printf("\nBanned services with Fail2Ban: Bans:Unbans\n");
|
||||
foreach my $service (sort {$a cmp $b} keys %ServicesBans) {
|
||||
printf(" %-55s [%3d:%-3d]\n", "$service:",
|
||||
$ServicesBans{$service}{'(all)'}{'Ban'},
|
||||
$ServicesBans{$service}{'(all)'}{'Unban'});
|
||||
delete $ServicesBans{$service}{'(all)'};
|
||||
my $totalSort = TotalCountOrder(%{$ServicesBans{$service}}, \&SortIP);
|
||||
if ($Detail >= 5) {
|
||||
foreach my $ip (sort $totalSort keys %{$ServicesBans{$service}}) {
|
||||
my $name = LookupIP($ip);
|
||||
printf(" %-53s %3d:%-3d\n",
|
||||
$name,
|
||||
$ServicesBans{$service}{$ip}{'Ban'},
|
||||
$ServicesBans{$service}{$ip}{'Unban'});
|
||||
if (($Detail >= 10) and ($ServicesBans{$service}{$ip}{'Failures'}>0)) {
|
||||
print " Failed ";
|
||||
foreach my $fails (@{$ServicesBans{$service}{$ip}{'Failures'}}) {
|
||||
print " $fails";
|
||||
}
|
||||
print " times";
|
||||
printf("\n %d Duplicate Ban attempts", $ServicesBans{$service}{$ip}{'AlreadyInTheList'}) ;
|
||||
printf("\n %d ReBans due to rules reinitilizations", $ServicesBans{$service}{$ip}{'ReBan'}) ;
|
||||
print "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($Detail>0) {
|
||||
if ($#IptablesErrors > 0) {
|
||||
printf("\n%d faulty iptables invocation(s)", $#IptablesErrors);
|
||||
if ($Detail > 5) {
|
||||
print ":\n";
|
||||
print @IptablesErrors ;
|
||||
}
|
||||
}
|
||||
if ($#ActionErrors > 0) {
|
||||
printf("\n%d error(s) returned from actions", $#ActionErrors);
|
||||
if ($Detail > 5) {
|
||||
print ":\n";
|
||||
print @ActionErrors ;
|
||||
}
|
||||
}
|
||||
if ($ReInitializations > 0) {
|
||||
printf("\n%d fail2ban rules reinitialization(s)", $ReInitializations);
|
||||
}
|
||||
if ($#OtherList >= 0) {
|
||||
print "\n**Unmatched Entries**\n";
|
||||
print @OtherList;
|
||||
}
|
||||
}
|
||||
|
||||
exit(0);
|
||||
|
||||
# vi: shiftwidth=3 tabstop=3 syntax=perl et
|
||||
# Local Variables:
|
||||
# mode: perl
|
||||
# perl-indent-level: 3
|
||||
# indent-tabs-mode: nil
|
||||
# End:
|
||||
@@ -15,3 +15,13 @@ join the [mailing list](https://lists.sourceforge.net/lists/listinfo/fail2ban-us
|
||||
|
||||
### You would like to contribute (new filters/actions/code/documentation)?
|
||||
send a [pull request](https://github.com/fail2ban/fail2ban/pulls)
|
||||
|
||||
Pull requests guidelines
|
||||
========================
|
||||
|
||||
- If there is an issue on github to be closed by the pull request, include
|
||||
```Closes #ISSUE``` (where ISSUE is issue's number)
|
||||
|
||||
- Add a brief summary of the change to the ChangeLog file into a corresponding
|
||||
section out of Fixes, New Features or Enhancements (improvements to existing
|
||||
features)
|
||||
|
||||
@@ -3,11 +3,93 @@
|
||||
| _/ _` | | |/ /| '_ \/ _` | ' \
|
||||
|_| \__,_|_|_/___|_.__/\__,_|_||_|
|
||||
|
||||
================================================================================
|
||||
Fail2Ban (version 0.9.0.dev) 2014/xx/xx
|
||||
================================================================================
|
||||
Fail2Ban: Changelog
|
||||
===================
|
||||
|
||||
ver. 0.9.1 (2014/xx/xx) - better, faster, stronger
|
||||
ver. 0.9.2 (2015/04/29) - better-quick-now-than-later
|
||||
----------
|
||||
|
||||
- Fixes:
|
||||
* Fix ufw action commands
|
||||
* infinite busy loop on _escapedTags match in substituteRecursiveTags gh-907.
|
||||
Thanks TonyThompson
|
||||
* port[s] typo in jail.conf/nginx-http-auth gh-913. Thanks Frederik Wagner
|
||||
(fnerdwq)
|
||||
* $ typo in jail.conf. Thanks Skibbi. Debian bug #767255
|
||||
* grep'ing for IP in *mail-whois-lines.conf should now match also
|
||||
at the beginning and EOL. Thanks Dean Lee
|
||||
* jail.conf
|
||||
- php-url-fopen: separate logpath entries by newline
|
||||
* failregex declared direct in jail was joined to single line (specifying of
|
||||
multiple expressions was not possible).
|
||||
* filters.d/exim.conf - cover different settings of exim logs
|
||||
details. Thanks bes.internal
|
||||
* filter.d/postfix-sasl.conf - failregex is now case insensitive
|
||||
* filters.d/postfix.conf - add 'Client host rejected error message' failregex
|
||||
* fail2ban/__init__.py - add strptime thread safety hack-around
|
||||
* recidive uses iptables-allports banaction by default now.
|
||||
Avoids problems with iptables versions not understanding 'all' for
|
||||
protocols and ports
|
||||
* filter.d/dovecot.conf
|
||||
- match pam_authenticate line from EL7
|
||||
- match unknown user line from EL7
|
||||
* Use use_poll=True for Python 2.7 and >=3.4 to overcome "Bad file
|
||||
descriptor" msgs issue (gh-161)
|
||||
* filter.d/postfix-sasl.conf - tweak failregex and add ignoreregex to ignore
|
||||
system authentication issues
|
||||
* fail2ban-regex reads filter file(s) completely, incl. '.local' file etc.
|
||||
(gh-954)
|
||||
* firewallcmd-* actions: split output into separate lines for grepping (gh-908)
|
||||
* Guard unicode encode/decode issues while storing records in the database.
|
||||
Fixes "binding parameter error (unsupported type)" (gh-973), thanks to kot
|
||||
for reporting
|
||||
* filter.d/sshd added regex for matching openSUSE ssh authentication failure
|
||||
* filter.d/asterisk.conf:
|
||||
- Dropped "Sending fake auth rejection" failregex since it incorrectly
|
||||
targets the asterisk server itself
|
||||
- match "hacking attempt detected" logs
|
||||
|
||||
- New Features:
|
||||
- New filters:
|
||||
- postfix-rbl Thanks Lee Clemens
|
||||
- apache-fakegooglebot.conf Thanks Lee Clemens
|
||||
- nginx-botsearch Thanks Frantisek Sumsal
|
||||
- drupal-auth Thanks Lee Clemens
|
||||
- New recursive embedded substitution feature added:
|
||||
- `<<PREF>HOST>` becomes `<IPV4HOST>` for PREF=`IPV4`;
|
||||
- `<<PREF>HOST>` becomes `1.2.3.4` for PREF=`IPV4` and IPV4HOST=`1.2.3.4`;
|
||||
- New interpolation feature for config readers - `%(known/parameter)s`.
|
||||
(means last known option with name `parameter`). This interpolation makes
|
||||
possible to extend a stock filter or jail regexp in .local file
|
||||
(opposite to simply set failregex/ignoreregex that overwrites it),
|
||||
see gh-867.
|
||||
- Monit config for fail2ban in files/monit/
|
||||
- New actions:
|
||||
- action.d/firewallcmd-multiport and action.d/firewallcmd-allports Thanks Donald Yandt
|
||||
- action.d/sendmail-geoip-lines.conf
|
||||
- action.d/nsupdate to update DNSBL. Thanks Andrew St. Jean
|
||||
- New status argument for fail2ban-client -- flavor:
|
||||
fail2ban-client status <jail> [flavor]
|
||||
- empty or "basic" works as-is
|
||||
- "cymru" additionally prints (ASN, Country RIR) per banned IP
|
||||
(requires dnspython or dnspython3)
|
||||
- Flush log at USR1 signal
|
||||
|
||||
- Enhancements:
|
||||
* Enable multiport for firewallcmd-new action. Closes gh-834
|
||||
* files/debian-initd migrated from the debian branch and should be
|
||||
suitable for manual installations now (thanks Juan Karlo de Guzman)
|
||||
* Define empty ignoreregex in filters which didn't have it to avoid
|
||||
warnings (gh-934)
|
||||
* action.d/{sendmail-*,xarf-login-attack}.conf - report local
|
||||
timezone not UTC time/zone. Closes gh-911
|
||||
* Conditionally log Ignore IP with reason (dns, ip, command). Closes gh-916
|
||||
* Absorbed DNSUtils.cidr into addr2bin in filter.py, added unittests
|
||||
* Added syslogsocket configuration to fail2ban.conf
|
||||
* Note in the jail.conf for the recidive jail to increase dbpurgeage (gh-964)
|
||||
|
||||
|
||||
ver. 0.9.1 (2014/10/29) - better, faster, stronger
|
||||
----------
|
||||
|
||||
- Refactoring (IMPORTANT -- Please review your setup and configuration):
|
||||
@@ -16,6 +98,8 @@ ver. 0.9.1 (2014/xx/xx) - better, faster, stronger
|
||||
provides defaults for the chain, port, protocol and name tags
|
||||
|
||||
- Fixes:
|
||||
* start of file2ban aborted (on slow hosts, systemd considers the server has
|
||||
been timed out and kills him), see gh-824
|
||||
* UTF-8 fixes in pure-ftp thanks to Johannes Weberhofer. Closes gh-806.
|
||||
* systemd backend error on bad utf-8 in python3
|
||||
* badips.py action error when logging HTTP error raised with badips request
|
||||
@@ -48,18 +132,26 @@ ver. 0.9.1 (2014/xx/xx) - better, faster, stronger
|
||||
* cyrus-imap -- also catch also failed logins via secured (imaps/pop3s).
|
||||
Regression was introduced while strengthening failregex in 0.8.11 (bd175f)
|
||||
Debian bug #755173
|
||||
* postfix-sasl -- added journalmatch. Thanks Luc Maisonobe
|
||||
* postfix-sasl - added journalmatch. Thanks Luc Maisonobe
|
||||
* postfix* - match with a new daemon string (postfix/submission/smtpd).
|
||||
Closes gh-804 . Thanks Paul Traina
|
||||
* apache - added filter for AH01630 client denied by server configuration.
|
||||
|
||||
- New features:
|
||||
- New filters:
|
||||
- monit Thanks Jason H Martin
|
||||
- directadmin Thanks niorg
|
||||
- apache-shellshock Thanks Eugene Hopkinson (SlowRiot)
|
||||
- New actions:
|
||||
- symbiosis-blacklist-allports for Bytemark symbiosis firewall
|
||||
- fail2ban-client can fetch the running server version
|
||||
- Added Cloudflare API action
|
||||
|
||||
- Enhancements
|
||||
* Start performance of fail2ban-client (and tests) increased, start time
|
||||
and cpu usage rapidly reduced. Introduced a shared storage logic, to
|
||||
bypass reading lots of config files (see gh-824).
|
||||
Thanks to Joost Molenaar for good catch (reported gh-820).
|
||||
* Fail2ban-regex - add print-all-matched option. Closes gh-652
|
||||
* Suppress fail2ban-client warnings for non-critical config options
|
||||
* Match non "Bye Bye" disconnect messages for sshd locked account regex
|
||||
@@ -71,6 +163,7 @@ ver. 0.9.1 (2014/xx/xx) - better, faster, stronger
|
||||
not affect SYSLOG output
|
||||
* Log unhandled exceptions
|
||||
* cyrus-imap: catch "user not found" attempts
|
||||
* Add support for Portsentry
|
||||
|
||||
ver. 0.9.0 (2014/03/14) - beta
|
||||
----------
|
||||
|
||||
@@ -81,6 +81,18 @@ some quick commands::
|
||||
status test
|
||||
|
||||
|
||||
Testing with vagrant
|
||||
--------------------
|
||||
|
||||
Testing can now be done inside a vagrant VM. Vagrantfile provided in
|
||||
source code repository established two VMs:
|
||||
|
||||
- VM "secure" which can be used for testing fail2ban code.
|
||||
- VM "attacker" which hcan be used to perform attack against our "secure" VM.
|
||||
|
||||
Both VMs are sharing the 192.168.200/24 network. If you are using this network
|
||||
take a look into the Vagrantfile and change the IP.
|
||||
|
||||
|
||||
Coding Standards
|
||||
================
|
||||
|
||||
@@ -1,122 +1,272 @@
|
||||
README.md
|
||||
README.Solaris
|
||||
ChangeLog
|
||||
TODO
|
||||
THANKS
|
||||
CONTRIBUTING.md
|
||||
COPYING
|
||||
ChangeLog
|
||||
DEVELOP
|
||||
FILTERS
|
||||
README.Solaris
|
||||
README.md
|
||||
RELEASE
|
||||
THANKS
|
||||
TODO
|
||||
Vagrantfile
|
||||
bin/fail2ban-client
|
||||
bin/fail2ban-regex
|
||||
bin/fail2ban-server
|
||||
bin/fail2ban-testcases
|
||||
config/action.d/apf.conf
|
||||
config/action.d/badips.conf
|
||||
config/action.d/badips.py
|
||||
config/action.d/blocklist_de.conf
|
||||
config/action.d/bsd-ipfw.conf
|
||||
config/action.d/cloudflare.conf
|
||||
config/action.d/complain.conf
|
||||
config/action.d/dshield.conf
|
||||
config/action.d/dummy.conf
|
||||
config/action.d/firewallcmd-allports.conf
|
||||
config/action.d/firewallcmd-ipset.conf
|
||||
config/action.d/firewallcmd-multiport.conf
|
||||
config/action.d/firewallcmd-new.conf
|
||||
config/action.d/hostsdeny.conf
|
||||
config/action.d/ipfilter.conf
|
||||
config/action.d/ipfw.conf
|
||||
config/action.d/iptables-allports.conf
|
||||
config/action.d/iptables-common.conf
|
||||
config/action.d/iptables-ipset-proto4.conf
|
||||
config/action.d/iptables-ipset-proto6-allports.conf
|
||||
config/action.d/iptables-ipset-proto6.conf
|
||||
config/action.d/iptables-multiport-log.conf
|
||||
config/action.d/iptables-multiport.conf
|
||||
config/action.d/iptables-new.conf
|
||||
config/action.d/iptables-xt_recent-echo.conf
|
||||
config/action.d/iptables.conf
|
||||
config/action.d/mail-buffered.conf
|
||||
config/action.d/mail-whois-lines.conf
|
||||
config/action.d/mail-whois.conf
|
||||
config/action.d/mail.conf
|
||||
config/action.d/mynetwatchman.conf
|
||||
config/action.d/nsupdate.conf
|
||||
config/action.d/nsupdate.conf
|
||||
config/action.d/osx-afctl.conf
|
||||
config/action.d/osx-ipfw.conf
|
||||
config/action.d/pf.conf
|
||||
config/action.d/route.conf
|
||||
config/action.d/sendmail-buffered.conf
|
||||
config/action.d/sendmail-common.conf
|
||||
config/action.d/sendmail-geoip-lines.conf
|
||||
config/action.d/sendmail-whois-ipjailmatches.conf
|
||||
config/action.d/sendmail-whois-ipmatches.conf
|
||||
config/action.d/sendmail-whois-lines.conf
|
||||
config/action.d/sendmail-whois-matches.conf
|
||||
config/action.d/sendmail-whois.conf
|
||||
config/action.d/sendmail.conf
|
||||
config/action.d/shorewall.conf
|
||||
config/action.d/smtp.py
|
||||
config/action.d/symbiosis-blacklist-allports.conf
|
||||
config/action.d/ufw.conf
|
||||
config/action.d/xarf-login-attack.conf
|
||||
config/fail2ban.conf
|
||||
config/filter.d/3proxy.conf
|
||||
config/filter.d/apache-auth.conf
|
||||
config/filter.d/apache-badbots.conf
|
||||
config/filter.d/apache-botsearch.conf
|
||||
config/filter.d/apache-common.conf
|
||||
config/filter.d/apache-fakegooglebot.conf
|
||||
config/filter.d/apache-modsecurity.conf
|
||||
config/filter.d/apache-nohome.conf
|
||||
config/filter.d/apache-noscript.conf
|
||||
config/filter.d/apache-overflows.conf
|
||||
config/filter.d/apache-shellshock.conf
|
||||
config/filter.d/assp.conf
|
||||
config/filter.d/asterisk.conf
|
||||
config/filter.d/botsearch-common.conf
|
||||
config/filter.d/common.conf
|
||||
config/filter.d/counter-strike.conf
|
||||
config/filter.d/courier-auth.conf
|
||||
config/filter.d/courier-smtp.conf
|
||||
config/filter.d/cyrus-imap.conf
|
||||
config/filter.d/directadmin.conf
|
||||
config/filter.d/dovecot.conf
|
||||
config/filter.d/dropbear.conf
|
||||
config/filter.d/ejabberd-auth.conf
|
||||
config/filter.d/exim-common.conf
|
||||
config/filter.d/exim-spam.conf
|
||||
config/filter.d/exim.conf
|
||||
config/filter.d/freeswitch.conf
|
||||
config/filter.d/groupoffice.conf
|
||||
config/filter.d/gssftpd.conf
|
||||
config/filter.d/guacamole.conf
|
||||
config/filter.d/horde.conf
|
||||
config/filter.d/ignorecommands
|
||||
config/filter.d/ignorecommands/apache-fakegooglebot
|
||||
config/filter.d/kerio.conf
|
||||
config/filter.d/lighttpd-auth.conf
|
||||
config/filter.d/monit.conf
|
||||
config/filter.d/mysqld-auth.conf
|
||||
config/filter.d/nagios.conf
|
||||
config/filter.d/named-refused.conf
|
||||
config/filter.d/nginx-botsearch.conf
|
||||
config/filter.d/nginx-http-auth.conf
|
||||
config/filter.d/nsd.conf
|
||||
config/filter.d/openwebmail.conf
|
||||
config/filter.d/oracleims.conf
|
||||
config/filter.d/pam-generic.conf
|
||||
config/filter.d/pam-generic.conf
|
||||
config/filter.d/pam-generic.conf
|
||||
config/filter.d/perdition.conf
|
||||
config/filter.d/php-url-fopen.conf
|
||||
config/filter.d/php-url-fopen.conf
|
||||
config/filter.d/php-url-fopen.conf
|
||||
config/filter.d/portsentry.conf
|
||||
config/filter.d/postfix-rbl.conf
|
||||
config/filter.d/postfix-sasl.conf
|
||||
config/filter.d/postfix-sasl.conf
|
||||
config/filter.d/postfix-sasl.conf
|
||||
config/filter.d/postfix.conf
|
||||
config/filter.d/proftpd.conf
|
||||
config/filter.d/pure-ftpd.conf
|
||||
config/filter.d/qmail.conf
|
||||
config/filter.d/recidive.conf
|
||||
config/filter.d/roundcube-auth.conf
|
||||
config/filter.d/selinux-common.conf
|
||||
config/filter.d/selinux-ssh.conf
|
||||
config/filter.d/sendmail-auth.conf
|
||||
config/filter.d/sendmail-reject.conf
|
||||
config/filter.d/sendmail-spam.conf
|
||||
config/filter.d/sieve.conf
|
||||
config/filter.d/sogo-auth.conf
|
||||
config/filter.d/solid-pop3d.conf
|
||||
config/filter.d/squid.conf
|
||||
config/filter.d/squirrelmail.conf
|
||||
config/filter.d/sshd-ddos.conf
|
||||
config/filter.d/sshd.conf
|
||||
config/filter.d/stunnel.conf
|
||||
config/filter.d/suhosin.conf
|
||||
config/filter.d/tine20.conf
|
||||
config/filter.d/uwimap-auth.conf
|
||||
config/filter.d/vsftpd.conf
|
||||
config/filter.d/webmin-auth.conf
|
||||
config/filter.d/wuftpd.conf
|
||||
config/filter.d/xinetd-fail.conf
|
||||
config/jail.conf
|
||||
config/paths-common.conf
|
||||
config/paths-debian.conf
|
||||
config/paths-fedora.conf
|
||||
config/paths-freebsd.conf
|
||||
config/paths-osx.conf
|
||||
doc/run-rootless.txt
|
||||
fail2ban-2to3
|
||||
fail2ban-testcases-all
|
||||
fail2ban-testcases-all-python3
|
||||
bin/fail2ban-client
|
||||
bin/fail2ban-server
|
||||
bin/fail2ban-testcases
|
||||
bin/fail2ban-regex
|
||||
doc/run-rootless.txt
|
||||
fail2ban/client/configreader.py
|
||||
fail2ban/client/configparserinc.py
|
||||
fail2ban/client/jailreader.py
|
||||
fail2ban/client/fail2banreader.py
|
||||
fail2ban/client/jailsreader.py
|
||||
fail2ban/client/beautifier.py
|
||||
fail2ban/client/filterreader.py
|
||||
fail2ban/client/actionreader.py
|
||||
fail2ban/__init__.py
|
||||
fail2ban/client/__init__.py
|
||||
fail2ban/client/actionreader.py
|
||||
fail2ban/client/beautifier.py
|
||||
fail2ban/client/configparserinc.py
|
||||
fail2ban/client/configreader.py
|
||||
fail2ban/client/configurator.py
|
||||
fail2ban/client/csocket.py
|
||||
fail2ban/server/asyncserver.py
|
||||
fail2ban/server/database.py
|
||||
fail2ban/server/filter.py
|
||||
fail2ban/server/filterpyinotify.py
|
||||
fail2ban/server/filtergamin.py
|
||||
fail2ban/server/filterpoll.py
|
||||
fail2ban/server/filtersystemd.py
|
||||
fail2ban/server/iso8601.py
|
||||
fail2ban/server/server.py
|
||||
fail2ban/client/fail2banreader.py
|
||||
fail2ban/client/filterreader.py
|
||||
fail2ban/client/jailreader.py
|
||||
fail2ban/client/jailsreader.py
|
||||
fail2ban/exceptions.py
|
||||
fail2ban/helpers.py
|
||||
fail2ban/protocol.py
|
||||
fail2ban/server/__init__.py
|
||||
fail2ban/server/action.py
|
||||
fail2ban/server/actions.py
|
||||
fail2ban/server/asyncserver.py
|
||||
fail2ban/server/banmanager.py
|
||||
fail2ban/server/database.py
|
||||
fail2ban/server/datedetector.py
|
||||
fail2ban/server/datetemplate.py
|
||||
fail2ban/server/faildata.py
|
||||
fail2ban/server/failmanager.py
|
||||
fail2ban/server/datedetector.py
|
||||
fail2ban/server/jailthread.py
|
||||
fail2ban/server/transmitter.py
|
||||
fail2ban/server/action.py
|
||||
fail2ban/server/ticket.py
|
||||
fail2ban/server/failregex.py
|
||||
fail2ban/server/filter.py
|
||||
fail2ban/server/filtergamin.py
|
||||
fail2ban/server/filterpoll.py
|
||||
fail2ban/server/filterpyinotify.py
|
||||
fail2ban/server/filtersystemd.py
|
||||
fail2ban/server/iso8601.py
|
||||
fail2ban/server/jail.py
|
||||
fail2ban/server/jails.py
|
||||
fail2ban/server/__init__.py
|
||||
fail2ban/server/banmanager.py
|
||||
fail2ban/server/datetemplate.py
|
||||
fail2ban/server/jailthread.py
|
||||
fail2ban/server/mytime.py
|
||||
fail2ban/server/failregex.py
|
||||
fail2ban/server/database.py
|
||||
fail2ban/tests/banmanagertestcase.py
|
||||
fail2ban/tests/failmanagertestcase.py
|
||||
fail2ban/tests/clientreadertestcase.py
|
||||
fail2ban/tests/filtertestcase.py
|
||||
fail2ban/server/server.py
|
||||
fail2ban/server/strptime.py
|
||||
fail2ban/server/ticket.py
|
||||
fail2ban/server/transmitter.py
|
||||
fail2ban/tests/__init__.py
|
||||
fail2ban/tests/dummyjail.py
|
||||
fail2ban/tests/samplestestcase.py
|
||||
fail2ban/tests/datedetectortestcase.py
|
||||
fail2ban/tests/action_d/__init__.py
|
||||
fail2ban/tests/action_d/test_badips.py
|
||||
fail2ban/tests/action_d/test_smtp.py
|
||||
fail2ban/tests/actionstestcase.py
|
||||
fail2ban/tests/actiontestcase.py
|
||||
fail2ban/tests/servertestcase.py
|
||||
fail2ban/tests/sockettestcase.py
|
||||
fail2ban/tests/utils.py
|
||||
fail2ban/tests/misctestcase.py
|
||||
fail2ban/tests/databasetestcase.py
|
||||
fail2ban/tests/config/jail.conf
|
||||
fail2ban/tests/banmanagertestcase.py
|
||||
fail2ban/tests/clientreadertestcase.py
|
||||
fail2ban/tests/config/action.d/brokenaction.conf
|
||||
fail2ban/tests/config/fail2ban.conf
|
||||
fail2ban/tests/config/filter.d/simple.conf
|
||||
fail2ban/tests/config/filter.d/test.conf
|
||||
fail2ban/tests/config/filter.d/test.local
|
||||
fail2ban/tests/config/jail.conf
|
||||
fail2ban/tests/config/paths-common.conf
|
||||
fail2ban/tests/config/paths-debian.conf
|
||||
fail2ban/tests/config/paths-freebsd.conf
|
||||
fail2ban/tests/config/paths-osx.conf
|
||||
fail2ban/tests/config/paths-debian.conf
|
||||
fail2ban/tests/config/filter.d/simple.conf
|
||||
fail2ban/tests/config/action.d/brokenaction.conf
|
||||
fail2ban/tests/files/config/apache-auth/digest/.htaccess
|
||||
fail2ban/tests/files/config/apache-auth/digest/.htpasswd
|
||||
fail2ban/tests/files/config/apache-auth/digest_time/.htaccess
|
||||
fail2ban/tests/files/config/apache-auth/digest_time/.htpasswd
|
||||
fail2ban/tests/databasetestcase.py
|
||||
fail2ban/tests/datedetectortestcase.py
|
||||
fail2ban/tests/dummyjail.py
|
||||
fail2ban/tests/failmanagertestcase.py
|
||||
fail2ban/tests/files/action.d/action.py
|
||||
fail2ban/tests/files/action.d/action_checkainfo.py
|
||||
fail2ban/tests/files/action.d/action_errors.py
|
||||
fail2ban/tests/files/action.d/action_modifyainfo.py
|
||||
fail2ban/tests/files/action.d/action_noAction.py
|
||||
fail2ban/tests/files/action.d/action_nomethod.py
|
||||
fail2ban/tests/files/config/apache-auth/README
|
||||
fail2ban/tests/files/config/apache-auth/basic/authz_owner/.htaccess
|
||||
fail2ban/tests/files/config/apache-auth/basic/authz_owner/cant_get_me.html
|
||||
fail2ban/tests/files/config/apache-auth/basic/authz_owner/.htpasswd
|
||||
fail2ban/tests/files/config/apache-auth/basic/authz_owner/cant_get_me.html
|
||||
fail2ban/tests/files/config/apache-auth/basic/file/.htaccess
|
||||
fail2ban/tests/files/config/apache-auth/basic/file/.htpasswd
|
||||
fail2ban/tests/files/config/apache-auth/digest.py
|
||||
fail2ban/tests/files/config/apache-auth/digest_wrongrelm/.htaccess
|
||||
fail2ban/tests/files/config/apache-auth/digest_wrongrelm/.htpasswd
|
||||
fail2ban/tests/files/config/apache-auth/digest/.htaccess
|
||||
fail2ban/tests/files/config/apache-auth/digest/.htpasswd
|
||||
fail2ban/tests/files/config/apache-auth/digest_anon/.htaccess
|
||||
fail2ban/tests/files/config/apache-auth/digest_anon/.htpasswd
|
||||
fail2ban/tests/files/config/apache-auth/README
|
||||
fail2ban/tests/files/config/apache-auth/digest_time/.htaccess
|
||||
fail2ban/tests/files/config/apache-auth/digest_time/.htpasswd
|
||||
fail2ban/tests/files/config/apache-auth/digest_wrongrelm/.htaccess
|
||||
fail2ban/tests/files/config/apache-auth/digest_wrongrelm/.htpasswd
|
||||
fail2ban/tests/files/config/apache-auth/noentry/.htaccess
|
||||
fail2ban/tests/files/database_v1.db
|
||||
fail2ban/tests/files/ignorecommand.py
|
||||
fail2ban/tests/files/filter.d/substition.conf
|
||||
fail2ban/tests/files/filter.d/testcase-common.conf
|
||||
fail2ban/tests/files/filter.d/testcase01.conf
|
||||
fail2ban/tests/files/testcase01.log
|
||||
fail2ban/tests/files/testcase02.log
|
||||
fail2ban/tests/files/testcase03.log
|
||||
fail2ban/tests/files/testcase04.log
|
||||
fail2ban/tests/files/testcase-usedns.log
|
||||
fail2ban/tests/files/testcase-journal.log
|
||||
fail2ban/tests/files/testcase-multiline.log
|
||||
fail2ban/tests/files/logs/bsd/syslog-plain.txt
|
||||
fail2ban/tests/files/logs/bsd/syslog-v.txt
|
||||
fail2ban/tests/files/logs/bsd/syslog-vv.txt
|
||||
fail2ban/tests/files/ignorecommand.py
|
||||
fail2ban/tests/files/logs/3proxy
|
||||
fail2ban/tests/files/logs/apache-auth
|
||||
fail2ban/tests/files/logs/apache-badbots
|
||||
fail2ban/tests/files/logs/apache-botscripts
|
||||
fail2ban/tests/files/logs/apache-botsearch
|
||||
fail2ban/tests/files/logs/apache-fakegooglebot
|
||||
fail2ban/tests/files/logs/apache-modsecurity
|
||||
fail2ban/tests/files/logs/apache-nohome
|
||||
fail2ban/tests/files/logs/apache-noscript
|
||||
fail2ban/tests/files/logs/apache-overflows
|
||||
fail2ban/tests/files/logs/apache-shellshock
|
||||
fail2ban/tests/files/logs/assp
|
||||
fail2ban/tests/files/logs/asterisk
|
||||
fail2ban/tests/files/logs/bsd/syslog-plain.txt
|
||||
fail2ban/tests/files/logs/bsd/syslog-v.txt
|
||||
fail2ban/tests/files/logs/bsd/syslog-vv.txt
|
||||
fail2ban/tests/files/logs/counter-strike
|
||||
fail2ban/tests/files/logs/courier-auth
|
||||
fail2ban/tests/files/logs/courier-smtp
|
||||
fail2ban/tests/files/logs/cyrus-imap
|
||||
fail2ban/tests/files/logs/directadmin
|
||||
fail2ban/tests/files/logs/dovecot
|
||||
fail2ban/tests/files/logs/dropbear
|
||||
fail2ban/tests/files/logs/ejabberd-auth
|
||||
@@ -126,193 +276,94 @@ fail2ban/tests/files/logs/freeswitch
|
||||
fail2ban/tests/files/logs/groupoffice
|
||||
fail2ban/tests/files/logs/gssftpd
|
||||
fail2ban/tests/files/logs/guacamole
|
||||
fail2ban/tests/files/logs/horde
|
||||
fail2ban/tests/files/logs/kerio
|
||||
fail2ban/tests/files/logs/lighttpd-auth
|
||||
fail2ban/tests/files/logs/monit
|
||||
fail2ban/tests/files/logs/mysqld-auth
|
||||
fail2ban/tests/files/logs/nagios
|
||||
fail2ban/tests/files/logs/named-refused
|
||||
fail2ban/tests/files/logs/nginx-botsearch
|
||||
fail2ban/tests/files/logs/nginx-http-auth
|
||||
fail2ban/tests/files/logs/nsd
|
||||
fail2ban/tests/files/logs/openwebmail
|
||||
fail2ban/tests/files/logs/oracleims
|
||||
fail2ban/tests/files/logs/pam-generic
|
||||
fail2ban/tests/files/logs/perdition
|
||||
fail2ban/tests/files/logs/php-url-fopen
|
||||
fail2ban/tests/files/logs/postfix-sasl
|
||||
fail2ban/tests/files/logs/named-refused
|
||||
fail2ban/tests/files/logs/nginx-http-auth
|
||||
fail2ban/tests/files/logs/pam-generic
|
||||
fail2ban/tests/files/logs/portsentry
|
||||
fail2ban/tests/files/logs/postfix
|
||||
fail2ban/tests/files/logs/postfix-rbl
|
||||
fail2ban/tests/files/logs/postfix-sasl
|
||||
fail2ban/tests/files/logs/proftpd
|
||||
fail2ban/tests/files/logs/pure-ftpd
|
||||
fail2ban/tests/files/logs/qmail
|
||||
fail2ban/tests/files/logs/recidive
|
||||
fail2ban/tests/files/logs/roundcube-auth
|
||||
fail2ban/tests/files/logs/selinux-ssh
|
||||
fail2ban/tests/files/logs/sendmail-auth
|
||||
fail2ban/tests/files/logs/sendmail-reject
|
||||
fail2ban/tests/files/logs/sendmail-spam
|
||||
fail2ban/tests/files/logs/sieve
|
||||
fail2ban/tests/files/logs/squid
|
||||
fail2ban/tests/files/logs/stunnel
|
||||
fail2ban/tests/files/logs/suhosin
|
||||
fail2ban/tests/files/logs/sogo-auth
|
||||
fail2ban/tests/files/logs/solid-pop3d
|
||||
fail2ban/tests/files/logs/squid
|
||||
fail2ban/tests/files/logs/squirrelmail
|
||||
fail2ban/tests/files/logs/sshd
|
||||
fail2ban/tests/files/logs/sshd-ddos
|
||||
fail2ban/tests/files/logs/stunnel
|
||||
fail2ban/tests/files/logs/suhosin
|
||||
fail2ban/tests/files/logs/tine20
|
||||
fail2ban/tests/files/logs/uwimap-auth
|
||||
fail2ban/tests/files/logs/vsftpd
|
||||
fail2ban/tests/files/logs/webmin-auth
|
||||
fail2ban/tests/files/logs/wuftpd
|
||||
fail2ban/tests/files/logs/uwimap-auth
|
||||
fail2ban/tests/files/logs/xinetd-fail
|
||||
fail2ban/tests/config/jail.conf
|
||||
fail2ban/tests/config/fail2ban.conf
|
||||
fail2ban/tests/config/filter.d/simple.conf
|
||||
fail2ban/tests/config/action.d/brokenaction.conf
|
||||
setup.py
|
||||
setup.cfg
|
||||
fail2ban/__init__.py
|
||||
fail2ban/exceptions.py
|
||||
fail2ban/helpers.py
|
||||
fail2ban/tests/files/testcase-journal.log
|
||||
fail2ban/tests/files/testcase-multiline.log
|
||||
fail2ban/tests/files/testcase-usedns.log
|
||||
fail2ban/tests/files/testcase01.log
|
||||
fail2ban/tests/files/testcase02.log
|
||||
fail2ban/tests/files/testcase03.log
|
||||
fail2ban/tests/files/testcase04.log
|
||||
fail2ban/tests/filtertestcase.py
|
||||
fail2ban/tests/misctestcase.py
|
||||
fail2ban/tests/samplestestcase.py
|
||||
fail2ban/tests/servertestcase.py
|
||||
fail2ban/tests/sockettestcase.py
|
||||
fail2ban/tests/utils.py
|
||||
fail2ban/version.py
|
||||
fail2ban/protocol.py
|
||||
setup.py
|
||||
setup.cfg
|
||||
kill-server
|
||||
config/jail.conf
|
||||
config/fail2ban.conf
|
||||
config/filter.d/common.conf
|
||||
config/filter.d/apache-auth.conf
|
||||
config/filter.d/apache-badbots.conf
|
||||
config/filter.d/apache-botsearch.conf
|
||||
config/filter.d/apache-modsecurity.conf
|
||||
config/filter.d/apache-nohome.conf
|
||||
config/filter.d/apache-noscript.conf
|
||||
config/filter.d/apache-overflows.conf
|
||||
config/filter.d/nginx-http-auth.conf
|
||||
config/filter.d/counter-strike.conf
|
||||
config/filter.d/courier-auth.conf
|
||||
config/filter.d/courier-smtp.conf
|
||||
config/filter.d/cyrus-imap.conf
|
||||
config/filter.d/ejabberd-auth.conf
|
||||
config/filter.d/exim.conf
|
||||
config/filter.d/freeswitch.conf
|
||||
config/filter.d/gssftpd.conf
|
||||
config/filter.d/kerio.conf
|
||||
config/filter.d/horde.conf
|
||||
config/filter.d/suhosin.conf
|
||||
config/filter.d/nagios.conf
|
||||
config/filter.d/named-refused.conf
|
||||
config/filter.d/nsd.conf
|
||||
config/filter.d/openwebmail.conf
|
||||
config/filter.d/pam-generic.conf
|
||||
config/filter.d/php-url-fopen.conf
|
||||
config/filter.d/postfix-sasl.conf
|
||||
config/filter.d/pam-generic.conf
|
||||
config/filter.d/php-url-fopen.conf
|
||||
config/filter.d/postfix-sasl.conf
|
||||
config/filter.d/postfix.conf
|
||||
config/filter.d/proftpd.conf
|
||||
config/filter.d/pure-ftpd.conf
|
||||
config/filter.d/qmail.conf
|
||||
config/filter.d/pam-generic.conf
|
||||
config/filter.d/php-url-fopen.conf
|
||||
config/filter.d/postfix-sasl.conf
|
||||
config/filter.d/sendmail-auth.conf
|
||||
config/filter.d/sendmail-reject.conf
|
||||
config/filter.d/sieve.conf
|
||||
config/filter.d/solid-pop3d.conf
|
||||
config/filter.d/squid.conf
|
||||
config/filter.d/sshd.conf
|
||||
config/filter.d/sshd-ddos.conf
|
||||
config/filter.d/stunnel.conf
|
||||
config/filter.d/vsftpd.conf
|
||||
config/filter.d/webmin-auth.conf
|
||||
config/filter.d/wuftpd.conf
|
||||
config/filter.d/xinetd-fail.conf
|
||||
config/filter.d/asterisk.conf
|
||||
config/filter.d/dovecot.conf
|
||||
config/filter.d/dropbear.conf
|
||||
config/filter.d/lighttpd-auth.conf
|
||||
config/filter.d/recidive.conf
|
||||
config/filter.d/roundcube-auth.conf
|
||||
config/filter.d/assp.conf
|
||||
config/filter.d/sogo-auth.conf
|
||||
config/filter.d/mysqld-auth.conf
|
||||
config/filter.d/selinux-common.conf
|
||||
config/filter.d/selinux-ssh.conf
|
||||
config/filter.d/3proxy.conf
|
||||
config/filter.d/apache-common.conf
|
||||
config/filter.d/exim-common.conf
|
||||
config/filter.d/exim-spam.conf
|
||||
config/filter.d/freeswitch.conf
|
||||
config/filter.d/groupoffice.conf
|
||||
config/filter.d/perdition.conf
|
||||
config/filter.d/uwimap-auth.conf
|
||||
config/filter.d/courier-auth.conf
|
||||
config/filter.d/courier-smtp.conf
|
||||
config/filter.d/ejabberd-auth.conf
|
||||
config/filter.d/guacamole.conf
|
||||
config/filter.d/sendmail-spam.conf
|
||||
config/action.d/apf.conf
|
||||
config/action.d/blocklist_de.conf
|
||||
config/action.d/osx-afctl.conf
|
||||
config/action.d/osx-ipfw.conf
|
||||
config/action.d/sendmail-common.conf
|
||||
config/action.d/badips.conf
|
||||
config/action.d/bsd-ipfw.conf
|
||||
config/action.d/dummy.conf
|
||||
config/action.d/firewallcmd-new.conf
|
||||
config/action.d/firewallcmd-ipset.conf
|
||||
config/action.d/iptables-ipset-proto6-allports.conf
|
||||
config/action.d/iptables-common.conf
|
||||
config/action.d/iptables-ipset-proto4.conf
|
||||
config/action.d/iptables-ipset-proto6.conf
|
||||
config/action.d/iptables-xt_recent-echo.conf
|
||||
config/action.d/route.conf
|
||||
config/action.d/complain.conf
|
||||
config/action.d/dshield.conf
|
||||
config/action.d/hostsdeny.conf
|
||||
config/action.d/ipfw.conf
|
||||
config/action.d/ipfilter.conf
|
||||
config/action.d/iptables.conf
|
||||
config/action.d/iptables-allports.conf
|
||||
config/action.d/iptables-multiport.conf
|
||||
config/action.d/iptables-multiport-log.conf
|
||||
config/action.d/iptables-new.conf
|
||||
config/action.d/mail.conf
|
||||
config/action.d/mail-buffered.conf
|
||||
config/action.d/mail-whois.conf
|
||||
config/action.d/mail-whois-lines.conf
|
||||
config/action.d/mynetwatchman.conf
|
||||
config/action.d/pf.conf
|
||||
config/action.d/sendmail.conf
|
||||
config/action.d/sendmail-buffered.conf
|
||||
config/action.d/sendmail-whois-ipmatches.conf
|
||||
config/action.d/sendmail-whois.conf
|
||||
config/action.d/sendmail-whois-lines.conf
|
||||
config/action.d/shorewall.conf
|
||||
config/action.d/xarf-login-attack.conf
|
||||
config/action.d/ufw.conf
|
||||
config/fail2ban.conf
|
||||
doc/run-rootless.txt
|
||||
man/fail2ban-client.1
|
||||
man/fail2ban.1
|
||||
man/jail.conf.5
|
||||
man/fail2ban-client.h2m
|
||||
man/fail2ban-server.1
|
||||
man/fail2ban-server.h2m
|
||||
man/fail2ban-regex.1
|
||||
man/fail2ban-regex.h2m
|
||||
man/generate-man
|
||||
files/gentoo-initd
|
||||
files/bash-completion
|
||||
files/cacti/README
|
||||
files/cacti/cacti_host_template_fail2ban.xml
|
||||
files/cacti/fail2ban_stats.sh
|
||||
files/debian-initd
|
||||
files/fail2ban-logrotate
|
||||
files/fail2ban-tmpfiles.conf
|
||||
files/fail2ban.service
|
||||
files/fail2ban.upstart
|
||||
files/gen_badbots
|
||||
files/gentoo-confd
|
||||
files/redhat-initd
|
||||
files/gentoo-initd
|
||||
files/ipmasq-ZZZzzz_fail2ban.rul
|
||||
files/logwatch/fail2ban
|
||||
files/macosx-initd
|
||||
files/monit/fail2ban
|
||||
files/nagios/README
|
||||
files/nagios/check_fail2ban
|
||||
files/redhat-initd
|
||||
files/solaris-fail2ban.xml
|
||||
files/solaris-svc-fail2ban
|
||||
files/suse-initd
|
||||
files/fail2ban-logrotate
|
||||
files/cacti/fail2ban_stats.sh
|
||||
files/cacti/cacti_host_template_fail2ban.xml
|
||||
files/cacti/README
|
||||
files/nagios/check_fail2ban
|
||||
files/nagios/README
|
||||
files/bash-completion
|
||||
files/fail2ban-tmpfiles.conf
|
||||
files/fail2ban.service
|
||||
files/ipmasq-ZZZzzz_fail2ban.rul
|
||||
files/gen_badbots
|
||||
kill-server
|
||||
man/fail2ban-client.1
|
||||
man/fail2ban-client.h2m
|
||||
man/fail2ban-regex.1
|
||||
man/fail2ban-regex.h2m
|
||||
man/fail2ban-server.1
|
||||
man/fail2ban-server.h2m
|
||||
man/fail2ban.1
|
||||
man/generate-man
|
||||
man/jail.conf.5
|
||||
setup.cfg
|
||||
setup.py
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
include ChangeLog COPYING DEVELOP FILTERS README.* THANKS TODO
|
||||
include ChangeLog COPYING DEVELOP FILTERS README.* THANKS TODO CONTRIBUTING* Vagrantfile
|
||||
graft doc
|
||||
graft files
|
||||
recursive-include config *.conf *.py
|
||||
recursive-include config/filter.d/ignorecommands *
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
/ _|__ _(_) |_ ) |__ __ _ _ _
|
||||
| _/ _` | | |/ /| '_ \/ _` | ' \
|
||||
|_| \__,_|_|_/___|_.__/\__,_|_||_|
|
||||
v0.9.0 2014/03/14
|
||||
v0.9.2 2015/04/29
|
||||
|
||||
## Fail2Ban: ban hosts that cause multiple authentication errors
|
||||
|
||||
@@ -33,11 +33,12 @@ Optional:
|
||||
- Linux >= 2.6.13
|
||||
- [gamin >= 0.0.21](http://www.gnome.org/~veillard/gamin)
|
||||
- [systemd >= 204](http://www.freedesktop.org/wiki/Software/systemd)
|
||||
- [dnspython](http://www.dnspython.org/)
|
||||
|
||||
To install, just do:
|
||||
|
||||
tar xvfj fail2ban-0.9.0.tar.bz2
|
||||
cd fail2ban-0.9.0
|
||||
tar xvfj fail2ban-0.9.2.tar.bz2
|
||||
cd fail2ban-0.9.2
|
||||
python setup.py install
|
||||
|
||||
This will install Fail2Ban into the python library directory. The executable
|
||||
|
||||
@@ -47,6 +47,10 @@ Preparation
|
||||
|
||||
* Ensure the MANIFEST is complete
|
||||
|
||||
ad-hoc bash script to run in a clean clone:
|
||||
|
||||
find -type f | grep -v -e '\.git' -e '/doc/' -e '\.travis' -e MANIFEST | sed -e 's,^\./,,g' | while read f; do grep -ne "^$f\$" MANIFEST >/dev/null || echo "$f" ; done
|
||||
|
||||
* Run::
|
||||
|
||||
python setup.py sdist
|
||||
@@ -57,24 +61,24 @@ Preparation
|
||||
|
||||
* Which indicates that testcases/files/logs/mysqld.log has been moved or is a directory::
|
||||
|
||||
tar -C /tmp -jxf dist/fail2ban-0.9.0.tar.bz2
|
||||
tar -C /tmp -jxf dist/fail2ban-0.9.2.tar.bz2
|
||||
|
||||
* clean up current direcory::
|
||||
|
||||
diff -rul --exclude \*.pyc . /tmp/fail2ban-0.9.0/
|
||||
diff -rul --exclude \*.pyc . /tmp/fail2ban-0.9.2/
|
||||
|
||||
* Only differences should be files that you don't want distributed.
|
||||
|
||||
|
||||
* Ensure the tests work from the tarball::
|
||||
|
||||
cd /tmp/fail2ban-0.9.0/ && export PYTHONPATH=`pwd` && bin/fail2ban-testcases
|
||||
cd /tmp/fail2ban-0.9.2/ && bin/fail2ban-testcases
|
||||
|
||||
* Add/finalize the corresponding entry in the ChangeLog
|
||||
|
||||
* To generate a list of committers use e.g.::
|
||||
|
||||
git shortlog -sn 0.8.12.. | sed -e 's,^[ 0-9\t]*,,g' | tr '\n' '\|' | sed -e 's:|:, :g'
|
||||
git shortlog -sn 0.9.2.. | sed -e 's,^[ 0-9\t]*,,g' | tr '\n' '\|' | sed -e 's:|:, :g'
|
||||
|
||||
* Ensure the top of the ChangeLog has the right version and current date.
|
||||
* Ensure the top entry of the ChangeLog has the right version and current date.
|
||||
@@ -97,7 +101,7 @@ Preparation
|
||||
* Tag the release by using a signed (and annotated) tag. Cut/paste
|
||||
release ChangeLog entry as tag annotation::
|
||||
|
||||
git tag -s 0.9.1
|
||||
git tag -s 0.9.2
|
||||
|
||||
Pre Release
|
||||
===========
|
||||
@@ -140,7 +144,7 @@ Pre Release
|
||||
|
||||
* https://bugs.mageia.org/buglist.cgi?quicksearch=fail2ban
|
||||
|
||||
* An potentially to the fail2ban-users email list.
|
||||
* And potentially to the fail2ban-users email list.
|
||||
|
||||
|
||||
* Wait for feedback from distributors
|
||||
@@ -181,7 +185,7 @@ Post Release
|
||||
|
||||
Add the following to the top of the ChangeLog::
|
||||
|
||||
ver. 0.9.1 (2014/XX/XXX) - wanna-be-released
|
||||
ver. 0.9.3 (2014/XX/XXX) - wanna-be-released
|
||||
-----------
|
||||
|
||||
- Fixes:
|
||||
|
||||
@@ -6,6 +6,7 @@ the project. If you have been left off, please let us know
|
||||
(preferably send a pull request on github with the "fix") and you will
|
||||
be added
|
||||
|
||||
Aaron Brice
|
||||
Adam Tkac
|
||||
Adrien Clerc
|
||||
ache
|
||||
@@ -13,6 +14,7 @@ ag4ve (Shawn)
|
||||
Alasdair D. Campbell
|
||||
Amir Caspi
|
||||
Amy
|
||||
Andrew St. Jean
|
||||
Andrey G. Grozin
|
||||
Andy Fragen
|
||||
Arturo 'Buanzo' Busleiman
|
||||
@@ -26,16 +28,20 @@ Christian Rauch
|
||||
Christophe Carles
|
||||
Christoph Haas
|
||||
Christos Psonis
|
||||
craneworks
|
||||
Cyril Jaquier
|
||||
Daniel B. Cid
|
||||
Daniel B.
|
||||
Daniel Black
|
||||
David Nutter
|
||||
Derek Atkins
|
||||
Donald Yandt
|
||||
Eric Gerbier
|
||||
Enrico Labedzki
|
||||
Eugene Hopkinson (SlowRiot)
|
||||
ftoppi
|
||||
François Boulogne
|
||||
Frantisek Sumsal
|
||||
Frédéric
|
||||
Georgiy Mernov
|
||||
Guilhem Lettron
|
||||
@@ -80,7 +86,9 @@ Michael Hanselmann
|
||||
Mika (mkl)
|
||||
Nick Munger
|
||||
onorua
|
||||
Orion Poplawski
|
||||
Paul Marrapese
|
||||
Paul Traina
|
||||
Noel Butler
|
||||
Patrick Börjesson
|
||||
Pressy
|
||||
|
||||
+2
-2
@@ -409,6 +409,7 @@ class Fail2banClient:
|
||||
# TODO: get away from stew of return codes and exception
|
||||
# handling -- handle via exceptions
|
||||
try:
|
||||
self.__configurator.Reload()
|
||||
self.__configurator.readAll()
|
||||
ret = self.__configurator.getOptions(jail)
|
||||
self.__configurator.convertToProtocol()
|
||||
@@ -418,12 +419,11 @@ class Fail2banClient:
|
||||
ret = False
|
||||
return ret
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def dumpConfig(cmd):
|
||||
for c in cmd:
|
||||
print c
|
||||
return True
|
||||
dumpConfig = staticmethod(dumpConfig)
|
||||
|
||||
|
||||
class ServerExecutionException(Exception):
|
||||
|
||||
+41
-31
@@ -220,6 +220,7 @@ class Fail2banRegex(object):
|
||||
self._datepattern_set = False
|
||||
self._journalmatch = None
|
||||
|
||||
self.share_config=dict()
|
||||
self._filter = Filter(None)
|
||||
self._ignoreregex = list()
|
||||
self._failregex = list()
|
||||
@@ -260,38 +261,47 @@ class Fail2banRegex(object):
|
||||
def readRegex(self, value, regextype):
|
||||
assert(regextype in ('fail', 'ignore'))
|
||||
regex = regextype + 'regex'
|
||||
if os.path.isfile(value):
|
||||
print "Use %11s file : %s" % (regex, value)
|
||||
reader = FilterReader(value, 'fail2ban-regex-jail', {})
|
||||
reader.setBaseDir(None)
|
||||
|
||||
if reader.readexplicit():
|
||||
reader.getOptions(None)
|
||||
readercommands = reader.convert()
|
||||
regex_values = [
|
||||
RegexStat(m[3])
|
||||
for m in filter(
|
||||
lambda x: x[0] == 'set' and x[2] == "add%sregex" % regextype,
|
||||
readercommands)]
|
||||
# Read out and set possible value of maxlines
|
||||
for command in readercommands:
|
||||
if command[2] == "maxlines":
|
||||
maxlines = int(command[3])
|
||||
try:
|
||||
self.setMaxLines(maxlines)
|
||||
except ValueError:
|
||||
print "ERROR: Invalid value for maxlines (%(maxlines)r) " \
|
||||
"read from %(value)s" % locals()
|
||||
return False
|
||||
elif command[2] == 'addjournalmatch':
|
||||
journalmatch = command[3]
|
||||
self.setJournalMatch(shlex.split(journalmatch))
|
||||
elif command[2] == 'datepattern':
|
||||
datepattern = command[3]
|
||||
self.setDatePattern(datepattern)
|
||||
if os.path.isfile(value) or os.path.isfile(value + '.conf'):
|
||||
if os.path.basename(os.path.dirname(value)) == 'filter.d':
|
||||
## within filter.d folder - use standard loading algorithm to load filter completely (with .local etc.):
|
||||
basedir = os.path.dirname(os.path.dirname(value))
|
||||
value = os.path.splitext(os.path.basename(value))[0]
|
||||
print "Use %11s filter file : %s, basedir: %s" % (regex, value, basedir)
|
||||
reader = FilterReader(value, 'fail2ban-regex-jail', {}, share_config=self.share_config, basedir=basedir)
|
||||
if not reader.read():
|
||||
print "ERROR: failed to load filter %s" % value
|
||||
return False
|
||||
else:
|
||||
print "ERROR: failed to read %s" % value
|
||||
return False
|
||||
## foreign file - readexplicit this file and includes if possible:
|
||||
print "Use %11s file : %s" % (regex, value)
|
||||
reader = FilterReader(value, 'fail2ban-regex-jail', {}, share_config=self.share_config)
|
||||
reader.setBaseDir(None)
|
||||
if not reader.readexplicit():
|
||||
print "ERROR: failed to read %s" % value
|
||||
return False
|
||||
reader.getOptions(None)
|
||||
readercommands = reader.convert()
|
||||
regex_values = [
|
||||
RegexStat(m[3])
|
||||
for m in filter(
|
||||
lambda x: x[0] == 'set' and x[2] == "add%sregex" % regextype,
|
||||
readercommands)]
|
||||
# Read out and set possible value of maxlines
|
||||
for command in readercommands:
|
||||
if command[2] == "maxlines":
|
||||
maxlines = int(command[3])
|
||||
try:
|
||||
self.setMaxLines(maxlines)
|
||||
except ValueError:
|
||||
print "ERROR: Invalid value for maxlines (%(maxlines)r) " \
|
||||
"read from %(value)s" % locals()
|
||||
return False
|
||||
elif command[2] == 'addjournalmatch':
|
||||
journalmatch = command[3]
|
||||
self.setJournalMatch(shlex.split(journalmatch))
|
||||
elif command[2] == 'datepattern':
|
||||
datepattern = command[3]
|
||||
self.setDatePattern(datepattern)
|
||||
else:
|
||||
print "Use %11s line : %s" % (regex, shortstr(value))
|
||||
regex_values = [RegexStat(value)]
|
||||
|
||||
@@ -111,6 +111,8 @@ class BadIPsAction(ActionBase):
|
||||
------
|
||||
HTTPError
|
||||
Any issues with badips.com request.
|
||||
ValueError
|
||||
If badips.com response didn't contain necessary information
|
||||
"""
|
||||
try:
|
||||
response = urlopen(
|
||||
@@ -122,7 +124,13 @@ class BadIPsAction(ActionBase):
|
||||
messages['err'])
|
||||
raise
|
||||
else:
|
||||
categories = json.loads(response.read().decode('utf-8'))['categories']
|
||||
response_json = json.loads(response.read().decode('utf-8'))
|
||||
if not 'categories' in response_json:
|
||||
err = "badips.com response lacked categories specification. Response was: %s" \
|
||||
% (response_json,)
|
||||
self._logSys.error(err)
|
||||
raise ValueError(err)
|
||||
categories = response_json['categories']
|
||||
categories_names = set(
|
||||
value['Name'] for value in categories)
|
||||
if incParents:
|
||||
|
||||
@@ -38,7 +38,7 @@ actioncheck =
|
||||
# Values: CMD
|
||||
#
|
||||
# requires an ipfw rule like "deny ip from table(1) to me"
|
||||
actionban = ipfw table <table> add <ip>
|
||||
actionban = e=`ipfw table <table> add <ip> 2>&1`; x=$?; [ $x -eq 0 -o "$e" = 'ipfw: setsockopt(IP_FW_TABLE_XADD): File exists' ] || { echo "$e" 1>&2; exit $x; }
|
||||
|
||||
|
||||
# Option: actionunban
|
||||
@@ -47,7 +47,7 @@ actionban = ipfw table <table> add <ip>
|
||||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionunban = ipfw table <table> delete <ip>
|
||||
actionunban = e=`ipfw table <table> delete <ip> 2>&1`; x=$?; [ $x -eq 0 -o "$e" = 'ipfw: setsockopt(IP_FW_TABLE_XDEL): No such process' ] || { echo "$e" 1>&2; exit $x; }
|
||||
|
||||
[Init]
|
||||
# Option: table
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# Fail2Ban configuration file
|
||||
#
|
||||
# Author: Donald Yandt
|
||||
# Because of the --remove-rules in stop this action requires firewalld-0.3.8+
|
||||
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
before = iptables-blocktype.conf
|
||||
|
||||
[Definition]
|
||||
|
||||
actionstart = firewall-cmd --direct --add-chain ipv4 filter f2b-<name>
|
||||
firewall-cmd --direct --add-rule ipv4 filter f2b-<name> 1000 -j RETURN
|
||||
firewall-cmd --direct --add-rule ipv4 filter <chain> 0 -j f2b-<name>
|
||||
|
||||
actionstop = firewall-cmd --direct --remove-rule ipv4 filter <chain> 0 -j f2b-<name>
|
||||
firewall-cmd --direct --remove-rules ipv4 filter f2b-<name>
|
||||
firewall-cmd --direct --remove-chain ipv4 filter f2b-<name>
|
||||
|
||||
|
||||
# Example actioncheck: firewall-cmd --direct --get-chains ipv4 filter | sed -e 's, ,\n,g' | grep -q '^f2b-recidive$'
|
||||
|
||||
actioncheck = firewall-cmd --direct --get-chains ipv4 filter | sed -e 's, ,\n,g' | grep -q '^f2b-<name>$'
|
||||
|
||||
actionban = firewall-cmd --direct --add-rule ipv4 filter f2b-<name> 0 -s <ip> -j <blocktype>
|
||||
|
||||
actionunban = firewall-cmd --direct --remove-rule ipv4 filter f2b-<name> 0 -s <ip> -j <blocktype>
|
||||
|
||||
[Init]
|
||||
|
||||
# Default name of the chain
|
||||
#
|
||||
name = default
|
||||
|
||||
chain = INPUT_direct
|
||||
|
||||
# DEV NOTES:
|
||||
#
|
||||
# Author: Donald Yandt
|
||||
# Uses "FirewallD" instead of the "iptables daemon".
|
||||
#
|
||||
#
|
||||
# Output:
|
||||
|
||||
# actionstart:
|
||||
# $ firewall-cmd --direct --add-chain ipv4 filter f2b-recidive
|
||||
# success
|
||||
# $ firewall-cmd --direct --add-rule ipv4 filter f2b-recidive 1000 -j RETURN
|
||||
# success
|
||||
# $ sudo firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 0 -j f2b-recidive
|
||||
# success
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# Fail2Ban configuration file
|
||||
#
|
||||
# Author: Donald Yandt
|
||||
# Because of the --remove-rules in stop this action requires firewalld-0.3.8+
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
before = iptables-blocktype.conf
|
||||
|
||||
[Definition]
|
||||
|
||||
actionstart = firewall-cmd --direct --add-chain ipv4 filter f2b-<name>
|
||||
firewall-cmd --direct --add-rule ipv4 filter f2b-<name> 1000 -j RETURN
|
||||
firewall-cmd --direct --add-rule ipv4 filter <chain> 0 -m state --state NEW -p <protocol> -m multiport --dports <port> -j f2b-<name>
|
||||
|
||||
actionstop = firewall-cmd --direct --remove-rule ipv4 filter <chain> 0 -m state --state NEW -p <protocol> -m multiport --dports <port> -j f2b-<name>
|
||||
firewall-cmd --direct --remove-rules ipv4 filter f2b-<name>
|
||||
firewall-cmd --direct --remove-chain ipv4 filter f2b-<name>
|
||||
|
||||
# Example actioncheck: firewall-cmd --direct --get-chains ipv4 filter | sed -e 's, ,\n,g' | grep -q '^f2b-apache-modsecurity$'
|
||||
|
||||
actioncheck = firewall-cmd --direct --get-chains ipv4 filter | sed -e 's, ,\n,g' | grep -q '^f2b-<name>$'
|
||||
|
||||
actionban = firewall-cmd --direct --add-rule ipv4 filter f2b-<name> 0 -s <ip> -j <blocktype>
|
||||
|
||||
actionunban = firewall-cmd --direct --remove-rule ipv4 filter f2b-<name> 0 -s <ip> -j <blocktype>
|
||||
|
||||
[Init]
|
||||
|
||||
# Default name of the chain
|
||||
name = default
|
||||
|
||||
chain = INPUT_direct
|
||||
|
||||
# Could also use port numbers separated by a comma.
|
||||
port = 1:65535
|
||||
|
||||
|
||||
# Option: protocol
|
||||
# Values: [ tcp | udp | icmp | all ]
|
||||
|
||||
protocol = tcp
|
||||
|
||||
|
||||
|
||||
# DEV NOTES:
|
||||
#
|
||||
# Author: Donald Yandt
|
||||
# Uses "FirewallD" instead of the "iptables daemon".
|
||||
#
|
||||
#
|
||||
# Output:
|
||||
# actionstart:
|
||||
# $ firewall-cmd --direct --add-chain ipv4 filter f2b-apache-modsecurity
|
||||
# success
|
||||
# $ firewall-cmd --direct --add-rule ipv4 filter f2b-apache-modsecurity 1000 -j RETURN
|
||||
# success
|
||||
# $ sudo firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 0 -m state --state NEW -p tcp -m multiport --dports 80,443 -j f2b-apache-modsecurity
|
||||
# success
|
||||
# actioncheck:
|
||||
# $ firewall-cmd --direct --get-chains ipv4 filter f2b-apache-modsecurity | sed -e 's, ,\n,g' | grep -q '^f2b-apache-modsecurity$'
|
||||
# f2b-apache-modsecurity
|
||||
|
||||
@@ -10,9 +10,9 @@ before = iptables-common.conf
|
||||
|
||||
actionstart = firewall-cmd --direct --add-chain ipv4 filter f2b-<name>
|
||||
firewall-cmd --direct --add-rule ipv4 filter f2b-<name> 1000 -j RETURN
|
||||
firewall-cmd --direct --add-rule ipv4 filter <chain> 0 -m state --state NEW -p <protocol> --dport <port> -j f2b-<name>
|
||||
firewall-cmd --direct --add-rule ipv4 filter <chain> 0 -m state --state NEW -p <protocol> -m multiport --dports <port> -j f2b-<name>
|
||||
|
||||
actionstop = firewall-cmd --direct --remove-rule ipv4 filter <chain> 0 -m state --state NEW -p <protocol> --dport <port> -j f2b-<name>
|
||||
actionstop = firewall-cmd --direct --remove-rule ipv4 filter <chain> 0 -m state --state NEW -p <protocol> -m multiport --dports <port> -j f2b-<name>
|
||||
firewall-cmd --direct --remove-rules ipv4 filter f2b-<name>
|
||||
firewall-cmd --direct --remove-chain ipv4 filter f2b-<name>
|
||||
|
||||
@@ -43,7 +43,7 @@ chain = INPUT_direct
|
||||
# success
|
||||
# $ firewall-cmd --direct --add-rule ipv4 filter fail2ban-name 1000 -j RETURN
|
||||
# success
|
||||
# $ sudo firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 0 -m state --state NEW -p tcp --dport 22 -j fail2ban-name
|
||||
# $ sudo firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 0 -m state --state NEW -p tcp -m multiport --dports 22 -j fail2ban-name
|
||||
# success
|
||||
# $ firewall-cmd --direct --get-chains ipv4 filter
|
||||
# fail2ban-name
|
||||
|
||||
@@ -42,7 +42,7 @@ actionban = printf %%b "Hi,\n
|
||||
Here is more information about <ip>:\n
|
||||
`whois <ip> || echo missing whois program`\n\n
|
||||
Lines containing IP:<ip> in <logpath>\n
|
||||
`grep '[^0-9]<ip>[^0-9]' <logpath>`\n\n
|
||||
`grep -E '(^|[^0-9])<ip>([^0-9]|$)' <logpath>`\n\n
|
||||
Regards,\n
|
||||
Fail2Ban"|mail -s "[Fail2Ban] <name>: banned <ip> from `uname -n`" <dest>
|
||||
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
# Fail2Ban configuration file
|
||||
#
|
||||
# Author: Andrew St. Jean
|
||||
#
|
||||
# Use nsupdate to perform dynamic DNS updates on a BIND zone file.
|
||||
# One may want to do this to update a local RBL with banned IP addresses.
|
||||
#
|
||||
# Options
|
||||
#
|
||||
# domain DNS domain that will appear in nsupdate add and delete
|
||||
# commands.
|
||||
#
|
||||
# ttl The time to live (TTL) in seconds of the TXT resource
|
||||
# record.
|
||||
#
|
||||
# rdata Data portion of the TXT resource record.
|
||||
#
|
||||
# nsupdatecmd Full path to the nsupdate command.
|
||||
#
|
||||
# keyfile Full path to TSIG key file used for authentication between
|
||||
# nsupdate and BIND.
|
||||
#
|
||||
# Create an nsupdate.local to set at least the <domain> and <keyfile>
|
||||
# options as they don't have default values.
|
||||
#
|
||||
# The ban and unban commands assume nsupdate will authenticate to the BIND
|
||||
# server using a TSIG key. The full path to the key file must be specified
|
||||
# in the <keyfile> parameter. Use this command to generate your TSIG key.
|
||||
#
|
||||
# dnssec-keygen -a HMAC-MD5 -b 256 -n HOST <key_name>
|
||||
#
|
||||
# Replace <key_name> with some meaningful name.
|
||||
#
|
||||
# This command will generate two files. Specify the .private file in the
|
||||
# <keyfile> option. Note that the .key file must also be present in the same
|
||||
# directory for nsupdate to use the key.
|
||||
#
|
||||
# Don't forget to add the key and appropriate allow-update or update-policy
|
||||
# option to your named.conf file.
|
||||
#
|
||||
|
||||
[Definition]
|
||||
|
||||
# Option: actionstart
|
||||
# Notes.: command executed once at the start of Fail2Ban.
|
||||
# Values: CMD
|
||||
#
|
||||
actionstart =
|
||||
|
||||
|
||||
# Option: actionstop
|
||||
# Notes.: command executed once at the end of Fail2Ban
|
||||
# Values: CMD
|
||||
#
|
||||
actionstop =
|
||||
|
||||
|
||||
# Option: actioncheck
|
||||
# Notes.: command executed once before each actionban command
|
||||
# Values: CMD
|
||||
#
|
||||
actioncheck =
|
||||
|
||||
# Option: actionban
|
||||
# Notes.: command executed when banning an IP. Take care that the
|
||||
# command is executed with Fail2Ban user rights.
|
||||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = echo <ip> | awk -F. '{print "prereq nxrrset "$4"."$3"."$2"."$1".<domain> TXT"; print "update add "$4"."$3"."$2"."$1".<domain> <ttl> IN TXT \"<rdata>\""; print "send"}' | <nsupdatecmd> -k <keyfile>
|
||||
|
||||
# Option: actionunban
|
||||
# Notes.: command executed when unbanning an IP. Take care that the
|
||||
# command is executed with Fail2Ban user rights.
|
||||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionunban = echo <ip> | awk -F. '{print "update delete "$4"."$3"."$2"."$1".<domain>"; print "send"}' | <nsupdatecmd> -k <keyfile>
|
||||
|
||||
[Init]
|
||||
|
||||
# Option: domain
|
||||
# Notes.: DNS domain that nsupdate will update.
|
||||
# Values: STRING
|
||||
#
|
||||
domain =
|
||||
|
||||
# Option: ttl
|
||||
# Notes.: time to live (TTL) in seconds of TXT resource record
|
||||
# added by nsupdate.
|
||||
# Values: NUM
|
||||
#
|
||||
ttl = 60
|
||||
|
||||
# Option: rdata
|
||||
# Notes.: data portion of the TXT resource record added by nsupdate.
|
||||
# Values: STRING
|
||||
#
|
||||
rdata = Your IP has been banned
|
||||
|
||||
# Option: nsupdatecmd
|
||||
# Notes.: specifies the full path to the nsupdate program that dynamically
|
||||
# updates BIND zone files.
|
||||
# Values: CMD
|
||||
#
|
||||
nsupdatecmd = /usr/bin/nsupdate
|
||||
|
||||
# Option: keyfile
|
||||
# Notes.: specifies the full path to the file containing the
|
||||
# TSIG key for communicating with BIND.
|
||||
# Values: STRING
|
||||
#
|
||||
keyfile =
|
||||
|
||||
@@ -15,7 +15,7 @@ after = sendmail-common.local
|
||||
# Values: CMD
|
||||
#
|
||||
actionstart = printf %%b "Subject: [Fail2Ban] <name>: started on `uname -n`
|
||||
Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"`
|
||||
Date: `LC_TIME=C date +"%%a, %%d %%h %%Y %%T %%z"`
|
||||
From: <sendername> <<sender>>
|
||||
To: <dest>\n
|
||||
Hi,\n
|
||||
@@ -28,7 +28,7 @@ actionstart = printf %%b "Subject: [Fail2Ban] <name>: started on `uname -n`
|
||||
# Values: CMD
|
||||
#
|
||||
actionstop = printf %%b "Subject: [Fail2Ban] <name>: stopped on `uname -n`
|
||||
Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"`
|
||||
Date: `LC_TIME=C date +"%%a, %%d %%h %%Y %%T %%z"`
|
||||
From: <sendername> <<sender>>
|
||||
To: <dest>\n
|
||||
Hi,\n
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Fail2Ban configuration file
|
||||
#
|
||||
# Author: Viktor Szépe
|
||||
#
|
||||
#
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
before = sendmail-common.conf
|
||||
|
||||
[Definition]
|
||||
|
||||
# Option: actionban
|
||||
# Notes.: Command executed when banning an IP. Take care that the
|
||||
# command is executed with Fail2Ban user rights.
|
||||
# You need to install geoiplookup and the GeoLite or GeoIP databases.
|
||||
# (geoip-bin and geoip-database in Debian)
|
||||
# The host command comes from bind9-host package.
|
||||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n`
|
||||
Date: `LC_TIME=C date +"%%a, %%d %%h %%Y %%T %%z"`
|
||||
From: <sendername> <<sender>>
|
||||
To: <dest>\n
|
||||
Hi,\n
|
||||
The IP <ip> has just been banned by Fail2Ban after
|
||||
<failures> attempts against <name>.\n\n
|
||||
Here is more information about <ip>:\n
|
||||
http://bgp.he.net/ip/<ip>
|
||||
http://www.projecthoneypot.org/ip_<ip>
|
||||
http://whois.domaintools.com/<ip>\n\n
|
||||
Country:`geoiplookup -f /usr/share/GeoIP/GeoIP.dat "<ip>" | cut -d':' -f2-`
|
||||
AS:`geoiplookup -f /usr/share/GeoIP/GeoIPASNum.dat "<ip>" | cut -d':' -f2-`
|
||||
hostname: `host -t A <ip> 2>&1`\n\n
|
||||
Lines containing IP:<ip> in <logpath>\n
|
||||
`grep -E '(^|[^0-9])<ip>([^0-9]|$)' <logpath>`\n\n
|
||||
Regards,\n
|
||||
Fail2Ban" | /usr/sbin/sendmail -f <sender> <dest>
|
||||
|
||||
[Init]
|
||||
|
||||
# Default name of the chain
|
||||
#
|
||||
name = default
|
||||
|
||||
# Path to the log files which contain relevant lines for the abuser IP
|
||||
#
|
||||
logpath = /dev/null
|
||||
@@ -17,13 +17,13 @@ before = sendmail-common.conf
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n`
|
||||
Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"`
|
||||
Date: `LC_TIME=C date +"%%a, %%d %%h %%Y %%T %%z"`
|
||||
From: <sendername> <<sender>>
|
||||
To: <dest>\n
|
||||
Hi,\n
|
||||
The IP <ip> has just been banned by Fail2Ban after
|
||||
<failures> attempts against <name>.\n\n
|
||||
Here are more information about <ip>:\n
|
||||
Here is more information about <ip>:\n
|
||||
`/usr/bin/whois <ip>`\n\n
|
||||
Matches for <name> with <ipjailfailures> failures IP:<ip>\n
|
||||
<ipjailmatches>\n\n
|
||||
|
||||
@@ -17,13 +17,13 @@ before = sendmail-common.conf
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n`
|
||||
Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"`
|
||||
Date: `LC_TIME=C date +"%%a, %%d %%h %%Y %%T %%z"`
|
||||
From: <sendername> <<sender>>
|
||||
To: <dest>\n
|
||||
Hi,\n
|
||||
The IP <ip> has just been banned by Fail2Ban after
|
||||
<failures> attempts against <name>.\n\n
|
||||
Here are more information about <ip>:\n
|
||||
Here is more information about <ip>:\n
|
||||
`/usr/bin/whois <ip>`\n\n
|
||||
Matches with <ipfailures> failures IP:<ip>\n
|
||||
<ipmatches>\n\n
|
||||
|
||||
@@ -17,7 +17,7 @@ before = sendmail-common.conf
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n`
|
||||
Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"`
|
||||
Date: `LC_TIME=C date +"%%a, %%d %%h %%Y %%T %%z"`
|
||||
From: <sendername> <<sender>>
|
||||
To: <dest>\n
|
||||
Hi,\n
|
||||
@@ -26,7 +26,7 @@ actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n`
|
||||
Here is more information about <ip>:\n
|
||||
`/usr/bin/whois <ip> || echo missing whois program`\n\n
|
||||
Lines containing IP:<ip> in <logpath>\n
|
||||
`grep '[^0-9]<ip>[^0-9]' <logpath>`\n\n
|
||||
`grep -E '(^|[^0-9])<ip>([^0-9]|$)' <logpath>`\n\n
|
||||
Regards,\n
|
||||
Fail2Ban" | /usr/sbin/sendmail -f <sender> <dest>
|
||||
|
||||
|
||||
@@ -17,13 +17,13 @@ before = sendmail-common.conf
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n`
|
||||
Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"`
|
||||
Date: `LC_TIME=C date +"%%a, %%d %%h %%Y %%T %%z"`
|
||||
From: <sendername> <<sender>>
|
||||
To: <dest>\n
|
||||
Hi,\n
|
||||
The IP <ip> has just been banned by Fail2Ban after
|
||||
<failures> attempts against <name>.\n\n
|
||||
Here are more information about <ip>:\n
|
||||
Here is more information about <ip>:\n
|
||||
`/usr/bin/whois <ip>`\n\n
|
||||
Matches:\n
|
||||
<matches>\n\n
|
||||
|
||||
@@ -17,7 +17,7 @@ before = sendmail-common.conf
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n`
|
||||
Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"`
|
||||
Date: `LC_TIME=C date +"%%a, %%d %%h %%Y %%T %%z"`
|
||||
From: <sendername> <<sender>>
|
||||
To: <dest>\n
|
||||
Hi,\n
|
||||
|
||||
@@ -17,7 +17,7 @@ before = sendmail-common.conf
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n`
|
||||
Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"`
|
||||
Date: `LC_TIME=C date +"%%a, %%d %%h %%Y %%T %%z"`
|
||||
From: <sendername> <<sender>>
|
||||
To: <dest>\n
|
||||
Hi,\n
|
||||
|
||||
@@ -13,9 +13,11 @@ actionstop =
|
||||
|
||||
actioncheck =
|
||||
|
||||
actionban = [ -n "<application>" ] && app="app <application>" ; ufw insert <insertpos> <blocktype> from <ip> to <destination> $app
|
||||
actionban = [ -n "<application>" ] && app="app <application>"
|
||||
ufw insert <insertpos> <blocktype> from <ip> to <destination> $app
|
||||
|
||||
actionunban = [ -n "<application>" ] && app="app <application>" ; ufw delete <blocktype> from <ip> to <destination> $app
|
||||
actionunban = [ -n "<application>" ] && app="app <application>"
|
||||
ufw delete <blocktype> from <ip> to <destination> $app
|
||||
|
||||
[Init]
|
||||
# Option: insertpos
|
||||
|
||||
@@ -46,7 +46,7 @@ actionban = oifs=${IFS}; IFS=.;SEP_IP=( <ip> ); set -- ${SEP_IP}; ADDRESSES=$(di
|
||||
REPORTID=<time>@`uname -n`
|
||||
TLP=<tlp>
|
||||
PORT=<port>
|
||||
DATE=`LC_TIME=C date -u --date=@<time> +"%%a, %%d %%h %%Y %%T +0000"`
|
||||
DATE=`LC_TIME=C date --date=@<time> +"%%a, %%d %%h %%Y %%T %%z"`
|
||||
if [ ! -z "$ADDRESSES" ]; then
|
||||
(printf -- %%b "<header>\n<message>\n<report>\n";
|
||||
date '+Note: Local timezone is %%z (%%Z)';
|
||||
|
||||
@@ -34,6 +34,12 @@ loglevel = INFO
|
||||
#
|
||||
logtarget = /var/log/fail2ban.log
|
||||
|
||||
# Option: syslogsocket
|
||||
# Notes: Set the syslog socket file. Only used when logtarget is SYSLOG
|
||||
# auto uses platform.system() to determine predefined paths
|
||||
# Values: [ auto | FILE ] Default: auto
|
||||
syslogsocket = auto
|
||||
|
||||
# Option: socket
|
||||
# Notes.: Set the socket file. This is used to communicate with the daemon. Do
|
||||
# not remove this file when Fail2ban runs. It will not be possible to
|
||||
|
||||
@@ -10,7 +10,7 @@ before = apache-common.conf
|
||||
[Definition]
|
||||
|
||||
|
||||
failregex = ^%(_apache_error_client)s (AH01797: )?client denied by server configuration: (uri )?\S*(, referer: \S+)?\s*$
|
||||
failregex = ^%(_apache_error_client)s (AH(01797|01630): )?client denied by server configuration: (uri )?\S*(, referer: \S+)?\s*$
|
||||
^%(_apache_error_client)s (AH01617: )?user .*? authentication failure for "\S*": Password Mismatch(, referer: \S+)?$
|
||||
^%(_apache_error_client)s (AH01618: )?user .*? not found(: )?\S*(, referer: \S+)?\s*$
|
||||
^%(_apache_error_client)s (AH01614: )?client used wrong authentication scheme: \S*(, referer: \S+)?\s*$
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
[INCLUDES]
|
||||
|
||||
# overwrite with apache-common.local if _apache_error_client is incorrect.
|
||||
# Load regexes for filtering from botsearch-common.conf
|
||||
before = apache-common.conf
|
||||
botsearch-common.conf
|
||||
|
||||
[Definition]
|
||||
|
||||
@@ -31,18 +33,8 @@ ignoreregex =
|
||||
|
||||
# Webroot represents the webroot on which all other files are based
|
||||
webroot = /var/www/
|
||||
# Block is the actual non-found directories to block
|
||||
block = (<webmail>|<phpmyadmin>|<wordpress>)[^,]*
|
||||
|
||||
# These are just convient definitions that assist the blocking of stuff that
|
||||
# isn't installed
|
||||
webmail = roundcube|(ext)?mail|horde|(v-?)?webmail
|
||||
|
||||
phpmyadmin = (typo3/|xampp/|admin/|)(pma|(php)?[Mm]y[Aa]dmin)
|
||||
|
||||
wordpress = wp-(login|signup)\.php
|
||||
|
||||
|
||||
# DEV Notes:
|
||||
#
|
||||
# Author: Daniel Black
|
||||
# Author: Daniel Black
|
||||
@@ -0,0 +1,14 @@
|
||||
# Fail2Ban filter for fake Googlebot User Agents
|
||||
|
||||
[Definition]
|
||||
|
||||
failregex = ^<HOST> .*Googlebot.*$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
|
||||
# DEV Notes:
|
||||
#
|
||||
# Author: Lee Clemens
|
||||
# Thanks: Johannes B. Ullrich, Ph.D.
|
||||
# Reference: https://isc.sans.edu/forums/diary/When+Google+isnt+Google/15968/
|
||||
@@ -0,0 +1,26 @@
|
||||
# Fail2Ban filter to block web requests containing custom headers attempting to exploit the shellshock bug
|
||||
#
|
||||
#
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
# overwrite with apache-common.local if _apache_error_client is incorrect.
|
||||
before = apache-common.conf
|
||||
|
||||
[Definition]
|
||||
|
||||
failregex = ^%(_apache_error_client)s (AH01215: )?/bin/(ba)?sh: warning: HTTP_.*?: ignoring function definition attempt(, referer: \S+)?\s*$
|
||||
^%(_apache_error_client)s (AH01215: )?/bin/(ba)?sh: error importing function definition for `HTTP_.*?'(, referer: \S+)?\s*$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
|
||||
# DEV Notes:
|
||||
#
|
||||
# https://wiki.apache.org/httpd/ListOfErrors for apache error IDs
|
||||
#
|
||||
# example log lines:
|
||||
# [Thu Sep 25 09:27:18.813902 2014] [cgi:error] [pid 16860] [client 89.207.132.76:59635] AH01215: /bin/bash: warning: HTTP_TEST: ignoring function definition attempt
|
||||
# [Thu Sep 25 09:29:56.141832 2014] [cgi:error] [pid 16864] [client 162.247.73.206:41273] AH01215: /bin/bash: error importing function definition for `HTTP_TEST'
|
||||
#
|
||||
# Author: Eugene Hopkinson (riot@riot.so)
|
||||
@@ -22,7 +22,7 @@ failregex = ^(%(__prefix_line)s|\[\]\s*)%(log_prefix)s Registration from '[^']*'
|
||||
^(%(__prefix_line)s|\[\]\s*)%(log_prefix)s No registration for peer '[^']*' \(from <HOST>\)$
|
||||
^(%(__prefix_line)s|\[\]\s*)%(log_prefix)s Host <HOST> failed MD5 authentication for '[^']*' \([^)]+\)$
|
||||
^(%(__prefix_line)s|\[\]\s*)%(log_prefix)s Failed to authenticate (user|device) [^@]+@<HOST>\S*$
|
||||
^(%(__prefix_line)s|\[\]\s*)%(log_prefix)s (?:handle_request_subscribe: )?Sending fake auth rejection for (device|user) \d*<sip:[^@]+@<HOST>>;tag=\w+\S*$
|
||||
^(%(__prefix_line)s|\[\]\s*)%(log_prefix)s hacking attempt detected '<HOST>'$
|
||||
^(%(__prefix_line)s|\[\]\s*)%(log_prefix)s SecurityEvent="(FailedACL|InvalidAccountID|ChallengeResponseFailed|InvalidPassword)",EventTV="[\d-]+",Severity="[\w]+",Service="[\w]+",EventVersion="\d+",AccountID="\d*",SessionID="0x[\da-f]+",LocalAddress="IPV[46]/(UD|TC)P/[\da-fA-F:.]+/\d+",RemoteAddress="IPV[46]/(UD|TC)P/<HOST>/\d+"(,Challenge="\w+",ReceivedChallenge="\w+")?(,ReceivedHash="[\da-f]+")?(,ACLName="\w+")?$
|
||||
^(%(__prefix_line)s|\[\]\s*WARNING%(__pid_re)s:?(?:\[C-[\da-f]*\])? )Ext\. s: "Rejecting unknown SIP connection from <HOST>"$
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generic configuration file for -botsearch filters
|
||||
|
||||
[Init]
|
||||
|
||||
# Block is the actual non-found directories to block
|
||||
block = \/?(<webmail>|<phpmyadmin>|<wordpress>|cgi-bin|mysqladmin)[^,]*
|
||||
|
||||
# These are just convient definitions that assist the blocking of stuff that
|
||||
# isn't installed
|
||||
webmail = roundcube|(ext)?mail|horde|(v-?)?webmail
|
||||
|
||||
phpmyadmin = (typo3/|xampp/|admin/|)(pma|(php)?[Mm]y[Aa]dmin)
|
||||
|
||||
wordpress = wp-(login|signup)\.php
|
||||
|
||||
# DEV Notes:
|
||||
# Taken from apache-botsearch filter
|
||||
#
|
||||
# Author: Frantisek Sumsal
|
||||
@@ -53,4 +53,8 @@ __bsd_syslog_verbose = (<[^.]+\.[^.]+>)
|
||||
# This can be optional (for instance if we match named native log files)
|
||||
__prefix_line = \s*%(__bsd_syslog_verbose)s?\s*(?:%(__hostname)s )?(?:%(__kernel_prefix)s )?(?:@vserver_\S+ )?%(__daemon_combs_re)s?\s%(__daemon_extra_re)s?\s*
|
||||
|
||||
# PAM authentication mechanism check for failures, e.g.: pam_unix, pam_sss,
|
||||
# pam_ldap
|
||||
__pam_auth = pam_unix
|
||||
|
||||
# Author: Yaroslav Halchenko
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
failregex = ^: Bad Rcon: "rcon \d+ "\S+" sv_contact ".*?"" from "<HOST>:\d+"$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
[Init]
|
||||
|
||||
|
||||
@@ -9,9 +9,10 @@ before = common.conf
|
||||
|
||||
_daemon = (auth|dovecot(-auth)?|auth-worker)
|
||||
|
||||
failregex = ^%(__prefix_line)s(pam_unix(\(dovecot:auth\))?:)?\s+authentication failure; logname=\S* uid=\S* euid=\S* tty=dovecot ruser=\S* rhost=<HOST>(\s+user=\S*)?\s*$
|
||||
failregex = ^%(__prefix_line)s(%(__pam_auth)s(\(dovecot:auth\))?:)?\s+authentication failure; logname=\S* uid=\S* euid=\S* tty=dovecot ruser=\S* rhost=<HOST>(\s+user=\S*)?\s*$
|
||||
^%(__prefix_line)s(pop3|imap)-login: (Info: )?(Aborted login|Disconnected)(: Inactivity)? \(((auth failed, \d+ attempts)( in \d+ secs)?|tried to use (disabled|disallowed) \S+ auth)\):( user=<\S*>,)?( method=\S+,)? rip=<HOST>(, lip=(\d{1,3}\.){3}\d{1,3})?(, TLS( handshaking(: SSL_accept\(\) failed: error:[\dA-F]+:SSL routines:[TLS\d]+_GET_CLIENT_HELLO:unknown protocol)?)?(: Disconnected)?)?(, session=<\S+>)?\s*$
|
||||
^%(__prefix_line)s(Info|dovecot: auth\(default\)): pam\(\S+,<HOST>\): pam_authenticate\(\) failed: (User not known to the underlying authentication module: \d+ Time\(s\)|Authentication failure \(password mismatch\?\))\s*$
|
||||
^%(__prefix_line)s(Info|dovecot: auth\(default\)|auth-worker\(\d+\)): pam\(\S+,<HOST>\): pam_authenticate\(\) failed: (User not known to the underlying authentication module: \d+ Time\(s\)|Authentication failure \(password mismatch\?\))\s*$
|
||||
^%(__prefix_line)sauth-worker\(\d+\): pam\(\S+,<HOST>\): unknown user\s*$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# Fail2Ban filter to block repeated failed login attempts to Drupal site(s)
|
||||
#
|
||||
#
|
||||
# Drupal must be setup to use Syslog, which defaults to the following format:
|
||||
#
|
||||
# !base_url|!timestamp|!type|!ip|!request_uri|!referer|!uid|!link|!message
|
||||
#
|
||||
#
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
before = common.conf
|
||||
|
||||
|
||||
[Definition]
|
||||
|
||||
failregex = ^%(__prefix_line)s(https?:\/\/)([\da-z\.-]+)\.([a-z\.]{2,6})(\/[\w\.-]+)*\|\d{10}\|user\|<HOST>\|.+\|.+\|\d\|.*\|Login attempt failed for .+\.$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
|
||||
# DEV Notes:
|
||||
#
|
||||
# https://www.drupal.org/documentation/modules/syslog
|
||||
#
|
||||
# Author: Lee Clemens
|
||||
@@ -14,10 +14,10 @@ before = exim-common.conf
|
||||
[Definition]
|
||||
|
||||
failregex = ^%(pid)s %(host_info)ssender verify fail for <\S+>: (?:Unknown user|Unrouteable address|all relevant MX records point to non-existent hosts)\s*$
|
||||
^%(pid)s \w+ authenticator failed for (\S+ )?\(\S+\) \[<HOST>\]: 535 Incorrect authentication data( \(set_id=.*\)|: \d+ Time\(s\))?\s*$
|
||||
^%(pid)s \w+ authenticator failed for (\S+ )?\(\S+\) \[<HOST>\](:\d+)?( I=\[\S+\](:\d+)?)?: 535 Incorrect authentication data( \(set_id=.*\)|: \d+ Time\(s\))?\s*$
|
||||
^%(pid)s %(host_info)sF=(<>|[^@]+@\S+) rejected RCPT [^@]+@\S+: (relay not permitted|Sender verify failed|Unknown user)\s*$
|
||||
^%(pid)s SMTP protocol synchronization error \([^)]*\): rejected (connection from|"\S+") %(host_info)s(next )?input=".*"\s*$
|
||||
^%(pid)s SMTP call from \S+ \[<HOST>\](:\d+)? (I=\[\S+\]:\d+ )?dropped: too many nonmail commands \(last was "\S+"\)\s*$
|
||||
^%(pid)s SMTP call from \S+ \[<HOST>\](:\d+)? (I=\[\S+\](:\d+)? )?dropped: too many nonmail commands \(last was "\S+"\)\s*$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
failregex = ^\[\]LOGIN FAILED for user: "\S+" from IP: <HOST>$
|
||||
|
||||
|
||||
ignoreregex =
|
||||
|
||||
# Author: Daniel Black
|
||||
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/python
|
||||
# Inspired by https://isc.sans.edu/forums/diary/When+Google+isnt+Google/15968/
|
||||
#
|
||||
# Written in Python to reuse built-in Python batteries and not depend on
|
||||
# presence of host and cut commands
|
||||
#
|
||||
import sys
|
||||
|
||||
def process_args(argv):
|
||||
if len(argv) != 2:
|
||||
sys.stderr.write("Please provide a single IP as an argument. Got: %s\n"
|
||||
% (argv[1:]))
|
||||
sys.exit(2)
|
||||
|
||||
ip = argv[1]
|
||||
|
||||
from fail2ban.server.filter import DNSUtils
|
||||
if not DNSUtils.isValidIP(ip):
|
||||
sys.stderr.write("Argument must be a single valid IP. Got: %s\n"
|
||||
% ip)
|
||||
sys.exit(3)
|
||||
return ip
|
||||
|
||||
def is_googlebot(ip):
|
||||
import re
|
||||
from fail2ban.server.filter import DNSUtils
|
||||
|
||||
host = DNSUtils.ipToName(ip)
|
||||
sys.exit(0 if (host and re.match('crawl-.*\.googlebot\.com', host)) else 1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
is_googlebot(process_args(sys.argv))
|
||||
@@ -6,6 +6,9 @@ failregex = ^ SMTP Spam attack detected from <HOST>,
|
||||
^ IP address <HOST> found in DNS blacklist \S+, mail from \S+ to \S+$
|
||||
^ Relay attempt from IP address <HOST>
|
||||
^ Attempt to deliver to unknown recipient \S+, from \S+, IP address <HOST>$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
[Init]
|
||||
|
||||
datepattern = ^\[%%d/%%b/%%Y %%H:%%M:%%S\]
|
||||
|
||||
@@ -7,3 +7,4 @@
|
||||
failregex = ^\[[A-Z]+\s+\]\s*error\s*:\s*Warning:\s+Client '<HOST>' supplied unknown user '\w+' accessing monit httpd$
|
||||
^\[[A-Z]+\s+\]\s*error\s*:\s*Warning:\s+Client '<HOST>' supplied wrong password for user '\w+' accessing monit httpd$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
@@ -38,6 +38,8 @@ failregex = ^%(__line_prefix)s( error:)?\s*client <HOST>#\S+( \([\S.]+\))?: (vie
|
||||
^%(__line_prefix)s( error:)?\s*client <HOST>#\S+( \([\S.]+\))?: zone transfer '\S+/AXFR/\w+' denied\s*$
|
||||
^%(__line_prefix)s( error:)?\s*client <HOST>#\S+( \([\S.]+\))?: bad zone transfer request: '\S+/IN': non-authoritative zone \(NOTAUTH\)\s*$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
# DEV Notes:
|
||||
# Trying to generalize the
|
||||
# structure which is general to capture general patterns in log
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Fail2Ban filter to match web requests for selected URLs that don't exist
|
||||
#
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
# Load regexes for filtering
|
||||
before = botsearch-common.conf
|
||||
|
||||
[Definition]
|
||||
|
||||
failregex = ^<HOST> \- \S+ \[\] \"(GET|POST) \/<block> \S+\" 404 .+$
|
||||
^ \[error\] \d+#\d+: \*\d+ (\S+ )?\"\S+\" (failed|is not found) \(2\: No such file or directory\), client\: <HOST>\, server\: \S*\, request: \"(GET|POST) \/<block> \S+\"\, .*?$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
|
||||
# DEV Notes:
|
||||
# Based on apache-botsearch filter
|
||||
#
|
||||
# Author: Frantisek Sumsal
|
||||
@@ -24,3 +24,5 @@ _daemon = nsd
|
||||
|
||||
failregex = ^\[\]%(__prefix_line)sinfo: ratelimit block .* query <HOST> TYPE255$
|
||||
^\[\]%(__prefix_line)sinfo: .* <HOST> refused, no acl matches\.$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
@@ -13,7 +13,7 @@ before = common.conf
|
||||
# Default: catch all failed logins
|
||||
_ttys_re=\S*
|
||||
|
||||
__pam_re=\(?pam_unix(?:\(\S+\))?\)?:?
|
||||
__pam_re=\(?%(__pam_auth)s(?:\(\S+\))?\)?:?
|
||||
_daemon = \S+
|
||||
|
||||
failregex = ^%(__prefix_line)s%(__pam_re)s\s+authentication failure; logname=\S* uid=\S* euid=\S* tty=%(_ttys_re)s ruser=\S* rhost=<HOST>(?:\s+user=.*)?\s*$
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Fail2Ban filter for failure attempts in Counter Strike-1.6
|
||||
#
|
||||
#
|
||||
|
||||
[Definition]
|
||||
|
||||
failregex = \/<HOST> Port\: [0-9]+ (TCP|UDP) Blocked$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
# Author: Pacop <pacoparu@gmail.com>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Fail2Ban filter for Postfix's RBL based Blocked hosts
|
||||
#
|
||||
#
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
# Read common prefixes. If any customizations available -- read them from
|
||||
# common.local
|
||||
before = common.conf
|
||||
|
||||
[Definition]
|
||||
|
||||
_daemon = postfix/smtpd
|
||||
|
||||
failregex = ^%(__prefix_line)sNOQUEUE: reject: RCPT from \S+\[<HOST>\]: 454 4\.7\.1 Service unavailable; Client host \[\S+\] blocked using .* from=<\S*> to=<\S+> proto=ESMTP helo=<\S*>$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
# Author: Lee Clemens
|
||||
@@ -7,9 +7,11 @@ before = common.conf
|
||||
|
||||
[Definition]
|
||||
|
||||
_daemon = postfix/smtpd
|
||||
_daemon = postfix/(submission/)?smtp(d|s)
|
||||
|
||||
failregex = ^%(__prefix_line)swarning: [-._\w]+\[<HOST>\]: SASL (?:LOGIN|PLAIN|(?:CRAM|DIGEST)-MD5) authentication failed(: [ A-Za-z0-9+/]*={0,2})?\s*$
|
||||
failregex = ^%(__prefix_line)swarning: [-._\w]+\[<HOST>\]: SASL ((?i)LOGIN|PLAIN|(?:CRAM|DIGEST)-MD5) authentication failed(: [ A-Za-z0-9+/:]*={0,2})?\s*$
|
||||
|
||||
ignoreregex = authentication failed: Connection lost to authentication server$
|
||||
|
||||
[Init]
|
||||
|
||||
|
||||
@@ -10,9 +10,10 @@ before = common.conf
|
||||
|
||||
[Definition]
|
||||
|
||||
_daemon = postfix/smtpd
|
||||
_daemon = postfix/(submission/)?smtp(d|s)
|
||||
|
||||
failregex = ^%(__prefix_line)sNOQUEUE: reject: RCPT from \S+\[<HOST>\]: 554 5\.7\.1 .*$
|
||||
^%(__prefix_line)sNOQUEUE: reject: RCPT from \S+\[<HOST>\]: 450 4\.7\.1 Client host rejected: cannot find your hostname, (\[\S*\]); from=<\S*> to=<\S+> proto=ESMTP helo=<\S*>$
|
||||
^%(__prefix_line)sNOQUEUE: reject: RCPT from \S+\[<HOST>\]: 450 4\.7\.1 : Helo command rejected: Host not found; from=<> to=<> proto=ESMTP helo= *$
|
||||
^%(__prefix_line)sNOQUEUE: reject: VRFY from \S+\[<HOST>\]: 550 5\.1\.1 .*$
|
||||
^%(__prefix_line)simproper command pipelining after \S+ from [^[]*\[<HOST>\]:?$
|
||||
|
||||
@@ -29,6 +29,8 @@ _jailname = recidive
|
||||
|
||||
failregex = ^(%(__prefix_line)s| %(_daemon)s%(__pid_re)s?:\s+)NOTICE\s+\[(?!%(_jailname)s\])(?:.*)\]\s+Ban\s+<HOST>\s*$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
[Init]
|
||||
|
||||
journalmatch = _SYSTEMD_UNIT=fail2ban.service PRIORITY=5
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
failregex = ^\s+\d\s<HOST>\s+[A-Z_]+_DENIED/403 .*$
|
||||
^\s+\d\s<HOST>\s+NONE/405 .*$
|
||||
|
||||
|
||||
ignoreregex =
|
||||
|
||||
# Author: Daniel Black
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
failregex = ^ \[LOGIN_ERROR\].*from <HOST>: Unknown user or password incorrect\.$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
[Init]
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ failregex = ^%(__prefix_line)s(?:error: PAM: )?[aA]uthentication (?:failure|erro
|
||||
^(?P<__prefix>%(__prefix_line)s)User .+ not allowed because account is locked<SKIPLINES>(?P=__prefix)(?:error: )?Received disconnect from <HOST>: 11: .+ \[preauth\]$
|
||||
^(?P<__prefix>%(__prefix_line)s)Disconnecting: Too many authentication failures for .+? \[preauth\]<SKIPLINES>(?P=__prefix)(?:error: )?Connection closed by <HOST> \[preauth\]$
|
||||
^(?P<__prefix>%(__prefix_line)s)Connection from <HOST> port \d+(?: on \S+ port \d+)?<SKIPLINES>(?P=__prefix)Disconnecting: Too many authentication failures for .+? \[preauth\]$
|
||||
^%(__prefix_line)spam_unix\(sshd:auth\):\s+authentication failure;\s*logname=\S*\s*uid=\d*\s*euid=\d*\s*tty=\S*\s*ruser=\S*\s*rhost=<HOST>\s.*$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
failregex = ^ LOG\d\[\d+:\d+\]:\ SSL_accept from <HOST>:\d+ : (?P<CODE>[\dA-F]+): error:(?P=CODE):SSL routines:SSL3_GET_CLIENT_CERTIFICATE:peer did not return a certificate$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
# DEV NOTES:
|
||||
#
|
||||
# Author: Daniel Black
|
||||
|
||||
@@ -10,7 +10,7 @@ before = common.conf
|
||||
|
||||
[Definition]
|
||||
|
||||
__pam_re=\(?pam_unix(?:\(\S+\))?\)?:?
|
||||
__pam_re=\(?%(__pam_auth)s(?:\(\S+\))?\)?:?
|
||||
_daemon = vsftpd
|
||||
|
||||
failregex = ^%(__prefix_line)s%(__pam_re)s\s+authentication failure; logname=\S* uid=\S* euid=\S* tty=(ftp)? ruser=\S* rhost=<HOST>(?:\s+user=.*)?\s*$
|
||||
|
||||
@@ -11,7 +11,7 @@ before = common.conf
|
||||
[Definition]
|
||||
|
||||
_daemon = wu-ftpd
|
||||
__pam_re=\(?pam_unix(?:\(wu-ftpd:auth\))?\)?:?
|
||||
__pam_re=\(?%(__pam_auth)s(?:\(wu-ftpd:auth\))?\)?:?
|
||||
|
||||
failregex = ^%(__prefix_line)sfailed login from \S+ \[<HOST>\]\s*$
|
||||
^%(__prefix_line)s%(__pam_re)s\s+authentication failure; logname=\S* uid=\S* euid=\S* tty=(ftp)? ruser=\S* rhost=<HOST>(?:\s+user=.*)?\s*$
|
||||
|
||||
+50
-8
@@ -79,6 +79,11 @@ maxretry = 5
|
||||
# See "journalmatch" in the jails associated filter config
|
||||
# auto: will try to use the following backends, in order:
|
||||
# pyinotify, gamin, polling.
|
||||
#
|
||||
# Note: if systemd backend is choses as the default but you enable a jail
|
||||
# for which logs are present only in its own log files, specify some other
|
||||
# backend for that jail (e.g. polling) and provide empty value for
|
||||
# journalmatch. See https://github.com/fail2ban/fail2ban/issues/959#issuecomment-74901200
|
||||
backend = auto
|
||||
|
||||
# "usedns" specifies if jails should trust hostnames in logs,
|
||||
@@ -277,18 +282,36 @@ logpath = %(apache_error_log)s
|
||||
maxretry = 2
|
||||
|
||||
|
||||
[apache-fakegooglebot]
|
||||
|
||||
port = http,https
|
||||
logpath = %(apache_access_log)s
|
||||
maxretry = 1
|
||||
ignorecommand = %(ignorecommands_dir)s/apache-fakegooglebot <ip>
|
||||
|
||||
|
||||
[apache-modsecurity]
|
||||
|
||||
port = http,https
|
||||
logpath = %(apache_error_log)s
|
||||
maxretry = 2
|
||||
|
||||
[apache-shellshock]
|
||||
|
||||
port = http,https
|
||||
logpath = %(apache_error_log)s
|
||||
maxretry = 1
|
||||
|
||||
[nginx-http-auth]
|
||||
|
||||
ports = http,https
|
||||
port = http,https
|
||||
logpath = %(nginx_error_log)s
|
||||
|
||||
[nginx-botsearch]
|
||||
|
||||
port = http,https
|
||||
logpath = %(nginx_error_log)s
|
||||
maxretry = 2
|
||||
|
||||
# Ban attackers that try to use PHP's URL-fopen() functionality
|
||||
# through GET/POST variables. - Experimental, with more than a year
|
||||
@@ -297,7 +320,8 @@ logpath = %(nginx_error_log)s
|
||||
[php-url-fopen]
|
||||
|
||||
port = http,https
|
||||
logpath = %(nginx_access_log)s %(apache_access_log)s
|
||||
logpath = %(nginx_access_log)s
|
||||
%(apache_access_log)s
|
||||
|
||||
|
||||
[suhosin]
|
||||
@@ -361,6 +385,11 @@ maxretry = 5
|
||||
#
|
||||
#
|
||||
|
||||
[drupal-auth]
|
||||
|
||||
port = http,https
|
||||
logpath = %(syslog_daemon)s
|
||||
|
||||
[guacamole]
|
||||
|
||||
port = http,https
|
||||
@@ -459,6 +488,13 @@ port = smtp,465,submission
|
||||
logpath = %(postfix_log)s
|
||||
|
||||
|
||||
[postfix-rbl]
|
||||
|
||||
port = smtp,465,submission
|
||||
logpath = %(syslog_mail)s
|
||||
maxretry = 1
|
||||
|
||||
|
||||
[sendmail-auth]
|
||||
|
||||
port = submission,465,smtp
|
||||
@@ -643,15 +679,16 @@ maxretry = 5
|
||||
|
||||
|
||||
# Jail for more extended banning of persistent abusers
|
||||
# !!! WARNING !!!
|
||||
# Make sure that your loglevel specified in fail2ban.conf/.local
|
||||
# is not at DEBUG level -- which might then cause fail2ban to fall into
|
||||
# an infinite loop constantly feeding itself with non-informative lines
|
||||
# !!! WARNINGS !!!
|
||||
# 1. Make sure that your loglevel specified in fail2ban.conf/.local
|
||||
# is not at DEBUG level -- which might then cause fail2ban to fall into
|
||||
# an infinite loop constantly feeding itself with non-informative lines
|
||||
# 2. Increase dbpurgeage defined in fail2ban.conf to e.g. 648000 (7.5 days)
|
||||
# to maintain entries for failed logins for sufficient amount of time
|
||||
[recidive]
|
||||
|
||||
logpath = /var/log/fail2ban.log
|
||||
port = all
|
||||
protocol = all
|
||||
banaction = iptables-allports
|
||||
bantime = 604800 ; 1 week
|
||||
findtime = 86400 ; 1 day
|
||||
maxretry = 5
|
||||
@@ -714,3 +751,8 @@ banaction = iptables-allports
|
||||
enabled = false
|
||||
logpath = /var/log/directadmin/login.log
|
||||
port = 2222
|
||||
|
||||
[portsentry]
|
||||
enabled = false
|
||||
logpath = /var/lib/portsentry/portsentry.history
|
||||
maxretry = 1
|
||||
|
||||
@@ -61,3 +61,6 @@ dovecot_log = %(syslog_mail_warn)s
|
||||
solidpop3d_log = %(syslog_local0)s
|
||||
|
||||
mysql_log = %(syslog_daemon)s
|
||||
|
||||
# Directory with ignorecommand scripts
|
||||
ignorecommands_dir = /etc/fail2ban/filter.d/ignorecommands
|
||||
|
||||
@@ -68,3 +68,7 @@ logging.notice = _root_notice
|
||||
|
||||
# add NOTICE to the priority map of all the levels
|
||||
logging.handlers.SysLogHandler.priority_map['NOTICE'] = 'notice'
|
||||
|
||||
from time import strptime
|
||||
# strptime thread safety hack-around - http://bugs.python.org/issue7980
|
||||
strptime("2012", "%Y")
|
||||
|
||||
@@ -26,7 +26,7 @@ __license__ = "GPL"
|
||||
|
||||
import os
|
||||
|
||||
from .configreader import ConfigReader, DefinitionInitConfigReader
|
||||
from .configreader import DefinitionInitConfigReader
|
||||
from ..helpers import getLogger
|
||||
|
||||
# Gets the instance of the logger.
|
||||
@@ -47,15 +47,19 @@ class ActionReader(DefinitionInitConfigReader):
|
||||
DefinitionInitConfigReader.__init__(
|
||||
self, file_, jailName, initOpts, **kwargs)
|
||||
|
||||
def setFile(self, fileName):
|
||||
self.__file = fileName
|
||||
DefinitionInitConfigReader.setFile(self, os.path.join("action.d", fileName))
|
||||
|
||||
def getFile(self):
|
||||
return self.__file
|
||||
|
||||
def setName(self, name):
|
||||
self._name = name
|
||||
|
||||
def getName(self):
|
||||
return self._name
|
||||
|
||||
def read(self):
|
||||
return ConfigReader.read(self, os.path.join("action.d", self._file))
|
||||
|
||||
def convert(self):
|
||||
head = ["set", self._jailName]
|
||||
stream = list()
|
||||
|
||||
@@ -85,6 +85,9 @@ class Beautifier:
|
||||
val = " ".join(res1[1]) if isinstance(res1[1], list) else res1[1]
|
||||
msg.append("%s %s:\t%s" % (prefix1, res1[0], val))
|
||||
msg = "\n".join(msg)
|
||||
elif inC[1] == "syslogsocket":
|
||||
msg = "Current syslog socket is:\n"
|
||||
msg = msg + "`- " + response
|
||||
elif inC[1] == "logtarget":
|
||||
msg = "Current logging target is:\n"
|
||||
msg = msg + "`- " + response
|
||||
|
||||
@@ -62,6 +62,7 @@ else: # pragma: no cover
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = getLogger(__name__)
|
||||
logLevel = 7
|
||||
|
||||
__all__ = ['SafeConfigParserWithIncludes']
|
||||
|
||||
@@ -98,30 +99,73 @@ after = 1.conf
|
||||
|
||||
if sys.version_info >= (3,2):
|
||||
# overload constructor only for fancy new Python3's
|
||||
def __init__(self, *args, **kwargs):
|
||||
def __init__(self, share_config=None, *args, **kwargs):
|
||||
kwargs = kwargs.copy()
|
||||
kwargs['interpolation'] = BasicInterpolationWithName()
|
||||
kwargs['inline_comment_prefixes'] = ";"
|
||||
super(SafeConfigParserWithIncludes, self).__init__(
|
||||
*args, **kwargs)
|
||||
self._cfg_share = share_config
|
||||
|
||||
#@staticmethod
|
||||
def getIncludes(resource, seen = []):
|
||||
else:
|
||||
def __init__(self, share_config=None, *args, **kwargs):
|
||||
SafeConfigParser.__init__(self, *args, **kwargs)
|
||||
self._cfg_share = share_config
|
||||
|
||||
@property
|
||||
def share_config(self):
|
||||
return self._cfg_share
|
||||
|
||||
def _getSharedSCPWI(self, filename):
|
||||
SCPWI = SafeConfigParserWithIncludes
|
||||
# read single one, add to return list, use sharing if possible:
|
||||
if self._cfg_share:
|
||||
# cache/share each file as include (ex: filter.d/common could be included in each filter config):
|
||||
hashv = 'inc:'+(filename if not isinstance(filename, list) else '\x01'.join(filename))
|
||||
cfg, i = self._cfg_share.get(hashv, (None, None))
|
||||
if cfg is None:
|
||||
cfg = SCPWI(share_config=self._cfg_share)
|
||||
i = cfg.read(filename, get_includes=False)
|
||||
self._cfg_share[hashv] = (cfg, i)
|
||||
elif logSys.getEffectiveLevel() <= logLevel:
|
||||
logSys.log(logLevel, " Shared file: %s", filename)
|
||||
else:
|
||||
# don't have sharing:
|
||||
cfg = SCPWI()
|
||||
i = cfg.read(filename, get_includes=False)
|
||||
return (cfg, i)
|
||||
|
||||
def _getIncludes(self, filenames, seen=[]):
|
||||
if not isinstance(filenames, list):
|
||||
filenames = [ filenames ]
|
||||
# retrieve or cache include paths:
|
||||
if self._cfg_share:
|
||||
# cache/share include list:
|
||||
hashv = 'inc-path:'+('\x01'.join(filenames))
|
||||
fileNamesFull = self._cfg_share.get(hashv)
|
||||
if fileNamesFull is None:
|
||||
fileNamesFull = []
|
||||
for filename in filenames:
|
||||
fileNamesFull += self.__getIncludesUncached(filename, seen)
|
||||
self._cfg_share[hashv] = fileNamesFull
|
||||
return fileNamesFull
|
||||
# don't have sharing:
|
||||
fileNamesFull = []
|
||||
for filename in filenames:
|
||||
fileNamesFull += self.__getIncludesUncached(filename, seen)
|
||||
return fileNamesFull
|
||||
|
||||
def __getIncludesUncached(self, resource, seen=[]):
|
||||
"""
|
||||
Given 1 config resource returns list of included files
|
||||
(recursively) with the original one as well
|
||||
Simple loops are taken care about
|
||||
"""
|
||||
|
||||
# Use a short class name ;)
|
||||
SCPWI = SafeConfigParserWithIncludes
|
||||
|
||||
parser = SafeConfigParser()
|
||||
try:
|
||||
if sys.version_info >= (3,2): # pragma: no cover
|
||||
parser.read(resource, encoding='utf-8')
|
||||
else:
|
||||
parser.read(resource)
|
||||
parser, i = self._getSharedSCPWI(resource)
|
||||
if not i:
|
||||
return []
|
||||
except UnicodeDecodeError, e:
|
||||
logSys.error("Error decoding config file '%s': %s" % (resource, e))
|
||||
return []
|
||||
@@ -141,22 +185,76 @@ after = 1.conf
|
||||
if r in seen:
|
||||
continue
|
||||
s = seen + [resource]
|
||||
option_list += SCPWI.getIncludes(r, s)
|
||||
option_list += self._getIncludes(r, s)
|
||||
# combine lists
|
||||
return newFiles[0][1] + [resource] + newFiles[1][1]
|
||||
#print "Includes list for " + resource + " is " + `resources`
|
||||
getIncludes = staticmethod(getIncludes)
|
||||
|
||||
def get_defaults(self):
|
||||
return self._defaults
|
||||
|
||||
def read(self, filenames):
|
||||
fileNamesFull = []
|
||||
def get_sections(self):
|
||||
return self._sections
|
||||
|
||||
def read(self, filenames, get_includes=True):
|
||||
if not isinstance(filenames, list):
|
||||
filenames = [ filenames ]
|
||||
for filename in filenames:
|
||||
fileNamesFull += SafeConfigParserWithIncludes.getIncludes(filename)
|
||||
logSys.debug("Reading files: %s" % fileNamesFull)
|
||||
# retrieve (and cache) includes:
|
||||
fileNamesFull = []
|
||||
if get_includes:
|
||||
fileNamesFull += self._getIncludes(filenames)
|
||||
else:
|
||||
fileNamesFull = filenames
|
||||
|
||||
if not fileNamesFull:
|
||||
return []
|
||||
|
||||
logSys.info(" Loading files: %s", fileNamesFull)
|
||||
|
||||
if get_includes or len(fileNamesFull) > 1:
|
||||
# read multiple configs:
|
||||
ret = []
|
||||
alld = self.get_defaults()
|
||||
alls = self.get_sections()
|
||||
for filename in fileNamesFull:
|
||||
# read single one, add to return list, use sharing if possible:
|
||||
cfg, i = self._getSharedSCPWI(filename)
|
||||
if i:
|
||||
ret += i
|
||||
# merge defaults and all sections to self:
|
||||
alld.update(cfg.get_defaults())
|
||||
for n, s in cfg.get_sections().iteritems():
|
||||
if isinstance(s, dict):
|
||||
s2 = alls.get(n)
|
||||
if isinstance(s2, dict):
|
||||
# save previous known values, for possible using in local interpolations later:
|
||||
sk = {}
|
||||
for k, v in s2.iteritems():
|
||||
if not k.startswith('known/'):
|
||||
sk['known/'+k] = v
|
||||
s2.update(sk)
|
||||
# merge section
|
||||
s2.update(s)
|
||||
else:
|
||||
alls[n] = s.copy()
|
||||
else:
|
||||
alls[n] = s
|
||||
|
||||
return ret
|
||||
|
||||
# read one config :
|
||||
if logSys.getEffectiveLevel() <= logLevel:
|
||||
logSys.log(logLevel, " Reading file: %s", fileNamesFull[0])
|
||||
# read file(s) :
|
||||
if sys.version_info >= (3,2): # pragma: no cover
|
||||
return SafeConfigParser.read(self, fileNamesFull, encoding='utf-8')
|
||||
else:
|
||||
return SafeConfigParser.read(self, fileNamesFull)
|
||||
|
||||
def merge_section(self, section, options, pref='known/'):
|
||||
alls = self.get_sections()
|
||||
sk = {}
|
||||
for k, v in options.iteritems():
|
||||
if pref == '' or not k.startswith(pref):
|
||||
sk[pref+k] = v
|
||||
alls[section].update(sk)
|
||||
|
||||
|
||||
+128
-21
@@ -27,24 +27,131 @@ __license__ = "GPL"
|
||||
import glob, os
|
||||
from ConfigParser import NoOptionError, NoSectionError
|
||||
|
||||
from .configparserinc import SafeConfigParserWithIncludes
|
||||
from .configparserinc import SafeConfigParserWithIncludes, logLevel
|
||||
from ..helpers import getLogger
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = getLogger(__name__)
|
||||
|
||||
class ConfigReader(SafeConfigParserWithIncludes):
|
||||
class ConfigReader():
|
||||
"""Generic config reader class.
|
||||
|
||||
A caching adapter which automatically reuses already shared configuration.
|
||||
"""
|
||||
|
||||
def __init__(self, use_config=None, share_config=None, **kwargs):
|
||||
# use given shared config if possible (see read):
|
||||
self._cfg_share = None
|
||||
self._cfg = None
|
||||
if use_config is not None:
|
||||
self._cfg = use_config
|
||||
# share config if possible:
|
||||
if share_config is not None:
|
||||
self._cfg_share = share_config
|
||||
self._cfg_share_kwargs = kwargs
|
||||
self._cfg_share_basedir = None
|
||||
elif self._cfg is None:
|
||||
self._cfg = ConfigReaderUnshared(**kwargs)
|
||||
|
||||
def setBaseDir(self, basedir):
|
||||
if self._cfg:
|
||||
self._cfg.setBaseDir(basedir)
|
||||
else:
|
||||
self._cfg_share_basedir = basedir
|
||||
|
||||
def getBaseDir(self):
|
||||
if self._cfg:
|
||||
return self._cfg.getBaseDir()
|
||||
else:
|
||||
return self._cfg_share_basedir
|
||||
|
||||
@property
|
||||
def share_config(self):
|
||||
return self._cfg_share
|
||||
|
||||
def read(self, name, once=True):
|
||||
""" Overloads a default (not shared) read of config reader.
|
||||
|
||||
To prevent mutiple reads of config files with it includes, reads into
|
||||
the config reader, if it was not yet cached/shared by 'name'.
|
||||
"""
|
||||
# already shared ?
|
||||
if not self._cfg:
|
||||
self._create_unshared(name)
|
||||
# performance feature - read once if using shared config reader:
|
||||
if once and self._cfg.read_cfg_files is not None:
|
||||
return self._cfg.read_cfg_files
|
||||
|
||||
# load:
|
||||
logSys.info("Loading configs for %s under %s ", name, self._cfg.getBaseDir())
|
||||
ret = self._cfg.read(name)
|
||||
|
||||
# save already read and return:
|
||||
self._cfg.read_cfg_files = ret
|
||||
return ret
|
||||
|
||||
def _create_unshared(self, name=''):
|
||||
""" Allocates and share a config file by it name.
|
||||
|
||||
Automatically allocates unshared or reuses shared handle by given 'name' and
|
||||
init arguments inside a given shared storage.
|
||||
"""
|
||||
if not self._cfg and self._cfg_share is not None:
|
||||
self._cfg = self._cfg_share.get(name)
|
||||
if not self._cfg:
|
||||
self._cfg = ConfigReaderUnshared(share_config=self._cfg_share, **self._cfg_share_kwargs)
|
||||
if self._cfg_share_basedir is not None:
|
||||
self._cfg.setBaseDir(self._cfg_share_basedir)
|
||||
self._cfg_share[name] = self._cfg
|
||||
else:
|
||||
self._cfg = ConfigReaderUnshared(**self._cfg_share_kwargs)
|
||||
|
||||
def sections(self):
|
||||
if self._cfg is not None:
|
||||
return self._cfg.sections()
|
||||
return []
|
||||
|
||||
def has_section(self, sec):
|
||||
if self._cfg is not None:
|
||||
return self._cfg.has_section(sec)
|
||||
return False
|
||||
|
||||
def merge_section(self, *args, **kwargs):
|
||||
if self._cfg is not None:
|
||||
return self._cfg.merge_section(*args, **kwargs)
|
||||
|
||||
def options(self, *args):
|
||||
if self._cfg is not None:
|
||||
return self._cfg.options(*args)
|
||||
return {}
|
||||
|
||||
def get(self, sec, opt):
|
||||
if self._cfg is not None:
|
||||
return self._cfg.get(sec, opt)
|
||||
return None
|
||||
|
||||
def getOptions(self, *args, **kwargs):
|
||||
if self._cfg is not None:
|
||||
return self._cfg.getOptions(*args, **kwargs)
|
||||
return {}
|
||||
|
||||
class ConfigReaderUnshared(SafeConfigParserWithIncludes):
|
||||
"""Unshared config reader (previously ConfigReader).
|
||||
|
||||
Do not use this class (internal not shared/cached represenation).
|
||||
Use ConfigReader instead.
|
||||
"""
|
||||
|
||||
DEFAULT_BASEDIR = '/etc/fail2ban'
|
||||
|
||||
def __init__(self, basedir=None):
|
||||
SafeConfigParserWithIncludes.__init__(self)
|
||||
def __init__(self, basedir=None, *args, **kwargs):
|
||||
SafeConfigParserWithIncludes.__init__(self, *args, **kwargs)
|
||||
self.read_cfg_files = None
|
||||
self.setBaseDir(basedir)
|
||||
self.__opts = None
|
||||
|
||||
def setBaseDir(self, basedir):
|
||||
if basedir is None:
|
||||
basedir = ConfigReader.DEFAULT_BASEDIR # stock system location
|
||||
basedir = ConfigReaderUnshared.DEFAULT_BASEDIR # stock system location
|
||||
self._basedir = basedir.rstrip('/')
|
||||
|
||||
def getBaseDir(self):
|
||||
@@ -55,7 +162,7 @@ class ConfigReader(SafeConfigParserWithIncludes):
|
||||
raise ValueError("Base configuration directory %s does not exist "
|
||||
% self._basedir)
|
||||
basename = os.path.join(self._basedir, filename)
|
||||
logSys.info("Reading configs for %s under %s " % (basename, self._basedir))
|
||||
logSys.debug("Reading configs for %s under %s " , filename, self._basedir)
|
||||
config_files = [ basename + ".conf" ]
|
||||
|
||||
# possible further customizations under a .conf.d directory
|
||||
@@ -71,14 +178,14 @@ class ConfigReader(SafeConfigParserWithIncludes):
|
||||
|
||||
if len(config_files):
|
||||
# at least one config exists and accessible
|
||||
logSys.debug("Reading config files: " + ', '.join(config_files))
|
||||
logSys.debug("Reading config files: %s", ', '.join(config_files))
|
||||
config_files_read = SafeConfigParserWithIncludes.read(self, config_files)
|
||||
missed = [ cf for cf in config_files if cf not in config_files_read ]
|
||||
if missed:
|
||||
logSys.error("Could not read config files: " + ', '.join(missed))
|
||||
logSys.error("Could not read config files: %s", ', '.join(missed))
|
||||
if config_files_read:
|
||||
return True
|
||||
logSys.error("Found no accessible config files for %r under %s" %
|
||||
logSys.error("Found no accessible config files for %r under %s",
|
||||
( filename, self.getBaseDir() ))
|
||||
return False
|
||||
else:
|
||||
@@ -98,7 +205,7 @@ class ConfigReader(SafeConfigParserWithIncludes):
|
||||
# 1 -> the name of the option
|
||||
# 2 -> the default value for the option
|
||||
|
||||
def getOptions(self, sec, options, pOptions = None):
|
||||
def getOptions(self, sec, options, pOptions=None):
|
||||
values = dict()
|
||||
for option in options:
|
||||
try:
|
||||
@@ -121,10 +228,8 @@ class ConfigReader(SafeConfigParserWithIncludes):
|
||||
logSys.warning("'%s' not defined in '%s'. Using default one: %r"
|
||||
% (option[1], sec, option[2]))
|
||||
values[option[1]] = option[2]
|
||||
else:
|
||||
logSys.debug(
|
||||
"Non essential option '%s' not defined in '%s'.",
|
||||
option[1], sec)
|
||||
elif logSys.getEffectiveLevel() <= logLevel:
|
||||
logSys.log(logLevel, "Non essential option '%s' not defined in '%s'.", option[1], sec)
|
||||
except ValueError:
|
||||
logSys.warning("Wrong value for '" + option[1] + "' in '" + sec +
|
||||
"'. Using default one: '" + `option[2]` + "'")
|
||||
@@ -133,12 +238,12 @@ class ConfigReader(SafeConfigParserWithIncludes):
|
||||
|
||||
class DefinitionInitConfigReader(ConfigReader):
|
||||
"""Config reader for files with options grouped in [Definition] and
|
||||
[Init] sections.
|
||||
[Init] sections.
|
||||
|
||||
Is a base class for readers of filters and actions, where definitions
|
||||
in jails might provide custom values for options defined in [Init]
|
||||
section.
|
||||
"""
|
||||
Is a base class for readers of filters and actions, where definitions
|
||||
in jails might provide custom values for options defined in [Init]
|
||||
section.
|
||||
"""
|
||||
|
||||
_configOpts = []
|
||||
|
||||
@@ -166,7 +271,9 @@ class DefinitionInitConfigReader(ConfigReader):
|
||||
|
||||
# needed for fail2ban-regex that doesn't need fancy directories
|
||||
def readexplicit(self):
|
||||
return SafeConfigParserWithIncludes.read(self, self._file)
|
||||
if not self._cfg:
|
||||
self._create_unshared(self._file)
|
||||
return SafeConfigParserWithIncludes.read(self._cfg, self._file)
|
||||
|
||||
def getOptions(self, pOpts):
|
||||
self._opts = ConfigReader.getOptions(
|
||||
|
||||
@@ -33,12 +33,20 @@ logSys = getLogger(__name__)
|
||||
|
||||
class Configurator:
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, force_enable=False, share_config=None):
|
||||
self.__settings = dict()
|
||||
self.__streams = dict()
|
||||
self.__fail2ban = Fail2banReader()
|
||||
self.__jails = JailsReader()
|
||||
|
||||
# always share all config readers:
|
||||
if share_config is None:
|
||||
share_config = dict()
|
||||
self.__share_config = share_config
|
||||
self.__fail2ban = Fail2banReader(share_config=share_config)
|
||||
self.__jails = JailsReader(force_enable=force_enable, share_config=share_config)
|
||||
|
||||
def Reload(self):
|
||||
# clear all shared handlers:
|
||||
self.__share_config.clear()
|
||||
|
||||
def setBaseDir(self, folderName):
|
||||
self.__fail2ban.setBaseDir(folderName)
|
||||
self.__jails.setBaseDir(folderName)
|
||||
|
||||
@@ -57,7 +57,7 @@ class CSocket:
|
||||
self.__csock.close()
|
||||
return ret
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def receive(sock):
|
||||
msg = EMPTY_BYTES
|
||||
while msg.rfind(CSocket.END_STRING) == -1:
|
||||
@@ -66,4 +66,3 @@ class CSocket:
|
||||
raise RuntimeError, "socket connection broken"
|
||||
msg = msg + chunk
|
||||
return loads(msg)
|
||||
receive = staticmethod(receive)
|
||||
|
||||
@@ -46,6 +46,7 @@ class Fail2banReader(ConfigReader):
|
||||
def getOptions(self):
|
||||
opts = [["string", "loglevel", "INFO" ],
|
||||
["string", "logtarget", "STDERR"],
|
||||
["string", "syslogsocket", "auto"],
|
||||
["string", "dbfile", "/var/lib/fail2ban/fail2ban.sqlite3"],
|
||||
["int", "dbpurgeage", 86400]]
|
||||
self.__opts = ConfigReader.getOptions(self, "Definition", opts)
|
||||
@@ -57,6 +58,8 @@ class Fail2banReader(ConfigReader):
|
||||
stream.append(["set", "loglevel", self.__opts[opt]])
|
||||
elif opt == "logtarget":
|
||||
stream.append(["set", "logtarget", self.__opts[opt]])
|
||||
elif opt == "syslogsocket":
|
||||
stream.append(["set", "syslogsocket", self.__opts[opt]])
|
||||
elif opt == "dbfile":
|
||||
stream.append(["set", "dbfile", self.__opts[opt]])
|
||||
elif opt == "dbpurgeage":
|
||||
|
||||
@@ -26,7 +26,7 @@ __license__ = "GPL"
|
||||
|
||||
import os, shlex
|
||||
|
||||
from .configreader import ConfigReader, DefinitionInitConfigReader
|
||||
from .configreader import DefinitionInitConfigReader
|
||||
from ..server.action import CommandAction
|
||||
from ..helpers import getLogger
|
||||
|
||||
@@ -40,15 +40,27 @@ class FilterReader(DefinitionInitConfigReader):
|
||||
["string", "failregex", ""],
|
||||
]
|
||||
|
||||
def read(self):
|
||||
return ConfigReader.read(self, os.path.join("filter.d", self._file))
|
||||
def setFile(self, fileName):
|
||||
self.__file = fileName
|
||||
DefinitionInitConfigReader.setFile(self, os.path.join("filter.d", fileName))
|
||||
|
||||
def convert(self):
|
||||
stream = list()
|
||||
def getFile(self):
|
||||
return self.__file
|
||||
|
||||
def getCombined(self):
|
||||
combinedopts = dict(list(self._opts.items()) + list(self._initOpts.items()))
|
||||
if not len(combinedopts):
|
||||
return {}
|
||||
opts = CommandAction.substituteRecursiveTags(combinedopts)
|
||||
if not opts:
|
||||
raise ValueError('recursive tag definitions unable to be resolved')
|
||||
return opts
|
||||
|
||||
def convert(self):
|
||||
stream = list()
|
||||
opts = self.getCombined()
|
||||
if not len(opts):
|
||||
return stream
|
||||
for opt, value in opts.iteritems():
|
||||
if opt == "failregex":
|
||||
for regex in value.split('\n'):
|
||||
@@ -59,7 +71,7 @@ class FilterReader(DefinitionInitConfigReader):
|
||||
for regex in value.split('\n'):
|
||||
# Do not send a command if the rule is empty.
|
||||
if regex != '':
|
||||
stream.append(["set", self._jailName, "addignoreregex", regex])
|
||||
stream.append(["set", self._jailName, "addignoreregex", regex])
|
||||
if self._initOpts:
|
||||
if 'maxlines' in self._initOpts:
|
||||
# We warn when multiline regex is used without maxlines > 1
|
||||
|
||||
@@ -27,7 +27,7 @@ __license__ = "GPL"
|
||||
import re, glob, os.path
|
||||
import json
|
||||
|
||||
from .configreader import ConfigReader
|
||||
from .configreader import ConfigReaderUnshared, ConfigReader
|
||||
from .filterreader import FilterReader
|
||||
from .actionreader import ActionReader
|
||||
from ..helpers import getLogger
|
||||
@@ -87,6 +87,8 @@ class JailReader(ConfigReader):
|
||||
return pathList
|
||||
|
||||
def getOptions(self):
|
||||
opts1st = [["bool", "enabled", False],
|
||||
["string", "filter", ""]]
|
||||
opts = [["bool", "enabled", False],
|
||||
["string", "logpath", None],
|
||||
["string", "logencoding", None],
|
||||
@@ -101,7 +103,9 @@ class JailReader(ConfigReader):
|
||||
["string", "ignoreip", None],
|
||||
["string", "filter", ""],
|
||||
["string", "action", ""]]
|
||||
self.__opts = ConfigReader.getOptions(self, self.__name, opts)
|
||||
|
||||
# Read first options only needed for merge defaults ('known/...' from filter):
|
||||
self.__opts = ConfigReader.getOptions(self, self.__name, opts1st)
|
||||
if not self.__opts:
|
||||
return False
|
||||
|
||||
@@ -111,16 +115,26 @@ class JailReader(ConfigReader):
|
||||
filterName, filterOpt = JailReader.extractOptions(
|
||||
self.__opts["filter"])
|
||||
self.__filter = FilterReader(
|
||||
filterName, self.__name, filterOpt, basedir=self.getBaseDir())
|
||||
filterName, self.__name, filterOpt, share_config=self.share_config, basedir=self.getBaseDir())
|
||||
ret = self.__filter.read()
|
||||
if ret:
|
||||
self.__filter.getOptions(self.__opts)
|
||||
else:
|
||||
# merge options from filter as 'known/...':
|
||||
self.__filter.getOptions(self.__opts)
|
||||
ConfigReader.merge_section(self, self.__name, self.__filter.getCombined(), 'known/')
|
||||
if not ret:
|
||||
logSys.error("Unable to read the filter")
|
||||
return False
|
||||
else:
|
||||
self.__filter = None
|
||||
logSys.warning("No filter set for jail %s" % self.__name)
|
||||
|
||||
# Read second all options (so variables like %(known/param) can be interpolated):
|
||||
self.__opts = ConfigReader.getOptions(self, self.__name, opts)
|
||||
if not self.__opts:
|
||||
return False
|
||||
|
||||
# cumulate filter options again (ignore given in jail):
|
||||
if self.__filter:
|
||||
self.__filter.getOptions(self.__opts)
|
||||
|
||||
# Read action
|
||||
for act in self.__opts["action"].split('\n'):
|
||||
@@ -141,7 +155,7 @@ class JailReader(ConfigReader):
|
||||
else:
|
||||
action = ActionReader(
|
||||
actName, self.__name, actOpt,
|
||||
basedir=self.getBaseDir())
|
||||
share_config=self.share_config, basedir=self.getBaseDir())
|
||||
ret = action.read()
|
||||
if ret:
|
||||
action.getOptions(self.__opts)
|
||||
@@ -202,7 +216,10 @@ class JailReader(ConfigReader):
|
||||
elif opt == "usedns":
|
||||
stream.append(["set", self.__name, "usedns", self.__opts[opt]])
|
||||
elif opt == "failregex":
|
||||
stream.append(["set", self.__name, "addfailregex", self.__opts[opt]])
|
||||
for regex in self.__opts[opt].split('\n'):
|
||||
# Do not send a command if the rule is empty.
|
||||
if regex != '':
|
||||
stream.append(["set", self.__name, "addfailregex", regex])
|
||||
elif opt == "ignorecommand":
|
||||
stream.append(["set", self.__name, "ignorecommand", self.__opts[opt]])
|
||||
elif opt == "ignoreregex":
|
||||
@@ -213,14 +230,14 @@ class JailReader(ConfigReader):
|
||||
if self.__filter:
|
||||
stream.extend(self.__filter.convert())
|
||||
for action in self.__actions:
|
||||
if isinstance(action, ConfigReader):
|
||||
if isinstance(action, (ConfigReaderUnshared, ConfigReader)):
|
||||
stream.extend(action.convert())
|
||||
else:
|
||||
stream.append(action)
|
||||
stream.insert(0, ["add", self.__name, backend])
|
||||
return stream
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def extractOptions(option):
|
||||
match = JailReader.optionCRE.match(option)
|
||||
if not match:
|
||||
@@ -235,4 +252,3 @@ class JailReader(ConfigReader):
|
||||
val for val in optmatch.group(2,3,4) if val is not None][0]
|
||||
option_opts[opt.strip()] = value.strip()
|
||||
return option_name, option_opts
|
||||
extractOptions = staticmethod(extractOptions)
|
||||
|
||||
@@ -68,9 +68,10 @@ class JailsReader(ConfigReader):
|
||||
for sec in sections:
|
||||
if sec == 'INCLUDES':
|
||||
continue
|
||||
jail = JailReader(sec, basedir=self.getBaseDir(),
|
||||
force_enable=self.__force_enable)
|
||||
jail.read()
|
||||
# use the cfg_share for filter/action caching and the same config for all
|
||||
# jails (use_config=...), therefore don't read it here:
|
||||
jail = JailReader(sec, force_enable=self.__force_enable,
|
||||
share_config=self.share_config, use_config=self._cfg)
|
||||
ret = jail.getOptions()
|
||||
if ret:
|
||||
if jail.isEnabled():
|
||||
|
||||
@@ -44,6 +44,8 @@ protocol = [
|
||||
["get loglevel", "gets the logging level"],
|
||||
["set logtarget <TARGET>", "sets logging target to <TARGET>. Can be STDOUT, STDERR, SYSLOG or a file"],
|
||||
["get logtarget", "gets logging target"],
|
||||
["set syslogsocket auto|<SOCKET>", "sets the syslog socket path to auto or <SOCKET>. Only used if logtarget is SYSLOG"],
|
||||
["get syslogsocket", "gets syslog socket path"],
|
||||
["flushlogs", "flushes the logtarget if a file and reopens it. For log rotation."],
|
||||
['', "DATABASE", ""],
|
||||
["set dbfile <FILE>", "set the location of fail2ban persistent datastore. Set to \"None\" to disable"],
|
||||
@@ -54,7 +56,7 @@ protocol = [
|
||||
["add <JAIL> <BACKEND>", "creates <JAIL> using <BACKEND>"],
|
||||
["start <JAIL>", "starts the jail <JAIL>"],
|
||||
["stop <JAIL>", "stops the jail <JAIL>. The jail is removed"],
|
||||
["status <JAIL>", "gets the current status of <JAIL>"],
|
||||
["status <JAIL> [FLAVOR]", "gets the current status of <JAIL>, with optional flavor or extended info"],
|
||||
['', "JAIL CONFIGURATION", ""],
|
||||
["set <JAIL> idle on|off", "sets the idle state of <JAIL>"],
|
||||
["set <JAIL> addignoreip <IP>", "adds <IP> to the ignore list of <JAIL>"],
|
||||
|
||||
+38
-29
@@ -362,6 +362,7 @@ class CommandAction(ActionBase):
|
||||
@classmethod
|
||||
def substituteRecursiveTags(cls, tags):
|
||||
"""Sort out tag definitions within other tags.
|
||||
Since v.0.9.2 supports embedded interpolation (see test cases for examples).
|
||||
|
||||
so: becomes:
|
||||
a = 3 a = 3
|
||||
@@ -378,38 +379,46 @@ class CommandAction(ActionBase):
|
||||
Dictionary of tags(keys) and their values, with tags
|
||||
within the values recursively replaced.
|
||||
"""
|
||||
t = re.compile(r'<([^ >]+)>')
|
||||
for tag in tags.iterkeys():
|
||||
if tag in cls._escapedTags:
|
||||
# Escaped so won't match
|
||||
continue
|
||||
value = str(tags[tag])
|
||||
m = t.search(value)
|
||||
done = []
|
||||
#logSys.log(5, 'TAG: %s, value: %s' % (tag, value))
|
||||
while m:
|
||||
found_tag = m.group(1)
|
||||
#logSys.log(5, 'found: %s' % found_tag)
|
||||
if found_tag == tag or found_tag in done:
|
||||
# recursive definitions are bad
|
||||
#logSys.log(5, 'recursion fail tag: %s value: %s' % (tag, value) )
|
||||
return False
|
||||
elif found_tag in cls._escapedTags:
|
||||
t = re.compile(r'<([^ <>]+)>')
|
||||
# repeat substitution while embedded-recursive (repFlag is True)
|
||||
while True:
|
||||
repFlag = False
|
||||
# substitute each value:
|
||||
for tag in tags.iterkeys():
|
||||
if tag in cls._escapedTags:
|
||||
# Escaped so won't match
|
||||
continue
|
||||
else:
|
||||
if tags.has_key(found_tag):
|
||||
value = value.replace('<%s>' % found_tag , tags[found_tag])
|
||||
#logSys.log(5, 'value now: %s' % value)
|
||||
done.append(found_tag)
|
||||
m = t.search(value, m.start())
|
||||
else:
|
||||
# Missing tags are ok so we just continue on searching.
|
||||
# cInfo can contain aInfo elements like <HOST> and valid shell
|
||||
value = str(tags[tag])
|
||||
# search and replace all tags within value, that can be interpolated using other tags:
|
||||
m = t.search(value)
|
||||
done = []
|
||||
#logSys.log(5, 'TAG: %s, value: %s' % (tag, value))
|
||||
while m:
|
||||
found_tag = m.group(1)
|
||||
#logSys.log(5, 'found: %s' % found_tag)
|
||||
if found_tag == tag or found_tag in done:
|
||||
# recursive definitions are bad
|
||||
#logSys.log(5, 'recursion fail tag: %s value: %s' % (tag, value) )
|
||||
return False
|
||||
if found_tag in cls._escapedTags or not tags.has_key(found_tag):
|
||||
# Escaped or missing tags - just continue on searching after end of match
|
||||
# Missing tags are ok - cInfo can contain aInfo elements like <HOST> and valid shell
|
||||
# constructs like <STDIN>.
|
||||
m = t.search(value, m.start() + 1)
|
||||
#logSys.log(5, 'TAG: %s, newvalue: %s' % (tag, value))
|
||||
tags[tag] = value
|
||||
m = t.search(value, m.end())
|
||||
continue
|
||||
value = value.replace('<%s>' % found_tag , tags[found_tag])
|
||||
#logSys.log(5, 'value now: %s' % value)
|
||||
done.append(found_tag)
|
||||
m = t.search(value, m.start())
|
||||
#logSys.log(5, 'TAG: %s, newvalue: %s' % (tag, value))
|
||||
# was substituted?
|
||||
if tags[tag] != value:
|
||||
# check still contains any tag - should be repeated (possible embedded-recursive substitution):
|
||||
if t.search(value):
|
||||
repFlag = True
|
||||
tags[tag] = value
|
||||
if not repFlag:
|
||||
break
|
||||
return tags
|
||||
|
||||
@staticmethod
|
||||
|
||||
+59
-12
@@ -243,6 +243,45 @@ class Actions(JailThread, Mapping):
|
||||
logSys.debug(self._jail.name + ": action terminated")
|
||||
return True
|
||||
|
||||
def __getBansMerged(self, mi, overalljails=False):
|
||||
"""Gets bans merged once, a helper for lambda(s), prevents stop of executing action by any exception inside.
|
||||
|
||||
This function never returns None for ainfo lambdas - always a ticket (merged or single one)
|
||||
and prevents any errors through merging (to guarantee ban actions will be executed).
|
||||
[TODO] move merging to observer - here we could wait for merge and read already merged info from a database
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mi : dict
|
||||
merge info, initial for lambda should contains {ip, ticket}
|
||||
overalljails : bool
|
||||
switch to get a merged bans :
|
||||
False - (default) bans merged for current jail only
|
||||
True - bans merged for all jails of current ip address
|
||||
|
||||
Returns
|
||||
-------
|
||||
BanTicket
|
||||
merged or self ticket only
|
||||
"""
|
||||
idx = 'all' if overalljails else 'jail'
|
||||
if idx in mi:
|
||||
return mi[idx] if mi[idx] is not None else mi['ticket']
|
||||
try:
|
||||
jail=self._jail
|
||||
ip=mi['ip']
|
||||
mi[idx] = None
|
||||
if overalljails:
|
||||
mi[idx] = jail.database.getBansMerged(ip=ip)
|
||||
else:
|
||||
mi[idx] = jail.database.getBansMerged(ip=ip, jail=jail)
|
||||
except Exception as e:
|
||||
logSys.error(
|
||||
"Failed to get %s bans merged, jail '%s': %s",
|
||||
idx, jail.name, e,
|
||||
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||
return mi[idx] if mi[idx] is not None else mi['ticket']
|
||||
|
||||
def __checkBan(self):
|
||||
"""Check for IP address to ban.
|
||||
|
||||
@@ -264,14 +303,12 @@ class Actions(JailThread, Mapping):
|
||||
aInfo["time"] = bTicket.getTime()
|
||||
aInfo["matches"] = "\n".join(bTicket.getMatches())
|
||||
if self._jail.database is not None:
|
||||
aInfo["ipmatches"] = lambda jail=self._jail: "\n".join(
|
||||
jail.database.getBansMerged(ip=ip).getMatches())
|
||||
aInfo["ipjailmatches"] = lambda jail=self._jail: "\n".join(
|
||||
jail.database.getBansMerged(ip=ip, jail=jail).getMatches())
|
||||
aInfo["ipfailures"] = lambda jail=self._jail: \
|
||||
jail.database.getBansMerged(ip=ip).getAttempt()
|
||||
aInfo["ipjailfailures"] = lambda jail=self._jail: \
|
||||
jail.database.getBansMerged(ip=ip, jail=jail).getAttempt()
|
||||
mi4ip = lambda overalljails=False, self=self, \
|
||||
mi={'ip':ip, 'ticket':bTicket}: self.__getBansMerged(mi, overalljails)
|
||||
aInfo["ipmatches"] = lambda: "\n".join(mi4ip(True).getMatches())
|
||||
aInfo["ipjailmatches"] = lambda: "\n".join(mi4ip().getMatches())
|
||||
aInfo["ipfailures"] = lambda: mi4ip(True).getAttempt()
|
||||
aInfo["ipjailfailures"] = lambda: mi4ip().getAttempt()
|
||||
if self.__banManager.addBanTicket(bTicket):
|
||||
logSys.notice("[%s] Ban %s" % (self._jail.name, aInfo["ip"]))
|
||||
for name, action in self._actions.iteritems():
|
||||
@@ -333,11 +370,21 @@ class Actions(JailThread, Mapping):
|
||||
self._jail.name, name, aInfo, e,
|
||||
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
"""Status of active bans, and total ban counts.
|
||||
def status(self, flavor="basic"):
|
||||
"""Status of current and total ban counts and current banned IP list.
|
||||
"""
|
||||
ret = [("Currently banned", self.__banManager.size()),
|
||||
# TODO: Allow this list to be printed as 'status' output
|
||||
supported_flavors = ["basic", "cymru"]
|
||||
if flavor is None or flavor not in supported_flavors:
|
||||
logSys.warning("Unsupported extended jail status flavor %r. Supported: %s" % (flavor, supported_flavors))
|
||||
# Always print this information (basic)
|
||||
ret = [("Currently banned", self.__banManager.size()),
|
||||
("Total banned", self.__banManager.getBanTotal()),
|
||||
("Banned IP list", self.__banManager.getBanList())]
|
||||
if flavor == "cymru":
|
||||
cymru_info = self.__banManager.getBanListExtendedCymruInfo()
|
||||
ret += \
|
||||
[("Banned ASN list", self.__banManager.geBanListExtendedASN(cymru_info)),
|
||||
("Banned Country list", self.__banManager.geBanListExtendedCountry(cymru_info)),
|
||||
("Banned RIR list", self.__banManager.geBanListExtendedRIR(cymru_info))]
|
||||
return ret
|
||||
|
||||
@@ -149,12 +149,12 @@ class AsyncServer(asyncore.dispatcher):
|
||||
self.__init = True
|
||||
# TODO Add try..catch
|
||||
# There's a bug report for Python 2.6/3.0 that use_poll=True yields some 2.5 incompatibilities:
|
||||
if sys.version_info >= (2, 6): # if python 2.6 or greater...
|
||||
logSys.debug("Detected Python 2.6 or greater. asyncore.loop() not using poll")
|
||||
asyncore.loop(use_poll = False) # fixes the "Unexpected communication problem" issue on Python 2.6 and 3.0
|
||||
else: # pragma: no cover
|
||||
logSys.debug("NOT Python 2.6/3.* - asyncore.loop() using poll")
|
||||
asyncore.loop(use_poll = True)
|
||||
if (sys.version_info >= (2, 7) and sys.version_info < (2, 8)) \
|
||||
or (sys.version_info >= (3, 4)): # if python 2.7 ...
|
||||
logSys.debug("Detected Python 2.7. asyncore.loop() using poll")
|
||||
asyncore.loop(use_poll=True) # workaround for the "Bad file descriptor" issue on Python 2.7, gh-161
|
||||
else:
|
||||
asyncore.loop(use_poll=False) # fixes the "Unexpected communication problem" issue on Python 2.6 and 3.0
|
||||
|
||||
##
|
||||
# Stops the communication server.
|
||||
@@ -175,12 +175,11 @@ class AsyncServer(asyncore.dispatcher):
|
||||
|
||||
# @param sock: socket file.
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def __markCloseOnExec(sock):
|
||||
fd = sock.fileno()
|
||||
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
|
||||
fcntl.fcntl(fd, fcntl.F_SETFD, flags|fcntl.FD_CLOEXEC)
|
||||
__markCloseOnExec = staticmethod(__markCloseOnExec)
|
||||
|
||||
##
|
||||
# AsyncServerException is used to wrap communication exceptions.
|
||||
|
||||
@@ -118,6 +118,124 @@ class BanManager:
|
||||
finally:
|
||||
self.__lock.release()
|
||||
|
||||
##
|
||||
# Returns normalized value
|
||||
#
|
||||
# @return value or "unknown" if value is None or empty string
|
||||
|
||||
@staticmethod
|
||||
def handleBlankResult(value):
|
||||
if value is None or len(value) == 0:
|
||||
return "unknown"
|
||||
else:
|
||||
return value
|
||||
|
||||
##
|
||||
# Returns Cymru DNS query information
|
||||
#
|
||||
# @return {"asn": [], "country": [], "rir": []} dict for self.__banList IPs
|
||||
|
||||
def getBanListExtendedCymruInfo(self):
|
||||
return_dict = {"asn": [], "country": [], "rir": []}
|
||||
try:
|
||||
import dns.exception
|
||||
import dns.resolver
|
||||
except ImportError:
|
||||
logSys.error("dnspython package is required but could not be imported")
|
||||
return_dict["asn"].append("error")
|
||||
return_dict["country"].append("error")
|
||||
return_dict["rir"].append("error")
|
||||
return return_dict
|
||||
self.__lock.acquire()
|
||||
try:
|
||||
for banData in self.__banList:
|
||||
ip = banData.getIP()
|
||||
# Reference: http://www.team-cymru.org/Services/ip-to-asn.html#dns
|
||||
# TODO: IPv6 compatibility
|
||||
reversed_ip = ".".join(reversed(ip.split(".")))
|
||||
question = "%s.origin.asn.cymru.com" % reversed_ip
|
||||
try:
|
||||
answers = dns.resolver.query(question, "TXT")
|
||||
for rdata in answers:
|
||||
asn, net, country, rir, changed =\
|
||||
[answer.strip("'\" ") for answer in rdata.to_text().split("|")]
|
||||
asn = self.handleBlankResult(asn)
|
||||
country = self.handleBlankResult(country)
|
||||
rir = self.handleBlankResult(rir)
|
||||
return_dict["asn"].append(self.handleBlankResult(asn))
|
||||
return_dict["country"].append(self.handleBlankResult(country))
|
||||
return_dict["rir"].append(self.handleBlankResult(rir))
|
||||
except dns.resolver.NXDOMAIN:
|
||||
return_dict["asn"].append("nxdomain")
|
||||
return_dict["country"].append("nxdomain")
|
||||
return_dict["rir"].append("nxdomain")
|
||||
except dns.exception.DNSException as dnse:
|
||||
logSys.error("Unhandled DNSException querying Cymru for %s TXT" % question)
|
||||
logSys.exception(dnse)
|
||||
except Exception as e:
|
||||
logSys.error("Unhandled Exception querying Cymru for %s TXT" % question)
|
||||
logSys.exception(e)
|
||||
except Exception as e:
|
||||
logSys.error("Failure looking up extended Cymru info")
|
||||
logSys.exception(e)
|
||||
finally:
|
||||
self.__lock.release()
|
||||
return return_dict
|
||||
|
||||
##
|
||||
# Returns list of Banned ASNs from Cymru info
|
||||
#
|
||||
# Use getBanListExtendedCymruInfo() to provide cymru_info
|
||||
#
|
||||
# @return list of Banned ASNs
|
||||
|
||||
def geBanListExtendedASN(self, cymru_info):
|
||||
self.__lock.acquire()
|
||||
try:
|
||||
return [asn for asn in cymru_info["asn"]]
|
||||
except Exception as e:
|
||||
logSys.error("Failed to lookup ASN")
|
||||
logSys.exception(e)
|
||||
return []
|
||||
finally:
|
||||
self.__lock.release()
|
||||
|
||||
##
|
||||
# Returns list of Banned Countries from Cymru info
|
||||
#
|
||||
# Use getBanListExtendedCymruInfo() to provide cymru_info
|
||||
#
|
||||
# @return list of Banned Countries
|
||||
|
||||
def geBanListExtendedCountry(self, cymru_info):
|
||||
self.__lock.acquire()
|
||||
try:
|
||||
return [country for country in cymru_info["country"]]
|
||||
except Exception as e:
|
||||
logSys.error("Failed to lookup Country")
|
||||
logSys.exception(e)
|
||||
return []
|
||||
finally:
|
||||
self.__lock.release()
|
||||
|
||||
##
|
||||
# Returns list of Banned RIRs from Cymru info
|
||||
#
|
||||
# Use getBanListExtendedCymruInfo() to provide cymru_info
|
||||
#
|
||||
# @return list of Banned RIRs
|
||||
|
||||
def geBanListExtendedRIR(self, cymru_info):
|
||||
self.__lock.acquire()
|
||||
try:
|
||||
return [rir for rir in cymru_info["rir"]]
|
||||
except Exception as e:
|
||||
logSys.error("Failed to lookup RIR")
|
||||
logSys.exception(e)
|
||||
return []
|
||||
finally:
|
||||
self.__lock.release()
|
||||
|
||||
##
|
||||
# Create a ban ticket.
|
||||
#
|
||||
@@ -126,7 +244,7 @@ class BanManager:
|
||||
# @param ticket the FailTicket
|
||||
# @return a BanTicket
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def createBanTicket(ticket):
|
||||
ip = ticket.getIP()
|
||||
#lastTime = ticket.getTime()
|
||||
@@ -134,7 +252,6 @@ class BanManager:
|
||||
banTicket = BanTicket(ip, lastTime, ticket.getMatches())
|
||||
banTicket.setAttempt(ticket.getAttempt())
|
||||
return banTicket
|
||||
createBanTicket = staticmethod(createBanTicket)
|
||||
|
||||
##
|
||||
# Add a ban ticket.
|
||||
|
||||
+85
-46
@@ -27,7 +27,7 @@ import sqlite3
|
||||
import json
|
||||
import locale
|
||||
from functools import wraps
|
||||
from threading import Lock
|
||||
from threading import RLock
|
||||
|
||||
from .mytime import MyTime
|
||||
from .ticket import FailTicket
|
||||
@@ -37,17 +37,51 @@ from ..helpers import getLogger
|
||||
logSys = getLogger(__name__)
|
||||
|
||||
if sys.version_info >= (3,):
|
||||
sqlite3.register_adapter(
|
||||
dict,
|
||||
lambda x: json.dumps(x, ensure_ascii=False).encode(
|
||||
locale.getpreferredencoding(), 'replace'))
|
||||
sqlite3.register_converter(
|
||||
"JSON",
|
||||
lambda x: json.loads(x.decode(
|
||||
locale.getpreferredencoding(), 'replace')))
|
||||
def _json_dumps_safe(x):
|
||||
try:
|
||||
x = json.dumps(x, ensure_ascii=False).encode(
|
||||
locale.getpreferredencoding(), 'replace')
|
||||
except Exception, e: # pragma: no cover
|
||||
logSys.error('json dumps failed: %s', e)
|
||||
x = '{}'
|
||||
return x
|
||||
def _json_loads_safe(x):
|
||||
try:
|
||||
x = json.loads(x.decode(
|
||||
locale.getpreferredencoding(), 'replace'))
|
||||
except Exception, e: # pragma: no cover
|
||||
logSys.error('json loads failed: %s', e)
|
||||
x = {}
|
||||
return x
|
||||
else:
|
||||
sqlite3.register_adapter(dict, json.dumps)
|
||||
sqlite3.register_converter("JSON", json.loads)
|
||||
def _normalize(x):
|
||||
if isinstance(x, dict):
|
||||
return dict((_normalize(k), _normalize(v)) for k, v in x.iteritems())
|
||||
elif isinstance(x, list):
|
||||
return [_normalize(element) for element in x]
|
||||
elif isinstance(x, unicode):
|
||||
return x.encode(locale.getpreferredencoding())
|
||||
else:
|
||||
return x
|
||||
def _json_dumps_safe(x):
|
||||
try:
|
||||
x = json.dumps(_normalize(x), ensure_ascii=False).decode(
|
||||
locale.getpreferredencoding(), 'replace')
|
||||
except Exception, e: # pragma: no cover
|
||||
logSys.error('json dumps failed: %s', e)
|
||||
x = '{}'
|
||||
return x
|
||||
def _json_loads_safe(x):
|
||||
try:
|
||||
x = _normalize(json.loads(x.decode(
|
||||
locale.getpreferredencoding(), 'replace')))
|
||||
except Exception, e: # pragma: no cover
|
||||
logSys.error('json loads failed: %s', e)
|
||||
x = {}
|
||||
return x
|
||||
|
||||
sqlite3.register_adapter(dict, _json_dumps_safe)
|
||||
sqlite3.register_converter("JSON", _json_loads_safe)
|
||||
|
||||
def commitandrollback(f):
|
||||
@wraps(f)
|
||||
@@ -123,7 +157,7 @@ class Fail2BanDb(object):
|
||||
|
||||
def __init__(self, filename, purgeAge=24*60*60):
|
||||
try:
|
||||
self._lock = Lock()
|
||||
self._lock = RLock()
|
||||
self._db = sqlite3.connect(
|
||||
filename, check_same_thread=False,
|
||||
detect_types=sqlite3.PARSE_DECLTYPES)
|
||||
@@ -365,12 +399,16 @@ class Fail2BanDb(object):
|
||||
del self._bansMergedCache[(ticket.getIP(), jail)]
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
del self._bansMergedCache[(ticket.getIP(), None)]
|
||||
except KeyError:
|
||||
pass
|
||||
#TODO: Implement data parts once arbitrary match keys completed
|
||||
cur.execute(
|
||||
"INSERT INTO bans(jail, ip, timeofban, data) VALUES(?, ?, ?, ?)",
|
||||
(jail.name, ticket.getIP(), int(round(ticket.getTime())),
|
||||
{"matches": ticket.getMatches(),
|
||||
"failures": ticket.getAttempt()}))
|
||||
"failures": ticket.getAttempt()}))
|
||||
|
||||
@commitandrollback
|
||||
def delBan(self, cur, jail, ticket):
|
||||
@@ -427,8 +465,8 @@ class Fail2BanDb(object):
|
||||
tickets = []
|
||||
for ip, timeofban, data in self._getBans(**kwargs):
|
||||
#TODO: Implement data parts once arbitrary match keys completed
|
||||
tickets.append(FailTicket(ip, timeofban, data['matches']))
|
||||
tickets[-1].setAttempt(data['failures'])
|
||||
tickets.append(FailTicket(ip, timeofban, data.get('matches')))
|
||||
tickets[-1].setAttempt(data.get('failures', 1))
|
||||
return tickets
|
||||
|
||||
def getBansMerged(self, ip=None, jail=None, bantime=None):
|
||||
@@ -455,40 +493,41 @@ class Fail2BanDb(object):
|
||||
in a list. When `ip` argument passed, a single `Ticket` is
|
||||
returned.
|
||||
"""
|
||||
cacheKey = None
|
||||
if bantime is None or bantime < 0:
|
||||
cacheKey = (ip, jail)
|
||||
if cacheKey in self._bansMergedCache:
|
||||
return self._bansMergedCache[cacheKey]
|
||||
with self._lock:
|
||||
cacheKey = None
|
||||
if bantime is None or bantime < 0:
|
||||
cacheKey = (ip, jail)
|
||||
if cacheKey in self._bansMergedCache:
|
||||
return self._bansMergedCache[cacheKey]
|
||||
|
||||
tickets = []
|
||||
ticket = None
|
||||
tickets = []
|
||||
ticket = None
|
||||
|
||||
results = list(self._getBans(ip=ip, jail=jail, bantime=bantime))
|
||||
if results:
|
||||
prev_banip = results[0][0]
|
||||
matches = []
|
||||
failures = 0
|
||||
for banip, timeofban, data in results:
|
||||
#TODO: Implement data parts once arbitrary match keys completed
|
||||
if banip != prev_banip:
|
||||
ticket = FailTicket(prev_banip, prev_timeofban, matches)
|
||||
ticket.setAttempt(failures)
|
||||
tickets.append(ticket)
|
||||
# Reset variables
|
||||
prev_banip = banip
|
||||
matches = []
|
||||
failures = 0
|
||||
matches.extend(data['matches'])
|
||||
failures += data['failures']
|
||||
prev_timeofban = timeofban
|
||||
ticket = FailTicket(banip, prev_timeofban, matches)
|
||||
ticket.setAttempt(failures)
|
||||
tickets.append(ticket)
|
||||
results = list(self._getBans(ip=ip, jail=jail, bantime=bantime))
|
||||
if results:
|
||||
prev_banip = results[0][0]
|
||||
matches = []
|
||||
failures = 0
|
||||
for banip, timeofban, data in results:
|
||||
#TODO: Implement data parts once arbitrary match keys completed
|
||||
if banip != prev_banip:
|
||||
ticket = FailTicket(prev_banip, prev_timeofban, matches)
|
||||
ticket.setAttempt(failures)
|
||||
tickets.append(ticket)
|
||||
# Reset variables
|
||||
prev_banip = banip
|
||||
matches = []
|
||||
failures = 0
|
||||
matches.extend(data.get('matches', []))
|
||||
failures += data.get('failures', 1)
|
||||
prev_timeofban = timeofban
|
||||
ticket = FailTicket(banip, prev_timeofban, matches)
|
||||
ticket.setAttempt(failures)
|
||||
tickets.append(ticket)
|
||||
|
||||
if cacheKey:
|
||||
self._bansMergedCache[cacheKey] = tickets if ip is None else ticket
|
||||
return tickets if ip is None else ticket
|
||||
if cacheKey:
|
||||
self._bansMergedCache[cacheKey] = tickets if ip is None else ticket
|
||||
return tickets if ip is None else ticket
|
||||
|
||||
@commitandrollback
|
||||
def purge(self, cur):
|
||||
|
||||
+46
-42
@@ -338,6 +338,10 @@ class Filter(JailThread):
|
||||
logSys.debug("Remove " + ip + " from ignore list")
|
||||
self.__ignoreIpList.remove(ip)
|
||||
|
||||
def logIgnoreIp(self, ip, log_ignore, ignore_source="unknown source"):
|
||||
if log_ignore:
|
||||
logSys.info("[%s] Ignore %s by %s" % (self.jail.name, ip, ignore_source))
|
||||
|
||||
def getIgnoreIP(self):
|
||||
return self.__ignoreIpList
|
||||
|
||||
@@ -349,7 +353,7 @@ class Filter(JailThread):
|
||||
# @param ip IP address
|
||||
# @return True if IP address is in ignore list
|
||||
|
||||
def inIgnoreIPList(self, ip):
|
||||
def inIgnoreIPList(self, ip, log_ignore=False):
|
||||
for i in self.__ignoreIpList:
|
||||
# An empty string is always false
|
||||
if i == "":
|
||||
@@ -363,22 +367,26 @@ class Filter(JailThread):
|
||||
"(?<=b)1+", bin(DNSUtils.addr2bin(s[1]))).group())
|
||||
s[1] = long(s[1])
|
||||
try:
|
||||
a = DNSUtils.cidr(s[0], s[1])
|
||||
b = DNSUtils.cidr(ip, s[1])
|
||||
a = DNSUtils.addr2bin(s[0], cidr=s[1])
|
||||
b = DNSUtils.addr2bin(ip, cidr=s[1])
|
||||
except Exception:
|
||||
# Check if IP in DNS
|
||||
ips = DNSUtils.dnsToIp(i)
|
||||
if ip in ips:
|
||||
self.logIgnoreIp(ip, log_ignore, ignore_source="dns")
|
||||
return True
|
||||
else:
|
||||
continue
|
||||
if a == b:
|
||||
self.logIgnoreIp(ip, log_ignore, ignore_source="ip")
|
||||
return True
|
||||
|
||||
if self.__ignoreCommand:
|
||||
command = CommandAction.replaceTag(self.__ignoreCommand, { 'ip': ip } )
|
||||
logSys.debug('ignore command: ' + command)
|
||||
return CommandAction.executeCmd(command)
|
||||
ret_ignore = CommandAction.executeCmd(command)
|
||||
self.logIgnoreIp(ip, log_ignore and ret_ignore, ignore_source="command")
|
||||
return ret_ignore
|
||||
|
||||
return False
|
||||
|
||||
@@ -418,8 +426,7 @@ class Filter(JailThread):
|
||||
logSys.debug("Ignore line since time %s < %s - %s"
|
||||
% (unixTime, MyTime.time(), self.getFindTime()))
|
||||
break
|
||||
if self.inIgnoreIPList(ip):
|
||||
logSys.info("[%s] Ignore %s" % (self.jail.name, ip))
|
||||
if self.inIgnoreIPList(ip, log_ignore=True):
|
||||
continue
|
||||
logSys.info("[%s] Found %s" % (self.jail.name, ip))
|
||||
## print "D: Adding a ticket for %s" % ((ip, unixTime, [line]),)
|
||||
@@ -529,8 +536,7 @@ class Filter(JailThread):
|
||||
logSys.error(e)
|
||||
return failList
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
def status(self, flavor="basic"):
|
||||
"""Status of failures detected by filter.
|
||||
"""
|
||||
ret = [("Currently failed", self.failManager.size()),
|
||||
@@ -686,11 +692,10 @@ class FileFilter(Filter):
|
||||
db.updateLog(self.jail, container)
|
||||
return True
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
def status(self, flavor="basic"):
|
||||
"""Status of Filter plus files being monitored.
|
||||
"""
|
||||
ret = super(FileFilter, self).status
|
||||
ret = super(FileFilter, self).status(flavor=flavor)
|
||||
path = [m.getFileName() for m in self.getLogPath()]
|
||||
ret.append(("File list", path))
|
||||
return ret
|
||||
@@ -792,11 +797,13 @@ class FileContainer:
|
||||
line = line.decode(self.getEncoding(), 'strict')
|
||||
except UnicodeDecodeError:
|
||||
logSys.warning(
|
||||
"Error decoding line from '%s' with '%s'. Continuing "
|
||||
"Error decoding line from '%s' with '%s'."
|
||||
" Consider setting logencoding=utf-8 (or another appropriate"
|
||||
" encoding) for this jail. Continuing"
|
||||
" to process line ignoring invalid characters: %r" %
|
||||
(self.getFileName(), self.getEncoding(), line))
|
||||
if sys.version_info >= (3,): # In python3, must be decoded
|
||||
line = line.decode(self.getEncoding(), 'ignore')
|
||||
# decode with replacing error chars:
|
||||
line = line.decode(self.getEncoding(), 'replace')
|
||||
return line
|
||||
|
||||
def close(self):
|
||||
@@ -838,7 +845,7 @@ class DNSUtils:
|
||||
|
||||
IP_CRE = re.compile("^(?:\d{1,3}\.){3}\d{1,3}$")
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def dnsToIp(dns):
|
||||
""" Convert a DNS into an IP address using the Python socket module.
|
||||
Thanks to Kevin Drapel.
|
||||
@@ -853,9 +860,16 @@ class DNSUtils:
|
||||
logSys.warning("Socket error raised trying to resolve hostname %s: %s"
|
||||
% (dns, e))
|
||||
return list()
|
||||
dnsToIp = staticmethod(dnsToIp)
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def ipToName(ip):
|
||||
try:
|
||||
return socket.gethostbyaddr(ip)[0]
|
||||
except socket.error, e:
|
||||
logSys.debug("Unable to find a name for the IP %s: %s" % (ip, e))
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def searchIP(text):
|
||||
""" Search if an IP address if directly available and return
|
||||
it.
|
||||
@@ -865,9 +879,8 @@ class DNSUtils:
|
||||
return match
|
||||
else:
|
||||
return None
|
||||
searchIP = staticmethod(searchIP)
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def isValidIP(string):
|
||||
""" Return true if str is a valid IP
|
||||
"""
|
||||
@@ -877,9 +890,8 @@ class DNSUtils:
|
||||
return True
|
||||
except socket.error:
|
||||
return False
|
||||
isValidIP = staticmethod(isValidIP)
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def textToIp(text, useDns):
|
||||
""" Return the IP of DNS found in a given text.
|
||||
"""
|
||||
@@ -901,28 +913,20 @@ class DNSUtils:
|
||||
text, ipList)
|
||||
|
||||
return ipList
|
||||
textToIp = staticmethod(textToIp)
|
||||
|
||||
#@staticmethod
|
||||
def cidr(i, n):
|
||||
""" Convert an IP address string with a CIDR mask into a 32-bit
|
||||
integer.
|
||||
@staticmethod
|
||||
def addr2bin(ipstring, cidr=None):
|
||||
""" Convert a string IPv4 address into binary form.
|
||||
If cidr is supplied, return the network address for the given block
|
||||
"""
|
||||
# 32-bit IPv4 address mask
|
||||
MASK = 0xFFFFFFFFL
|
||||
return ~(MASK >> n) & MASK & DNSUtils.addr2bin(i)
|
||||
cidr = staticmethod(cidr)
|
||||
if cidr is None:
|
||||
return struct.unpack("!L", socket.inet_aton(ipstring))[0]
|
||||
else:
|
||||
MASK = 0xFFFFFFFFL
|
||||
return ~(MASK >> cidr) & MASK & DNSUtils.addr2bin(ipstring)
|
||||
|
||||
#@staticmethod
|
||||
def addr2bin(string):
|
||||
""" Convert a string IPv4 address into an unsigned integer.
|
||||
@staticmethod
|
||||
def bin2addr(ipbin):
|
||||
""" Convert a binary IPv4 address into string n.n.n.n form.
|
||||
"""
|
||||
return struct.unpack("!L", socket.inet_aton(string))[0]
|
||||
addr2bin = staticmethod(addr2bin)
|
||||
|
||||
#@staticmethod
|
||||
def bin2addr(addr):
|
||||
""" Convert a numeric IPv4 address into string n.n.n.n form.
|
||||
"""
|
||||
return socket.inet_ntoa(struct.pack("!L", addr))
|
||||
bin2addr = staticmethod(bin2addr)
|
||||
return socket.inet_ntoa(struct.pack("!L", ipbin))
|
||||
|
||||
@@ -167,9 +167,10 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
|
||||
logelements.append(logentry['_HOSTNAME'])
|
||||
if logentry.get('SYSLOG_IDENTIFIER'):
|
||||
logelements.append(logentry['SYSLOG_IDENTIFIER'])
|
||||
if logentry.get('SYSLOG_PID') or logentry.get('_PID'):
|
||||
logelements[-1] += ("[%i]" % logentry.get(
|
||||
'SYSLOG_PID', logentry['_PID']))
|
||||
if logentry.get('SYSLOG_PID'):
|
||||
logelements[-1] += ("[%i]" % logentry['SYSLOG_PID'])
|
||||
elif logentry.get('_PID'):
|
||||
logelements[-1] += ("[%i]" % logentry['_PID'])
|
||||
logelements[-1] += ":"
|
||||
elif logentry.get('_COMM'):
|
||||
logelements.append(logentry['_COMM'])
|
||||
@@ -258,9 +259,8 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
|
||||
or "jailless") +" filter terminated")
|
||||
return True
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
ret = super(FilterSystemd, self).status
|
||||
def status(self, flavor="basic"):
|
||||
ret = super(FilterSystemd, self).status(flavor=flavor)
|
||||
ret.append(("Journal matches",
|
||||
[" + ".join(" ".join(match) for match in self.__matches)]))
|
||||
return ret
|
||||
|
||||
@@ -174,13 +174,12 @@ class Jail:
|
||||
self.filter.idle = value
|
||||
self.actions.idle = value
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
def status(self, flavor="basic"):
|
||||
"""The status of the jail.
|
||||
"""
|
||||
return [
|
||||
("Filter", self.filter.status),
|
||||
("Actions", self.actions.status),
|
||||
("Filter", self.filter.status(flavor=flavor)),
|
||||
("Actions", self.actions.status(flavor=flavor)),
|
||||
]
|
||||
|
||||
def putFailTicket(self, ticket):
|
||||
@@ -214,7 +213,7 @@ class Jail:
|
||||
if self.database is not None:
|
||||
for ticket in self.database.getBansMerged(
|
||||
jail=self, bantime=self.actions.getBanTime()):
|
||||
if not self.filter.inIgnoreIPList(ticket.getIP()):
|
||||
if not self.filter.inIgnoreIPList(ticket.getIP(), log_ignore=True):
|
||||
self.__queue.put(ticket)
|
||||
logSys.info("Jail '%s' started" % self.name)
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ __license__ = "GPL"
|
||||
|
||||
import sys
|
||||
from threading import Thread
|
||||
from abc import abstractproperty, abstractmethod
|
||||
from abc import abstractmethod
|
||||
|
||||
from ..helpers import excepthook
|
||||
|
||||
@@ -66,8 +66,8 @@ class JailThread(Thread):
|
||||
excepthook(*sys.exc_info())
|
||||
self.run = run_with_except_hook
|
||||
|
||||
@abstractproperty
|
||||
def status(self): # pragma: no cover - abstract
|
||||
@abstractmethod
|
||||
def status(self, flavor="basic"): # pragma: no cover - abstract
|
||||
"""Abstract - Should provide status information.
|
||||
"""
|
||||
pass
|
||||
|
||||
+41
-35
@@ -26,65 +26,71 @@ import time, datetime
|
||||
##
|
||||
# MyTime class.
|
||||
#
|
||||
# This class is a wrapper around time.time() and time.gmtime(). When
|
||||
# performing unit test, it is very useful to get a fixed value from these
|
||||
# functions.
|
||||
# Thus, time.time() and time.gmtime() should never be called directly.
|
||||
# This wrapper should be called instead. The API are equivalent.
|
||||
|
||||
class MyTime:
|
||||
|
||||
"""A wrapper around time module primarily for testing purposes
|
||||
|
||||
This class is a wrapper around time.time() and time.gmtime(). When
|
||||
performing unit test, it is very useful to get a fixed value from
|
||||
these functions. Thus, time.time() and time.gmtime() should never
|
||||
be called directly. This wrapper should be called instead. The API
|
||||
are equivalent.
|
||||
"""
|
||||
|
||||
myTime = None
|
||||
|
||||
##
|
||||
# Sets the current time.
|
||||
#
|
||||
# Use None in order to always get the real current time.
|
||||
#
|
||||
# @param t the time to set or None
|
||||
|
||||
#@staticmethod
|
||||
|
||||
@staticmethod
|
||||
def setTime(t):
|
||||
"""Set current time.
|
||||
|
||||
Use None in order to always get the real current time.
|
||||
|
||||
@param t the time to set or None
|
||||
"""
|
||||
|
||||
MyTime.myTime = t
|
||||
setTime = staticmethod(setTime)
|
||||
|
||||
##
|
||||
# Equivalent to time.time()
|
||||
#
|
||||
# @return time.time() if setTime was called with None
|
||||
|
||||
#@staticmethod
|
||||
|
||||
@staticmethod
|
||||
def time():
|
||||
"""Decorate time.time() for the purpose of testing mocking
|
||||
|
||||
@return time.time() if setTime was called with None
|
||||
"""
|
||||
|
||||
if MyTime.myTime is None:
|
||||
return time.time()
|
||||
else:
|
||||
return MyTime.myTime
|
||||
time = staticmethod(time)
|
||||
|
||||
##
|
||||
# Equivalent to time.gmtime()
|
||||
#
|
||||
# @return time.gmtime() if setTime was called with None
|
||||
|
||||
#@staticmethod
|
||||
|
||||
@staticmethod
|
||||
def gmtime():
|
||||
"""Decorate time.gmtime() for the purpose of testing mocking
|
||||
|
||||
@return time.gmtime() if setTime was called with None
|
||||
"""
|
||||
if MyTime.myTime is None:
|
||||
return time.gmtime()
|
||||
else:
|
||||
return time.gmtime(MyTime.myTime)
|
||||
gmtime = staticmethod(gmtime)
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def now():
|
||||
"""Decorate datetime.now() for the purpose of testing mocking
|
||||
|
||||
@return datetime.now() if setTime was called with None
|
||||
"""
|
||||
if MyTime.myTime is None:
|
||||
return datetime.datetime.now()
|
||||
else:
|
||||
return datetime.datetime.fromtimestamp(MyTime.myTime)
|
||||
now = staticmethod(now)
|
||||
|
||||
@staticmethod
|
||||
def localtime(x=None):
|
||||
"""Decorate time.localtime() for the purpose of testing mocking
|
||||
|
||||
@return time.localtime() if setTime was called with None
|
||||
"""
|
||||
if MyTime.myTime is None or x is not None:
|
||||
return time.localtime(x)
|
||||
else:
|
||||
return time.localtime(MyTime.myTime)
|
||||
localtime = staticmethod(localtime)
|
||||
|
||||
+61
-11
@@ -25,7 +25,7 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||
__license__ = "GPL"
|
||||
|
||||
from threading import Lock, RLock
|
||||
import logging, logging.handlers, sys, os, signal
|
||||
import logging, logging.handlers, sys, os, signal, stat
|
||||
|
||||
from .jails import Jails
|
||||
from .filter import FileFilter, JournalFilter
|
||||
@@ -55,20 +55,33 @@ class Server:
|
||||
self.__asyncServer = AsyncServer(self.__transm)
|
||||
self.__logLevel = None
|
||||
self.__logTarget = None
|
||||
self.__syslogSocket = None
|
||||
self.__autoSyslogSocketPaths = {
|
||||
'Darwin': '/var/run/syslog',
|
||||
'FreeBSD': '/var/run/log',
|
||||
'Linux': '/dev/log',
|
||||
}
|
||||
# Set logging level
|
||||
self.setLogLevel("INFO")
|
||||
self.setLogTarget("STDOUT")
|
||||
|
||||
self.setSyslogSocket("auto")
|
||||
|
||||
|
||||
def __sigTERMhandler(self, signum, frame):
|
||||
logSys.debug("Caught signal %d. Exiting" % signum)
|
||||
self.quit()
|
||||
|
||||
def __sigUSR1handler(self, signum, fname):
|
||||
logSys.debug("Caught signal %d. Flushing logs" % signum)
|
||||
self.flushLogs()
|
||||
|
||||
def start(self, sock, pidfile, force = False):
|
||||
logSys.info("Starting Fail2ban v" + version.version)
|
||||
|
||||
# Install signal handlers
|
||||
signal.signal(signal.SIGTERM, self.__sigTERMhandler)
|
||||
signal.signal(signal.SIGINT, self.__sigTERMhandler)
|
||||
signal.signal(signal.SIGUSR1, self.__sigUSR1handler)
|
||||
|
||||
# Ensure unhandled exceptions are logged
|
||||
sys.excepthook = excepthook
|
||||
@@ -320,9 +333,9 @@ class Server:
|
||||
finally:
|
||||
self.__lock.release()
|
||||
|
||||
def statusJail(self, name):
|
||||
return self.__jails[name].status
|
||||
|
||||
def statusJail(self, name, flavor="basic"):
|
||||
return self.__jails[name].status(flavor=flavor)
|
||||
|
||||
# Logging
|
||||
|
||||
##
|
||||
@@ -360,7 +373,7 @@ class Server:
|
||||
return self.__logLevel
|
||||
finally:
|
||||
self.__loggingLock.release()
|
||||
|
||||
|
||||
##
|
||||
# Sets the logging target.
|
||||
#
|
||||
@@ -376,7 +389,21 @@ class Server:
|
||||
# Syslog daemons already add date to the message.
|
||||
formatter = logging.Formatter("%(name)s[%(process)d]: %(levelname)s %(message)s")
|
||||
facility = logging.handlers.SysLogHandler.LOG_DAEMON
|
||||
hdlr = logging.handlers.SysLogHandler("/dev/log", facility=facility)
|
||||
if self.__syslogSocket == "auto":
|
||||
import platform
|
||||
self.__syslogSocket = self.__autoSyslogSocketPaths.get(
|
||||
platform.system())
|
||||
if self.__syslogSocket is not None\
|
||||
and os.path.exists(self.__syslogSocket)\
|
||||
and stat.S_ISSOCK(os.stat(
|
||||
self.__syslogSocket).st_mode):
|
||||
hdlr = logging.handlers.SysLogHandler(
|
||||
self.__syslogSocket, facility=facility)
|
||||
else:
|
||||
logSys.error(
|
||||
"Syslog socket file: %s does not exists"
|
||||
" or is not a socket" % self.__syslogSocket)
|
||||
return False
|
||||
elif target == "STDOUT":
|
||||
hdlr = logging.StreamHandler(sys.stdout)
|
||||
elif target == "STDERR":
|
||||
@@ -412,21 +439,44 @@ class Server:
|
||||
logger.addHandler(hdlr)
|
||||
# Does not display this message at startup.
|
||||
if not self.__logTarget is None:
|
||||
logSys.info("Changed logging target to %s for Fail2ban v%s" %
|
||||
(target, version.version))
|
||||
logSys.info(
|
||||
"Changed logging target to %s for Fail2ban v%s"
|
||||
% ((target
|
||||
if target != "SYSLOG"
|
||||
else "%s (%s)"
|
||||
% (target, self.__syslogSocket)),
|
||||
version.version))
|
||||
# Sets the logging target.
|
||||
self.__logTarget = target
|
||||
return True
|
||||
finally:
|
||||
self.__loggingLock.release()
|
||||
|
||||
|
||||
##
|
||||
# Sets the syslog socket.
|
||||
#
|
||||
# syslogsocket is the full path to the syslog socket
|
||||
# @param syslogsocket the syslog socket path
|
||||
def setSyslogSocket(self, syslogsocket):
|
||||
self.__syslogSocket = syslogsocket
|
||||
# Conditionally reload, logtarget depends on socket path when SYSLOG
|
||||
return self.__logTarget != "SYSLOG"\
|
||||
or self.setLogTarget(self.__logTarget)
|
||||
|
||||
def getLogTarget(self):
|
||||
try:
|
||||
self.__loggingLock.acquire()
|
||||
return self.__logTarget
|
||||
finally:
|
||||
self.__loggingLock.release()
|
||||
|
||||
|
||||
def getSyslogSocket(self):
|
||||
try:
|
||||
self.__loggingLock.acquire()
|
||||
return self.__syslogSocket
|
||||
finally:
|
||||
self.__loggingLock.release()
|
||||
|
||||
def flushLogs(self):
|
||||
if self.__logTarget not in ['STDERR', 'STDOUT', 'SYSLOG']:
|
||||
for handler in getLogger("fail2ban").handlers:
|
||||
|
||||
@@ -55,7 +55,7 @@ class Ticket:
|
||||
def __eq__(self, other):
|
||||
try:
|
||||
return self.__ip == other.__ip and \
|
||||
round(self.__time,2) == round(other.__time,2) and \
|
||||
round(self.__time, 2) == round(other.__time, 2) and \
|
||||
self.__attempt == other.__attempt and \
|
||||
self.__matches == other.__matches
|
||||
except AttributeError:
|
||||
|
||||
@@ -121,6 +121,12 @@ class Transmitter:
|
||||
return self.__server.getLogTarget()
|
||||
else:
|
||||
raise Exception("Failed to change log target")
|
||||
elif name == "syslogsocket":
|
||||
value = command[1]
|
||||
if self.__server.setSyslogSocket(value):
|
||||
return self.__server.getSyslogSocket()
|
||||
else:
|
||||
raise Exception("Failed to change syslog socket")
|
||||
#Database
|
||||
elif name == "dbfile":
|
||||
self.__server.setDatabase(command[1])
|
||||
@@ -264,6 +270,8 @@ class Transmitter:
|
||||
return self.__server.getLogLevel()
|
||||
elif name == "logtarget":
|
||||
return self.__server.getLogTarget()
|
||||
elif name == "syslogsocket":
|
||||
return self.__server.getSyslogSocket()
|
||||
#Database
|
||||
elif name == "dbfile":
|
||||
db = self.__server.getDatabase()
|
||||
@@ -333,5 +341,8 @@ class Transmitter:
|
||||
elif len(command) == 1:
|
||||
name = command[0]
|
||||
return self.__server.statusJail(name)
|
||||
elif len(command) == 2:
|
||||
name = command[0]
|
||||
flavor = command[1]
|
||||
return self.__server.statusJail(name, flavor=flavor)
|
||||
raise Exception("Invalid command (no status)")
|
||||
|
||||
|
||||
@@ -22,11 +22,7 @@ import unittest
|
||||
import sys
|
||||
|
||||
from ..dummyjail import DummyJail
|
||||
|
||||
if os.path.exists('config/fail2ban.conf'):
|
||||
CONFIG_DIR = "config"
|
||||
else:
|
||||
CONFIG_DIR='/etc/fail2ban'
|
||||
from ..utils import CONFIG_DIR
|
||||
|
||||
if sys.version_info >= (2,7):
|
||||
class BadIPsActionTest(unittest.TestCase):
|
||||
|
||||
@@ -30,10 +30,7 @@ else:
|
||||
|
||||
from ..dummyjail import DummyJail
|
||||
|
||||
if os.path.exists('config/fail2ban.conf'):
|
||||
CONFIG_DIR = "config"
|
||||
else:
|
||||
CONFIG_DIR='/etc/fail2ban'
|
||||
from ..utils import CONFIG_DIR
|
||||
|
||||
class TestSMTPServer(smtpd.SMTPServer):
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ class ExecuteActions(LogCaptureTestCase):
|
||||
|
||||
self.__actions.stop()
|
||||
self.__actions.join()
|
||||
self.assertEqual(self.__actions.status,[("Currently banned", 0 ),
|
||||
self.assertEqual(self.__actions.status(),[("Currently banned", 0 ),
|
||||
("Total banned", 0 ), ("Banned IP list", [] )])
|
||||
|
||||
|
||||
|
||||
@@ -59,6 +59,8 @@ class CommandActionTest(LogCaptureTestCase):
|
||||
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C>'}), {'A': '<C>'})
|
||||
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C> <D> <X>','X':'fun'}), {'A': '<C> <D> fun', 'X':'fun'})
|
||||
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C> <B>', 'B': 'cool'}), {'A': '<C> cool', 'B': 'cool'})
|
||||
# Escaped tags should be ignored
|
||||
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<matches> <B>', 'B': 'cool'}), {'A': '<matches> cool', 'B': 'cool'})
|
||||
# Multiple stuff on same line is ok
|
||||
self.assertEqual(CommandAction.substituteRecursiveTags({'failregex': 'to=<honeypot> fromip=<IP> evilperson=<honeypot>', 'honeypot': 'pokie', 'ignoreregex': ''}),
|
||||
{ 'failregex': "to=pokie fromip=<IP> evilperson=pokie",
|
||||
@@ -71,6 +73,14 @@ class CommandActionTest(LogCaptureTestCase):
|
||||
'ABC': '123 192.0.2.0',
|
||||
'xyz': '890 123 192.0.2.0',
|
||||
})
|
||||
# obscure embedded case
|
||||
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<<PREF>HOST>', 'PREF': 'IPV4'}),
|
||||
{'A': '<IPV4HOST>', 'PREF': 'IPV4'})
|
||||
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<<PREF>HOST>', 'PREF': 'IPV4', 'IPV4HOST': '1.2.3.4'}),
|
||||
{'A': '1.2.3.4', 'PREF': 'IPV4', 'IPV4HOST': '1.2.3.4'})
|
||||
# more embedded within a string and two interpolations
|
||||
self.assertEqual(CommandAction.substituteRecursiveTags({'A': 'A <IP<PREF>HOST> B IP<PREF> C', 'PREF': 'V4', 'IPV4HOST': '1.2.3.4'}),
|
||||
{'A': 'A 1.2.3.4 B IPV4 C', 'PREF': 'V4', 'IPV4HOST': '1.2.3.4'})
|
||||
|
||||
def testReplaceTag(self):
|
||||
aInfo = {
|
||||
@@ -186,8 +196,10 @@ class CommandActionTest(LogCaptureTestCase):
|
||||
# Should take a minute
|
||||
self.assertRaises(
|
||||
RuntimeError, CommandAction.executeCmd, 'sleep 60', timeout=2)
|
||||
self.assertAlmostEqual(time.time() - stime, 2, places=0)
|
||||
self.assertTrue(self._is_logged('sleep 60 -- timed out after 2 seconds'))
|
||||
# give a test still 1 second, because system could be too busy
|
||||
self.assertTrue(time.time() >= stime + 2 and time.time() <= stime + 3)
|
||||
self.assertTrue(self._is_logged('sleep 60 -- timed out after 2 seconds')
|
||||
or self._is_logged('sleep 60 -- timed out after 3 seconds'))
|
||||
self.assertTrue(self._is_logged('sleep 60 -- killed with SIGTERM'))
|
||||
|
||||
def testCaptureStdOutErr(self):
|
||||
|
||||
@@ -30,7 +30,6 @@ from ..server.banmanager import BanManager
|
||||
from ..server.ticket import BanTicket
|
||||
|
||||
class AddFailure(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
"""Call before every test case."""
|
||||
self.__ticket = BanTicket('193.168.0.128', 1167605999.0)
|
||||
@@ -39,19 +38,77 @@ class AddFailure(unittest.TestCase):
|
||||
|
||||
def tearDown(self):
|
||||
"""Call after every test case."""
|
||||
|
||||
pass
|
||||
|
||||
def testAdd(self):
|
||||
self.assertEqual(self.__banManager.size(), 1)
|
||||
|
||||
|
||||
def testAddDuplicate(self):
|
||||
self.assertFalse(self.__banManager.addBanTicket(self.__ticket))
|
||||
self.assertEqual(self.__banManager.size(), 1)
|
||||
|
||||
|
||||
def testInListOK(self):
|
||||
ticket = BanTicket('193.168.0.128', 1167605999.0)
|
||||
self.assertTrue(self.__banManager._inBanList(ticket))
|
||||
|
||||
|
||||
def testInListNOK(self):
|
||||
ticket = BanTicket('111.111.1.111', 1167605999.0)
|
||||
self.assertFalse(self.__banManager._inBanList(ticket))
|
||||
|
||||
|
||||
|
||||
class StatusExtendedCymruInfo(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Call before every test case."""
|
||||
self.__ban_ip = "93.184.216.34"
|
||||
self.__asn = "15133"
|
||||
self.__country = "EU"
|
||||
self.__rir = "ripencc"
|
||||
ticket = BanTicket(self.__ban_ip, 1167605999.0)
|
||||
self.__banManager = BanManager()
|
||||
self.assertTrue(self.__banManager.addBanTicket(ticket))
|
||||
|
||||
def tearDown(self):
|
||||
"""Call after every test case."""
|
||||
pass
|
||||
|
||||
def testCymruInfo(self):
|
||||
cymru_info = self.__banManager.getBanListExtendedCymruInfo()
|
||||
if "assertDictEqual" in dir(self):
|
||||
self.assertDictEqual(cymru_info, {"asn": [self.__asn],
|
||||
"country": [self.__country],
|
||||
"rir": [self.__rir]})
|
||||
else:
|
||||
# Python 2.6 does not support assertDictEqual()
|
||||
self.assertEqual(cymru_info["asn"], [self.__asn])
|
||||
self.assertEqual(cymru_info["country"], [self.__country])
|
||||
self.assertEqual(cymru_info["rir"], [self.__rir])
|
||||
|
||||
def testCymruInfoASN(self):
|
||||
self.assertEqual(
|
||||
self.__banManager.geBanListExtendedASN(self.__banManager.getBanListExtendedCymruInfo()),
|
||||
[self.__asn])
|
||||
|
||||
def testCymruInfoCountry(self):
|
||||
self.assertEqual(
|
||||
self.__banManager.geBanListExtendedCountry(self.__banManager.getBanListExtendedCymruInfo()),
|
||||
[self.__country])
|
||||
|
||||
def testCymruInfoRIR(self):
|
||||
self.assertEqual(
|
||||
self.__banManager.geBanListExtendedRIR(self.__banManager.getBanListExtendedCymruInfo()),
|
||||
[self.__rir])
|
||||
|
||||
def testCymruInfoNxdomain(self):
|
||||
ticket = BanTicket("10.0.0.0", 1167605999.0)
|
||||
self.__banManager = BanManager()
|
||||
self.assertTrue(self.__banManager.addBanTicket(ticket))
|
||||
cymru_info = self.__banManager.getBanListExtendedCymruInfo()
|
||||
if "assertDictEqual" in dir(self):
|
||||
self.assertDictEqual(cymru_info, {"asn": ["nxdomain"],
|
||||
"country": ["nxdomain"],
|
||||
"rir": ["nxdomain"]})
|
||||
else:
|
||||
# Python 2.6 does not support assertDictEqual()
|
||||
self.assertEqual(cymru_info["asn"], ["nxdomain"])
|
||||
self.assertEqual(cymru_info["country"], ["nxdomain"])
|
||||
self.assertEqual(cymru_info["rir"], ["nxdomain"])
|
||||
|
||||
@@ -21,9 +21,9 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko"
|
||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2013 Yaroslav Halchenko"
|
||||
__license__ = "GPL"
|
||||
|
||||
import os, glob, shutil, tempfile, unittest
|
||||
|
||||
from ..client.configreader import ConfigReader
|
||||
import os, glob, shutil, tempfile, unittest, re, logging
|
||||
from ..client.configreader import ConfigReaderUnshared
|
||||
from ..client import configparserinc
|
||||
from ..client.jailreader import JailReader
|
||||
from ..client.filterreader import FilterReader
|
||||
from ..client.jailsreader import JailsReader
|
||||
@@ -32,8 +32,10 @@ from ..client.configurator import Configurator
|
||||
from .utils import LogCaptureTestCase
|
||||
|
||||
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
|
||||
|
||||
from .utils import CONFIG_DIR
|
||||
|
||||
STOCK = os.path.exists(os.path.join('config','fail2ban.conf'))
|
||||
CONFIG_DIR='config' if STOCK else '/etc/fail2ban'
|
||||
|
||||
IMPERFECT_CONFIG = os.path.join(os.path.dirname(__file__), 'config')
|
||||
|
||||
@@ -42,7 +44,7 @@ class ConfigReaderTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Call before every test case."""
|
||||
self.d = tempfile.mkdtemp(prefix="f2b-temp")
|
||||
self.c = ConfigReader(basedir=self.d)
|
||||
self.c = ConfigReaderUnshared(basedir=self.d)
|
||||
|
||||
def tearDown(self):
|
||||
"""Call after every test case."""
|
||||
@@ -153,12 +155,16 @@ c = d ;in line comment
|
||||
|
||||
class JailReaderTest(LogCaptureTestCase):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(JailReaderTest, self).__init__(*args, **kwargs)
|
||||
self.__share_cfg = {}
|
||||
|
||||
def testIncorrectJail(self):
|
||||
jail = JailReader('XXXABSENTXXX', basedir=CONFIG_DIR)
|
||||
jail = JailReader('XXXABSENTXXX', basedir=CONFIG_DIR, share_config = self.__share_cfg)
|
||||
self.assertRaises(ValueError, jail.read)
|
||||
|
||||
def testJailActionEmpty(self):
|
||||
jail = JailReader('emptyaction', basedir=IMPERFECT_CONFIG)
|
||||
jail = JailReader('emptyaction', basedir=IMPERFECT_CONFIG, share_config = self.__share_cfg)
|
||||
self.assertTrue(jail.read())
|
||||
self.assertTrue(jail.getOptions())
|
||||
self.assertTrue(jail.isEnabled())
|
||||
@@ -166,7 +172,7 @@ class JailReaderTest(LogCaptureTestCase):
|
||||
self.assertTrue(self._is_logged('No actions were defined for emptyaction'))
|
||||
|
||||
def testJailActionFilterMissing(self):
|
||||
jail = JailReader('missingbitsjail', basedir=IMPERFECT_CONFIG)
|
||||
jail = JailReader('missingbitsjail', basedir=IMPERFECT_CONFIG, share_config = self.__share_cfg)
|
||||
self.assertTrue(jail.read())
|
||||
self.assertFalse(jail.getOptions())
|
||||
self.assertTrue(jail.isEnabled())
|
||||
@@ -174,7 +180,7 @@ class JailReaderTest(LogCaptureTestCase):
|
||||
self.assertTrue(self._is_logged('Unable to read the filter'))
|
||||
|
||||
def TODOtestJailActionBrokenDef(self):
|
||||
jail = JailReader('brokenactiondef', basedir=IMPERFECT_CONFIG)
|
||||
jail = JailReader('brokenactiondef', basedir=IMPERFECT_CONFIG, share_config = self.__share_cfg)
|
||||
self.assertTrue(jail.read())
|
||||
self.assertFalse(jail.getOptions())
|
||||
self.assertTrue(jail.isEnabled())
|
||||
@@ -185,7 +191,7 @@ class JailReaderTest(LogCaptureTestCase):
|
||||
|
||||
if STOCK:
|
||||
def testStockSSHJail(self):
|
||||
jail = JailReader('sshd', basedir=CONFIG_DIR) # we are running tests from root project dir atm
|
||||
jail = JailReader('sshd', basedir=CONFIG_DIR, share_config = self.__share_cfg) # we are running tests from root project dir atm
|
||||
self.assertTrue(jail.read())
|
||||
self.assertTrue(jail.getOptions())
|
||||
self.assertFalse(jail.isEnabled())
|
||||
@@ -332,16 +338,94 @@ class FilterReaderTest(unittest.TestCase):
|
||||
filterReader.getOptions(None)
|
||||
self.assertRaises(ValueError, FilterReader.convert, filterReader)
|
||||
|
||||
def testFilterReaderExplicit(self):
|
||||
# read explicit uses absolute path:
|
||||
path_ = os.path.abspath(os.path.join(TEST_FILES_DIR, "filter.d"))
|
||||
filterReader = FilterReader(os.path.join(path_, "testcase01.conf"), "testcase01", {})
|
||||
self.assertEqual(filterReader.readexplicit(),
|
||||
[os.path.join(path_, "testcase-common.conf"), os.path.join(path_, "testcase01.conf")]
|
||||
)
|
||||
try:
|
||||
filterReader.getOptions(None)
|
||||
# from included common
|
||||
filterReader.get('Definition', '__prefix_line')
|
||||
# from testcase01
|
||||
filterReader.get('Definition', 'failregex')
|
||||
filterReader.get('Definition', 'ignoreregex')
|
||||
except Exception, e: # pragma: no cover - failed if reachable
|
||||
self.fail('unexpected options after readexplicit: %s' % (e))
|
||||
|
||||
class JailsReaderTestCache(LogCaptureTestCase):
|
||||
|
||||
def _readWholeConf(self, basedir, force_enable=False, share_config=None):
|
||||
# read whole configuration like a file2ban-client ...
|
||||
configurator = Configurator(force_enable=force_enable, share_config=share_config)
|
||||
configurator.setBaseDir(basedir)
|
||||
configurator.readEarly()
|
||||
configurator.getEarlyOptions()
|
||||
configurator.readAll()
|
||||
# from here we test a cache with all includes / before / after :
|
||||
self.assertTrue(configurator.getOptions(None))
|
||||
|
||||
def _getLoggedReadCount(self, filematch):
|
||||
cnt = 0
|
||||
for s in self.getLog().rsplit('\n'):
|
||||
if re.match(r"^\s*Reading files?: .*/"+filematch, s):
|
||||
cnt += 1
|
||||
return cnt
|
||||
|
||||
def testTestJailConfCache(self):
|
||||
saved_ll = configparserinc.logLevel
|
||||
configparserinc.logLevel = logging.DEBUG
|
||||
basedir = tempfile.mkdtemp("fail2ban_conf")
|
||||
try:
|
||||
shutil.rmtree(basedir)
|
||||
shutil.copytree(CONFIG_DIR, basedir)
|
||||
shutil.copy(CONFIG_DIR + '/jail.conf', basedir + '/jail.local')
|
||||
shutil.copy(CONFIG_DIR + '/fail2ban.conf', basedir + '/fail2ban.local')
|
||||
|
||||
# common sharing handle for this test:
|
||||
share_cfg = dict()
|
||||
|
||||
# read whole configuration like a file2ban-client ...
|
||||
self._readWholeConf(basedir, share_config=share_cfg)
|
||||
# how many times jail.local was read:
|
||||
cnt = self._getLoggedReadCount('jail.local')
|
||||
# if cnt > 1:
|
||||
# self.printLog()
|
||||
self.assertTrue(cnt == 1, "Unexpected count by reading of jail files, cnt = %s" % cnt)
|
||||
|
||||
# read whole configuration like a file2ban-client, again ...
|
||||
# but this time force enable all jails, to check filter and action cached also:
|
||||
self._readWholeConf(basedir, force_enable=True, share_config=share_cfg)
|
||||
cnt = self._getLoggedReadCount(r'jail\.local')
|
||||
# still one (no more reads):
|
||||
self.assertTrue(cnt == 1, "Unexpected count by second reading of jail files, cnt = %s" % cnt)
|
||||
|
||||
# same with filter:
|
||||
cnt = self._getLoggedReadCount(r'filter\.d/common\.conf')
|
||||
self.assertTrue(cnt == 1, "Unexpected count by reading of filter files, cnt = %s" % cnt)
|
||||
# same with action:
|
||||
cnt = self._getLoggedReadCount(r'action\.d/iptables-common\.conf')
|
||||
self.assertTrue(cnt == 1, "Unexpected count by reading of action files, cnt = %s" % cnt)
|
||||
finally:
|
||||
shutil.rmtree(basedir)
|
||||
configparserinc.logLevel = saved_ll
|
||||
|
||||
|
||||
class JailsReaderTest(LogCaptureTestCase):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(JailsReaderTest, self).__init__(*args, **kwargs)
|
||||
self.__share_cfg = {}
|
||||
|
||||
def testProvidingBadBasedir(self):
|
||||
if not os.path.exists('/XXX'):
|
||||
reader = JailsReader(basedir='/XXX')
|
||||
self.assertRaises(ValueError, reader.read)
|
||||
|
||||
def testReadTestJailConf(self):
|
||||
jails = JailsReader(basedir=IMPERFECT_CONFIG)
|
||||
jails = JailsReader(basedir=IMPERFECT_CONFIG, share_config=self.__share_cfg)
|
||||
self.assertTrue(jails.read())
|
||||
self.assertFalse(jails.getOptions())
|
||||
self.assertRaises(ValueError, jails.convert)
|
||||
@@ -349,6 +433,11 @@ class JailsReaderTest(LogCaptureTestCase):
|
||||
self.maxDiff = None
|
||||
self.assertEqual(sorted(comm_commands),
|
||||
sorted([['add', 'emptyaction', 'auto'],
|
||||
['add', 'test-known-interp', 'auto'],
|
||||
['set', 'test-known-interp', 'addfailregex', 'failure test 1 (filter.d/test.conf) <HOST>'],
|
||||
['set', 'test-known-interp', 'addfailregex', 'failure test 2 (filter.d/test.local) <HOST>'],
|
||||
['set', 'test-known-interp', 'addfailregex', 'failure test 3 (jail.local) <HOST>'],
|
||||
['start', 'test-known-interp'],
|
||||
['add', 'missinglogfiles', 'auto'],
|
||||
['set', 'missinglogfiles', 'addfailregex', '<IP>'],
|
||||
['add', 'brokenaction', 'auto'],
|
||||
@@ -370,8 +459,24 @@ class JailsReaderTest(LogCaptureTestCase):
|
||||
self.assertTrue(self._is_logged("No file(s) found for glob /weapons/of/mass/destruction"))
|
||||
|
||||
if STOCK:
|
||||
def testReadStockActionConf(self):
|
||||
for actionConfig in glob.glob(os.path.join(CONFIG_DIR, 'action.d', '*.conf')):
|
||||
actionName = os.path.basename(actionConfig).replace('.conf', '')
|
||||
actionReader = ActionReader(actionName, "TEST", {}, basedir=CONFIG_DIR)
|
||||
self.assertTrue(actionReader.read())
|
||||
actionReader.getOptions({}) # populate _opts
|
||||
if not actionName.endswith('-common'):
|
||||
self.assertTrue('Definition' in actionReader.sections(),
|
||||
msg="Action file %r is lacking [Definition] section" % actionConfig)
|
||||
# all must have some actionban defined
|
||||
self.assertTrue(actionReader._opts.get('actionban', '').strip(),
|
||||
msg="Action file %r is lacking actionban" % actionConfig)
|
||||
self.assertTrue('Init' in actionReader.sections(),
|
||||
msg="Action file %r is lacking [Init] section" % actionConfig)
|
||||
|
||||
|
||||
def testReadStockJailConf(self):
|
||||
jails = JailsReader(basedir=CONFIG_DIR) # we are running tests from root project dir atm
|
||||
jails = JailsReader(basedir=CONFIG_DIR, share_config=self.__share_cfg) # we are running tests from root project dir atm
|
||||
self.assertTrue(jails.read()) # opens fine
|
||||
self.assertTrue(jails.getOptions()) # reads fine
|
||||
comm_commands = jails.convert()
|
||||
@@ -432,7 +537,7 @@ class JailsReaderTest(LogCaptureTestCase):
|
||||
|
||||
# Verify that all filters found under config/ have a jail
|
||||
def testReadStockJailFilterComplete(self):
|
||||
jails = JailsReader(basedir=CONFIG_DIR, force_enable=True)
|
||||
jails = JailsReader(basedir=CONFIG_DIR, force_enable=True, share_config=self.__share_cfg)
|
||||
self.assertTrue(jails.read()) # opens fine
|
||||
self.assertTrue(jails.getOptions()) # reads fine
|
||||
# grab all filter names
|
||||
@@ -449,7 +554,7 @@ class JailsReaderTest(LogCaptureTestCase):
|
||||
def testReadStockJailConfForceEnabled(self):
|
||||
# more of a smoke test to make sure that no obvious surprises
|
||||
# on users' systems when enabling shipped jails
|
||||
jails = JailsReader(basedir=CONFIG_DIR, force_enable=True) # we are running tests from root project dir atm
|
||||
jails = JailsReader(basedir=CONFIG_DIR, force_enable=True, share_config=self.__share_cfg) # we are running tests from root project dir atm
|
||||
self.assertTrue(jails.read()) # opens fine
|
||||
self.assertTrue(jails.getOptions()) # reads fine
|
||||
comm_commands = jails.convert(allow_no_files=True)
|
||||
@@ -518,7 +623,8 @@ class JailsReaderTest(LogCaptureTestCase):
|
||||
'/var/lib/fail2ban/fail2ban.sqlite3'],
|
||||
['set', 'dbpurgeage', 86400],
|
||||
['set', 'loglevel', "INFO"],
|
||||
['set', 'logtarget', '/var/log/fail2ban.log']])
|
||||
['set', 'logtarget', '/var/log/fail2ban.log'],
|
||||
['set', 'syslogsocket', 'auto']])
|
||||
|
||||
# and if we force change configurator's fail2ban's baseDir
|
||||
# there should be an error message (test visually ;) --
|
||||
@@ -544,7 +650,7 @@ action = testaction1[actname=test1]
|
||||
filter = testfilter1
|
||||
""")
|
||||
jailfd.close()
|
||||
jails = JailsReader(basedir=basedir)
|
||||
jails = JailsReader(basedir=basedir, share_config=self.__share_cfg)
|
||||
self.assertTrue(jails.read())
|
||||
self.assertTrue(jails.getOptions())
|
||||
comm_commands = jails.convert(allow_no_files=True)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#[INCLUDES]
|
||||
#before = common.conf
|
||||
|
||||
[Definition]
|
||||
failregex = failure test 1 (filter.d/test.conf) <HOST>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
#[INCLUDES]
|
||||
#before = common.conf
|
||||
|
||||
[Definition]
|
||||
failregex = %(known/failregex)s
|
||||
failure test 2 (filter.d/test.local) <HOST>
|
||||
|
||||
@@ -13,6 +13,12 @@ failregex = <IP>
|
||||
ignoreregex =
|
||||
ignoreip =
|
||||
|
||||
[test-known-interp]
|
||||
enabled = true
|
||||
filter = test
|
||||
failregex = %(known/failregex)s
|
||||
failure test 3 (jail.local) <HOST>
|
||||
|
||||
[missinglogfiles]
|
||||
enabled = true
|
||||
logpath = /weapons/of/mass/destruction
|
||||
|
||||
@@ -32,18 +32,21 @@ import shutil
|
||||
from ..server.filter import FileContainer
|
||||
from ..server.mytime import MyTime
|
||||
from ..server.ticket import FailTicket
|
||||
from ..server.actions import Actions
|
||||
from .dummyjail import DummyJail
|
||||
try:
|
||||
from ..server.database import Fail2BanDb
|
||||
except ImportError:
|
||||
Fail2BanDb = None
|
||||
from .utils import LogCaptureTestCase
|
||||
|
||||
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
|
||||
|
||||
class DatabaseTest(unittest.TestCase):
|
||||
class DatabaseTest(LogCaptureTestCase):
|
||||
|
||||
def setUp(self):
|
||||
"""Call before every test case."""
|
||||
super(DatabaseTest, self).setUp()
|
||||
if Fail2BanDb is None and sys.version_info >= (2,7): # pragma: no cover
|
||||
raise unittest.SkipTest(
|
||||
"Unable to import fail2ban database module as sqlite is not "
|
||||
@@ -55,6 +58,7 @@ class DatabaseTest(unittest.TestCase):
|
||||
|
||||
def tearDown(self):
|
||||
"""Call after every test case."""
|
||||
super(DatabaseTest, self).tearDown()
|
||||
if Fail2BanDb is None: # pragma: no cover
|
||||
return
|
||||
# Cleanup
|
||||
@@ -173,6 +177,37 @@ class DatabaseTest(unittest.TestCase):
|
||||
self.assertTrue(
|
||||
isinstance(self.db.getBans(jail=self.jail)[0], FailTicket))
|
||||
|
||||
def testAddBanInvalidEncoded(self):
|
||||
if Fail2BanDb is None: # pragma: no cover
|
||||
return
|
||||
self.testAddJail()
|
||||
# invalid + valid, invalid + valid unicode, invalid + valid dual converted (like in filter:readline by fallback) ...
|
||||
tickets = [
|
||||
FailTicket("127.0.0.1", 0, ['user "\xd1\xe2\xe5\xf2\xe0"', 'user "\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f"']),
|
||||
FailTicket("127.0.0.2", 0, ['user "\xd1\xe2\xe5\xf2\xe0"', u'user "\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f"']),
|
||||
FailTicket("127.0.0.3", 0, ['user "\xd1\xe2\xe5\xf2\xe0"', b'user "\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f"'.decode('utf-8', 'replace')])
|
||||
]
|
||||
self.db.addBan(self.jail, tickets[0])
|
||||
self.db.addBan(self.jail, tickets[1])
|
||||
self.db.addBan(self.jail, tickets[2])
|
||||
|
||||
readtickets = self.db.getBans(jail=self.jail)
|
||||
self.assertEqual(len(readtickets), 3)
|
||||
## python 2 or 3 :
|
||||
invstr = u'user "\ufffd\ufffd\ufffd\ufffd\ufffd"'.encode('utf-8', 'replace')
|
||||
self.assertTrue(
|
||||
readtickets[0] == FailTicket("127.0.0.1", 0, [invstr, 'user "\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f"'])
|
||||
or readtickets[0] == tickets[0]
|
||||
)
|
||||
self.assertTrue(
|
||||
readtickets[1] == FailTicket("127.0.0.2", 0, [invstr, u'user "\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f"'.encode('utf-8', 'replace')])
|
||||
or readtickets[1] == tickets[1]
|
||||
)
|
||||
self.assertTrue(
|
||||
readtickets[2] == FailTicket("127.0.0.3", 0, [invstr, 'user "\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f"'])
|
||||
or readtickets[2] == tickets[2]
|
||||
)
|
||||
|
||||
def testDelBan(self):
|
||||
self.testAddBan()
|
||||
ticket = self.db.getBans(jail=self.jail)[0]
|
||||
@@ -267,6 +302,22 @@ class DatabaseTest(unittest.TestCase):
|
||||
tickets = self.db.getBansMerged(bantime=-1)
|
||||
self.assertEqual(len(tickets), 2)
|
||||
|
||||
def testActionWithDB(self):
|
||||
# test action together with database functionality
|
||||
self.testAddJail() # Jail required
|
||||
self.jail.database = self.db;
|
||||
actions = Actions(self.jail)
|
||||
actions.add(
|
||||
"action_checkainfo",
|
||||
os.path.join(TEST_FILES_DIR, "action.d/action_checkainfo.py"),
|
||||
{})
|
||||
ticket = FailTicket("1.2.3.4", MyTime.time(), ['test', 'test'])
|
||||
ticket.setAttempt(5)
|
||||
self.jail.putFailTicket(ticket)
|
||||
actions._Actions__checkBan()
|
||||
self.assertTrue(self._is_logged("ban ainfo %s, %s, %s, %s" % (True, True, True, True)))
|
||||
|
||||
|
||||
def testPurge(self):
|
||||
if Fail2BanDb is None: # pragma: no cover
|
||||
return
|
||||
|
||||
@@ -86,6 +86,7 @@ class DateDetectorTest(unittest.TestCase):
|
||||
(False, "23-Jan-2005 21:59:59.02"),
|
||||
(False, "23-Jan-2005 21:59:59 +0100"),
|
||||
(False, "23-01-2005 21:59:59"),
|
||||
(True, "1106513999"), # Portsetry
|
||||
(False, "01-23-2005 21:59:59.252"), # reported on f2b, causes Feb29 fix to break
|
||||
(False, "@4000000041f4104f00000000"), # TAI64N
|
||||
(False, "2005-01-23T20:59:59.252Z"), #ISO 8601 (UTC)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
from fail2ban.server.action import ActionBase
|
||||
|
||||
class TestAction(ActionBase):
|
||||
|
||||
def ban(self, aInfo):
|
||||
self._logSys.info("ban ainfo %s, %s, %s, %s",
|
||||
aInfo["ipmatches"] != '', aInfo["ipjailmatches"] != '', aInfo["ipfailures"] > 0, aInfo["ipjailfailures"] > 0
|
||||
)
|
||||
|
||||
def unban(self, aInfo):
|
||||
pass
|
||||
|
||||
Action = TestAction
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user