diff --git a/.travis.yml b/.travis.yml index f443ff12..362556ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,12 +7,16 @@ python: - "2.7" - "3.2" - "3.3" +before_install: + - sudo apt-get update -qq install: - pip install pyinotify - - if [[ $TRAVIS_PYTHON_VERSION == 2.[6-7] ]] || [[ $TRAVIS_PYTHON_VERSION == 3.3 ]]; then pip install -q coveralls; fi + - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then sudo apt-get install -qq python-gamin; fi + - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then pip install -q coveralls; fi before_script: - if [[ $TRAVIS_PYTHON_VERSION == 3* ]]; then ./fail2ban-2to3; fi script: - - if [[ $TRAVIS_PYTHON_VERSION == 2.[6-7] ]] || [[ $TRAVIS_PYTHON_VERSION == 3.3 ]]; then coverage run --rcfile=.travis_coveragerc fail2ban-testcases; else python ./fail2ban-testcases; fi + - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then export PYTHONPATH="$PYTHONPATH:/usr/share/pyshared:/usr/lib/pyshared/python2.7"; fi + - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then coverage run --rcfile=.travis_coveragerc bin/fail2ban-testcases; else python bin/fail2ban-testcases; fi after_script: - - if [[ $TRAVIS_PYTHON_VERSION == 2.[6-7] ]] || [[ $TRAVIS_PYTHON_VERSION == 3.3 ]]; then coveralls; fi + - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then coveralls; fi diff --git a/.travis_coveragerc b/.travis_coveragerc index 4d4b7ebd..ac4a15d5 100644 --- a/.travis_coveragerc +++ b/.travis_coveragerc @@ -4,4 +4,3 @@ branch = True omit = /usr/* /home/travis/virtualenv/* - server/filtergamin.py diff --git a/ChangeLog b/ChangeLog index eabd6e81..e5979331 100644 --- a/ChangeLog +++ b/ChangeLog @@ -4,9 +4,21 @@ |_| \__,_|_|_/___|_.__/\__,_|_||_| ================================================================================ -Fail2Ban (version 0.8.8) 2012/12/06 +Fail2Ban (version 0.9.0a) 20??/??/?? ================================================================================ + +ver. 0.9.0 (20??/??/??) - alpha +---------- + +Will carry all fixes in 0.8.x series and new features and enhancements + +- Fixes: +- New features: + Steven Hiscocks + * Multiline failregex. Close gh-54 +- Enhancements: + ver. 0.8.8 (2012/12/06) - stable ---------- - Fixes: diff --git a/DEVELOP b/DEVELOP index 623aee12..0fe80596 100644 --- a/DEVELOP +++ b/DEVELOP @@ -24,9 +24,9 @@ Request feature. You can find more details on the Fail2Ban wiki Testing ======= -Existing tests can be run by executing `fail2ban-testcases`. This has options -like --log-level that will probably be useful. `fail2ban-testcases --help` for -full options. +Existing tests can be run by executing `bin/fail2ban-testcases`. This has +options like --log-level that will probably be useful. +`bin/fail2ban-testcases --help` forfull options. Test cases should cover all usual cases, all exception cases and all inside / outside boundary conditions. @@ -39,7 +39,7 @@ Install the package python-coverage to visualise your test coverage. Run the following (note: on Debian-based systems, the script is called `python-coverage`): -coverage run fail2ban-testcases +coverage run bin/fail2ban-testcases coverage html Then look at htmlcov/index.html and see how much coverage your test cases @@ -249,7 +249,7 @@ Takes care about executing start/check/ban/unban/stop commands Releasing ========= -# Ensure the version is correct in ./common/version.py +# Ensure the version is correct in ./fail2ban/version.py # Add/finalize the corresponding entry in the ChangeLog @@ -271,7 +271,7 @@ Releasing # Run the following and update the wiki with output: - python -c 'import common.protocol; common.protocol.printWiki()' + python -c 'import fail2ban.protocol; fail2ban.protocol.printWiki()' # Email users and development list of release diff --git a/MANIFEST b/MANIFEST index fb6f84e4..6395b50d 100644 --- a/MANIFEST +++ b/MANIFEST @@ -5,65 +5,66 @@ THANKS COPYING DEVELOP doc/run-rootless.txt -fail2ban-client -fail2ban-server -fail2ban-testcases -fail2ban-regex fail2ban-2to3 -client/configreader.py -client/configparserinc.py -client/jailreader.py -client/fail2banreader.py -client/jailsreader.py -client/beautifier.py -client/filterreader.py -client/actionreader.py -client/__init__.py -client/configurator.py -client/csocket.py -server/asyncserver.py -server/filter.py -server/filterpyinotify.py -server/filtergamin.py -server/filterpoll.py -server/iso8601.py -server/server.py -server/actions.py -server/faildata.py -server/failmanager.py -server/datedetector.py -server/jailthread.py -server/transmitter.py -server/action.py -server/ticket.py -server/jail.py -server/jails.py -server/__init__.py -server/banmanager.py -server/datetemplate.py -server/mytime.py -server/failregex.py -testcases/files/testcase-usedns.log -testcases/banmanagertestcase.py -testcases/failmanagertestcase.py -testcases/clientreadertestcase.py -testcases/filtertestcase.py -testcases/__init__.py -testcases/datedetectortestcase.py -testcases/actiontestcase.py -testcases/servertestcase.py -testcases/sockettestcase.py -testcases/files/testcase01.log -testcases/files/testcase02.log -testcases/files/testcase03.log -testcases/files/testcase04.log +bin/fail2ban-client +bin/fail2ban-server +bin/fail2ban-testcases +bin/fail2ban-regex +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/client/__init__.py +fail2ban/client/configurator.py +fail2ban/client/csocket.py +fail2ban/server/asyncserver.py +fail2ban/server/filter.py +fail2ban/server/filterpyinotify.py +fail2ban/server/filtergamin.py +fail2ban/server/filterpoll.py +fail2ban/server/iso8601.py +fail2ban/server/server.py +fail2ban/server/actions.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/jail.py +fail2ban/server/jails.py +fail2ban/server/__init__.py +fail2ban/server/banmanager.py +fail2ban/server/datetemplate.py +fail2ban/server/mytime.py +fail2ban/server/failregex.py +fail2ban/tests/banmanagertestcase.py +fail2ban/tests/failmanagertestcase.py +fail2ban/tests/clientreadertestcase.py +fail2ban/tests/filtertestcase.py +fail2ban/tests/__init__.py +fail2ban/tests/datedetectortestcase.py +fail2ban/tests/actiontestcase.py +fail2ban/tests/servertestcase.py +fail2ban/tests/sockettestcase.py +fail2ban/tests/utils.py +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 setup.py setup.cfg -common/__init__.py -common/exceptions.py -common/helpers.py -common/version.py -common/protocol.py +fail2ban/__init__.py +fail2ban/exceptions.py +fail2ban/helpers.py +fail2ban/version.py +fail2ban/protocol.py config/jail.conf config/filter.d/common.conf config/filter.d/apache-auth.conf diff --git a/README b/README index cab683f5..4a6460d5 100644 --- a/README +++ b/README @@ -4,7 +4,7 @@ |_| \__,_|_|_/___|_.__/\__,_|_||_| ================================================================================ -Fail2Ban (version 0.8.8) 2012/07/31 +Fail2Ban (version 0.9.0a0) 20??/??/?? ================================================================================ Fail2Ban scans log files like /var/log/pwdfail and bans IP that makes too many diff --git a/fail2ban-client b/bin/fail2ban-client similarity index 96% rename from fail2ban-client rename to bin/fail2ban-client index d8147f02..8068d60f 100755 --- a/fail2ban-client +++ b/bin/fail2ban-client @@ -25,19 +25,11 @@ __license__ = "GPL" import sys, string, os, pickle, re, logging, signal import getopt, time, shlex, socket -# Inserts our own modules path first in the list -# fix for bug #343821 -try: - from common.version import version -except ImportError, e: - sys.path.insert(1, "/usr/share/fail2ban") - from common.version import version - -# Now we can import the rest of modules -from common.protocol import printFormatted -from client.csocket import CSocket -from client.configurator import Configurator -from client.beautifier import Beautifier +from fail2ban.version import version +from fail2ban.protocol import printFormatted +from fail2ban.client.csocket import CSocket +from fail2ban.client.configurator import Configurator +from fail2ban.client.beautifier import Beautifier # Gets the instance of the logger. logSys = logging.getLogger("fail2ban.client") diff --git a/fail2ban-regex b/bin/fail2ban-regex similarity index 94% rename from fail2ban-regex rename to bin/fail2ban-regex index ce2f898d..9233a671 100755 --- a/fail2ban-regex +++ b/bin/fail2ban-regex @@ -23,19 +23,12 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2012 Yaroslav Halchenko" __license__ = "GPL" import getopt, sys, time, logging, os, locale - -# Inserts our own modules path first in the list -# fix for bug #343821 -try: - from common.version import version -except ImportError, e: - sys.path.insert(1, "/usr/share/fail2ban") - from common.version import version - -from client.configparserinc import SafeConfigParserWithIncludes from ConfigParser import NoOptionError, NoSectionError, MissingSectionHeaderError -from server.filter import Filter -from server.failregex import RegexException + +from fail2ban.version import version +from fail2ban.client.configparserinc import SafeConfigParserWithIncludes +from fail2ban.server.filter import Filter +from fail2ban.server.failregex import RegexException # Gets the instance of the logger. logSys = logging.getLogger("fail2ban.regex") @@ -116,6 +109,7 @@ class Fail2banRegex: print " -h, --help display this help message" print " -V, --version print the version" print " -v, --verbose verbose output" + print " -l INT, --maxlines=INT set maxlines for multi-line regex default: 1" print print "Log:" print " string a string representing a log line" @@ -146,6 +140,14 @@ class Fail2banRegex: self.__verbose = True elif opt[0] in ["-e", "--encoding"]: self.encoding = opt[1] + elif opt[0] in ["-l", "--maxlines"]: + try: + self.__filter.setMaxLines(int(opt[1])) + except ValueError: + print "Invlaid value for maxlines: %s" % ( + opt[1]) + fail2banRegex.dispUsage() + sys.exit(-1) #@staticmethod def logIsFile(value): @@ -323,8 +325,8 @@ if __name__ == "__main__": fail2banRegex = Fail2banRegex() # Reads the command line options. try: - cmdOpts = 'e:hVcv' - cmdLongOpts = ['encoding=', 'help', 'version', 'verbose'] + cmdOpts = 'hVcvl:e:' + cmdLongOpts = ['help', 'version', 'verbose', 'maxlines=', 'encoding='] optList, args = getopt.getopt(sys.argv[1:], cmdOpts, cmdLongOpts) except getopt.GetoptError: fail2banRegex.dispUsage() diff --git a/fail2ban-server b/bin/fail2ban-server similarity index 94% rename from fail2ban-server rename to bin/fail2ban-server index e1231e50..aba19ab5 100755 --- a/fail2ban-server +++ b/bin/fail2ban-server @@ -24,15 +24,8 @@ __license__ = "GPL" import getopt, sys, logging, os -# Inserts our own modules path first in the list -# fix for bug #343821 -try: - from common.version import version -except ImportError, e: - sys.path.insert(1, "/usr/share/fail2ban") - from common.version import version - -from server.server import Server +from fail2ban.version import version +from fail2ban.server.server import Server # Gets the instance of the logger. logSys = logging.getLogger("fail2ban") diff --git a/fail2ban-testcases b/bin/fail2ban-testcases similarity index 87% rename from fail2ban-testcases rename to bin/fail2ban-testcases index e00cc908..68a31786 100755 --- a/fail2ban-testcases +++ b/bin/fail2ban-testcases @@ -27,18 +27,23 @@ __license__ = "GPL" import unittest, logging, sys, time, os -from common.version import version -from testcases import banmanagertestcase -from testcases import clientreadertestcase -from testcases import failmanagertestcase -from testcases import filtertestcase -from testcases import servertestcase -from testcases import datedetectortestcase -from testcases import actiontestcase -from testcases import sockettestcase +# Check if local fail2ban module exists, and use if it exists by +# modifying the path. This is such that tests can be used in dev +# environment. +if os.path.exists("fail2ban/__init__.py"): + sys.path.insert(0, ".") +from fail2ban.version import version +from fail2ban.tests import banmanagertestcase +from fail2ban.tests import clientreadertestcase +from fail2ban.tests import failmanagertestcase +from fail2ban.tests import filtertestcase +from fail2ban.tests import servertestcase +from fail2ban.tests import datedetectortestcase +from fail2ban.tests import actiontestcase +from fail2ban.tests import sockettestcase -from testcases.utils import FormatterWithTraceBack -from server.mytime import MyTime +from fail2ban.tests.utils import FormatterWithTraceBack +from fail2ban.server.mytime import MyTime from optparse import OptionParser, Option @@ -147,6 +152,7 @@ tests.addTest(unittest.makeSuite(banmanagertestcase.AddFailure)) # ClientReaders tests.addTest(unittest.makeSuite(clientreadertestcase.ConfigReaderTest)) tests.addTest(unittest.makeSuite(clientreadertestcase.JailReaderTest)) +tests.addTest(unittest.makeSuite(clientreadertestcase.FilterReaderTest)) tests.addTest(unittest.makeSuite(clientreadertestcase.JailsReaderTest)) # CSocket and AsyncServer tests.addTest(unittest.makeSuite(sockettestcase.Socket)) @@ -168,20 +174,20 @@ tests.addTest(unittest.makeSuite(datedetectortestcase.DateDetectorTest)) # Extensive use-tests of different available filters backends # -from server.filterpoll import FilterPoll +from fail2ban.server.filterpoll import FilterPoll filters = [FilterPoll] # always available # Additional filters available only if external modules are available # yoh: Since I do not know better way for parametric tests # with good old unittest try: - from server.filtergamin import FilterGamin + from fail2ban.server.filtergamin import FilterGamin filters.append(FilterGamin) except Exception, e: # pragma: no cover print "I: Skipping gamin backend testing. Got exception '%s'" % e try: - from server.filterpyinotify import FilterPyinotify + from fail2ban.server.filterpyinotify import FilterPyinotify filters.append(FilterPyinotify) except Exception, e: # pragma: no cover print "I: Skipping pyinotify backend testing. Got exception '%s'" % e diff --git a/config/filter.d/mysqld-auth.conf b/config/filter.d/mysqld-auth.conf new file mode 100644 index 00000000..197c8232 --- /dev/null +++ b/config/filter.d/mysqld-auth.conf @@ -0,0 +1,31 @@ +# Fail2Ban configuration file for unsuccesfull MySQL authentication attempts +# +# Authors: Artur Penttinen +# Yaroslav O. Halchenko +# + +[INCLUDES] + +# Read common prefixes. If any customizations available -- read them from +# common.local +before = common.conf + + +[Definition] + +#_daemon = mysqld + +# Option: failregex +# Notes.: regex to match the password failures messages in the logfile. The +# host must be matched by a group named "host". The tag "" can +# be used for standard IP/hostname matching and is only an alias for +# (?:::f{4,6}:)?(?P[\w\-.^_]+) +# Values: TEXT +# 130322 11:26:54 [Warning] Access denied for user 'root'@'127.0.0.1' (using password: YES) +failregex = Access denied for user '\w+'@'' (to database '[^']*'|\(using password: (YES|NO)\))*\s*$ + +# Option: ignoreregex +# Notes.: regex to ignore. If this regex matches, the line is ignored. +# Values: TEXT +# +ignoreregex = diff --git a/config/jail.conf b/config/jail.conf index 195c8e2f..d0005537 100644 --- a/config/jail.conf +++ b/config/jail.conf @@ -352,6 +352,19 @@ action = iptables-multiport[name=asterisk-udp, port="5060,5061", protocol=udp] logpath = /var/log/asterisk/messages maxretry = 10 +# To log wrong MySQL access attempts add to /etc/my.cnf: +# log-error=/var/log/mysqld.log +# log-warning = 2 +[mysqld-iptables] + +enabled = false +filter = mysqld-auth +action = iptables[name=mysql, port=3306, protocol=tcp] + sendmail-whois[name=MySQL, dest=root, sender=fail2ban@example.com] +logpath = /var/log/mysqld.log +maxretry = 5 + + # Jail for more extended banning of persistent abusers # !!! WARNING !!! # Make sure that your loglevel specified in fail2ban.conf/.local diff --git a/fail2ban-testcases-all b/fail2ban-testcases-all index fd33dce4..6b399337 100755 --- a/fail2ban-testcases-all +++ b/fail2ban-testcases-all @@ -9,7 +9,7 @@ for python in /usr/{,local/}bin/python2.[0-9]{,.*}{,-dbg} do [ -e "$python" ] || continue echo "Testing using $python" - $python ./fail2ban-testcases "$@" || failed+=" $python" + $python bin/fail2ban-testcases "$@" || failed+=" $python" done if [ ! -z "$failed" ]; then diff --git a/client/__init__.py b/fail2ban/__init__.py similarity index 100% rename from client/__init__.py rename to fail2ban/__init__.py diff --git a/common/__init__.py b/fail2ban/client/__init__.py similarity index 100% rename from common/__init__.py rename to fail2ban/client/__init__.py diff --git a/client/actionreader.py b/fail2ban/client/actionreader.py similarity index 98% rename from client/actionreader.py rename to fail2ban/client/actionreader.py index 9ad1ef28..787a41c7 100644 --- a/client/actionreader.py +++ b/fail2ban/client/actionreader.py @@ -31,7 +31,7 @@ import logging from configreader import ConfigReader # Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.client.config") +logSys = logging.getLogger(__name__) class ActionReader(ConfigReader): diff --git a/client/beautifier.py b/fail2ban/client/beautifier.py similarity index 97% rename from client/beautifier.py rename to fail2ban/client/beautifier.py index 1653ff63..153ab905 100644 --- a/client/beautifier.py +++ b/fail2ban/client/beautifier.py @@ -23,10 +23,10 @@ __license__ = "GPL" import logging -from common.exceptions import UnknownJailException, DuplicateJailException +from fail2ban.exceptions import UnknownJailException, DuplicateJailException # Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.client.config") +logSys = logging.getLogger(__name__) ## # Beautify the output of the client. diff --git a/client/configparserinc.py b/fail2ban/client/configparserinc.py similarity index 98% rename from client/configparserinc.py rename to fail2ban/client/configparserinc.py index 7ac8b4a5..0ffa0728 100644 --- a/client/configparserinc.py +++ b/fail2ban/client/configparserinc.py @@ -31,7 +31,7 @@ import logging, os from ConfigParser import SafeConfigParser # Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.client.config") +logSys = logging.getLogger(__name__) class SafeConfigParserWithIncludes(SafeConfigParser): """ diff --git a/client/configreader.py b/fail2ban/client/configreader.py similarity index 98% rename from client/configreader.py rename to fail2ban/client/configreader.py index 243c843c..6f1e7740 100644 --- a/client/configreader.py +++ b/fail2ban/client/configreader.py @@ -32,7 +32,7 @@ from configparserinc import SafeConfigParserWithIncludes from ConfigParser import NoOptionError, NoSectionError # Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.client.config") +logSys = logging.getLogger(__name__) class ConfigReader(SafeConfigParserWithIncludes): diff --git a/client/configurator.py b/fail2ban/client/configurator.py similarity index 97% rename from client/configurator.py rename to fail2ban/client/configurator.py index 2097fd54..23ee88c6 100644 --- a/client/configurator.py +++ b/fail2ban/client/configurator.py @@ -33,7 +33,7 @@ from fail2banreader import Fail2banReader from jailsreader import JailsReader # Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.client.config") +logSys = logging.getLogger(__name__) class Configurator: diff --git a/client/csocket.py b/fail2ban/client/csocket.py similarity index 100% rename from client/csocket.py rename to fail2ban/client/csocket.py diff --git a/client/fail2banreader.py b/fail2ban/client/fail2banreader.py similarity index 97% rename from client/fail2banreader.py rename to fail2ban/client/fail2banreader.py index c8f42976..e388f9ae 100644 --- a/client/fail2banreader.py +++ b/fail2ban/client/fail2banreader.py @@ -31,7 +31,7 @@ import logging from configreader import ConfigReader # Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.client.config") +logSys = logging.getLogger(__name__) class Fail2banReader(ConfigReader): diff --git a/client/filterreader.py b/fail2ban/client/filterreader.py similarity index 97% rename from client/filterreader.py rename to fail2ban/client/filterreader.py index 7dba3579..8b00446e 100644 --- a/client/filterreader.py +++ b/fail2ban/client/filterreader.py @@ -31,7 +31,7 @@ import logging from configreader import ConfigReader # Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.client.config") +logSys = logging.getLogger(__name__) class FilterReader(ConfigReader): diff --git a/client/jailreader.py b/fail2ban/client/jailreader.py similarity index 97% rename from client/jailreader.py rename to fail2ban/client/jailreader.py index 3b825186..6e35bc0b 100644 --- a/client/jailreader.py +++ b/fail2ban/client/jailreader.py @@ -34,7 +34,7 @@ from filterreader import FilterReader from actionreader import ActionReader # Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.client.config") +logSys = logging.getLogger(__name__) class JailReader(ConfigReader): @@ -65,6 +65,7 @@ class JailReader(ConfigReader): ["string", "logencoding", "auto"], ["string", "backend", "auto"], ["int", "maxretry", 3], + ["int", "maxlines", 1], ["int", "findtime", 600], ["int", "bantime", 600], ["string", "usedns", "warn"], @@ -123,6 +124,8 @@ class JailReader(ConfigReader): backend = self.__opts[opt] elif opt == "maxretry": stream.append(["set", self.__name, "maxretry", self.__opts[opt]]) + elif opt == "maxlines": + stream.append(["set", self.__name, "maxlines", self.__opts[opt]]) elif opt == "ignoreip": for ip in self.__opts[opt].split(): # Do not send a command if the rule is empty. diff --git a/client/jailsreader.py b/fail2ban/client/jailsreader.py similarity index 98% rename from client/jailsreader.py rename to fail2ban/client/jailsreader.py index 91e178d6..345a225f 100644 --- a/client/jailsreader.py +++ b/fail2ban/client/jailsreader.py @@ -32,7 +32,7 @@ from configreader import ConfigReader from jailreader import JailReader # Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.client.config") +logSys = logging.getLogger(__name__) class JailsReader(ConfigReader): diff --git a/common/exceptions.py b/fail2ban/exceptions.py similarity index 100% rename from common/exceptions.py rename to fail2ban/exceptions.py diff --git a/common/helpers.py b/fail2ban/helpers.py similarity index 100% rename from common/helpers.py rename to fail2ban/helpers.py diff --git a/common/protocol.py b/fail2ban/protocol.py similarity index 97% rename from common/protocol.py rename to fail2ban/protocol.py index 1ac8bef5..cacde4c2 100644 --- a/common/protocol.py +++ b/fail2ban/protocol.py @@ -68,6 +68,7 @@ protocol = [ ["set banip ", "manually Ban for "], ["set unbanip ", "manually Unban in "], ["set maxretry ", "sets the number of failures before banning the host for "], +["set maxlines ", "sets the number of to buffer for regex search for "], ["set addaction ", "adds a new action named for "], ["set delaction ", "removes the action from "], ["set setcinfo ", "sets for of the action for "], @@ -87,6 +88,7 @@ protocol = [ ["get bantime", "gets the time a host is banned for "], ["get usedns", "gets the usedns setting for "], ["get maxretry", "gets the number of failures allowed for "], +["get maxlines", "gets the number of lines to buffer for "], ["get addaction", "gets the last action which has been added for "], ["get actionstart ", "gets the start command for the action for "], ["get actionstop ", "gets the stop command for the action for "], diff --git a/server/__init__.py b/fail2ban/server/__init__.py similarity index 100% rename from server/__init__.py rename to fail2ban/server/__init__.py diff --git a/server/action.py b/fail2ban/server/action.py similarity index 99% rename from server/action.py rename to fail2ban/server/action.py index 204539c1..5fc31b28 100644 --- a/server/action.py +++ b/fail2ban/server/action.py @@ -32,7 +32,7 @@ import threading #from subprocess import call # Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.actions.action") +logSys = logging.getLogger(__name__) # Create a lock for running system commands _cmd_lock = threading.Lock() diff --git a/server/actions.py b/fail2ban/server/actions.py similarity index 99% rename from server/actions.py rename to fail2ban/server/actions.py index ddcc83d6..c0373239 100644 --- a/server/actions.py +++ b/fail2ban/server/actions.py @@ -34,7 +34,7 @@ from mytime import MyTime import time, logging # Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.actions") +logSys = logging.getLogger(__name__) ## # Execute commands. diff --git a/server/asyncserver.py b/fail2ban/server/asyncserver.py similarity index 98% rename from server/asyncserver.py rename to fail2ban/server/asyncserver.py index ee5ec0c6..1f46d936 100644 --- a/server/asyncserver.py +++ b/fail2ban/server/asyncserver.py @@ -28,11 +28,12 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier" __license__ = "GPL" from pickle import dumps, loads, HIGHEST_PROTOCOL -from common import helpers import asyncore, asynchat, socket, os, logging, sys, traceback +from fail2ban import helpers + # Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.server") +logSys = logging.getLogger(__name__) ## # Request handler class. diff --git a/server/banmanager.py b/fail2ban/server/banmanager.py similarity index 99% rename from server/banmanager.py rename to fail2ban/server/banmanager.py index 1143f791..9c81f252 100644 --- a/server/banmanager.py +++ b/fail2ban/server/banmanager.py @@ -33,7 +33,7 @@ from mytime import MyTime import logging # Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.action") +logSys = logging.getLogger(__name__) ## # Banning Manager. diff --git a/server/datedetector.py b/fail2ban/server/datedetector.py similarity index 96% rename from server/datedetector.py rename to fail2ban/server/datedetector.py index 7876580d..c384041d 100644 --- a/server/datedetector.py +++ b/fail2ban/server/datedetector.py @@ -33,7 +33,7 @@ from datetemplate import DateStrptime, DateTai64n, DateEpoch, DateISO8601 from threading import Lock # Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.filter.datedetector") +logSys = logging.getLogger(__name__) class DateDetector: @@ -155,6 +155,12 @@ class DateDetector: template.setRegex("^<\d{2}/\d{2}/\d{2}@\d{2}:\d{2}:\d{2}>") template.setPattern("<%m/%d/%y@%H:%M:%S>") self._appendTemplate(template) + # MySQL: 130322 11:46:11 + template = DateStrptime() + template.setName("MonthDayYear Hour:Minute:Second") + template.setRegex("^\d{2}\d{2}\d{2} +\d{1,2}:\d{2}:\d{2}") + template.setPattern("%y%m%d %H:%M:%S") + self._appendTemplate(template) finally: self.__lock.release() diff --git a/server/datetemplate.py b/fail2ban/server/datetemplate.py similarity index 99% rename from server/datetemplate.py rename to fail2ban/server/datetemplate.py index ab623978..f77f00c9 100644 --- a/server/datetemplate.py +++ b/fail2ban/server/datetemplate.py @@ -33,7 +33,7 @@ from mytime import MyTime import iso8601 import logging -logSys = logging.getLogger("fail2ban.datetemplate") +logSys = logging.getLogger(__name__) class DateTemplate: diff --git a/server/faildata.py b/fail2ban/server/faildata.py similarity index 97% rename from server/faildata.py rename to fail2ban/server/faildata.py index 1f0bda04..efda51e1 100644 --- a/server/faildata.py +++ b/fail2ban/server/faildata.py @@ -30,7 +30,7 @@ __license__ = "GPL" import logging # Gets the instance of the logger. -logSys = logging.getLogger("fail2ban") +logSys = logging.getLogger(__name__) class FailData: diff --git a/server/failmanager.py b/fail2ban/server/failmanager.py similarity index 98% rename from server/failmanager.py rename to fail2ban/server/failmanager.py index 02f16ce3..60e71c7b 100644 --- a/server/failmanager.py +++ b/fail2ban/server/failmanager.py @@ -33,7 +33,7 @@ from threading import Lock import logging # Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.filter") +logSys = logging.getLogger(__name__) class FailManager: diff --git a/server/failregex.py b/fail2ban/server/failregex.py similarity index 63% rename from server/failregex.py rename to fail2ban/server/failregex.py index b194d472..890cd364 100644 --- a/server/failregex.py +++ b/fail2ban/server/failregex.py @@ -48,10 +48,15 @@ class Regex: # Perform shortcuts expansions. # Replace "" with default regular expression for host. regex = regex.replace("", "(?:::f{4,6}:)?(?P[\w\-.^_]+)") + # Replace "" with regular expression for multiple lines. + regexSplit = regex.split("") + regex = regexSplit[0] + for n, regexLine in enumerate(regexSplit[1:]): + regex += "\n(?P(?:(.*\n)*?))" % n + regexLine if regex.lstrip() == '': raise RegexException("Cannot add empty regex") try: - self._regexObj = re.compile(regex) + self._regexObj = re.compile(regex, re.MULTILINE) self._regex = regex except sre_constants.error: raise RegexException("Unable to compile regular expression '%s'" % @@ -76,6 +81,19 @@ class Regex: def search(self, value): self._matchCache = self._regexObj.search(value) + if self.hasMatched(): + # Find start of the first line where the match was found + try: + self._matchLineStart = self._matchCache.string.rindex( + "\n", 0, self._matchCache.start() +1 ) + 1 + except ValueError: + self._matchLineStart = 0 + # Find end of the last line where the match was found + try: + self._matchLineEnd = self._matchCache.string.index( + "\n", self._matchCache.end() - 1) + 1 + except ValueError: + self._matchLineEnd = len(self._matchCache.string) ## # Checks if the previous call to search() matched. @@ -88,6 +106,54 @@ class Regex: else: return False + ## + # Returns skipped lines. + # + # This returns skipped lines captured by the tag. + # @return list of skipped lines + + def getSkippedLines(self): + if not self._matchCache: + return [] + skippedLines = "" + n = 0 + while True: + try: + skippedLines += self._matchCache.group("skiplines%i" % n) + n += 1 + except IndexError: + break + return skippedLines.splitlines(True) + + ## + # Returns unmatched lines. + # + # This returns unmatched lines including captured by the tag. + # @return list of unmatched lines + + def getUnmatchedLines(self): + if not self.hasMatched(): + return [] + unmatchedLines = ( + self._matchCache.string[:self._matchLineStart].splitlines(True) + + self.getSkippedLines() + + self._matchCache.string[self._matchLineEnd:].splitlines(True)) + return unmatchedLines + + ## + # Returns matched lines. + # + # This returns matched lines by excluding those captured + # by the tag. + # @return list of matched lines + + def getMatchedLines(self): + if not self.hasMatched(): + return [] + matchedLines = self._matchCache.string[ + self._matchLineStart:self._matchLineEnd].splitlines(True) + return [line for line in matchedLines + if line not in self.getSkippedLines()] ## # Exception dedicated to the class Regex. diff --git a/server/filter.py b/fail2ban/server/filter.py similarity index 94% rename from server/filter.py rename to fail2ban/server/filter.py index c813f6be..b5f910f3 100644 --- a/server/filter.py +++ b/fail2ban/server/filter.py @@ -38,7 +38,7 @@ from failregex import FailRegex, Regex, RegexException import logging, re, os, fcntl, time, sys, locale, codecs # Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.filter") +logSys = logging.getLogger(__name__) ## # Log reader class. @@ -71,6 +71,12 @@ class Filter(JailThread): self.__findTime = 6000 ## The ignore IP list. self.__ignoreIpList = [] + ## Size of line buffer + self.__lineBufferSize = 1 + ## Line buffer + self.__lineBuffer = [] + ## Store last time stamp, applicable for multi-line + self.__lastTimeLine = "" self.dateDetector = DateDetector() self.dateDetector.addDefaultTemplate() @@ -206,6 +212,23 @@ class Filter(JailThread): def getMaxRetry(self): return self.failManager.getMaxRetry() + ## + # Set the maximum line buffer size. + # + # @param value the line buffer size + + def setMaxLines(self, value): + self.__lineBufferSize = max(1, value) + logSys.info("Set maxLines = %i" % self.__lineBufferSize) + + ## + # Get the maximum line buffer size. + # + # @return the line buffer size + + def getMaxLines(self): + return self.__lineBufferSize + ## # Main loop. # @@ -295,14 +318,17 @@ class Filter(JailThread): if timeMatch: # Lets split into time part and log part of the line timeLine = timeMatch.group() + self.__lastTimeLine = timeLine # Lets leave the beginning in as well, so if there is no # anchore at the beginning of the time regexp, we don't # at least allow injection. Should be harmless otherwise logLine = line[:timeMatch.start()] + line[timeMatch.end():] else: - timeLine = line + timeLine = self.__lastTimeLine or line logLine = line - return self.findFailure(timeLine, logLine) + self.__lineBuffer = ((self.__lineBuffer + + [logLine])[-self.__lineBufferSize:]) + return self.findFailure(timeLine, "".join(self.__lineBuffer)) def processLineAndAdd(self, line): """Processes the line for failures and populates failManager @@ -345,14 +371,15 @@ class Filter(JailThread): def findFailure(self, timeLine, logLine): failList = list() - # Checks if we must ignore this line. - if self.ignoreLine(logLine): - # The ignoreregex matched. Return. - return failList # Iterates over all the regular expressions. for failRegex in self.__failRegex: failRegex.search(logLine) if failRegex.hasMatched(): + # Checks if we must ignore this match. + if self.ignoreLine("".join(failRegex.getMatchedLines())): + # The ignoreregex matched. Remove ignored match. + self.__lineBuffer = failRegex.getUnmatchedLines() + continue # The failregex matched. date = self.dateDetector.getUnixTime(timeLine) if date == None: @@ -362,6 +389,7 @@ class Filter(JailThread): "in order to get support for this format." % (logLine, timeLine)) else: + self.__lineBuffer = failRegex.getUnmatchedLines() try: host = failRegex.getHost() ipMatch = DNSUtils.textToIp(host, self.__useDns) diff --git a/server/filtergamin.py b/fail2ban/server/filtergamin.py similarity index 98% rename from server/filtergamin.py rename to fail2ban/server/filtergamin.py index cff5aa54..e324b677 100644 --- a/server/filtergamin.py +++ b/fail2ban/server/filtergamin.py @@ -30,7 +30,7 @@ from mytime import MyTime import time, logging, gamin # Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.filter") +logSys = logging.getLogger(__name__) ## # Log reader class. diff --git a/server/filterpoll.py b/fail2ban/server/filterpoll.py similarity index 98% rename from server/filterpoll.py rename to fail2ban/server/filterpoll.py index f0e23ac1..3217e958 100644 --- a/server/filterpoll.py +++ b/fail2ban/server/filterpoll.py @@ -33,7 +33,7 @@ from mytime import MyTime import time, logging, os # Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.filter") +logSys = logging.getLogger(__name__) ## # Log reader class. diff --git a/server/filterpyinotify.py b/fail2ban/server/filterpyinotify.py similarity index 99% rename from server/filterpyinotify.py rename to fail2ban/server/filterpyinotify.py index e86498b0..786c6dfa 100644 --- a/server/filterpyinotify.py +++ b/fail2ban/server/filterpyinotify.py @@ -38,7 +38,7 @@ if not hasattr(pyinotify, '__version__') \ from os.path import dirname, sep as pathsep # Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.filter") +logSys = logging.getLogger(__name__) ## # Log reader class. diff --git a/server/iso8601.py b/fail2ban/server/iso8601.py similarity index 100% rename from server/iso8601.py rename to fail2ban/server/iso8601.py diff --git a/server/jail.py b/fail2ban/server/jail.py similarity index 99% rename from server/jail.py rename to fail2ban/server/jail.py index dee64e7f..fa2a8fa5 100644 --- a/server/jail.py +++ b/fail2ban/server/jail.py @@ -28,7 +28,7 @@ import Queue, logging from actions import Actions # Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.jail") +logSys = logging.getLogger(__name__) class Jail: diff --git a/server/jails.py b/fail2ban/server/jails.py similarity index 98% rename from server/jails.py rename to fail2ban/server/jails.py index 4bf5f971..7ea1dde0 100644 --- a/server/jails.py +++ b/fail2ban/server/jails.py @@ -21,7 +21,7 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko" __copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2013- Yaroslav Halchenko" __license__ = "GPL" -from common.exceptions import DuplicateJailException, UnknownJailException +from fail2ban.exceptions import DuplicateJailException, UnknownJailException from jail import Jail from threading import Lock diff --git a/server/jailthread.py b/fail2ban/server/jailthread.py similarity index 98% rename from server/jailthread.py rename to fail2ban/server/jailthread.py index 343ea7e2..98fd4066 100644 --- a/server/jailthread.py +++ b/fail2ban/server/jailthread.py @@ -31,7 +31,7 @@ from threading import Thread import logging # Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.server") +logSys = logging.getLogger(__name__) class JailThread(Thread): diff --git a/server/mytime.py b/fail2ban/server/mytime.py similarity index 100% rename from server/mytime.py rename to fail2ban/server/mytime.py diff --git a/server/server.py b/fail2ban/server/server.py similarity index 96% rename from server/server.py rename to fail2ban/server/server.py index 05d08aaf..7eec52d2 100644 --- a/server/server.py +++ b/fail2ban/server/server.py @@ -32,11 +32,11 @@ from jails import Jails from transmitter import Transmitter from asyncserver import AsyncServer from asyncserver import AsyncServerException -from common import version +from fail2ban import version import logging, logging.handlers, sys, os, signal # Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.server") +logSys = logging.getLogger(__name__) class Server: @@ -223,6 +223,12 @@ class Server: def getMaxRetry(self, name): return self.__jails.getFilter(name).getMaxRetry() + def setMaxLines(self, name, value): + self.__jails.getFilter(name).setMaxLines(value) + + def getMaxLines(self, name): + return self.__jails.getFilter(name).getMaxLines() + # Action def addAction(self, name, value): self.__jails.getAction(name).addAction(value) @@ -326,7 +332,7 @@ class Server: logLevel = logging.WARNING elif value == 3: logLevel = logging.INFO - logging.getLogger("fail2ban").setLevel(logLevel) + logging.getLogger(__name__).parent.parent.setLevel(logLevel) finally: self.__loggingLock.release() @@ -375,9 +381,10 @@ class Server: return False # Removes previous handlers -- in reverse order since removeHandler # alter the list in-place and that can confuses the iterable - for handler in logging.getLogger("fail2ban").handlers[::-1]: + logger = logging.getLogger(__name__).parent.parent + for handler in logger.handlers[::-1]: # Remove the handler. - logging.getLogger("fail2ban").removeHandler(handler) + logger.removeHandler(handler) # And try to close -- it might be closed already try: handler.flush() @@ -390,7 +397,7 @@ class Server: # with older Pythons -- seems to be safe to ignore there # tell the handler to use this format hdlr.setFormatter(formatter) - logging.getLogger("fail2ban").addHandler(hdlr) + logger.addHandler(hdlr) # Does not display this message at startup. if not self.__logTarget == None: logSys.info("Changed logging target to %s for Fail2ban v%s" % diff --git a/server/ticket.py b/fail2ban/server/ticket.py similarity index 98% rename from server/ticket.py rename to fail2ban/server/ticket.py index c03761c1..95643d15 100644 --- a/server/ticket.py +++ b/fail2ban/server/ticket.py @@ -30,7 +30,7 @@ __license__ = "GPL" import logging # Gets the instance of the logger. -logSys = logging.getLogger("fail2ban") +logSys = logging.getLogger(__name__) class Ticket: diff --git a/server/transmitter.py b/fail2ban/server/transmitter.py similarity index 97% rename from server/transmitter.py rename to fail2ban/server/transmitter.py index 900edf0c..7d4d2509 100644 --- a/server/transmitter.py +++ b/fail2ban/server/transmitter.py @@ -30,7 +30,7 @@ __license__ = "GPL" import logging, time # Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.comm") +logSys = logging.getLogger(__name__) class Transmitter: @@ -175,6 +175,10 @@ class Transmitter: value = command[2] self.__server.setMaxRetry(name, int(value)) return self.__server.getMaxRetry(name) + elif command[1] == "maxlines": + value = command[2] + self.__server.setMaxLines(name, int(value)) + return self.__server.getMaxLines(name) # command elif command[1] == "bantime": value = command[2] @@ -256,6 +260,8 @@ class Transmitter: return self.__server.getFindTime(name) elif command[1] == "maxretry": return self.__server.getMaxRetry(name) + elif command[1] == "maxlines": + return self.__server.getMaxLines(name) # Action elif command[1] == "bantime": return self.__server.getBanTime(name) diff --git a/testcases/__init__.py b/fail2ban/tests/__init__.py similarity index 100% rename from testcases/__init__.py rename to fail2ban/tests/__init__.py diff --git a/testcases/actiontestcase.py b/fail2ban/tests/actiontestcase.py similarity index 98% rename from testcases/actiontestcase.py rename to fail2ban/tests/actiontestcase.py index b8292c27..e0ea3a9b 100644 --- a/testcases/actiontestcase.py +++ b/fail2ban/tests/actiontestcase.py @@ -29,9 +29,10 @@ __license__ = "GPL" import unittest, time import logging, sys -from server.action import Action from StringIO import StringIO +from fail2ban.server.action import Action + class ExecuteAction(unittest.TestCase): def setUp(self): diff --git a/testcases/banmanagertestcase.py b/fail2ban/tests/banmanagertestcase.py similarity index 95% rename from testcases/banmanagertestcase.py rename to fail2ban/tests/banmanagertestcase.py index 6d0ce55a..8d0f1929 100644 --- a/testcases/banmanagertestcase.py +++ b/fail2ban/tests/banmanagertestcase.py @@ -28,8 +28,9 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier" __license__ = "GPL" import unittest -from server.banmanager import BanManager -from server.ticket import BanTicket + +from fail2ban.server.banmanager import BanManager +from fail2ban.server.ticket import BanTicket class AddFailure(unittest.TestCase): diff --git a/testcases/clientreadertestcase.py b/fail2ban/tests/clientreadertestcase.py similarity index 70% rename from testcases/clientreadertestcase.py rename to fail2ban/tests/clientreadertestcase.py index fad16f04..99d9a50e 100644 --- a/testcases/clientreadertestcase.py +++ b/fail2ban/tests/clientreadertestcase.py @@ -22,10 +22,18 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2013 Yaroslav Halchenko" __license__ = "GPL" import os, shutil, tempfile, unittest -from client.configreader import ConfigReader -from client.jailreader import JailReader -from client.jailsreader import JailsReader -from client.configurator import Configurator + +from fail2ban.client.configreader import ConfigReader +from fail2ban.client.jailreader import JailReader +from fail2ban.client.filterreader import FilterReader +from fail2ban.client.jailsreader import JailsReader +from fail2ban.client.configurator import Configurator + +TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files") +if os.path.exists('config/fail2ban.conf'): + CONFIG_DIR='config' +else: + CONFIG_DIR='/etc/fail2ban' class ConfigReaderTest(unittest.TestCase): @@ -98,7 +106,7 @@ option = %s class JailReaderTest(unittest.TestCase): def testStockSSHJail(self): - jail = JailReader('ssh-iptables', basedir='config') # we are running tests from root project dir atm + jail = JailReader('ssh-iptables', basedir=CONFIG_DIR) # we are running tests from root project dir atm self.assertTrue(jail.read()) self.assertTrue(jail.getOptions()) self.assertFalse(jail.isEnabled()) @@ -109,6 +117,36 @@ class JailReaderTest(unittest.TestCase): expected = ['mail-whois', {'name': 'SSH'}] result = JailReader.splitAction(action) self.assertEquals(expected, result) + +class FilterReaderTest(unittest.TestCase): + + def testConvert(self): + output = [['set', 'testcase01', 'addfailregex', + "^\\s*(?:\\S+ )?(?:kernel: \\[\\d+\\.\\d+\\] )?(?:@vserver_\\S+ )" + "?(?:(?:\\[\\d+\\])?:\\s+[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?|" + "[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?(?:\\[\\d+\\])?:)?\\s*(?:" + "error: PAM: )?Authentication failure for .* from \\s*$"], + ['set', 'testcase01', 'addfailregex', + "^\\s*(?:\\S+ )?(?:kernel: \\[\\d+\\.\\d+\\] )?(?:@vserver_\\S+ )" + "?(?:(?:\\[\\d+\\])?:\\s+[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?|" + "[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?(?:\\[\\d+\\])?:)?\\s*(?:" + "error: PAM: )?User not known to the underlying authentication mo" + "dule for .* from \\s*$"], + ['set', 'testcase01', 'addfailregex', + "^\\s*(?:\\S+ )?(?:kernel: \\[\\d+\\.\\d+\\] )?(?:@vserver_\\S+ )" + "?(?:(?:\\[\\d+\\])?:\\s+[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?|" + "[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?(?:\\[\\d+\\])?:)?\\s*(?:" + "error: PAM: )?User not known to the\\nunderlying authentication." + "+$^.+ module for .* from \\s*$"], + ['set', 'testcase01', 'addignoreregex', + "^.+ john from host 192.168.1.1\\s*$"]] + filterReader = FilterReader("testcase01", "testcase01") + filterReader.setBaseDir(TEST_FILES_DIR) + filterReader.read() + #filterReader.getOptions(["failregex", "ignoreregex"]) + filterReader.getOptions(None) + + self.assertEquals(filterReader.convert(), output) class JailsReaderTest(unittest.TestCase): @@ -118,7 +156,7 @@ class JailsReaderTest(unittest.TestCase): self.assertRaises(ValueError, reader.read) def testReadStockJailConf(self): - jails = JailsReader(basedir='config') # we are running tests from root project dir atm + jails = JailsReader(basedir=CONFIG_DIR) # 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() @@ -129,7 +167,7 @@ class JailsReaderTest(unittest.TestCase): 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', force_enable=True) # we are running tests from root project dir atm + jails = JailsReader(basedir=CONFIG_DIR, force_enable=True) # 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() @@ -151,8 +189,8 @@ class JailsReaderTest(unittest.TestCase): def testConfigurator(self): configurator = Configurator() - configurator.setBaseDir('config') - self.assertEqual(configurator.getBaseDir(), 'config') + configurator.setBaseDir(CONFIG_DIR) + self.assertEqual(configurator.getBaseDir(), CONFIG_DIR) configurator.readEarly() opts = configurator.getEarlyOptions() @@ -165,4 +203,4 @@ class JailsReaderTest(unittest.TestCase): # otherwise just a code smoke test) configurator._Configurator__jails.setBaseDir('/tmp') self.assertEqual(configurator._Configurator__jails.getBaseDir(), '/tmp') - self.assertEqual(configurator.getBaseDir(), 'config') + self.assertEqual(configurator.getBaseDir(), CONFIG_DIR) diff --git a/testcases/datedetectortestcase.py b/fail2ban/tests/datedetectortestcase.py similarity index 97% rename from testcases/datedetectortestcase.py rename to fail2ban/tests/datedetectortestcase.py index 64af1fab..e30027e1 100644 --- a/testcases/datedetectortestcase.py +++ b/fail2ban/tests/datedetectortestcase.py @@ -28,8 +28,9 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier" __license__ = "GPL" import unittest -from server.datedetector import DateDetector -from server.datetemplate import DateTemplate + +from fail2ban.server.datedetector import DateDetector +from fail2ban.server.datetemplate import DateTemplate class DateDetectorTest(unittest.TestCase): @@ -84,6 +85,7 @@ class DateDetectorTest(unittest.TestCase): "2005-01-23T21:59:59.252Z", #ISO 8601 "2005-01-23T21:59:59-05:00Z", #ISO 8601 with TZ "<01/23/05@21:59:59>", + "050123 21:59:59", # MySQL ): log = sdate + "[sshd] error: PAM: Authentication failure" # exclude diff --git a/testcases/failmanagertestcase.py b/fail2ban/tests/failmanagertestcase.py similarity index 96% rename from testcases/failmanagertestcase.py rename to fail2ban/tests/failmanagertestcase.py index ffee4ff1..7a714122 100644 --- a/testcases/failmanagertestcase.py +++ b/fail2ban/tests/failmanagertestcase.py @@ -28,8 +28,9 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier" __license__ = "GPL" import unittest, socket, time, pickle -from server.failmanager import FailManager, FailManagerEmpty -from server.ticket import FailTicket + +from fail2ban.server.failmanager import FailManager, FailManagerEmpty +from fail2ban.server.ticket import FailTicket class AddFailure(unittest.TestCase): diff --git a/fail2ban/tests/files/filter.d/testcase-common.conf b/fail2ban/tests/files/filter.d/testcase-common.conf new file mode 100644 index 00000000..af7df1f7 --- /dev/null +++ b/fail2ban/tests/files/filter.d/testcase-common.conf @@ -0,0 +1,41 @@ +# Generic configuration items (to be used as interpolations) in other +# filters or actions configurations +# +# Author: Yaroslav Halchenko +# +# $Revision$ +# + +[DEFAULT] + +# Daemon definition is to be specialized (if needed) in .conf file +_daemon = \S* + +# +# Shortcuts for easier comprehension of the failregex +# +# PID. +# EXAMPLES: [123] +__pid_re = (?:\[\d+\]) + +# Daemon name (with optional source_file:line or whatever) +# EXAMPLES: pam_rhosts_auth, [sshd], pop(pam_unix) +__daemon_re = [\[\(]?%(_daemon)s(?:\(\S+\))?[\]\)]?:? + +# Combinations of daemon name and PID +# EXAMPLES: sshd[31607], pop(pam_unix)[4920] +__daemon_combs_re = (?:%(__pid_re)s?:\s+%(__daemon_re)s|%(__daemon_re)s%(__pid_re)s?:) + +# Some messages have a kernel prefix with a timestamp +# EXAMPLES: kernel: [769570.846956] +__kernel_prefix = kernel: \[\d+\.\d+\] + +__hostname = \S+ + +# +# Common line prefixes (beginnings) which could be used in filters +# +# [hostname] [vserver tag] daemon_id spaces +# this can be optional (for instance if we match named native log files) +__prefix_line = \s*(?:%(__hostname)s )?(?:%(__kernel_prefix)s )?(?:@vserver_\S+ )?%(__daemon_combs_re)s?\s* + diff --git a/fail2ban/tests/files/filter.d/testcase01.conf b/fail2ban/tests/files/filter.d/testcase01.conf new file mode 100644 index 00000000..4a3a95e9 --- /dev/null +++ b/fail2ban/tests/files/filter.d/testcase01.conf @@ -0,0 +1,34 @@ +# Fail2Ban configuration file +# +# Author: Cyril Jaquier +# +# $Revision$ +# + +[INCLUDES] + +# Read common prefixes. If any customizations available -- read them from +# common.local +before = testcase-common.conf + + +[Definition] + +_daemon = sshd + +# Option: failregex +# Notes.: regex to match the password failures messages in the logfile. The +# host must be matched by a group named "host". The tag "" can +# be used for standard IP/hostname matching and is only an alias for +# (?:::f{4,6}:)?(?P[\w\-.^_]+) +# Values: TEXT +# +failregex = ^%(__prefix_line)s(?:error: PAM: )?Authentication failure for .* from \s*$ + ^%(__prefix_line)s(?:error: PAM: )?User not known to the underlying authentication module for .* from \s*$ + ^%(__prefix_line)s(?:error: PAM: )?User not known to the\nunderlying authentication.+$^.+ module for .* from \s*$ + +# Option: ignoreregex +# Notes.: regex to ignore. If this regex matches, the line is ignored. +# Values: TEXT +# +ignoreregex = ^.+ john from host 192.168.1.1\s*$ diff --git a/testcases/files/logs/apache-overflows b/fail2ban/tests/files/logs/apache-overflows similarity index 100% rename from testcases/files/logs/apache-overflows rename to fail2ban/tests/files/logs/apache-overflows diff --git a/testcases/files/logs/asterisk b/fail2ban/tests/files/logs/asterisk similarity index 100% rename from testcases/files/logs/asterisk rename to fail2ban/tests/files/logs/asterisk diff --git a/testcases/files/logs/dovecot b/fail2ban/tests/files/logs/dovecot similarity index 100% rename from testcases/files/logs/dovecot rename to fail2ban/tests/files/logs/dovecot diff --git a/testcases/files/logs/exim b/fail2ban/tests/files/logs/exim similarity index 100% rename from testcases/files/logs/exim rename to fail2ban/tests/files/logs/exim diff --git a/testcases/files/logs/lighttpd b/fail2ban/tests/files/logs/lighttpd similarity index 100% rename from testcases/files/logs/lighttpd rename to fail2ban/tests/files/logs/lighttpd diff --git a/fail2ban/tests/files/logs/mysqld.log b/fail2ban/tests/files/logs/mysqld.log new file mode 100644 index 00000000..b3a73078 --- /dev/null +++ b/fail2ban/tests/files/logs/mysqld.log @@ -0,0 +1,6 @@ +130324 0:04:00 [Warning] Access denied for user 'root'@'192.168.1.35' (using password: NO) +130324 8:24:09 [Warning] Access denied for user 'root'@'220.95.238.171' (using password: NO) +130324 17:56:13 [Warning] Access denied for user 'root'@'61.160.223.112' (using password: NO) +130324 17:56:14 [Warning] Access denied for user 'root'@'61.160.223.112' (using password: YES) +130324 19:01:39 [Warning] Access denied for user 'root'@'61.147.108.35' (using password: NO) +130324 19:01:40 [Warning] Access denied for user 'root'@'61.147.108.35' (using password: YES) diff --git a/testcases/files/logs/named-refused b/fail2ban/tests/files/logs/named-refused similarity index 100% rename from testcases/files/logs/named-refused rename to fail2ban/tests/files/logs/named-refused diff --git a/testcases/files/logs/pam-generic b/fail2ban/tests/files/logs/pam-generic similarity index 100% rename from testcases/files/logs/pam-generic rename to fail2ban/tests/files/logs/pam-generic diff --git a/testcases/files/logs/postfix b/fail2ban/tests/files/logs/postfix similarity index 100% rename from testcases/files/logs/postfix rename to fail2ban/tests/files/logs/postfix diff --git a/testcases/files/logs/proftpd b/fail2ban/tests/files/logs/proftpd similarity index 100% rename from testcases/files/logs/proftpd rename to fail2ban/tests/files/logs/proftpd diff --git a/testcases/files/logs/pure-ftpd b/fail2ban/tests/files/logs/pure-ftpd similarity index 100% rename from testcases/files/logs/pure-ftpd rename to fail2ban/tests/files/logs/pure-ftpd diff --git a/testcases/files/logs/roundcube-auth b/fail2ban/tests/files/logs/roundcube-auth similarity index 100% rename from testcases/files/logs/roundcube-auth rename to fail2ban/tests/files/logs/roundcube-auth diff --git a/testcases/files/logs/sasl b/fail2ban/tests/files/logs/sasl similarity index 100% rename from testcases/files/logs/sasl rename to fail2ban/tests/files/logs/sasl diff --git a/testcases/files/logs/sogo-auth b/fail2ban/tests/files/logs/sogo-auth similarity index 100% rename from testcases/files/logs/sogo-auth rename to fail2ban/tests/files/logs/sogo-auth diff --git a/testcases/files/logs/sshd b/fail2ban/tests/files/logs/sshd similarity index 100% rename from testcases/files/logs/sshd rename to fail2ban/tests/files/logs/sshd diff --git a/testcases/files/logs/sshd-ddos b/fail2ban/tests/files/logs/sshd-ddos similarity index 100% rename from testcases/files/logs/sshd-ddos rename to fail2ban/tests/files/logs/sshd-ddos diff --git a/testcases/files/logs/vsftpd b/fail2ban/tests/files/logs/vsftpd similarity index 100% rename from testcases/files/logs/vsftpd rename to fail2ban/tests/files/logs/vsftpd diff --git a/testcases/files/logs/webmin-auth b/fail2ban/tests/files/logs/webmin-auth similarity index 100% rename from testcases/files/logs/webmin-auth rename to fail2ban/tests/files/logs/webmin-auth diff --git a/testcases/files/logs/wu-ftpd b/fail2ban/tests/files/logs/wu-ftpd similarity index 100% rename from testcases/files/logs/wu-ftpd rename to fail2ban/tests/files/logs/wu-ftpd diff --git a/fail2ban/tests/files/testcase-multiline.log b/fail2ban/tests/files/testcase-multiline.log new file mode 100644 index 00000000..a8d977ab --- /dev/null +++ b/fail2ban/tests/files/testcase-multiline.log @@ -0,0 +1,35 @@ +Aug 14 11:58:58 yyyy rsyncd[9874]: connect from example.com (192.0.43.10) +Aug 14 11:58:58 yyyy rsyncd[23864]: connect from example.com (192.0.43.10) +Aug 14 11:59:58 yyyy rsyncd[23864]: rsync on xxx/ from example.com (192.0.43.10) +Aug 14 11:59:58 yyyy rsyncd[23864]: building file list +Aug 14 11:59:58 yyyy rsyncd[28101]: connect from irrelevant (192.0.43.11) +Aug 14 11:59:58 yyyy rsyncd[28101]: rsync on xxx/ from irrelevant (192.0.43.11) +Aug 14 11:59:58 yyyy rsyncd[28101]: building file list +Aug 14 11:59:58 yyyy rsyncd[28101]: sent 294382 bytes received 781 bytes total size 29221543998 +Aug 14 11:59:58 yyyy rsyncd[18067]: sent 2833586339 bytes received 65115 bytes total size 29221543998 +Aug 14 11:59:58 yyyy smartd[2635]: Device: /dev/sdb [SAT], SMART Usage Attribute: 194 Temperature_Celsius changed from 116 to 115 +Aug 14 11:59:58 yyyy rsyncd[1762]: connect from irrelevant (192.0.43.11) +Aug 14 11:59:58 yyyy rsyncd[1762]: rsync on xxx/ from irrelevant (192.0.43.11) + +Aug 14 11:59:58 yyyy rsyncd[1762]: building file list +Aug 14 11:59:58 yyyy rsyncd[1762]: sent 294382 bytes received 781 bytes total size 29221543998 +Aug 14 11:59:58 yyyy sendmail[30222]: r0NNNlC0030222: from=, size=6420, class=0, nrcpts=1, msgid=<0.0.9881290652.3772024cf8879cycvau18081.0@example.com>, bodytype=8BITMIME, proto=ESMTP, daemon=MTA, relay=[192.0.43.15] (may be forged) +Aug 14 11:59:58 yyyy smartd[2635]: Device: /dev/sda [SAT], starting scheduled Short Self-Test. +Aug 14 11:59:58 yyyy smartd[2635]: Device: /dev/sdb [SAT], SMART Usage Attribute: 194 Temperature_Celsius changed from 115 to 116 +Aug 14 11:59:58 yyyy smartd[2635]: Device: /dev/sdb [SAT], starting scheduled Short Self-Test. +Aug 14 11:59:58 yyyy smartd[2635]: Device: /dev/sda [SAT], previous self-test completed without error +Aug 14 11:59:58 yyyy smartd[2635]: Device: /dev/sdb [SAT], previous self-test completed without error + + +Aug 14 11:59:58 yyyy rsyncd[7788]: connect from irrelevant (192.0.43.11) +Aug 14 11:59:58 yyyy rsyncd[7788]: rsync on xxx/ from irrelevant (192.0.43.11) +Aug 14 11:59:58 yyyy rsyncd[7788]: building file list +Aug 14 11:59:58 yyyy rsyncd[21919]: sent 2836906453 bytes received 6768 bytes total size 29221543998 +Aug 14 11:59:58 yyyy rsyncd[23864]: rsync error: timeout in data send/receive (code 30) at io.c(137) [sender=3.0.9] +Aug 14 11:59:58 yyyy spamd[19119]: spamd: result: Y 11 - AWL,BAYES_50,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,HTML_MESSAGE,RCVD_IN_BRBL_LASTEXT,RCVD_IN_PSBL,RCVD_IN_RP_RNBL,RDNS_NONE,URIBL_BLACK,URIBL_DBL_SPAM scantime=1.2,size=6910,user=sa-milt,uid=499,required_score=5.0,rhost=localhost,raddr=127.0.0.1,rport=57429,mid=<0.0.9881290652.3772024cf8879cycvau18081.0@example.com>,bayes=0.536244,autolearn=no +Aug 14 11:59:58 yyyy rsyncd[5534]: connect from irrelevant (192.0.43.11) +Aug 14 11:59:58 yyyy rsyncd[5534]: rsync on xxx/ from irrelevant (192.0.43.11) +Aug 14 11:59:58 yyyy rsyncd[5534]: building file list +Aug 14 11:59:58 yyyy rsyncd[7788]: rsync error: Received SIGINT +Aug 14 11:59:58 yyyy rsyncd[5534]: sent 294382 bytes received 781 bytes total size 29221543998 +Aug 14 11:59:59 yyyy rsyncd[9874]: rsync error: timeout in data send/receive (code 30) at io.c(137) [sender=3.0.9] diff --git a/testcases/files/testcase-usedns.log b/fail2ban/tests/files/testcase-usedns.log similarity index 100% rename from testcases/files/testcase-usedns.log rename to fail2ban/tests/files/testcase-usedns.log diff --git a/testcases/files/testcase01.log b/fail2ban/tests/files/testcase01.log similarity index 100% rename from testcases/files/testcase01.log rename to fail2ban/tests/files/testcase01.log diff --git a/testcases/files/testcase02.log b/fail2ban/tests/files/testcase02.log similarity index 100% rename from testcases/files/testcase02.log rename to fail2ban/tests/files/testcase02.log diff --git a/testcases/files/testcase03.log b/fail2ban/tests/files/testcase03.log similarity index 100% rename from testcases/files/testcase03.log rename to fail2ban/tests/files/testcase03.log diff --git a/testcases/files/testcase04.log b/fail2ban/tests/files/testcase04.log similarity index 59% rename from testcases/files/testcase04.log rename to fail2ban/tests/files/testcase04.log index c0304d06..987abec6 100644 --- a/testcases/files/testcase04.log +++ b/fail2ban/tests/files/testcase04.log @@ -1,15 +1,15 @@ Sep 21 22:03:07 [sshd] Invalid user toto from 212.41.96.185 -1124012400 [sshd] Invalid user fuck from 212.41.96.185 +1124012400 [sshd] Invalid user duck from 212.41.96.185 Sep 21 21:03:38 [sshd] Invalid user toto from 212.41.96.185 -1124012500 [sshd] Invalid user fuck from 212.41.96.185 +1124012500 [sshd] Invalid user duck from 212.41.96.185 Sep 21 21:03:46 [sshd] Invalid user toto from 212.41.96.185 -Aug 14 11:58:48 [sshd] Invalid user fuck from 212.41.96.185 +Aug 14 11:58:48 [sshd] Invalid user duck from 212.41.96.185 Aug 14 11:59:58 [sshd] Invalid user toto from 212.41.96.185 -Sep 21 21:04:03 [sshd] Invalid user fuck from 212.41.96.185 +Sep 21 21:04:03 [sshd] Invalid user duck from 212.41.96.185 - Last output repeated twice - 2005/08/14 11:57:00 [sshd] Invalid user toto from 212.41.96.186 -2005/08/14 11:58:00 [sshd] Invalid user fuck from 212.41.96.186 +2005/08/14 11:58:00 [sshd] Invalid user duck from 212.41.96.186 2005/08/14 11:59:00 [sshd] Invalid user toto from 212.41.96.186 -2005/08/14 12:00:00 [sshd] Invalid user fuck from 212.41.96.186 +2005/08/14 12:00:00 [sshd] Invalid user duck from 212.41.96.186 - Last output repeated twice - Sep 21 21:09:01 [sshd] Invalid user toto from 212.41.96.185 diff --git a/testcases/filtertestcase.py b/fail2ban/tests/filtertestcase.py similarity index 88% rename from testcases/filtertestcase.py rename to fail2ban/tests/filtertestcase.py index a71c620a..460f2b45 100644 --- a/testcases/filtertestcase.py +++ b/fail2ban/tests/filtertestcase.py @@ -28,11 +28,13 @@ import sys import time import tempfile -from server.jail import Jail -from server.filterpoll import FilterPoll -from server.filter import FileFilter, DNSUtils -from server.failmanager import FailManager -from server.failmanager import FailManagerEmpty +from fail2ban.server.jail import Jail +from fail2ban.server.filterpoll import FilterPoll +from fail2ban.server.filter import FileFilter, DNSUtils +from fail2ban.server.failmanager import FailManager +from fail2ban.server.failmanager import FailManagerEmpty + +TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files") # # Useful helpers @@ -179,7 +181,7 @@ class IgnoreIP(unittest.TestCase): class LogFile(unittest.TestCase): - FILENAME = "testcases/files/testcase01.log" + FILENAME = os.path.join(TEST_FILES_DIR, "testcase01.log") def setUp(self): """Call before every test case.""" @@ -519,11 +521,12 @@ def get_monitor_failures_testcase(Filter_): class GetFailures(unittest.TestCase): - FILENAME_01 = "testcases/files/testcase01.log" - FILENAME_02 = "testcases/files/testcase02.log" - FILENAME_03 = "testcases/files/testcase03.log" - FILENAME_04 = "testcases/files/testcase04.log" - FILENAME_USEDNS = "testcases/files/testcase-usedns.log" + FILENAME_01 = os.path.join(TEST_FILES_DIR, "testcase01.log") + FILENAME_02 = os.path.join(TEST_FILES_DIR, "testcase02.log") + FILENAME_03 = os.path.join(TEST_FILES_DIR, "testcase03.log") + FILENAME_04 = os.path.join(TEST_FILES_DIR, "testcase04.log") + FILENAME_USEDNS = os.path.join(TEST_FILES_DIR, "testcase-usedns.log") + FILENAME_MULTILINE = os.path.join(TEST_FILES_DIR, "testcase-multiline.log") # so that they could be reused by other tests FAILURES_01 = ('193.168.0.128', 3, 1124013599.0, @@ -629,6 +632,53 @@ class GetFailures(unittest.TestCase): self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan) + def testGetFailuresMultiLine(self): + output = [("192.0.43.10", 2, 1124013599.0), + ("192.0.43.11", 1, 1124013598.0)] + self.filter.addLogPath(GetFailures.FILENAME_MULTILINE) + self.filter.addFailRegex("^.*rsyncd\[(?P\d+)\]: connect from .+ \(\)$^.+ rsyncd\[(?P=pid)\]: rsync error: .*$") + self.filter.setMaxLines(100) + self.filter.setMaxRetry(1) + + self.filter.getFailures(GetFailures.FILENAME_MULTILINE) + + _assert_correct_last_attempt(self, self.filter, output.pop()) + _assert_correct_last_attempt(self, self.filter, output.pop()) + + self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan) + + def testGetFailuresMultiLineIgnoreRegex(self): + output = [("192.0.43.10", 2, 1124013599.0)] + self.filter.addLogPath(GetFailures.FILENAME_MULTILINE) + self.filter.addFailRegex("^.*rsyncd\[(?P\d+)\]: connect from .+ \(\)$^.+ rsyncd\[(?P=pid)\]: rsync error: .*$") + self.filter.addIgnoreRegex("rsync error: Received SIGINT") + self.filter.setMaxLines(100) + self.filter.setMaxRetry(1) + + self.filter.getFailures(GetFailures.FILENAME_MULTILINE) + + _assert_correct_last_attempt(self, self.filter, output.pop()) + + self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan) + + def testGetFailuresMultiLineMultiRegex(self): + output = [("192.0.43.10", 2, 1124013599.0), + ("192.0.43.11", 1, 1124013598.0), + ("192.0.43.15", 1, 1124013598.0)] + self.filter.addLogPath(GetFailures.FILENAME_MULTILINE) + self.filter.addFailRegex("^.*rsyncd\[(?P\d+)\]: connect from .+ \(\)$^.+ rsyncd\[(?P=pid)\]: rsync error: .*$") + self.filter.addFailRegex("^.* sendmail\[.*, msgid=<(?P[^>]+).*relay=\[\].*$^.+ spamd: result: Y \d+ .*,mid=<(?P=msgid)>(,bayes=[.\d]+)?(,autolearn=\S+)?\s*$") + self.filter.setMaxLines(100) + self.filter.setMaxRetry(1) + + self.filter.getFailures(GetFailures.FILENAME_MULTILINE) + + _assert_correct_last_attempt(self, self.filter, output.pop()) + _assert_correct_last_attempt(self, self.filter, output.pop()) + _assert_correct_last_attempt(self, self.filter, output.pop()) + + self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan) + class DNSUtilsTests(unittest.TestCase): def testUseDns(self): diff --git a/testcases/servertestcase.py b/fail2ban/tests/servertestcase.py similarity index 97% rename from testcases/servertestcase.py rename to fail2ban/tests/servertestcase.py index e4d3bf0d..fa6a0b91 100644 --- a/testcases/servertestcase.py +++ b/fail2ban/tests/servertestcase.py @@ -28,8 +28,11 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier" __license__ = "GPL" import unittest, socket, time, tempfile, os, locale -from server.server import Server -from common.exceptions import UnknownJailException + +from fail2ban.server.server import Server +from fail2ban.exceptions import UnknownJailException + +TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files") class StartStop(unittest.TestCase): @@ -279,14 +282,14 @@ class Transmitter(TransmitterBase): self.jailAddDelTest( "logpath", [ - "testcases/files/testcase01.log", - "testcases/files/testcase02.log", - "testcases/files/testcase03.log", + os.path.join(TEST_FILES_DIR, "testcase01.log"), + os.path.join(TEST_FILES_DIR, "testcase02.log"), + os.path.join(TEST_FILES_DIR, "testcase03.log"), ], self.jailName ) # Try duplicates - value = "testcases/files/testcase04.log" + value = os.path.join(TEST_FILES_DIR, "testcase04.log") self.assertEqual( self.transm.proceed(["set", self.jailName, "addlogpath", value]), (0, [value])) diff --git a/testcases/sockettestcase.py b/fail2ban/tests/sockettestcase.py similarity index 95% rename from testcases/sockettestcase.py rename to fail2ban/tests/sockettestcase.py index 4cd5a687..bbca8dde 100644 --- a/testcases/sockettestcase.py +++ b/fail2ban/tests/sockettestcase.py @@ -28,8 +28,9 @@ __copyright__ = "Copyright (c) 2013 Steven Hiscocks" __license__ = "GPL" import unittest, time, tempfile, os, threading -from server.asyncserver import AsyncServer, AsyncServerException -from client.csocket import CSocket + +from fail2ban.server.asyncserver import AsyncServer, AsyncServerException +from fail2ban.client.csocket import CSocket class Socket(unittest.TestCase): diff --git a/testcases/utils.py b/fail2ban/tests/utils.py similarity index 100% rename from testcases/utils.py rename to fail2ban/tests/utils.py diff --git a/common/version.py b/fail2ban/version.py similarity index 92% rename from common/version.py rename to fail2ban/version.py index 2a1c0d0b..a4499b21 100644 --- a/common/version.py +++ b/fail2ban/version.py @@ -22,7 +22,7 @@ # $Revision$ __author__ = "Cyril Jaquier, Yaroslav Halchenko" -__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2012 Yaroslav Halchenko" +__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2013 Yaroslav Halchenko" __license__ = "GPL" -version = "0.8.8" +version = "0.9.0a0" diff --git a/files/nagios/README b/files/nagios/README new file mode 100644 index 00000000..28e84495 --- /dev/null +++ b/files/nagios/README @@ -0,0 +1,104 @@ +Description +----------- +This plugin checks if the fail2ban server is running and how many IPs are currently banned. +You can use this plugin to monitor all the jails or just a specific jail. + + +How to use +---------- +Just have to run the following command: + $ ./check_fail2ban --help + +If you need to use this script with NRPE you just have to do the +following steps: + +1 allow your user to run the script with the sudo rights. Just add + something like that in your /etc/sudoers (use visudo) : + nagios ALL=(ALL) NOPASSWD: //check_fail2ban + +2 then just add this kind of line in your NRPE config file : + command[check_fail2ban]=/usr/bin/sudo //check_fail2ban + +3 don't forget to restart your NRPE daemon + +/!\ be careful to let no one able to update the check_fail2ban ;) +------------------------------------------------------------------------------ + + +Notes (from f2ban.txt) +----- +It seems that Fail2ban is currently not working, please login and check + +HELP: + +1.) stop the Service +/etc/init.d/fail2ban stop + +2.) delete the socket if available +rm /tmp/fail2ban.sock + +3.) start the Service +/etc/init.d/fail2ban start + +4.) check if fail2ban is working +fail2ban-client ping +Answer should be "pong" + +5.) if the answer is not "pong" run away or CRY FOR HELP ;-) + + +Help +---- + +Usage: //check_fail2ban [-p] [-D "CHECK FAIL2BAN ACTIVITY"] [-v] [-c 2] [-w 1] [-s //socket] [-P /usr/bin/fail2ban-client] + +Options: + -h, --help + Print detailed help screen + -V, --version + Print version information + -D, --display=STRING + To modify the output display + default is "CHECK FAIL2BAN ACTIVITY" + -P, --path-fail2ban_client=STRING + Specify the path to the tw_cli binary + default value is /usr/bin/fail2ban-client + -c, --critical=INT + Specify a critical threshold + default is 2 + -w, --warning=INT + Specify a warning threshold + default is 1 + -s, --socket=STRING + Specify a socket path + default is unset + -p, --perfdata + If you want to activate the perfdata output + -v, --verbose + Show details for command-line debugging (Nagios may truncate the output) + + +Example +------- + +# for a specific jail +$ ./check_fail2ban --verbose -p -j ssh -w 1 -c 5 -P /usr/bin/fail2ban-client +DEBUG : fail2ban_client_path: /usr/bin/fail2ban-client +DEBUG : /usr/bin/fail2ban-client exists and is executable +DEBUG : final fail2ban command: /usr/bin/fail2ban-client +DEBUG : warning threshold : 1, critical threshold : 5 +DEBUG : it seems the connection with the fail2ban server is ok +CHECK FAIL2BAN ACTIVITY - OK - 0 current banned IP(s) for the specific jail ssh | currentBannedIP=0 + +# for all the current jails +$ ./check_fail2ban --verbose -p -w 1 -c 5 -P /usr/bin/fail2ban-client +DEBUG : fail2ban_client_path: /usr/bin/fail2ban-client +DEBUG : /usr/bin/fail2ban-client exists and is executable +DEBUG : final fail2ban command: /usr/bin/fail2ban-client +DEBUG : warning threshold : 1, critical threshold : 5 +DEBUG : it seems the connection with the fail2ban server is ok +DEBUG : jails list: apache, ssh-ddos, ssh +DEBUG : the jail apache has currently 0 banned IPs +DEBUG : the jail ssh-ddos has currently 0 banned IPs +DEBUG : the jail ssh has currently 0 banned IPs +CHECK FAIL2BAN ACTIVITY - OK - 3 detected jails with 0 current banned IP(s) | currentBannedIP=0 diff --git a/files/nagios/check_fail2ban b/files/nagios/check_fail2ban index 2b38e8a9..77a63393 100755 --- a/files/nagios/check_fail2ban +++ b/files/nagios/check_fail2ban @@ -1,105 +1,346 @@ -#!/bin/bash +#!/usr/bin/perl + +# ------------------------------------------------------- +# -=- -=- +# ------------------------------------------------------- # -# Usage: ./check_fail2ban -############################################################################################### -# Description: -# This plugin will check the status of Fail2ban. +# Description : This plugin checks if the fail2ban server is running +# and how many IPs are currently banned. +# # -# Created: 2008-10-25 (Sebastian Mueller) +# inspired by the work of Sebastian Mueller - http://www.elchtest.eu +# # -# Changes: 2008-10-26 fixed some issues (Sebastian Mueller) -# Changes: 2009-01-25 add the second check, when server is not replying and the -# process is hang-up (Sebastian Mueller) +# Version : 0.1 +# ------------------------------------------------------- +# In : +# - see the How to use section # -# please visit my website http://www.elchtest.eu or my personal WIKI http://wiki.elchtest.eu +# Out : +# - only print on the standard output # -################################################################################################ -# if you have any questions, send a mail to linux@krabbe-offline.de +# Features : +# - perfdata output +# - works with only a specific jail # -# this script is for my personal use. read the script before running/using it!!! +# Fix Me/Todo : +# - too many things ;) but let me know what do you think about it # +# #################################################################### + +# #################################################################### +# GPL v2 +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. # -# YOU HAVE BEEN WARNED. THIS MAY DESTROY YOUR MACHINE. I ACCEPT NO RESPONSIBILITY. -############################################################################################### +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# #################################################################### + +# #################################################################### +# How to use : +# ------------ +# +# Just have to run the following command: +# $ ./check_fail2ban --help +# +# If you need to use this script with NRPE you just have to do the +# following steps: +# +# 1 allow your user to run the script with the sudo rights. Just add +# something like that in your /etc/sudoers (use visudo) : +# nagios ALL=(ALL) NOPASSWD: //check_fail2ban +# +# 2 then just add this kind of line in your NRPE config file : +# command[check_fail2ban]=/usr/bin/sudo //check_fail2ban +# +# 3 don't forget to restart your NRPE daemon +# +# +# /!\ be careful to let no one able to update the check_fail2ban ;) +# ------------------------------------------------------------------------------ +# +# #################################################################### + +# #################################################################### +# Changelog : +# ----------- +# +# -------------------------------------------------------------------- +# Date:12/03/2013 Version:0.1 Author:Erwan Ben Souiden +# >> creation +# #################################################################### + +# #################################################################### +# Don't touch anything under this line! +# You shall not pass - Gandalf is watching you +# #################################################################### + +use strict; +use warnings; +use Getopt::Long qw(:config no_ignore_case); + +# Generic variables +# ----------------- +my $version = '0.1'; +my $author = 'Erwan Labynocle Ben Souiden'; +my $a_mail = 'erwan@aleikoum.net'; +my $script_name = 'check_fail2ban'; +my $verbose_value = 0; +my $version_value = 0; +my $more_value = 0; +my $help_value = 0; +my $perfdata_value = 0; +my %ERRORS=('OK'=>0,'WARNING'=>1,'CRITICAL'=>2,'UNKNOWN'=>3,'DEPENDENT'=>4); + +# Plugin default variables +# ------------------------ +my $display = 'CHECK FAIL2BAN ACTIVITY'; +my ($critical,$warning) = (2,1); +my $fail2ban_client_path = '/usr/bin/fail2ban-client'; +my $fail2ban_socket = ''; +my $jail_specific = ''; + +GetOptions ( + 'P=s' => \ $fail2ban_client_path, + 'path-fail2ban_client=s' => \ $fail2ban_client_path, + 'j=s' => \ $jail_specific, + 'jail=s' => \ $jail_specific, + 'w=i' => \ $warning, + 'warning=i' => \ $warning, + 'socket=s' => \ $fail2ban_socket, + 'S=s' => \ $fail2ban_socket, + 'c=i' => \ $critical, + 'critical=i' => \ $critical, + 'V' => \ $version_value, + 'version' => \ $version_value, + 'h' => \ $help_value, + 'H' => \ $help_value, + 'help' => \ $help_value, + 'display=s' => \ $display, + 'D=s' => \ $display, + 'perfdata' => \ $perfdata_value, + 'p' => \ $perfdata_value, + 'v' => \ $verbose_value, + 'verbose' => \ $verbose_value +); + +print_usage() if ($help_value); +print_version() if ($version_value); -SECOND_CHECK=0 -STATE_OK=0 -STATE_CRITICAL=2 - -###################################################################### -# Read the Status from fail2ban-client -###################################################################### -check_processes_fail2ban() -{ - - F2B=`sudo -u root fail2ban-client ping | awk -F " " '{print $3}'` - exit_fail2ban=0 - - if [[ $F2B = "pong" ]]; then - exit_fail2ban=$STATE_OK - else - exit_fail2ban=$STATE_CRITICAL - fi +# Syntax check of your specified options +# -------------------------------------- +print "DEBUG : fail2ban_client_path: $fail2ban_client_path\n" if ($verbose_value); +if (($fail2ban_client_path eq "")) { + print $display.'- one or more following arguments are missing: fail2ban_client_path'."\n"; + exit $ERRORS{"UNKNOWN"}; } -###################################################################### -# first check in the Background, PID will be killed when no response -# after 10 seconds, might be possible, otherwise the script will be -# present in your memory all the time -###################################################################### -check_processes_fail2ban & -pid=$! +if(! -x $fail2ban_client_path) { + print $display.' - '.$fail2ban_client_path.' is not executable by you'."\n"; + exit $ERRORS{"UNKNOWN"}; +} +print "DEBUG : $fail2ban_client_path exists and is executable\n" if ($verbose_value); -typeset -i i=0 -while ps $pid >/dev/null -do - sleep 1 - i=$i+1 -if [ $i -ge 10 ] - then - kill $pid - SECOND_CHECK=1 - exit_fail2ban=$STATE_CRITICAL - break -fi -done +my $fail2ban_cmd = $fail2ban_client_path; +$fail2ban_cmd .= " -s $fail2ban_socket" if ($fail2ban_socket); -###################################################################### -# when the Server response (does not mean the FAIL2BAN is working) -# in the first step, then it will run again and test the Service -# and provide the real status -###################################################################### +print "DEBUG : final fail2ban command: $fail2ban_cmd\n" if ($verbose_value); + +print "DEBUG : warning threshold : $warning, critical threshold : $critical\n" if ($verbose_value); +if (($critical < 0) or ($warning < 0) or ($critical < $warning)) { + print $display.' - the thresholds must be integers and the critical threshold higher or equal than the warning threshold'."\n"; + exit $ERRORS{"UNKNOWN"}; +} + +# Core script +# ----------- +my ($how_many_jail,$how_many_banned,$return_print,$plugstate) = (0,0,"","OK"); -if [ $SECOND_CHECK -eq 0 ]; then - check_processes_fail2ban - elif [ $SECOND_CHECK -eq 1 ]; then - exit_fail2ban=$STATE_CRITICAL -fi +### Test the connection to the fail2ban server +my @command_output = `$fail2ban_cmd ping`; +my $return_code = $?; +if ($return_code) { + print $display.'CRITICAL - non-zero exit code during testing fail2ban-client ping, check if the server is running and if you have the good permissions'; + exit $ERRORS{"CRITICAL"}; +} +else { + print "DEBUG : it seems the connection with the fail2ban server is ok\n" if ($verbose_value); +} +### Only if you specify one jail +if ($jail_specific) { + my $current_ban_number = currently_ban("$fail2ban_cmd","$jail_specific"); + if ($current_ban_number == -1) { + print $display.' - CRITICAL - impossible to retrieve info about the jail '.$jail_specific; + exit $ERRORS{"CRITICAL"}; + } + else { + $how_many_banned = int($current_ban_number); + $return_print = $how_many_banned.' current banned IP(s) for the specific jail '.$jail_specific; + } +} +### To analyze all the jail +else { + # Retrieve the jails list + my @jail_list = obtain_jail_list("$fail2ban_cmd"); + if ($jail_list[0] eq "-1") { + print $display.' - CRITICAL - impossible to retrieve the jail list'."\n"; + exit $ERRORS{"CRITICAL"}; + } -###################################################################### -# Main Menu -###################################################################### + foreach (@jail_list) { + $how_many_jail ++; + + my $jail_name = $_; + $jail_name =~ tr/ //ds; + + my $current_ban_number = currently_ban("$fail2ban_cmd","$jail_name"); + if ($current_ban_number == -1) { + print "DEBUG : problem to parse the current banned IPs for jail $jail_name\n" if ($verbose_value); + } + else { + print "DEBUG : the jail $jail_name has currently $current_ban_number banned IPs\n" if ($verbose_value); + $how_many_banned += int($current_ban_number); + } + } + $return_print = $how_many_jail.' detected jails with '.$how_many_banned.' current banned IP(s)'; +} + +### Final +$plugstate = "CRITICAL" if ($how_many_banned >= $critical); +$plugstate = "WARNING" if (($how_many_banned >= $warning) && ($how_many_banned < $critical)); + +$return_print = $display." - ".$plugstate." - ".$return_print; +$return_print .= " | currentBannedIP=$how_many_banned" if ($perfdata_value); + +print $return_print; +exit $ERRORS{"$plugstate"}; -final_exit=$exit_fail2ban -if [ $final_exit -eq 0 ]; then - echo "SYSTEM OK - Fail2ban is working normally" - exitstatus=$STATE_OK -elif [ $final_exit -ne "0" ]; then - echo "SYSTEM WARNING - Fail2Ban is not working" -###################################################################### -# If don't have a Nagios Server for monitoring, remove the comment and -# add your Mail Address. You can check it with a Cron Job once an hour. -# put a txt file on your server and describe how to fix the issue, this -# could be attached to the mail. -###################################################################### -# mutt -s "FAIL2BAN NOT WORKING" your@example.com < /home/f2ban.txt +# #################################################################### +# function 1 : display the help +# ----------------------------- +sub print_usage { + print </$script_name [-p] [-D "$display"] [-v] [-c 2] [-w 1] [-s //socket] [-P /usr/bin/fail2ban-client] + +Options: + -h, --help + Print detailed help screen + -V, --version + Print version information + -D, --display=STRING + To modify the output display + default is "CHECK FAIL2BAN ACTIVITY" + -P, --path-fail2ban_client=STRING + Specify the path to the tw_cli binary + default value is /usr/bin/fail2ban-client + -c, --critical=INT + Specify a critical threshold + default is 2 + -w, --warning=INT + Specify a warning threshold + default is 1 + -s, --socket=STRING + Specify a socket path + default is unset + -p, --perfdata + If you want to activate the perfdata output + -v, --verbose + Show details for command-line debugging (Nagios may truncate the output) + +Send email to $a_mail if you have questions +regarding use of this software. To submit patches or suggest improvements, +send email to $a_mail +This plugin has been created by $author + +Hope you will enjoy it ;) + +Remember : + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +EOT + exit $ERRORS{"UNKNOWN"}; +} + +# function 2 : display version information +# ---------------------------------------- +sub print_version { + print <