mirror of https://github.com/fail2ban/fail2ban
Primarily a bugfix release 0.8.8
-----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.12 (GNU/Linux) iEYEABECAAYFAlDAFpkACgkQjRFFY3XAJMgAgQCg1ZQHPpU7S6EQxM4sxELuJepl KV4AnRw/G7RX33ezTvdzAEYutKf+QJVB =PFlG -----END PGP SIGNATURE----- Merge tag '0.8.8' into debian Primarily a bugfix release 0.8.8 * tag '0.8.8': (31 commits) Getting ready for 0.8.8 release (changelog, version boost) BF: guarantee that IP is stored as a base, non-unicode str (Closes gh-91) ENH: BF (forgotten import) for prev commit + removed duplicate Author, adjusted __ fields for that in fail2ban-* scripts ENH: until we make it proper module -- adjust sys.path only if system-wide run ENH: fail2ban-testcases-all -- pass cmdline options to fail2ban-testcases ENH: To help with gh-87 added hints into the log on some failure return codes (e.g. 0x7f00 for this one) ENH: trying to go native travis-ci python way to take advantage of virtualenv's with older pythons BF: typo BF: added a little shell script to excercise tests against all available Python versions ENH: travis -- try to run tests against all available python versions NF: rudimentary .travis.yml for travis-ci.org service BF: do not enable pyinotify backend if pyinotify is too old (Closes gh-80) DOC: forgotten --help entry for " unban " ENH: downgrade "already banned" from WARN to INFO level (Closes gh-79) minor: added a note on now "negative" log entries on "POSSIBLE BREAK-IN ATTEMPT" DOC: minor "fixes" in DEVELOP Added in while loop to process the Fail Manager after the requested banned IP was added to its queue. This solves the issue of needing to touch the log file that is being monitored to get the IP to be banned accordingly. Added in import of FailManagerEmpty exception class. ENH: refactored previous commit to make it more Pythonic (With prev commit closes gh-86, gh-81) Added in command option to unban and IP, just like using 'banip'. Command looks like: fail2ban-client set <jail name> unbanip <ip> BF: in code we should use MyTime wrapper instead of time module directly ...pull/808/head
commit
e484ef0a26
|
@ -0,0 +1,11 @@
|
||||||
|
# vim ft=yaml
|
||||||
|
# travis-ci.org definition for Fail2Ban build
|
||||||
|
language: python
|
||||||
|
python:
|
||||||
|
- "2.5"
|
||||||
|
- "2.6"
|
||||||
|
- "2.7"
|
||||||
|
install:
|
||||||
|
- "pip install pyinotify"
|
||||||
|
script:
|
||||||
|
- python ./fail2ban-testcases
|
34
ChangeLog
34
ChangeLog
|
@ -4,9 +4,41 @@
|
||||||
|_| \__,_|_|_/___|_.__/\__,_|_||_|
|
|_| \__,_|_|_/___|_.__/\__,_|_||_|
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
Fail2Ban (version 0.8.7) 2012/07/31
|
Fail2Ban (version 0.8.8) 2012/12/06
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
|
ver. 0.8.8 (2012/12/06) - stable
|
||||||
|
----------
|
||||||
|
- Fixes:
|
||||||
|
Alan Jenkins
|
||||||
|
* [8c38907] Removed 'POSSIBLE BREAK-IN ATTEMPT' from sshd filter to avoid
|
||||||
|
banning due to misconfigured DNS. Close gh-64
|
||||||
|
Yaroslav Halchenko
|
||||||
|
* [83109bc] IMPORTANT: escape the content of <matches> (if used in
|
||||||
|
custom action files) since its value could contain arbitrary
|
||||||
|
symbols. Thanks for discovery go to the NBS System security
|
||||||
|
team
|
||||||
|
* [0935566,5becaf8] Various python 2.4 and 2.5 compatibility fixes. Close gh-83
|
||||||
|
* [b159eab] do not enable pyinotify backend if pyinotify < 0.8.3
|
||||||
|
* [37a2e59] store IP as a base, non-unicode str to avoid spurious messages
|
||||||
|
in the console. Close gh-91
|
||||||
|
- New features:
|
||||||
|
David Engeset
|
||||||
|
* [2d672d1,6288ec2] 'unbanip' command for the client + avoidance of touching
|
||||||
|
the log file to take 'banip' or 'unbanip' in effect. Close gh-81, gh-86
|
||||||
|
Yaroslav Halchenko
|
||||||
|
- Enhancements:
|
||||||
|
* [2d66f31] replaced uninformative "Invalid command" message with warning log
|
||||||
|
exception why command actually failed
|
||||||
|
* [958a1b0] improved failregex to "support" auth.backend = "htdigest"
|
||||||
|
* [9e7a3b7] until we make it proper module -- adjusted sys.path only if
|
||||||
|
system-wide run
|
||||||
|
* [f52ba99] downgraded "already banned" from WARN to INFO level. Closes gh-79
|
||||||
|
* [f105379] added hints into the log on some failure return codes (e.g. 0x7f00
|
||||||
|
for this gh-87)
|
||||||
|
* Various others: travis-ci integration, script to run tests
|
||||||
|
against all available Python versions, etc
|
||||||
|
|
||||||
ver. 0.8.7.1 (2012/07/31) - stable
|
ver. 0.8.7.1 (2012/07/31) - stable
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
|
20
DEVELOP
20
DEVELOP
|
@ -58,18 +58,20 @@ one)::
|
||||||
RF-Note just a note which might be useful to address while doing RF
|
RF-Note just a note which might be useful to address while doing RF
|
||||||
|
|
||||||
JailThread -> Filter -> FileFilter -> {FilterPoll, FilterPyinotify, ...}
|
JailThread -> Filter -> FileFilter -> {FilterPoll, FilterPyinotify, ...}
|
||||||
| | * FileContainer
|
| * FileContainer
|
||||||
| + FailManager
|
+ FailManager
|
||||||
| + DateDetector
|
+ DateDetector
|
||||||
\- -> Actions
|
+ Jail (provided in __init__) which contains this Filter
|
||||||
* Actions
|
(used for passing tickets from FailManager to Jail's __queue)
|
||||||
+ BanManager
|
|
||||||
|
|
||||||
Server
|
Server
|
||||||
+ Jails
|
+ Jails
|
||||||
* Jail
|
* Jail
|
||||||
+ Filter
|
+ Filter (in __filter)
|
||||||
* tickets (in __queue)
|
* tickets (in __queue)
|
||||||
|
+ Actions (in __action)
|
||||||
|
* Action
|
||||||
|
+ BanManager
|
||||||
|
|
||||||
|
|
||||||
failmanager.py
|
failmanager.py
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
@ -147,7 +149,7 @@ one way or another provide
|
||||||
except FailManagerEmpty:
|
except FailManagerEmpty:
|
||||||
self.failManager.cleanup(MyTime.time())
|
self.failManager.cleanup(MyTime.time())
|
||||||
|
|
||||||
thus channeling "ban tickets" from their failManager to a
|
thus channeling "ban tickets" from their failManager to the
|
||||||
corresponding jail.
|
corresponding jail.
|
||||||
|
|
||||||
action.py
|
action.py
|
||||||
|
|
6
README
6
README
|
@ -4,7 +4,7 @@
|
||||||
|_| \__,_|_|_/___|_.__/\__,_|_||_|
|
|_| \__,_|_|_/___|_.__/\__,_|_||_|
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
Fail2Ban (version 0.8.7) 2012/07/31
|
Fail2Ban (version 0.8.8) 2012/07/31
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
Fail2Ban scans log files like /var/log/pwdfail and bans IP that makes too many
|
Fail2Ban scans log files like /var/log/pwdfail and bans IP that makes too many
|
||||||
|
@ -31,8 +31,8 @@ Optional:
|
||||||
|
|
||||||
To install, just do:
|
To install, just do:
|
||||||
|
|
||||||
> tar xvfj fail2ban-0.8.7.tar.bz2
|
> tar xvfj fail2ban-0.8.8.tar.bz2
|
||||||
> cd fail2ban-0.8.7
|
> cd fail2ban-0.8.8
|
||||||
> python setup.py install
|
> python setup.py install
|
||||||
|
|
||||||
This will install Fail2Ban into /usr/share/fail2ban. The executable scripts are
|
This will install Fail2Ban into /usr/share/fail2ban. The executable scripts are
|
||||||
|
|
|
@ -64,6 +64,7 @@ protocol = [
|
||||||
["set <JAIL> bantime <TIME>", "sets the number of seconds <TIME> a host will be banned for <JAIL>"],
|
["set <JAIL> bantime <TIME>", "sets the number of seconds <TIME> a host will be banned for <JAIL>"],
|
||||||
["set <JAIL> usedns <VALUE>", "sets the usedns mode for <JAIL>"],
|
["set <JAIL> usedns <VALUE>", "sets the usedns mode for <JAIL>"],
|
||||||
["set <JAIL> banip <IP>", "manually Ban <IP> for <JAIL>"],
|
["set <JAIL> banip <IP>", "manually Ban <IP> for <JAIL>"],
|
||||||
|
["set <JAIL> unbanip <IP>", "manually Unban <IP> in <JAIL>"],
|
||||||
["set <JAIL> maxretry <RETRY>", "sets the number of failures <RETRY> before banning the host for <JAIL>"],
|
["set <JAIL> maxretry <RETRY>", "sets the number of failures <RETRY> before banning the host for <JAIL>"],
|
||||||
["set <JAIL> addaction <ACT>", "adds a new action named <NAME> for <JAIL>"],
|
["set <JAIL> addaction <ACT>", "adds a new action named <NAME> for <JAIL>"],
|
||||||
["set <JAIL> delaction <ACT>", "removes the action <NAME> from <JAIL>"],
|
["set <JAIL> delaction <ACT>", "removes the action <NAME> from <JAIL>"],
|
||||||
|
|
|
@ -21,8 +21,8 @@
|
||||||
#
|
#
|
||||||
# $Revision$
|
# $Revision$
|
||||||
|
|
||||||
__author__ = "Cyril Jaquier"
|
__author__ = "Cyril Jaquier, Yaroslav Halchenko"
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2012 Yaroslav Halchenko"
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2012 Yaroslav Halchenko"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
version = "0.8.7"
|
version = "0.8.8"
|
||||||
|
|
|
@ -12,8 +12,22 @@
|
||||||
# any other addresses found in the whois record, with a few exceptions.
|
# any other addresses found in the whois record, with a few exceptions.
|
||||||
# If no addresses are found, no e-mail is sent.
|
# If no addresses are found, no e-mail is sent.
|
||||||
#
|
#
|
||||||
# $Revision$
|
# WARNING
|
||||||
|
# -------
|
||||||
#
|
#
|
||||||
|
# Please do not use this action unless you are certain that fail2ban
|
||||||
|
# does not result in "false positives" for your deployment. False
|
||||||
|
# positive reports could serve a mis-favor to the original cause by
|
||||||
|
# flooding corresponding contact addresses, and complicating the work
|
||||||
|
# of administration personnel responsible for handling (verified) legit
|
||||||
|
# complains.
|
||||||
|
#
|
||||||
|
# Please consider using e.g. sendmail-whois-lines.conf action which
|
||||||
|
# would send the reports with relevant information to you, so the
|
||||||
|
# report could be first reviewed and then forwarded to a corresponding
|
||||||
|
# contact if legit.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
[Definition]
|
[Definition]
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,7 @@
|
||||||
# Notes.: regex to match wrong passwords as notified by lighttpd's auth Module
|
# Notes.: regex to match wrong passwords as notified by lighttpd's auth Module
|
||||||
# Values: TEXT
|
# Values: TEXT
|
||||||
#
|
#
|
||||||
failregex = .*http_auth.*password doesn\'t match.*IP: <HOST>\s*$
|
failregex = .*http_auth.*(password doesn\'t match|wrong password).*IP: <HOST>\s*$
|
||||||
|
|
||||||
|
|
||||||
# Option: ignoreregex
|
# Option: ignoreregex
|
||||||
# Notes.: regex to ignore. If this regex matches, the line is ignored.
|
# Notes.: regex to ignore. If this regex matches, the line is ignored.
|
||||||
|
|
|
@ -32,7 +32,6 @@ failregex = ^%(__prefix_line)s(?:error: PAM: )?Authentication failure for .* fro
|
||||||
^%(__prefix_line)sUser .+ from <HOST> not allowed because listed in DenyUsers\s*$
|
^%(__prefix_line)sUser .+ from <HOST> not allowed because listed in DenyUsers\s*$
|
||||||
^%(__prefix_line)s(?:pam_unix\(sshd:auth\):\s)?authentication failure; logname=\S* uid=\S* euid=\S* tty=\S* ruser=\S* rhost=<HOST>(?:\s+user=.*)?\s*$
|
^%(__prefix_line)s(?:pam_unix\(sshd:auth\):\s)?authentication failure; logname=\S* uid=\S* euid=\S* tty=\S* ruser=\S* rhost=<HOST>(?:\s+user=.*)?\s*$
|
||||||
^%(__prefix_line)srefused connect from \S+ \(<HOST>\)\s*$
|
^%(__prefix_line)srefused connect from \S+ \(<HOST>\)\s*$
|
||||||
^%(__prefix_line)sAddress <HOST> .* POSSIBLE BREAK-IN ATTEMPT!*\s*$
|
|
||||||
^%(__prefix_line)sUser .+ from <HOST> not allowed because none of user's groups are listed in AllowGroups\s*$
|
^%(__prefix_line)sUser .+ from <HOST> not allowed because none of user's groups are listed in AllowGroups\s*$
|
||||||
|
|
||||||
# Option: ignoreregex
|
# Option: ignoreregex
|
||||||
|
|
|
@ -18,13 +18,7 @@
|
||||||
# along with Fail2Ban; if not, write to the Free Software
|
# along with Fail2Ban; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
# Author: Cyril Jaquier
|
|
||||||
#
|
|
||||||
# $Revision$
|
|
||||||
|
|
||||||
__author__ = "Cyril Jaquier"
|
__author__ = "Cyril Jaquier"
|
||||||
__version__ = "$Revision$"
|
|
||||||
__date__ = "$Date$"
|
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
|
@ -33,6 +27,8 @@ import getopt, time, shlex, socket
|
||||||
|
|
||||||
# Inserts our own modules path first in the list
|
# Inserts our own modules path first in the list
|
||||||
# fix for bug #343821
|
# fix for bug #343821
|
||||||
|
if os.path.abspath(__file__).startswith('/usr/'):
|
||||||
|
# makes sense to use system-wide library iff -client is also under /usr/
|
||||||
sys.path.insert(1, "/usr/share/fail2ban")
|
sys.path.insert(1, "/usr/share/fail2ban")
|
||||||
|
|
||||||
# Now we can import our modules
|
# Now we can import our modules
|
||||||
|
@ -69,7 +65,7 @@ class Fail2banClient:
|
||||||
def dispVersion(self):
|
def dispVersion(self):
|
||||||
print "Fail2Ban v" + version
|
print "Fail2Ban v" + version
|
||||||
print
|
print
|
||||||
print "Copyright (c) 2004-2008 Cyril Jaquier"
|
print "Copyright (c) 2004-2008 Cyril Jaquier, 2008- Fail2Ban Contributors"
|
||||||
print "Copyright of modifications held by their respective authors."
|
print "Copyright of modifications held by their respective authors."
|
||||||
print "Licensed under the GNU General Public License v2 (GPL)."
|
print "Licensed under the GNU General Public License v2 (GPL)."
|
||||||
print
|
print
|
||||||
|
|
|
@ -15,20 +15,16 @@
|
||||||
# along with Fail2Ban; if not, write to the Free Software
|
# along with Fail2Ban; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
# Author: Cyril Jaquier
|
__author__ = "Cyril Jaquier, Yaroslav Halchenko"
|
||||||
#
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2012 Yaroslav Halchenko"
|
||||||
# $Revision$
|
|
||||||
|
|
||||||
__author__ = "Cyril Jaquier"
|
|
||||||
__version__ = "$Revision$"
|
|
||||||
__date__ = "$Date$"
|
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import getopt, sys, time, logging, os
|
import getopt, sys, time, logging, os
|
||||||
|
|
||||||
# Inserts our own modules path first in the list
|
# Inserts our own modules path first in the list
|
||||||
# fix for bug #343821
|
# fix for bug #343821
|
||||||
|
if os.path.abspath(__file__).startswith('/usr/'):
|
||||||
|
# makes sense to use system-wide library iff -regex is also under /usr/
|
||||||
sys.path.insert(1, "/usr/share/fail2ban")
|
sys.path.insert(1, "/usr/share/fail2ban")
|
||||||
|
|
||||||
from client.configparserinc import SafeConfigParserWithIncludes
|
from client.configparserinc import SafeConfigParserWithIncludes
|
||||||
|
|
|
@ -18,20 +18,16 @@
|
||||||
# along with Fail2Ban; if not, write to the Free Software
|
# along with Fail2Ban; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
# Author: Cyril Jaquier
|
|
||||||
#
|
|
||||||
# $Revision$
|
|
||||||
|
|
||||||
__author__ = "Cyril Jaquier"
|
__author__ = "Cyril Jaquier"
|
||||||
__version__ = "$Revision$"
|
|
||||||
__date__ = "$Date$"
|
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import getopt, sys, logging
|
import getopt, sys, logging, os
|
||||||
|
|
||||||
# Inserts our own modules path first in the list
|
# Inserts our own modules path first in the list
|
||||||
# fix for bug #343821
|
# fix for bug #343821
|
||||||
|
if os.path.abspath(__file__).startswith('/usr/'):
|
||||||
|
# makes sense to use system-wide library iff -server is also under /usr/
|
||||||
sys.path.insert(1, "/usr/share/fail2ban")
|
sys.path.insert(1, "/usr/share/fail2ban")
|
||||||
|
|
||||||
from common.version import version
|
from common.version import version
|
||||||
|
@ -61,7 +57,7 @@ class Fail2banServer:
|
||||||
def dispVersion(self):
|
def dispVersion(self):
|
||||||
print "Fail2Ban v" + version
|
print "Fail2Ban v" + version
|
||||||
print
|
print
|
||||||
print "Copyright (c) 2004-2008 Cyril Jaquier"
|
print "Copyright (c) 2004-2008 Cyril Jaquier, 2008- Fail2Ban Contributors"
|
||||||
print "Copyright of modifications held by their respective authors."
|
print "Copyright of modifications held by their respective authors."
|
||||||
print "Licensed under the GNU General Public License v2 (GPL)."
|
print "Licensed under the GNU General Public License v2 (GPL)."
|
||||||
print
|
print
|
||||||
|
|
|
@ -20,12 +20,8 @@
|
||||||
# along with Fail2Ban; if not, write to the Free Software
|
# along with Fail2Ban; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
# Author: Cyril Jaquier
|
|
||||||
|
|
||||||
__author__ = "Cyril Jaquier"
|
__author__ = "Cyril Jaquier"
|
||||||
__version__ = "$Revision$"
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2012- Yaroslav Halchenko"
|
||||||
__date__ = "$Date$"
|
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
|
|
||||||
|
@ -100,7 +96,8 @@ logSys.addHandler(stdout)
|
||||||
# Let know the version
|
# Let know the version
|
||||||
#
|
#
|
||||||
if not opts.log_level or opts.log_level != 'fatal':
|
if not opts.log_level or opts.log_level != 'fatal':
|
||||||
print "Fail2ban " + version + " test suite. Please wait..."
|
print "Fail2ban %s test suite. Python %s. Please wait..." \
|
||||||
|
% (version, str(sys.version).replace('\n', ''))
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -125,6 +122,7 @@ tests.addTest(unittest.makeSuite(filtertestcase.LogFile))
|
||||||
tests.addTest(unittest.makeSuite(filtertestcase.LogFileMonitor))
|
tests.addTest(unittest.makeSuite(filtertestcase.LogFileMonitor))
|
||||||
tests.addTest(unittest.makeSuite(filtertestcase.GetFailures))
|
tests.addTest(unittest.makeSuite(filtertestcase.GetFailures))
|
||||||
tests.addTest(unittest.makeSuite(filtertestcase.DNSUtilsTests))
|
tests.addTest(unittest.makeSuite(filtertestcase.DNSUtilsTests))
|
||||||
|
tests.addTest(unittest.makeSuite(filtertestcase.JailTests))
|
||||||
|
|
||||||
# DateDetector
|
# DateDetector
|
||||||
tests.addTest(unittest.makeSuite(datedetectortestcase.DateDetectorTest))
|
tests.addTest(unittest.makeSuite(datedetectortestcase.DateDetectorTest))
|
||||||
|
@ -142,14 +140,14 @@ filters = [FilterPoll] # always available
|
||||||
try:
|
try:
|
||||||
from server.filtergamin import FilterGamin
|
from server.filtergamin import FilterGamin
|
||||||
filters.append(FilterGamin)
|
filters.append(FilterGamin)
|
||||||
except:
|
except Exception, e:
|
||||||
pass
|
print "I: Skipping gamin backend testing. Got exception '%s'" % e
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from server.filterpyinotify import FilterPyinotify
|
from server.filterpyinotify import FilterPyinotify
|
||||||
filters.append(FilterPyinotify)
|
filters.append(FilterPyinotify)
|
||||||
except:
|
except Exception, e:
|
||||||
pass
|
print "I: Skipping pyinotify backend testing. Got exception '%s'" % e
|
||||||
|
|
||||||
for Filter_ in filters:
|
for Filter_ in filters:
|
||||||
tests.addTest(unittest.makeSuite(
|
tests.addTest(unittest.makeSuite(
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Simple helper script to exercise unittests using all available
|
||||||
|
# (under /usr/bin and /usr/local/bin python2.*)
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
failed=
|
||||||
|
for python in /usr/{,local/}bin/python2.[0-9]{,.*}{,-dbg}
|
||||||
|
do
|
||||||
|
[ -e "$python" ] || continue
|
||||||
|
echo "Testing using $python"
|
||||||
|
$python ./fail2ban-testcases "$@" || failed+=" $python"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ! -z "$failed" ]; then
|
||||||
|
echo "E: Failed with $failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
|
@ -37,6 +37,17 @@ logSys = logging.getLogger("fail2ban.actions.action")
|
||||||
# Create a lock for running system commands
|
# Create a lock for running system commands
|
||||||
_cmd_lock = threading.Lock()
|
_cmd_lock = threading.Lock()
|
||||||
|
|
||||||
|
# Some hints on common abnormal exit codes
|
||||||
|
_RETCODE_HINTS = {
|
||||||
|
0x7f00: '"Command not found". Make sure that all commands in %(realCmd)r '
|
||||||
|
'are in the PATH of fail2ban-server process '
|
||||||
|
'(grep -a PATH= /proc/`pidof -x fail2ban-server`/environ). '
|
||||||
|
'You may want to start '
|
||||||
|
'"fail2ban-server -f" separately, initiate it with '
|
||||||
|
'"fail2ban-client reload" in another shell session and observe if '
|
||||||
|
'additional informative error messages appear in the terminals.'
|
||||||
|
}
|
||||||
|
|
||||||
##
|
##
|
||||||
# Execute commands.
|
# Execute commands.
|
||||||
#
|
#
|
||||||
|
@ -231,6 +242,13 @@ class Action:
|
||||||
stopCmd = Action.replaceTag(self.__actionStop, self.__cInfo)
|
stopCmd = Action.replaceTag(self.__actionStop, self.__cInfo)
|
||||||
return Action.executeCmd(stopCmd)
|
return Action.executeCmd(stopCmd)
|
||||||
|
|
||||||
|
def escapeTag(tag):
|
||||||
|
for c in '\\#&;`|*?~<>^()[]{}$\n':
|
||||||
|
if c in tag:
|
||||||
|
tag = tag.replace(c, '\\' + c)
|
||||||
|
return tag
|
||||||
|
escapeTag = staticmethod(escapeTag)
|
||||||
|
|
||||||
##
|
##
|
||||||
# Replaces tags in query with property values in aInfo.
|
# Replaces tags in query with property values in aInfo.
|
||||||
#
|
#
|
||||||
|
@ -243,8 +261,13 @@ class Action:
|
||||||
""" Replace tags in query
|
""" Replace tags in query
|
||||||
"""
|
"""
|
||||||
string = query
|
string = query
|
||||||
for tag in aInfo:
|
for tag, value in aInfo.iteritems():
|
||||||
string = string.replace('<' + tag + '>', str(aInfo[tag]))
|
value = str(value) # assure string
|
||||||
|
if tag == 'matches':
|
||||||
|
# That one needs to be escaped since its content is
|
||||||
|
# out of our control
|
||||||
|
value = Action.escapeTag(value)
|
||||||
|
string = string.replace('<' + tag + '>', value)
|
||||||
# New line
|
# New line
|
||||||
string = string.replace("<br>", '\n')
|
string = string.replace("<br>", '\n')
|
||||||
return string
|
return string
|
||||||
|
@ -318,7 +341,11 @@ class Action:
|
||||||
logSys.debug("%s returned successfully" % realCmd)
|
logSys.debug("%s returned successfully" % realCmd)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
msg = _RETCODE_HINTS.get(retcode, None)
|
||||||
logSys.error("%s returned %x" % (realCmd, retcode))
|
logSys.error("%s returned %x" % (realCmd, retcode))
|
||||||
|
if msg:
|
||||||
|
logSys.info("HINT on %x: %s"
|
||||||
|
% (retcode, msg % locals()))
|
||||||
except OSError, e:
|
except OSError, e:
|
||||||
logSys.error("%s failed with %s" % (realCmd, e))
|
logSys.error("%s failed with %s" % (realCmd, e))
|
||||||
finally:
|
finally:
|
||||||
|
|
|
@ -120,6 +120,19 @@ class Actions(JailThread):
|
||||||
def getBanTime(self):
|
def getBanTime(self):
|
||||||
return self.__banManager.getBanTime()
|
return self.__banManager.getBanTime()
|
||||||
|
|
||||||
|
##
|
||||||
|
# Remove a banned IP now, rather than waiting for it to expire, even if set to never expire.
|
||||||
|
#
|
||||||
|
# @return the IP string or 'None' if not unbanned.
|
||||||
|
def removeBannedIP(self, ip):
|
||||||
|
# Find the ticket with the IP.
|
||||||
|
ticket = self.__banManager.getTicketByIP(ip)
|
||||||
|
if ticket is not None:
|
||||||
|
# Unban the IP.
|
||||||
|
self.__unBan(ticket)
|
||||||
|
return ip
|
||||||
|
return 'None'
|
||||||
|
|
||||||
##
|
##
|
||||||
# Main loop.
|
# Main loop.
|
||||||
#
|
#
|
||||||
|
@ -163,13 +176,13 @@ class Actions(JailThread):
|
||||||
aInfo["time"] = bTicket.getTime()
|
aInfo["time"] = bTicket.getTime()
|
||||||
aInfo["matches"] = "".join(bTicket.getMatches())
|
aInfo["matches"] = "".join(bTicket.getMatches())
|
||||||
if self.__banManager.addBanTicket(bTicket):
|
if self.__banManager.addBanTicket(bTicket):
|
||||||
logSys.warn("[%s] Ban %s" % (self.jail.getName(), str(aInfo["ip"])))
|
logSys.warn("[%s] Ban %s" % (self.jail.getName(), aInfo["ip"]))
|
||||||
for action in self.__actions:
|
for action in self.__actions:
|
||||||
action.execActionBan(aInfo)
|
action.execActionBan(aInfo)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
logSys.warn("[%s] %s already banned" % (self.jail.getName(),
|
logSys.info("[%s] %s already banned" % (self.jail.getName(),
|
||||||
str(aInfo["ip"])))
|
aInfo["ip"]))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
|
@ -223,3 +223,21 @@ class BanManager:
|
||||||
return uBList
|
return uBList
|
||||||
finally:
|
finally:
|
||||||
self.__lock.release()
|
self.__lock.release()
|
||||||
|
|
||||||
|
##
|
||||||
|
# Gets the ticket for the specified IP.
|
||||||
|
#
|
||||||
|
# @return the ticket for the IP or False.
|
||||||
|
def getTicketByIP(self, ip):
|
||||||
|
try:
|
||||||
|
self.__lock.acquire()
|
||||||
|
|
||||||
|
# Find the ticket the IP goes with and return it
|
||||||
|
for i, ticket in enumerate(self.__banList):
|
||||||
|
if ticket.getIP() == ip:
|
||||||
|
# Return the ticket after removing (popping)
|
||||||
|
# if from the ban list.
|
||||||
|
return self.__banList.pop(i)
|
||||||
|
finally:
|
||||||
|
self.__lock.release()
|
||||||
|
return None # if none found
|
||||||
|
|
|
@ -27,6 +27,7 @@ __date__ = "$Date$"
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
|
from failmanager import FailManagerEmpty
|
||||||
from failmanager import FailManager
|
from failmanager import FailManager
|
||||||
from ticket import FailTicket
|
from ticket import FailTicket
|
||||||
from jailthread import JailThread
|
from jailthread import JailThread
|
||||||
|
@ -220,10 +221,18 @@ class Filter(JailThread):
|
||||||
# to enable banip fail2ban-client BAN command
|
# to enable banip fail2ban-client BAN command
|
||||||
|
|
||||||
def addBannedIP(self, ip):
|
def addBannedIP(self, ip):
|
||||||
unixTime = time.time()
|
unixTime = MyTime.time()
|
||||||
for i in xrange(self.failManager.getMaxRetry()):
|
for i in xrange(self.failManager.getMaxRetry()):
|
||||||
self.failManager.addFailure(FailTicket(ip, unixTime))
|
self.failManager.addFailure(FailTicket(ip, unixTime))
|
||||||
|
|
||||||
|
# Perform the banning of the IP now.
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
ticket = self.failManager.toBan()
|
||||||
|
self.jail.putFailTicket(ticket)
|
||||||
|
except FailManagerEmpty:
|
||||||
|
self.failManager.cleanup(MyTime.time())
|
||||||
|
|
||||||
return ip
|
return ip
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
|
@ -23,12 +23,18 @@ __author__ = "Cyril Jaquier, Lee Clemens, Yaroslav Halchenko"
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2012 Lee Clemens, 2012 Yaroslav Halchenko"
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2012 Lee Clemens, 2012 Yaroslav Halchenko"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
|
from distutils.version import LooseVersion
|
||||||
|
|
||||||
from failmanager import FailManagerEmpty
|
from failmanager import FailManagerEmpty
|
||||||
from filter import FileFilter
|
from filter import FileFilter
|
||||||
from mytime import MyTime
|
from mytime import MyTime
|
||||||
|
|
||||||
import time, logging, pyinotify
|
import time, logging, pyinotify
|
||||||
|
|
||||||
|
if not hasattr(pyinotify, '__version__') \
|
||||||
|
or LooseVersion(pyinotify.__version__) < '0.8.3':
|
||||||
|
raise ImportError("Fail2Ban requires pyinotify >= 0.8.3")
|
||||||
|
|
||||||
from os.path import dirname, sep as pathsep
|
from os.path import dirname, sep as pathsep
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
|
|
|
@ -33,7 +33,9 @@ logSys = logging.getLogger("fail2ban.jail")
|
||||||
class Jail:
|
class Jail:
|
||||||
|
|
||||||
#Known backends. Each backend should have corresponding __initBackend method
|
#Known backends. Each backend should have corresponding __initBackend method
|
||||||
_BACKENDS = ('pyinotify', 'gamin', 'polling')
|
# yoh: stored in a list instead of a tuple since only
|
||||||
|
# list had .index until 2.6
|
||||||
|
_BACKENDS = ['pyinotify', 'gamin', 'polling']
|
||||||
|
|
||||||
def __init__(self, name, backend = "auto"):
|
def __init__(self, name, backend = "auto"):
|
||||||
self.__name = name
|
self.__name = name
|
||||||
|
|
|
@ -241,6 +241,9 @@ class Server:
|
||||||
def setBanIP(self, name, value):
|
def setBanIP(self, name, value):
|
||||||
return self.__jails.getFilter(name).addBannedIP(value)
|
return self.__jails.getFilter(name).addBannedIP(value)
|
||||||
|
|
||||||
|
def setUnbanIP(self, name, value):
|
||||||
|
return self.__jails.getAction(name).removeBannedIP(value)
|
||||||
|
|
||||||
def getBanTime(self, name):
|
def getBanTime(self, name):
|
||||||
return self.__jails.getAction(name).getBanTime()
|
return self.__jails.getAction(name).getBanTime()
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ class Ticket:
|
||||||
@param matches (log) lines caused the ticket
|
@param matches (log) lines caused the ticket
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.__ip = ip
|
self.setIP(ip)
|
||||||
self.__time = time
|
self.__time = time
|
||||||
self.__attempt = 0
|
self.__attempt = 0
|
||||||
self.__file = None
|
self.__file = None
|
||||||
|
@ -54,6 +54,9 @@ class Ticket:
|
||||||
|
|
||||||
|
|
||||||
def setIP(self, value):
|
def setIP(self, value):
|
||||||
|
if isinstance(value, basestring):
|
||||||
|
# guarantee using regular str instead of unicode for the IP
|
||||||
|
value = str(value)
|
||||||
self.__ip = value
|
self.__ip = value
|
||||||
|
|
||||||
def getIP(self):
|
def getIP(self):
|
||||||
|
|
|
@ -55,7 +55,8 @@ class Transmitter:
|
||||||
ret = self.__commandHandler(command)
|
ret = self.__commandHandler(command)
|
||||||
ack = 0, ret
|
ack = 0, ret
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logSys.warn("Invalid command: " + `command`)
|
logSys.warn("Command %r has failed. Received %r"
|
||||||
|
% (command, e))
|
||||||
ack = 1, e
|
ack = 1, e
|
||||||
return ack
|
return ack
|
||||||
|
|
||||||
|
@ -174,6 +175,9 @@ class Transmitter:
|
||||||
elif command[1] == "banip":
|
elif command[1] == "banip":
|
||||||
value = command[2]
|
value = command[2]
|
||||||
return self.__server.setBanIP(name,value)
|
return self.__server.setBanIP(name,value)
|
||||||
|
elif command[1] == "unbanip":
|
||||||
|
value = command[2]
|
||||||
|
return self.__server.setUnbanIP(name,value)
|
||||||
elif command[1] == "addaction":
|
elif command[1] == "addaction":
|
||||||
value = command[2]
|
value = command[2]
|
||||||
self.__server.addAction(name, value)
|
self.__server.addAction(name, value)
|
||||||
|
|
6
setup.py
6
setup.py
|
@ -18,13 +18,7 @@
|
||||||
# along with Fail2Ban; if not, write to the Free Software
|
# along with Fail2Ban; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
# Author: Cyril Jaquier
|
|
||||||
#
|
|
||||||
# $Revision$
|
|
||||||
|
|
||||||
__author__ = "Cyril Jaquier"
|
__author__ = "Cyril Jaquier"
|
||||||
__version__ = "$Revision$"
|
|
||||||
__date__ = "$Date$"
|
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,9 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import unittest, time
|
import unittest, time
|
||||||
|
import logging, sys
|
||||||
from server.action import Action
|
from server.action import Action
|
||||||
|
from StringIO import StringIO
|
||||||
|
|
||||||
class ExecuteAction(unittest.TestCase):
|
class ExecuteAction(unittest.TestCase):
|
||||||
|
|
||||||
|
@ -36,15 +38,43 @@ class ExecuteAction(unittest.TestCase):
|
||||||
"""Call before every test case."""
|
"""Call before every test case."""
|
||||||
self.__action = Action("Test")
|
self.__action = Action("Test")
|
||||||
|
|
||||||
|
# For extended testing of what gets output into logging
|
||||||
|
# system, we will redirect it to a string
|
||||||
|
logSys = logging.getLogger("fail2ban")
|
||||||
|
|
||||||
|
# Keep old settings
|
||||||
|
self._old_level = logSys.level
|
||||||
|
self._old_handlers = logSys.handlers
|
||||||
|
# Let's log everything into a string
|
||||||
|
self._log = StringIO()
|
||||||
|
logSys.handlers = [logging.StreamHandler(self._log)]
|
||||||
|
logSys.setLevel(getattr(logging, 'DEBUG'))
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
"""Call after every test case."""
|
"""Call after every test case."""
|
||||||
|
# print "O: >>%s<<" % self._log.getvalue()
|
||||||
|
logSys = logging.getLogger("fail2ban")
|
||||||
|
logSys.handlers = self._old_handlers
|
||||||
|
logSys.level = self._old_level
|
||||||
self.__action.execActionStop()
|
self.__action.execActionStop()
|
||||||
|
|
||||||
|
def _is_logged(self, s):
|
||||||
|
return s in self._log.getvalue()
|
||||||
|
|
||||||
def testExecuteActionBan(self):
|
def testExecuteActionBan(self):
|
||||||
self.__action.setActionStart("touch /tmp/fail2ban.test")
|
self.__action.setActionStart("touch /tmp/fail2ban.test")
|
||||||
self.__action.setActionStop("rm -f /tmp/fail2ban.test")
|
self.__action.setActionStop("rm -f /tmp/fail2ban.test")
|
||||||
self.__action.setActionBan("echo -n")
|
self.__action.setActionBan("echo -n")
|
||||||
self.__action.setActionCheck("[ -e /tmp/fail2ban.test ]")
|
self.__action.setActionCheck("[ -e /tmp/fail2ban.test ]")
|
||||||
|
|
||||||
self.assertTrue(self.__action.execActionBan(None))
|
self.assertFalse(self._is_logged('returned'))
|
||||||
|
# no action was actually executed yet
|
||||||
|
|
||||||
|
self.assertTrue(self.__action.execActionBan(None))
|
||||||
|
self.assertTrue(self._is_logged('Invariant check failed'))
|
||||||
|
self.assertTrue(self._is_logged('returned successfully'))
|
||||||
|
|
||||||
|
|
||||||
|
def testExecuteIncorrectCmd(self):
|
||||||
|
Action.executeCmd('/bin/ls >/dev/null\nbogusXXX now 2>/dev/null')
|
||||||
|
self.assertTrue(self._is_logged('HINT on 7f00: "Command not found"'))
|
||||||
|
|
|
@ -35,11 +35,11 @@ class AddFailure(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Call before every test case."""
|
"""Call before every test case."""
|
||||||
self.__items = [['193.168.0.128', 1167605999.0],
|
self.__items = [[u'193.168.0.128', 1167605999.0],
|
||||||
['193.168.0.128', 1167605999.0],
|
[u'193.168.0.128', 1167605999.0],
|
||||||
['193.168.0.128', 1167605999.0],
|
[u'193.168.0.128', 1167605999.0],
|
||||||
['193.168.0.128', 1167605999.0],
|
[u'193.168.0.128', 1167605999.0],
|
||||||
['193.168.0.128', 1167605999.0],
|
[u'193.168.0.128', 1167605999.0],
|
||||||
['87.142.124.10', 1167605999.0],
|
['87.142.124.10', 1167605999.0],
|
||||||
['87.142.124.10', 1167605999.0],
|
['87.142.124.10', 1167605999.0],
|
||||||
['87.142.124.10', 1167605999.0],
|
['87.142.124.10', 1167605999.0],
|
||||||
|
@ -80,6 +80,7 @@ class AddFailure(unittest.TestCase):
|
||||||
#ticket = FailTicket('193.168.0.128', None)
|
#ticket = FailTicket('193.168.0.128', None)
|
||||||
ticket = self.__failManager.toBan()
|
ticket = self.__failManager.toBan()
|
||||||
self.assertEqual(ticket.getIP(), "193.168.0.128")
|
self.assertEqual(ticket.getIP(), "193.168.0.128")
|
||||||
|
self.assertTrue(isinstance(ticket.getIP(), str))
|
||||||
|
|
||||||
def testbanNOK(self):
|
def testbanNOK(self):
|
||||||
self.__failManager.setMaxRetry(10)
|
self.__failManager.setMaxRetry(10)
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
#authentification failure (mod_auth)
|
#authentification failure (mod_auth)
|
||||||
2011-12-25 17:09:20: (http_auth.c.875) password doesn't match for /gitweb/ username: francois, IP: 4.4.4.4
|
2011-12-25 17:09:20: (http_auth.c.875) password doesn't match for /gitweb/ username: francois, IP: 4.4.4.4
|
||||||
|
2012-09-26 10:24:35: (http_auth.c.1136) digest: auth failed for xxx : wrong password, IP: 4.4.4.4
|
||||||
|
|
|
@ -22,6 +22,7 @@ Feb 25 14:34:11 belka sshd[31607]: User root from ferrari.inescn.pt not allowed
|
||||||
Nov 11 23:33:27 Server sshd[5174]: refused connect from _U2FsdGVkX19P3BCJmFBHhjLza8BcMH06WCUVwttMHpE=_@::ffff:218.249.210.161 (::ffff:218.249.210.161)
|
Nov 11 23:33:27 Server sshd[5174]: refused connect from _U2FsdGVkX19P3BCJmFBHhjLza8BcMH06WCUVwttMHpE=_@::ffff:218.249.210.161 (::ffff:218.249.210.161)
|
||||||
|
|
||||||
#7 added exclamation mark to BREAK-IN
|
#7 added exclamation mark to BREAK-IN
|
||||||
|
# Now should be a negative since we decided not to catch those
|
||||||
Oct 15 19:51:35 server sshd[7592]: Address 1.2.3.4 maps to 1234.bbbbbb.com, but this does not map back to the address - POSSIBLE BREAK-IN ATTEMPT
|
Oct 15 19:51:35 server sshd[7592]: Address 1.2.3.4 maps to 1234.bbbbbb.com, but this does not map back to the address - POSSIBLE BREAK-IN ATTEMPT
|
||||||
Oct 15 19:51:35 server sshd[7592]: Address 1.2.3.4 maps to 1234.bbbbbb.com, but this does not map back to the address - POSSIBLE BREAK-IN ATTEMPT!
|
Oct 15 19:51:35 server sshd[7592]: Address 1.2.3.4 maps to 1234.bbbbbb.com, but this does not map back to the address - POSSIBLE BREAK-IN ATTEMPT!
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ import sys
|
||||||
import time
|
import time
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
from server.jail import Jail
|
||||||
from server.filterpoll import FilterPoll
|
from server.filterpoll import FilterPoll
|
||||||
from server.filter import FileFilter, DNSUtils
|
from server.filter import FileFilter, DNSUtils
|
||||||
from server.failmanager import FailManager
|
from server.failmanager import FailManager
|
||||||
|
@ -626,3 +627,10 @@ class DNSUtilsTests(unittest.TestCase):
|
||||||
self.assertEqual(res, ['192.0.43.10'])
|
self.assertEqual(res, ['192.0.43.10'])
|
||||||
else:
|
else:
|
||||||
self.assertEqual(res, [])
|
self.assertEqual(res, [])
|
||||||
|
|
||||||
|
class JailTests(unittest.TestCase):
|
||||||
|
|
||||||
|
def testSetBackend_gh83(self):
|
||||||
|
# smoke test
|
||||||
|
jail = Jail('test', backend='polling') # Must not fail to initiate
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue