Merge commit '0.8.8-160-g74e76e0' into 0.9

* commit '0.8.8-160-g74e76e0': (65 commits)
  TST+BF: Use separate coveragerc for Travis CI
  RF+TST: bring inBanList back from private to protected and enabled its rudimentary unittests
  TST: coverage ignore Travis CI python virtual environments
  ENH: increase waiting to 4 sec for gamin/pyinotify
  TST+BF: Fix incorrect commands for coveralls support
  TST: Add support for coveralls for python 2.6 and python 2.7
  ENH: deleted trailing spaces in fail2ban- cmdline tools
  DOC: minor change -- refer to the fail2ban manpage
  TST: be more aggressive in cleanup of temp files + use mktemp instead of mkstemp
  ENH(BF?): overload open() (for buffering) within filtertestcase to guarantee atomic writing
  BF: delay check for the existence of config directory until read()
  DOC: minor fix ups of manpages. fixes #159
  non-static (get|set)BaseDir for Configurator. fixes #160
  ENH: Slight tune ups for fresh SOGo filter + comment into the sample log file
  ENH: postfix filter -- react also on (450 4.7.1) with empty from/to. fixes #126
  TST: basic testing of reading the shipped jail.conf (forcing all jails to be enabled)
  ENH: allow to force enable all jails (for testing), do not crash for jails without actions (just warn)
  ENH: minor -- add default value into the warning if option had none provided
  ENH: _copy_lines_between_files -- read all needed, and only then write/flush at once
  ENH: move pyinotify callback debug message into callback + delay string interpolations
  ...

Conflicts:
	fail2ban-testcases
	testcases/clientreadertestcase.py -- fix for setBaseDir will follow
pull/165/merge
Yaroslav Halchenko 2013-03-30 18:27:20 -04:00
commit 03f6c42352
64 changed files with 1140 additions and 435 deletions

2
.gitignore vendored
View File

@ -4,3 +4,5 @@ dist
*.pyc *.pyc
htmlcov htmlcov
.coverage .coverage
*.orig
*.rej

View File

@ -6,6 +6,9 @@ python:
- "2.6" - "2.6"
- "2.7" - "2.7"
install: install:
- "pip install pyinotify" - pip install pyinotify
- if [[ $TRAVIS_PYTHON_VERSION == 2.[6-7] ]]; then pip install -q coveralls; fi
script: script:
- python ./fail2ban-testcases - if [[ $TRAVIS_PYTHON_VERSION == 2.[6-7] ]]; then coverage run --rcfile=.travis_coveragerc fail2ban-testcases; else python ./fail2ban-testcases; fi
after_script:
- if [[ $TRAVIS_PYTHON_VERSION == 2.[6-7] ]]; then coveralls; fi

7
.travis_coveragerc Normal file
View File

@ -0,0 +1,7 @@
[run]
branch = True
omit =
/usr/*
/home/travis/virtualenv/*
server/filtergamin.py

39
DEVELOP
View File

@ -44,7 +44,7 @@ coverage html
Then look at htmlcov/index.html and see how much coverage your test cases Then look at htmlcov/index.html and see how much coverage your test cases
exert over the codebase. Full coverage is a good thing however it may not be exert over the codebase. Full coverage is a good thing however it may not be
complete. Try to ensure tests cover as many independant paths through the complete. Try to ensure tests cover as many independent paths through the
code. code.
Manual Execution. To run in a development environment do: Manual Execution. To run in a development environment do:
@ -67,7 +67,7 @@ status test
Coding Standards Coding Standards
================ ================
Style Style
----- -----
@ -106,9 +106,12 @@ Git
Use the following tags in your commit messages: Use the following tags in your commit messages:
'ENH:' for enhancements
'BF:' for bug fixes 'BF:' for bug fixes
'DOC:' for documentation fixes 'DOC:' for documentation fixes
'ENH:' for enhancements
'TST:' for commits concerning tests only (thus not touching the main code-base)
Multiple tags could be joined with +, e.g. "BF+TST:".
Adding Actions Adding Actions
-------------- --------------
@ -246,24 +249,30 @@ Takes care about executing start/check/ban/unban/stop commands
Releasing Releasing
========= =========
Ensure the version is correct in ./common/version.py # Ensure the version is correct in ./common/version.py
Add/finalize the corresponding entry in the ChangeLog # Add/finalize the corresponding entry in the ChangeLog
# update man pages # Update man pages
(cd man ; ./generate-man )
git commit -m 'update man pages for release' man/* (cd man ; ./generate-man )
git commit -m 'update man pages for release' man/*
python setup.py check # Make sure the tests pass
python setup.py sdist
python setup.py bdist_rpm
python setup.py upload
Run the following and update the wiki with output: ./fail2ban-testcases-all
python -c 'import common.protocol; common.protocol.printWiki()' # Prepare/upload source and rpm binary distributions
email users and development list of release python setup.py check
python setup.py sdist
python setup.py bdist_rpm
python setup.py upload
# Run the following and update the wiki with output:
python -c 'import common.protocol; common.protocol.printWiki()'
# Email users and development list of release
TODO notifying distributors etc. TODO notifying distributors etc.

View File

@ -42,6 +42,7 @@ server/banmanager.py
server/datetemplate.py server/datetemplate.py
server/mytime.py server/mytime.py
server/failregex.py server/failregex.py
testcases/files/testcase-usedns.log
testcases/banmanagertestcase.py testcases/banmanagertestcase.py
testcases/failmanagertestcase.py testcases/failmanagertestcase.py
testcases/clientreadertestcase.py testcases/clientreadertestcase.py
@ -50,6 +51,7 @@ testcases/__init__.py
testcases/datedetectortestcase.py testcases/datedetectortestcase.py
testcases/actiontestcase.py testcases/actiontestcase.py
testcases/servertestcase.py testcases/servertestcase.py
testcases/sockettestcase.py
testcases/files/testcase01.log testcases/files/testcase01.log
testcases/files/testcase02.log testcases/files/testcase02.log
testcases/files/testcase03.log testcases/files/testcase03.log
@ -57,6 +59,7 @@ testcases/files/testcase04.log
setup.py setup.py
setup.cfg setup.cfg
common/__init__.py common/__init__.py
common/exceptions.py
common/helpers.py common/helpers.py
common/version.py common/version.py
common/protocol.py common/protocol.py
@ -88,6 +91,17 @@ config/filter.d/vsftpd.conf
config/filter.d/webmin-auth.conf config/filter.d/webmin-auth.conf
config/filter.d/wuftpd.conf config/filter.d/wuftpd.conf
config/filter.d/xinetd-fail.conf config/filter.d/xinetd-fail.conf
config/filter.d/asterisk.conf
config/filter.d/dovecot.conf
config/filter.d/dropbear.conf
config/filter.d/lighttpd-auth.conf
config/filter.d/recidive.conf
config/filter.d/roundcube-auth.conf
config/action.d/dummy.conf
config/action.d/iptables-ipset-proto4.conf
config/action.d/iptables-ipset-proto6.conf
config/action.d/iptables-xt_recent-echo.conf
config/action.d/route.conf
config/action.d/complain.conf config/action.d/complain.conf
config/action.d/dshield.conf config/action.d/dshield.conf
config/action.d/hostsdeny.conf config/action.d/hostsdeny.conf
@ -110,6 +124,8 @@ config/action.d/sendmail-whois-lines.conf
config/action.d/shorewall.conf config/action.d/shorewall.conf
config/fail2ban.conf config/fail2ban.conf
man/fail2ban-client.1 man/fail2ban-client.1
man/fail2ban.1
man/jail.conf.5
man/fail2ban-client.h2m man/fail2ban-client.h2m
man/fail2ban-server.1 man/fail2ban-server.1
man/fail2ban-server.h2m man/fail2ban-server.h2m

10
README
View File

@ -51,10 +51,12 @@ call fail2ban-server directly.
Configuration: Configuration:
-------------- --------------
You can configure Fail2ban using the files in /etc/fail2ban. It is possible to You can configure Fail2Ban using the files in /etc/fail2ban. It is
configure the server using commands sent to it by fail2ban-client. The available possible to configure the server using commands sent to it by
commands are described in the man page of fail2ban-client. Please refer to it or fail2ban-client. The available commands are described in the
to the website: http://www.fail2ban.org fail2ban-client(1) manpage. Also see fail2ban(1) manpage for further
references and find even more documentation on the website:
http://www.fail2ban.org
Contact: Contact:
-------- --------

2
THANKS
View File

@ -13,6 +13,7 @@ Christian Rauch
Christoph Haas Christoph Haas
Christos Psonis Christos Psonis
Daniel B. Cid Daniel B. Cid
Daniel Black
David Nutter David Nutter
Eric Gerbier Eric Gerbier
Guillaume Delvit Guillaume Delvit
@ -38,6 +39,7 @@ Robert Edeker
Russell Odom Russell Odom
Sireyessire Sireyessire
Stephen Gildea Stephen Gildea
Steven Hiscocks
Tom Pike Tom Pike
Tyler Tyler
Vaclav Misek Vaclav Misek

4
TODO
View File

@ -16,9 +16,9 @@ Legend:
- Run tests though all filters/examples files - (see sshd example file) as unit - Run tests though all filters/examples files - (see sshd example file) as unit
test test
- Removed relative imports * Removed relative imports
- Cleanup fail2ban-client and fail2ban-server. Move code to server/ and client/ * Cleanup fail2ban-client and fail2ban-server. Move code to server/ and client/
- Add timeout to external commands (signal alarm, watchdog thread, etc) - Add timeout to external commands (signal alarm, watchdog thread, etc)

View File

@ -35,8 +35,8 @@ logSys = logging.getLogger("fail2ban.client.config")
class ActionReader(ConfigReader): class ActionReader(ConfigReader):
def __init__(self, action, name): def __init__(self, action, name, **kwargs):
ConfigReader.__init__(self) ConfigReader.__init__(self, **kwargs)
self.__file = action[0] self.__file = action[0]
self.__cInfo = action[1] self.__cInfo = action[1]
self.__name = name self.__name = name

View File

@ -27,7 +27,7 @@ __date__ = "$Date$"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
import logging, os import glob, logging, os
from configparserinc import SafeConfigParserWithIncludes from configparserinc import SafeConfigParserWithIncludes
from ConfigParser import NoOptionError, NoSectionError from ConfigParser import NoOptionError, NoSectionError
@ -35,36 +35,64 @@ from ConfigParser import NoOptionError, NoSectionError
logSys = logging.getLogger("fail2ban.client.config") logSys = logging.getLogger("fail2ban.client.config")
class ConfigReader(SafeConfigParserWithIncludes): class ConfigReader(SafeConfigParserWithIncludes):
DEFAULT_BASEDIR = '/etc/fail2ban'
BASE_DIRECTORY = "/etc/fail2ban/" def __init__(self, basedir=None):
def __init__(self):
SafeConfigParserWithIncludes.__init__(self) SafeConfigParserWithIncludes.__init__(self)
self.setBaseDir(basedir)
self.__opts = None self.__opts = None
#@staticmethod def setBaseDir(self, basedir):
def setBaseDir(folderName): if basedir is None:
path = folderName.rstrip('/') basedir = ConfigReader.DEFAULT_BASEDIR # stock system location
ConfigReader.BASE_DIRECTORY = path + '/' self._basedir = basedir.rstrip('/')
setBaseDir = staticmethod(setBaseDir)
def getBaseDir(self):
#@staticmethod return self._basedir
def getBaseDir():
return ConfigReader.BASE_DIRECTORY
getBaseDir = staticmethod(getBaseDir)
def read(self, filename): def read(self, filename):
basename = ConfigReader.BASE_DIRECTORY + filename if not (os.path.exists(self._basedir) and os.access(self._basedir, os.R_OK | os.X_OK)):
logSys.debug("Reading " + basename) raise ValueError("Base configuration directory %s either does not exist "
bConf = basename + ".conf" "or is not accessible" % self._basedir)
bLocal = basename + ".local" basename = os.path.join(self._basedir, filename)
if os.path.exists(bConf) or os.path.exists(bLocal): logSys.debug("Reading configs for %s under %s " % (basename, self._basedir))
SafeConfigParserWithIncludes.read(self, [bConf, bLocal]) config_files = [ basename + ".conf",
basename + ".local" ]
# choose only existing ones
config_files = filter(os.path.exists, config_files)
# possible further customizations under a .conf.d directory
config_dir = basename + '.d'
if os.path.exists(config_dir):
if os.path.isdir(config_dir) and os.access(config_dir, os.X_OK | os.R_OK):
# files must carry .conf suffix as well
config_files += sorted(glob.glob('%s/*.conf' % config_dir))
else:
logSys.warn("%s exists but not a directory or not accessible"
% config_dir)
# check if files are accessible, warn if any is not accessible
# and remove it from the list
config_files_accessible = []
for f in config_files:
if os.access(f, os.R_OK):
config_files_accessible.append(f)
else:
logSys.warn("%s exists but not accessible - skipping" % f)
if len(config_files_accessible):
# at least one config exists and accessible
SafeConfigParserWithIncludes.read(self, config_files_accessible)
return True return True
else: else:
logSys.error(bConf + " and " + bLocal + " do not exist") logSys.error("Found no accessible config files for %r " % filename
+ (["under %s" % self.getBaseDir(),
"among existing ones: " + ', '.join(config_files)][bool(len(config_files))]))
return False return False
## ##
# Read the options. # Read the options.
# #
@ -94,8 +122,8 @@ class ConfigReader(SafeConfigParserWithIncludes):
values[option[1]] = option[2] values[option[1]] = option[2]
except NoOptionError: except NoOptionError:
if not option[2] == None: if not option[2] == None:
logSys.warn("'%s' not defined in '%s'. Using default value" logSys.warn("'%s' not defined in '%s'. Using default one: %r"
% (option[1], sec)) % (option[1], sec, option[2]))
values[option[1]] = option[2] values[option[1]] = option[2]
except ValueError: except ValueError:
logSys.warn("Wrong value for '" + option[1] + "' in '" + sec + logSys.warn("Wrong value for '" + option[1] + "' in '" + sec +

View File

@ -43,15 +43,19 @@ class Configurator:
self.__fail2ban = Fail2banReader() self.__fail2ban = Fail2banReader()
self.__jails = JailsReader() self.__jails = JailsReader()
#@staticmethod def setBaseDir(self, folderName):
def setBaseDir(folderName): self.__fail2ban.setBaseDir(folderName)
ConfigReader.setBaseDir(folderName) self.__jails.setBaseDir(folderName)
setBaseDir = staticmethod(setBaseDir)
#@staticmethod def getBaseDir(self):
def getBaseDir(): fail2ban_basedir = self.__fail2ban.getBaseDir()
return ConfigReader.getBaseDir() jails_basedir = self.__jails.getBaseDir()
getBaseDir = staticmethod(getBaseDir) if fail2ban_basedir != jails_basedir:
logSys.error("fail2ban.conf and jails.conf readers have differing "
"basedirs: %r and %r. "
"Returning the one for fail2ban.conf"
% (fail2ban_basedir, jails_basedir))
return fail2ban_basedir
def readEarly(self): def readEarly(self):
self.__fail2ban.read() self.__fail2ban.read()

View File

@ -35,8 +35,8 @@ logSys = logging.getLogger("fail2ban.client.config")
class Fail2banReader(ConfigReader): class Fail2banReader(ConfigReader):
def __init__(self): def __init__(self, **kwargs):
ConfigReader.__init__(self) ConfigReader.__init__(self, **kwargs)
def read(self): def read(self):
ConfigReader.read(self, "fail2ban") ConfigReader.read(self, "fail2ban")

View File

@ -35,8 +35,8 @@ logSys = logging.getLogger("fail2ban.client.config")
class FilterReader(ConfigReader): class FilterReader(ConfigReader):
def __init__(self, fileName, name): def __init__(self, fileName, name, **kwargs):
ConfigReader.__init__(self) ConfigReader.__init__(self, **kwargs)
self.__file = fileName self.__file = fileName
self.__name = name self.__name = name

View File

@ -40,10 +40,11 @@ class JailReader(ConfigReader):
actionCRE = re.compile("^((?:\w|-|_|\.)+)(?:\[(.*)\])?$") actionCRE = re.compile("^((?:\w|-|_|\.)+)(?:\[(.*)\])?$")
def __init__(self, name): def __init__(self, name, force_enable=False, **kwargs):
ConfigReader.__init__(self) ConfigReader.__init__(self, **kwargs)
self.__name = name self.__name = name
self.__filter = None self.__filter = None
self.__force_enable = force_enable
self.__actions = list() self.__actions = list()
def setName(self, value): def setName(self, value):
@ -53,10 +54,10 @@ class JailReader(ConfigReader):
return self.__name return self.__name
def read(self): def read(self):
ConfigReader.read(self, "jail") return ConfigReader.read(self, "jail")
def isEnabled(self): def isEnabled(self):
return self.__opts["enabled"] return self.__force_enable or self.__opts["enabled"]
def getOptions(self): def getOptions(self):
opts = [["bool", "enabled", "false"], opts = [["bool", "enabled", "false"],
@ -76,7 +77,8 @@ class JailReader(ConfigReader):
if self.isEnabled(): if self.isEnabled():
# Read filter # Read filter
self.__filter = FilterReader(self.__opts["filter"], self.__name) self.__filter = FilterReader(self.__opts["filter"], self.__name,
basedir=self.getBaseDir())
ret = self.__filter.read() ret = self.__filter.read()
if ret: if ret:
self.__filter.getOptions(self.__opts) self.__filter.getOptions(self.__opts)
@ -87,8 +89,10 @@ class JailReader(ConfigReader):
# Read action # Read action
for act in self.__opts["action"].split('\n'): for act in self.__opts["action"].split('\n'):
try: try:
if not act: # skip empty actions
continue
splitAct = JailReader.splitAction(act) splitAct = JailReader.splitAction(act)
action = ActionReader(splitAct, self.__name) action = ActionReader(splitAct, self.__name, basedir=self.getBaseDir())
ret = action.read() ret = action.read()
if ret: if ret:
action.getOptions(self.__opts) action.getOptions(self.__opts)
@ -97,8 +101,10 @@ class JailReader(ConfigReader):
raise AttributeError("Unable to read action") raise AttributeError("Unable to read action")
except Exception, e: except Exception, e:
logSys.error("Error in action definition " + act) logSys.error("Error in action definition " + act)
logSys.debug(e) logSys.debug("Caught exception: %s" % (e,))
return False return False
if not len(self.__actions):
logSys.warn("No actions were defined for %s" % self.__name)
return True return True
def convert(self): def convert(self):
@ -145,12 +151,20 @@ class JailReader(ConfigReader):
def splitAction(action): def splitAction(action):
m = JailReader.actionCRE.match(action) m = JailReader.actionCRE.match(action)
d = dict() d = dict()
if not m.group(2) == None: mgroups = m.groups()
if len(mgroups) == 2:
action_name, action_opts = mgroups
elif len(mgroups) == 1:
action_name, action_opts = mgroups[0], None
else:
raise ValueError("While reading action %s we should have got up to "
"2 groups. Got: %r" % (action, mgroups))
if not action_opts is None:
# Huge bad hack :( This method really sucks. TODO Reimplement it. # Huge bad hack :( This method really sucks. TODO Reimplement it.
actions = "" actions = ""
escapeChar = None escapeChar = None
allowComma = False allowComma = False
for c in m.group(2): for c in action_opts:
if c in ('"', "'") and not allowComma: if c in ('"', "'") and not allowComma:
# Start # Start
escapeChar = c escapeChar = c
@ -175,6 +189,6 @@ class JailReader(ConfigReader):
try: try:
d[p[0].strip()] = p[1].strip() d[p[0].strip()] = p[1].strip()
except IndexError: except IndexError:
logSys.error("Invalid argument %s in '%s'" % (p, m.group(2))) logSys.error("Invalid argument %s in '%s'" % (p, action_opts))
return [m.group(1), d] return [action_name, d]
splitAction = staticmethod(splitAction) splitAction = staticmethod(splitAction)

View File

@ -36,12 +36,20 @@ logSys = logging.getLogger("fail2ban.client.config")
class JailsReader(ConfigReader): class JailsReader(ConfigReader):
def __init__(self): def __init__(self, force_enable=False, **kwargs):
ConfigReader.__init__(self) """
Parameters
----------
force_enable : bool, optional
Passed to JailReader to force enable the jails.
It is for internal use
"""
ConfigReader.__init__(self, **kwargs)
self.__jails = list() self.__jails = list()
self.__force_enable = force_enable
def read(self): def read(self):
ConfigReader.read(self, "jail") return ConfigReader.read(self, "jail")
def getOptions(self, section = None): def getOptions(self, section = None):
opts = [] opts = []
@ -49,7 +57,7 @@ class JailsReader(ConfigReader):
if section: if section:
# Get the options of a specific jail. # Get the options of a specific jail.
jail = JailReader(section) jail = JailReader(section, basedir=self.getBaseDir(), force_enable=self.__force_enable)
jail.read() jail.read()
ret = jail.getOptions() ret = jail.getOptions()
if ret: if ret:
@ -62,7 +70,7 @@ class JailsReader(ConfigReader):
else: else:
# Get the options of all jails. # Get the options of all jails.
for sec in self.sections(): for sec in self.sections():
jail = JailReader(sec) jail = JailReader(sec, basedir=self.getBaseDir(), force_enable=self.__force_enable)
jail.read() jail.read()
ret = jail.getOptions() ret = jail.getOptions()
if ret: if ret:

View File

@ -52,10 +52,7 @@ actioncheck =
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <failtime> unix timestamp of the last failure
# <bantime> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionban = ADDRESSES=`whois <ip> | perl -e 'while (<STDIN>) { next if /^changed|@(ripe|apnic)\.net/io; $m += (/abuse|trouble:|report|spam|security/io?3:0); if (/([a-z0-9_\-\.+]+@[a-z0-9\-]+(\.[[a-z0-9\-]+)+)/io) { while (s/([a-z0-9_\-\.+]+@[a-z0-9\-]+(\.[[a-z0-9\-]+)+)//io) { if ($m) { $a{lc($1)}=$m } else { $b{lc($1)}=$m } } $m=0 } else { $m && --$m } } if (%%a) {print join(",",keys(%%a))} else {print join(",",keys(%%b))}'` actionban = ADDRESSES=`whois <ip> | perl -e 'while (<STDIN>) { next if /^changed|@(ripe|apnic)\.net/io; $m += (/abuse|trouble:|report|spam|security/io?3:0); if (/([a-z0-9_\-\.+]+@[a-z0-9\-]+(\.[[a-z0-9\-]+)+)/io) { while (s/([a-z0-9_\-\.+]+@[a-z0-9\-]+(\.[[a-z0-9\-]+)+)//io) { if ($m) { $a{lc($1)}=$m } else { $b{lc($1)}=$m } } $m=0 } else { $m && --$m } } if (%%a) {print join(",",keys(%%a))} else {print join(",",keys(%%b))}'`
@ -67,9 +64,7 @@ actionban = ADDRESSES=`whois <ip> | perl -e 'while (<STDIN>) { next if /^changed
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <bantime> unix timestamp of the ban time
# <unbantime> unix timestamp of the unban time
# Values: CMD # Values: CMD
# #
actionunban = actionunban =

View File

@ -54,9 +54,7 @@ actioncheck =
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
# See http://www.dshield.org/specs.html for more on report format/notes # See http://www.dshield.org/specs.html for more on report format/notes
@ -91,9 +89,7 @@ actionban = TZONE=`date +%%z | sed 's/\([+-]..\)\(..\)/\1:\2/'`
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionunban = if [ -f <tmpfile>.first ]; then actionunban = if [ -f <tmpfile>.first ]; then
@ -159,7 +155,6 @@ minreportinterval = 3600
# submit the batch, even if we haven't reached <lines> yet. Note that # submit the batch, even if we haven't reached <lines> yet. Note that
# this is only checked on each ban/unban, and that we always send # this is only checked on each ban/unban, and that we always send
# anything in the buffer on shutdown. Must be greater than # anything in the buffer on shutdown. Must be greater than
# <minreportinterval>.
# Values: [ NUM ] Default: 21600 (6 hours) # Values: [ NUM ] Default: 21600 (6 hours)
# #
maxbufferage = 21600 maxbufferage = 21600

View File

@ -29,9 +29,7 @@ actioncheck =
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionban = printf %%b "+<ip>\n" >> /tmp/fail2ban.dummy actionban = printf %%b "+<ip>\n" >> /tmp/fail2ban.dummy
@ -39,9 +37,7 @@ actionban = printf %%b "+<ip>\n" >> /tmp/fail2ban.dummy
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionunban = printf %%b "-<ip>\n" >> /tmp/fail2ban.dummy actionunban = printf %%b "-<ip>\n" >> /tmp/fail2ban.dummy

View File

@ -28,9 +28,7 @@ actioncheck =
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionban = IP=<ip> && actionban = IP=<ip> &&
@ -39,9 +37,7 @@ actionban = IP=<ip> &&
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionunban = IP=<ip> && sed -i.old /ALL:\ $IP/d <file> actionunban = IP=<ip> && sed -i.old /ALL:\ $IP/d <file>

View File

@ -34,9 +34,7 @@ actioncheck =
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionban = echo block in quick from <ip>/32 | /sbin/ipf -f - actionban = echo block in quick from <ip>/32 | /sbin/ipf -f -
@ -45,9 +43,7 @@ actionban = echo block in quick from <ip>/32 | /sbin/ipf -f -
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
# note -r option used to remove matching rule # note -r option used to remove matching rule

View File

@ -32,9 +32,7 @@ actioncheck =
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionban = ipfw add deny tcp from <ip> to <localhost> <port> actionban = ipfw add deny tcp from <ip> to <localhost> <port>
@ -43,9 +41,7 @@ actionban = ipfw add deny tcp from <ip> to <localhost> <port>
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionunban = ipfw delete `ipfw list | grep -i <ip> | awk '{print $1;}'` actionunban = ipfw delete `ipfw list | grep -i <ip> | awk '{print $1;}'`

View File

@ -34,9 +34,7 @@ actioncheck = iptables -n -L <chain> | grep -q 'fail2ban-<name>[ \t]'
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionban = iptables -I fail2ban-<name> 1 -s <ip> -j DROP actionban = iptables -I fail2ban-<name> 1 -s <ip> -j DROP
@ -44,9 +42,7 @@ actionban = iptables -I fail2ban-<name> 1 -s <ip> -j DROP
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionunban = iptables -D fail2ban-<name> -s <ip> -j DROP actionunban = iptables -D fail2ban-<name> -s <ip> -j DROP

View File

@ -38,7 +38,7 @@ actionstop = iptables -D INPUT -p <protocol> -m multiport --dports <port> -m set
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# Values: CMD # Values: CMD
# #
actionban = ipset --test fail2ban-<name> <ip> || ipset --add fail2ban-<name> <ip> actionban = ipset --test fail2ban-<name> <ip> || ipset --add fail2ban-<name> <ip>
@ -46,7 +46,7 @@ actionban = ipset --test fail2ban-<name> <ip> || ipset --add fail2ban-<name> <i
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# Values: CMD # Values: CMD
# #
actionunban = ipset --test fail2ban-<name> <ip> && ipset --del fail2ban-<name> <ip> actionunban = ipset --test fail2ban-<name> <ip> && ipset --del fail2ban-<name> <ip>

View File

@ -38,7 +38,7 @@ actionstop = iptables -D INPUT -p <protocol> -m multiport --dports <port> -m set
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# Values: CMD # Values: CMD
# #
actionban = ipset add fail2ban-<name> <ip> timeout <bantime> -exist actionban = ipset add fail2ban-<name> <ip> timeout <bantime> -exist
@ -46,7 +46,7 @@ actionban = ipset add fail2ban-<name> <ip> timeout <bantime> -exist
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# Values: CMD # Values: CMD
# #
actionunban = ipset del fail2ban-<name> <ip> -exist actionunban = ipset del fail2ban-<name> <ip> -exist

View File

@ -42,9 +42,7 @@ actioncheck = iptables -n -L fail2ban-<name>-log >/dev/null
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionban = iptables -I fail2ban-<name> 1 -s <ip> -j fail2ban-<name>-log actionban = iptables -I fail2ban-<name> 1 -s <ip> -j fail2ban-<name>-log
@ -52,9 +50,7 @@ actionban = iptables -I fail2ban-<name> 1 -s <ip> -j fail2ban-<name>-log
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionunban = iptables -D fail2ban-<name> -s <ip> -j fail2ban-<name>-log actionunban = iptables -D fail2ban-<name> -s <ip> -j fail2ban-<name>-log

View File

@ -32,9 +32,7 @@ actioncheck = iptables -n -L <chain> | grep -q 'fail2ban-<name>[ \t]'
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionban = iptables -I fail2ban-<name> 1 -s <ip> -j DROP actionban = iptables -I fail2ban-<name> 1 -s <ip> -j DROP
@ -42,9 +40,7 @@ actionban = iptables -I fail2ban-<name> 1 -s <ip> -j DROP
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionunban = iptables -D fail2ban-<name> -s <ip> -j DROP actionunban = iptables -D fail2ban-<name> -s <ip> -j DROP

View File

@ -34,9 +34,7 @@ actioncheck = iptables -n -L <chain> | grep -q 'fail2ban-<name>[ \t]'
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionban = iptables -I fail2ban-<name> 1 -s <ip> -j DROP actionban = iptables -I fail2ban-<name> 1 -s <ip> -j DROP
@ -44,9 +42,7 @@ actionban = iptables -I fail2ban-<name> 1 -s <ip> -j DROP
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionunban = iptables -D fail2ban-<name> -s <ip> -j DROP actionunban = iptables -D fail2ban-<name> -s <ip> -j DROP

View File

@ -46,9 +46,7 @@ actioncheck = test -e /proc/net/xt_recent/fail2ban-<name>
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionban = echo +<ip> > /proc/net/xt_recent/fail2ban-<name> actionban = echo +<ip> > /proc/net/xt_recent/fail2ban-<name>
@ -56,9 +54,7 @@ actionban = echo +<ip> > /proc/net/xt_recent/fail2ban-<name>
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionunban = echo -<ip> > /proc/net/xt_recent/fail2ban-<name> actionunban = echo -<ip> > /proc/net/xt_recent/fail2ban-<name>

View File

@ -32,9 +32,7 @@ actioncheck = iptables -n -L <chain> | grep -q 'fail2ban-<name>[ \t]'
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionban = iptables -I fail2ban-<name> 1 -s <ip> -j DROP actionban = iptables -I fail2ban-<name> 1 -s <ip> -j DROP
@ -42,9 +40,7 @@ actionban = iptables -I fail2ban-<name> 1 -s <ip> -j DROP
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionunban = iptables -D fail2ban-<name> -s <ip> -j DROP actionunban = iptables -D fail2ban-<name> -s <ip> -j DROP

View File

@ -43,9 +43,7 @@ actioncheck =
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionban = printf %%b "`date`: <ip> (<failures> failures)\n" >> <tmpfile> actionban = printf %%b "`date`: <ip> (<failures> failures)\n" >> <tmpfile>
@ -62,9 +60,7 @@ actionban = printf %%b "`date`: <ip> (<failures> failures)\n" >> <tmpfile>
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionunban = actionunban =

View File

@ -34,10 +34,7 @@ actioncheck =
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <failtime> unix timestamp of the last failure
# <bantime> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionban = printf %%b "Hi,\n actionban = printf %%b "Hi,\n
@ -53,9 +50,7 @@ actionban = printf %%b "Hi,\n
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <bantime> unix timestamp of the ban time
# <unbantime> unix timestamp of the unban time
# Values: CMD # Values: CMD
# #
actionunban = actionunban =

View File

@ -34,9 +34,7 @@ actioncheck =
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionban = printf %%b "Hi,\n actionban = printf %%b "Hi,\n
@ -50,9 +48,7 @@ actionban = printf %%b "Hi,\n
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionunban = actionunban =

View File

@ -34,9 +34,7 @@ actioncheck =
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionban = printf %%b "Hi,\n actionban = printf %%b "Hi,\n
@ -48,9 +46,7 @@ actionban = printf %%b "Hi,\n
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionunban = actionunban =

View File

@ -49,9 +49,7 @@ actioncheck =
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
# #
@ -71,9 +69,7 @@ actionban = MNWLOGIN=`perl -e '$s=shift;$s=~s/([\W])/"%%".uc(sprintf("%%2.2x",or
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionunban = actionunban =

View File

@ -52,9 +52,7 @@ actioncheck =
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionban = printf %%b "`date`: <ip> (<failures> failures)\n" >> <tmpfile> actionban = printf %%b "`date`: <ip> (<failures> failures)\n" >> <tmpfile>
@ -74,9 +72,7 @@ actionban = printf %%b "`date`: <ip> (<failures> failures)\n" >> <tmpfile>
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionunban = actionunban =

View File

@ -42,9 +42,7 @@ actioncheck =
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip>
@ -64,9 +62,7 @@ actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip>
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionunban = actionunban =

View File

@ -42,9 +42,7 @@ actioncheck =
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip>
@ -62,9 +60,7 @@ actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip>
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionunban = actionunban =

View File

@ -42,9 +42,7 @@ actioncheck =
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip>
@ -60,9 +58,7 @@ actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip>
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionunban = actionunban =

View File

@ -36,9 +36,7 @@ actioncheck =
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionban = shorewall drop <ip> actionban = shorewall drop <ip>
@ -46,9 +44,7 @@ actionban = shorewall drop <ip>
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.
# Tags: <ip> IP address # Tags: See jail.conf(5) man page
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD # Values: CMD
# #
actionunban = shorewall allow <ip> actionunban = shorewall allow <ip>

View File

@ -15,6 +15,7 @@
# Values: TEXT # Values: TEXT
# #
failregex = reject: RCPT from (.*)\[<HOST>\]: 554 failregex = reject: RCPT from (.*)\[<HOST>\]: 554
reject: RCPT from (.*)\[<HOST>\]: 450 4\.7\.1 : Helo command rejected: Host not found; from=<> to=<> proto=ESMTP helo= *$
# 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.

View File

@ -0,0 +1,20 @@
# /etc/fail2ban/filter.d/sogo-auth.conf
#
# Fail2Ban configuration file
# By Arnd Brandes
# SOGo
#
[Definition]
# Option: failregex
# Filter Ban in /var/log/sogo/sogo.log
# Note: the error log may contain multiple hosts, whereas the first one
# is the client and all others are poxys. We match the first one, only
failregex = Login from '<HOST>' for user '.*' might not have worked( - password policy: \d* grace: -?\d* expire: -?\d* bound: -?\d*)?\s*$
# Option: ignoreregex
# Notes.: regex to ignore. If this regex matches, the line is ignored.
# Values: TEXT
#
ignoreregex =

View File

@ -212,6 +212,20 @@ filter = roundcube-auth
action = iptables[name=RoundCube, port="http,https"] action = iptables[name=RoundCube, port="http,https"]
logpath = /var/log/roundcube/userlogins logpath = /var/log/roundcube/userlogins
# Monitor SOGo groupware server
[sogo-iptables]
enabled = false
filter = sogo-auth
port = http, https
# without proxy this would be:
# port = 20000
action = iptables[name=SOGo, port="http,https"]
logpath = /var/log/sogo/sogo.log
# Ban attackers that try to use PHP's URL-fopen() functionality # Ban attackers that try to use PHP's URL-fopen() functionality
# through GET/POST variables. - Experimental, with more than a year # through GET/POST variables. - Experimental, with more than a year
# of usage in production environments. # of usage in production environments.

View File

@ -63,7 +63,7 @@ class Fail2banClient:
self.__conf["interactive"] = False self.__conf["interactive"] = False
self.__conf["socket"] = None self.__conf["socket"] = None
self.__conf["pidfile"] = None self.__conf["pidfile"] = None
def dispVersion(self): def dispVersion(self):
print "Fail2Ban v" + version print "Fail2Ban v" + version
print print
@ -73,7 +73,7 @@ class Fail2banClient:
print print
print "Written by Cyril Jaquier <cyril.jaquier@fail2ban.org>." print "Written by Cyril Jaquier <cyril.jaquier@fail2ban.org>."
print "Many contributions by Yaroslav O. Halchenko <debian@onerussian.com>." print "Many contributions by Yaroslav O. Halchenko <debian@onerussian.com>."
def dispUsage(self): def dispUsage(self):
""" Prints Fail2Ban command line options and exits """ Prints Fail2Ban command line options and exits
""" """
@ -95,17 +95,17 @@ class Fail2banClient:
print " -V, --version print the version" print " -V, --version print the version"
print print
print "Command:" print "Command:"
# Prints the protocol # Prints the protocol
printFormatted() printFormatted()
print print
print "Report bugs to https://github.com/fail2ban/fail2ban/issues" print "Report bugs to https://github.com/fail2ban/fail2ban/issues"
def dispInteractive(self): def dispInteractive(self):
print "Fail2Ban v" + version + " reads log file that contains password failure report" print "Fail2Ban v" + version + " reads log file that contains password failure report"
print "and bans the corresponding IP addresses using firewall rules." print "and bans the corresponding IP addresses using firewall rules."
print print
def __sigTERMhandler(self, signum, frame): def __sigTERMhandler(self, signum, frame):
# Print a new line because we probably come from wait # Print a new line because we probably come from wait
@ -139,10 +139,10 @@ class Fail2banClient:
elif opt[0] in ["-V", "--version"]: elif opt[0] in ["-V", "--version"]:
self.dispVersion() self.dispVersion()
sys.exit(0) sys.exit(0)
def __ping(self): def __ping(self):
return self.__processCmd([["ping"]], False) return self.__processCmd([["ping"]], False)
def __processCmd(self, cmd, showRet = True): def __processCmd(self, cmd, showRet = True):
beautifier = Beautifier() beautifier = Beautifier()
for c in cmd: for c in cmd:
@ -167,7 +167,7 @@ class Fail2banClient:
logSys.error(e) logSys.error(e)
return False return False
return True return True
## ##
# Process a command line. # Process a command line.
# #
@ -241,13 +241,13 @@ class Fail2banClient:
return False return False
else: else:
return self.__processCmd([cmd]) return self.__processCmd([cmd])
## ##
# Start Fail2Ban server. # Start Fail2Ban server.
# #
# Start the Fail2ban server in daemon mode. # Start the Fail2ban server in daemon mode.
def __startServerAsync(self, socket, pidfile, force = False): def __startServerAsync(self, socket, pidfile, force = False):
# Forks the current process. # Forks the current process.
pid = os.fork() pid = os.fork()
@ -278,7 +278,7 @@ class Fail2banClient:
except OSError: except OSError:
logSys.error("Could not start %s" % self.SERVER) logSys.error("Could not start %s" % self.SERVER)
os.exit(-1) os.exit(-1)
def __waitOnServer(self): def __waitOnServer(self):
# Wait for the server to start # Wait for the server to start
cnt = 0 cnt = 0
@ -306,16 +306,16 @@ class Fail2banClient:
cnt += 1 cnt += 1
if self.__conf["verbose"] > 1: if self.__conf["verbose"] > 1:
sys.stdout.write('\n') sys.stdout.write('\n')
def start(self, argv): def start(self, argv):
# Command line options # Command line options
self.__argv = argv self.__argv = argv
# Install signal handlers # Install signal handlers
signal.signal(signal.SIGTERM, self.__sigTERMhandler) signal.signal(signal.SIGTERM, self.__sigTERMhandler)
signal.signal(signal.SIGINT, self.__sigTERMhandler) signal.signal(signal.SIGINT, self.__sigTERMhandler)
# Reads the command line options. # Reads the command line options.
try: try:
cmdOpts = 'hc:s:p:xdviqV' cmdOpts = 'hc:s:p:xdviqV'
@ -324,9 +324,9 @@ class Fail2banClient:
except getopt.GetoptError: except getopt.GetoptError:
self.dispUsage() self.dispUsage()
return False return False
self.__getCmdLineOptions(optList) self.__getCmdLineOptions(optList)
verbose = self.__conf["verbose"] verbose = self.__conf["verbose"]
if verbose <= 0: if verbose <= 0:
logSys.setLevel(logging.ERROR) logSys.setLevel(logging.ERROR)
@ -346,7 +346,7 @@ class Fail2banClient:
# Set the configuration path # Set the configuration path
self.__configurator.setBaseDir(self.__conf["conf"]) self.__configurator.setBaseDir(self.__conf["conf"])
# Set socket path # Set socket path
self.__configurator.readEarly() self.__configurator.readEarly()
conf = self.__configurator.getEarlyOptions() conf = self.__configurator.getEarlyOptions()
@ -360,7 +360,7 @@ class Fail2banClient:
ret = self.__readConfig() ret = self.__readConfig()
self.dumpConfig(self.__stream) self.dumpConfig(self.__stream)
return ret return ret
# Interactive mode # Interactive mode
if self.__conf["interactive"]: if self.__conf["interactive"]:
try: try:
@ -401,14 +401,14 @@ class Fail2banClient:
self.__configurator.convertToProtocol() self.__configurator.convertToProtocol()
self.__stream = self.__configurator.getConfigStream() self.__stream = self.__configurator.getConfigStream()
return ret return ret
def __readJailConfig(self, jail): def __readJailConfig(self, jail):
self.__configurator.readAll() self.__configurator.readAll()
ret = self.__configurator.getOptions(jail) ret = self.__configurator.getOptions(jail)
self.__configurator.convertToProtocol() self.__configurator.convertToProtocol()
self.__stream = self.__configurator.getConfigStream() self.__stream = self.__configurator.getConfigStream()
return ret return ret
#@staticmethod #@staticmethod
def dumpConfig(cmd): def dumpConfig(cmd):
for c in cmd: for c in cmd:

View File

@ -50,24 +50,24 @@ class RegexStat:
def __str__(self): def __str__(self):
return "%s(%r) %d failed: %s" \ return "%s(%r) %d failed: %s" \
% (self.__class__, self.__failregex, self.__stats, self.__ipList) % (self.__class__, self.__failregex, self.__stats, self.__ipList)
def inc(self): def inc(self):
self.__stats += 1 self.__stats += 1
def getStats(self): def getStats(self):
return self.__stats return self.__stats
def getFailRegex(self): def getFailRegex(self):
return self.__failregex return self.__failregex
def appendIP(self, value): def appendIP(self, value):
self.__ipList.extend(value) self.__ipList.extend(value)
def getIPList(self): def getIPList(self):
return self.__ipList return self.__ipList
class Fail2banRegex: class Fail2banRegex:
test = None test = None
CONFIG_DEFAULTS = {'configpath' : "/etc/fail2ban/"} CONFIG_DEFAULTS = {'configpath' : "/etc/fail2ban/"}
@ -87,7 +87,7 @@ class Fail2banRegex:
self.__logging_level = self.__verbose and logging.DEBUG or logging.WARN self.__logging_level = self.__verbose and logging.DEBUG or logging.WARN
logging.getLogger("fail2ban").addHandler(self.__hdlr) logging.getLogger("fail2ban").addHandler(self.__hdlr)
logging.getLogger("fail2ban").setLevel(logging.ERROR) logging.getLogger("fail2ban").setLevel(logging.ERROR)
#@staticmethod #@staticmethod
def dispVersion(): def dispVersion():
print "Fail2Ban v" + version print "Fail2Ban v" + version
@ -99,7 +99,7 @@ class Fail2banRegex:
print "Written by Cyril Jaquier <cyril.jaquier@fail2ban.org>." print "Written by Cyril Jaquier <cyril.jaquier@fail2ban.org>."
print "Many contributions by Yaroslav O. Halchenko <debian@onerussian.com>." print "Many contributions by Yaroslav O. Halchenko <debian@onerussian.com>."
dispVersion = staticmethod(dispVersion) dispVersion = staticmethod(dispVersion)
#@staticmethod #@staticmethod
def dispUsage(): def dispUsage():
print "Usage: "+sys.argv[0]+" [OPTIONS] <LOG> <REGEX> [IGNOREREGEX]" print "Usage: "+sys.argv[0]+" [OPTIONS] <LOG> <REGEX> [IGNOREREGEX]"
@ -129,7 +129,7 @@ class Fail2banRegex:
print print
print "Report bugs to https://github.com/fail2ban/fail2ban/issues" print "Report bugs to https://github.com/fail2ban/fail2ban/issues"
dispUsage = staticmethod(dispUsage) dispUsage = staticmethod(dispUsage)
def getCmdLineOptions(self, optList): def getCmdLineOptions(self, optList):
""" Gets the command line options """ Gets the command line options
""" """
@ -213,7 +213,7 @@ class Fail2banRegex:
print "Use regex line : " + stripReg print "Use regex line : " + stripReg
self.__failregex = [RegexStat(value)] self.__failregex = [RegexStat(value)]
return True return True
def testIgnoreRegex(self, line): def testIgnoreRegex(self, line):
found = False found = False
for regex in self.__ignoreregex: for regex in self.__ignoreregex:
@ -230,7 +230,7 @@ class Fail2banRegex:
finally: finally:
self.__filter.delIgnoreRegex(0) self.__filter.delIgnoreRegex(0)
logging.getLogger("fail2ban").setLevel(self.__logging_level) logging.getLogger("fail2ban").setLevel(self.__logging_level)
def testRegex(self, line): def testRegex(self, line):
found = False found = False
for regex in self.__ignoreregex: for regex in self.__ignoreregex:
@ -260,7 +260,7 @@ class Fail2banRegex:
logging.getLogger("fail2ban").setLevel(logging.CRITICAL) logging.getLogger("fail2ban").setLevel(logging.CRITICAL)
for regex in self.__ignoreregex: for regex in self.__ignoreregex:
self.__filter.delIgnoreRegex(0) self.__filter.delIgnoreRegex(0)
def printStats(self): def printStats(self):
print print
print "Results" print "Results"
@ -309,20 +309,20 @@ class Fail2banRegex:
print " %s (%s)%s" % ( print " %s (%s)%s" % (
ip[0], timeString, ip[2] and " (already matched)" or "") ip[0], timeString, ip[2] and " (already matched)" or "")
print print
print "Date template hits:" print "Date template hits:"
for template in self.__filter.dateDetector.getTemplates(): for template in self.__filter.dateDetector.getTemplates():
if self.__verbose or template.getHits(): if self.__verbose or template.getHits():
print `template.getHits()` + " hit(s): " + template.getName() print `template.getHits()` + " hit(s): " + template.getName()
print print
print "Success, the total number of match is " + str(total) print "Success, the total number of match is " + str(total)
print print
print "However, look at the above section 'Running tests' which could contain important" print "However, look at the above section 'Running tests' which could contain important"
print "information." print "information."
return True return True
if __name__ == "__main__": if __name__ == "__main__":
fail2banRegex = Fail2banRegex() fail2banRegex = Fail2banRegex()
# Reads the command line options. # Reads the command line options.

View File

@ -46,7 +46,7 @@ logSys = logging.getLogger("fail2ban")
# Its first goal was to protect a SSH server. # Its first goal was to protect a SSH server.
class Fail2banServer: class Fail2banServer:
def __init__(self): def __init__(self):
self.__server = None self.__server = None
self.__argv = None self.__argv = None
@ -55,7 +55,7 @@ class Fail2banServer:
self.__conf["force"] = False self.__conf["force"] = False
self.__conf["socket"] = "/var/run/fail2ban/fail2ban.sock" self.__conf["socket"] = "/var/run/fail2ban/fail2ban.sock"
self.__conf["pidfile"] = "/var/run/fail2ban/fail2ban.pid" self.__conf["pidfile"] = "/var/run/fail2ban/fail2ban.pid"
def dispVersion(self): def dispVersion(self):
print "Fail2Ban v" + version print "Fail2Ban v" + version
print print
@ -65,7 +65,7 @@ class Fail2banServer:
print print
print "Written by Cyril Jaquier <cyril.jaquier@fail2ban.org>." print "Written by Cyril Jaquier <cyril.jaquier@fail2ban.org>."
print "Many contributions by Yaroslav O. Halchenko <debian@onerussian.com>." print "Many contributions by Yaroslav O. Halchenko <debian@onerussian.com>."
def dispUsage(self): def dispUsage(self):
""" Prints Fail2Ban command line options and exits """ Prints Fail2Ban command line options and exits
""" """
@ -88,7 +88,7 @@ class Fail2banServer:
print " -V, --version print the version" print " -V, --version print the version"
print print
print "Report bugs to https://github.com/fail2ban/fail2ban/issues" print "Report bugs to https://github.com/fail2ban/fail2ban/issues"
def __getCmdLineOptions(self, optList): def __getCmdLineOptions(self, optList):
""" Gets the command line options """ Gets the command line options
""" """
@ -109,11 +109,11 @@ class Fail2banServer:
if opt[0] in ["-V", "--version"]: if opt[0] in ["-V", "--version"]:
self.dispVersion() self.dispVersion()
sys.exit(0) sys.exit(0)
def start(self, argv): def start(self, argv):
# Command line options # Command line options
self.__argv = argv self.__argv = argv
# Reads the command line options. # Reads the command line options.
try: try:
cmdOpts = 'bfs:p:xhV' cmdOpts = 'bfs:p:xhV'
@ -122,9 +122,9 @@ class Fail2banServer:
except getopt.GetoptError: except getopt.GetoptError:
self.dispUsage() self.dispUsage()
sys.exit(-1) sys.exit(-1)
self.__getCmdLineOptions(optList) self.__getCmdLineOptions(optList)
try: try:
self.__server = Server(self.__conf["background"]) self.__server = Server(self.__conf["background"])
self.__server.start(self.__conf["socket"], self.__server.start(self.__conf["socket"],
@ -135,7 +135,7 @@ class Fail2banServer:
logSys.exception(e) logSys.exception(e)
self.__server.quit() self.__server.quit()
return False return False
if __name__ == "__main__": if __name__ == "__main__":
server = Fail2banServer() server = Fail2banServer()
if server.start(sys.argv): if server.start(sys.argv):

View File

@ -35,6 +35,9 @@ from testcases import filtertestcase
from testcases import servertestcase from testcases import servertestcase
from testcases import datedetectortestcase from testcases import datedetectortestcase
from testcases import actiontestcase from testcases import actiontestcase
from testcases import sockettestcase
from testcases.utils import FormatterWithTraceBack
from server.mytime import MyTime from server.mytime import MyTime
from optparse import OptionParser, Option from optparse import OptionParser, Option
@ -51,12 +54,14 @@ def get_opt_parser():
choices=('debug', 'info', 'warn', 'error', 'fatal'), choices=('debug', 'info', 'warn', 'error', 'fatal'),
default=None, default=None,
help="Log level for the logger to use during running tests"), help="Log level for the logger to use during running tests"),
])
p.add_options([
Option('-n', "--no-network", action="store_true", Option('-n', "--no-network", action="store_true",
dest="no_network", dest="no_network",
help="Do not run tests that require the network"), help="Do not run tests that require the network"),
Option("-t", "--log-traceback", action='store_true',
help="Enrich log-messages with compressed tracebacks"),
Option("--full-traceback", action='store_true',
help="Either to make the tracebacks full, not compressed (as by default)"),
]) ])
return p return p
@ -88,12 +93,21 @@ else: # pragma: no cover
# Add the default logging handler # Add the default logging handler
stdout = logging.StreamHandler(sys.stdout) stdout = logging.StreamHandler(sys.stdout)
fmt = ' %(message)s'
if opts.log_traceback:
Formatter = FormatterWithTraceBack
fmt = (opts.full_traceback and ' %(tb)s' or ' %(tbc)s') + fmt
else:
Formatter = logging.Formatter
# Custom log format for the verbose tests runs # Custom log format for the verbose tests runs
if verbosity > 1: # pragma: no cover if verbosity > 1: # pragma: no cover
stdout.setFormatter(logging.Formatter(' %(asctime)-15s %(thread)s %(message)s')) stdout.setFormatter(Formatter(' %(asctime)-15s %(thread)s' + fmt))
else: # pragma: no cover else: # pragma: no cover
# just prefix with the space # just prefix with the space
stdout.setFormatter(logging.Formatter(' %(message)s')) stdout.setFormatter(Formatter(fmt))
logSys.addHandler(stdout) logSys.addHandler(stdout)
# #
@ -130,9 +144,13 @@ tests.addTest(unittest.makeSuite(actiontestcase.ExecuteAction))
tests.addTest(unittest.makeSuite(failmanagertestcase.AddFailure)) tests.addTest(unittest.makeSuite(failmanagertestcase.AddFailure))
# BanManager # BanManager
tests.addTest(unittest.makeSuite(banmanagertestcase.AddFailure)) tests.addTest(unittest.makeSuite(banmanagertestcase.AddFailure))
# ClientReader # ClientReaders
tests.addTest(unittest.makeSuite(clientreadertestcase.ConfigReaderTest))
tests.addTest(unittest.makeSuite(clientreadertestcase.JailReaderTest)) tests.addTest(unittest.makeSuite(clientreadertestcase.JailReaderTest))
tests.addTest(unittest.makeSuite(clientreadertestcase.FilterReaderTest)) tests.addTest(unittest.makeSuite(clientreadertestcase.FilterReaderTest))
tests.addTest(unittest.makeSuite(clientreadertestcase.JailsReaderTest))
# CSocket and AsyncServer
tests.addTest(unittest.makeSuite(sockettestcase.Socket))
# Filter # Filter
if not opts.no_network: if not opts.no_network:
@ -173,6 +191,9 @@ for Filter_ in filters:
tests.addTest(unittest.makeSuite( tests.addTest(unittest.makeSuite(
filtertestcase.get_monitor_failures_testcase(Filter_))) filtertestcase.get_monitor_failures_testcase(Filter_)))
# Server test for logging elements which break logging used to support
# testcases analysis
tests.addTest(unittest.makeSuite(servertestcase.TransmitterLogging))
# #
# Run the tests # Run the tests

44
man/fail2ban.1 Normal file
View File

@ -0,0 +1,44 @@
.TH FAIL2BAN "1" "March 2013" "Fail2Ban"
.SH NAME
fail2ban \- a set of server and client programs to limit brute force authentication attempts.
.SH DESCRIPTION
Fail2Ban consists of a client, server and configuration files to limit
brute force authentication attempts.
The server program \fBfail2ban-server\fR is responsible for monitoring
log files and issuing ban/unban commands. It gets configured through
a simple protocol by \fBfail2ban-client\fR, which can also read
configuration files and issue corresponding configuration commands to
the server.
For details on the configuration of fail2ban see the jail.conf(5)
manual page. A jail (as specified in jail.conf) couples filters and
actions definitions for any given list of files to get monitored.
For details on the command-line options of fail2ban-server see the
fail2ban-server(1) manual page.
For details on the command-line options and commands for configuring
the server via fail2ban-client see the fail2ban-client(1) manual page.
For testing regular expressions specified in a filter using the
fail2ban-regex program may be of use and its manual page is
fail2ban-regex(1).
.SH FILES
\fI/etc/fail2ban/*\fR
.SH AUTHOR
Manual page written by Daniel Black and Yaroslav Halchenko
.SH "REPORTING BUGS"
Report bugs to https://github.com/fail2ban/fail2ban/issues
.SH COPYRIGHT
Copyright \(co 2013
.br
Copyright of modifications held by their respective authors.
Licensed under the GNU General Public License v2 (GPL).
.SH "SEE ALSO"
.br
fail2ban-server(1)
fail2ban-client(1)
fail2ban-regex(1)
jail.conf(5)

168
man/jail.conf.5 Normal file
View File

@ -0,0 +1,168 @@
.TH JAIL.CONF "5" "March 2013" "Fail2Ban" "Fail2Ban Configuration"
.SH NAME
jail.conf \- configuration for the fail2ban server
.SH SYNOPSIS
.I jail.conf / jail.local
.I action.d/*.conf action.d/*.local
.I filter.d/*.conf filter.d/*.local
.SH DESCRIPTION
Fail2ban has three configuration file types. Action files are the commands for banning and unbanning of IP address,
Filter files tell fail2ban how to detect authentication failures, and Jail configurations combine filters with actions into jails.
There are *.conf files that are distributed by fail2ban and *.local file that contain user customizations.
It is recommended that *.conf files should remain unchanged. If needed, customizations should be provided in *.local files.
For instance, if you would like to customize the [ssh-iptables-ipset] jail, create a jail.local to extend jail.conf
(the configuration for the fail2ban server). The jail.local file will be the following if you only need to enable
it:
.TP
\fIjail.local\fR
[ssh-iptables-ipset]
enabled = true
.PP
Override only the settings you need to change and the rest of the configuration will come from the corresponding
*.conf file.
\fI*.d/\fR
.RS
In addition to .local, for any .conf file there can be a corresponding
\fI.d/\fR directory to contain additional .conf files that will be read after the
appropriate .local file. Last parsed file will take precidence over
identical entries, parsed alphabetically, e.g.
.RS
\fIjail.d/01_enable.conf\fR - to enable a specific jail
.RE
.RS
\fIjail.d/02_custom_port.conf\fR - containing specific configuration entry to change the port of the jail specified in the configuration
.RE
.RS
\fIfail2ban.d/01_custom_log.conf\fR - containing specific configuration entry to use a different log path.
.RE
.RE
.SH DEFAULT
The following options are applicable to all jails. Their meaning is described in the default \fIjail.conf\fR file.
.TP
\fBignoreip\fR
.TP
\fBbantime\fR
.TP
\fBfindtime\fR
.TP
\fBmaxretry\fR
.TP
\fBbackend\fR
.TP
\fBusedns\fR
.SH "ACTION FILES"
Action files specify which commands are executed to ban and unban an IP address. They are located under \fI/etc/fail2ban/action.d\fR.
Like with jail.conf files, if you desire local changes create an \fI[actionname].local\fR file in the \fI/etc/fail2ban/action.d\fR directory
and override the required settings.
Action files are ini files that have two sections, \fBDefinition\fR and \fBInit\fR .
The [Init] section allows for action-specific settings. In \fIjail.conf/jail.local\fR these can be overwritten for a particular jail as options to the jail.
The following commands can be present in the [Definition] section.
.TP
\fBactionstart\fR
command(s) executed when the jail starts.
.TP
\fBactionstop\fR
command(s) executed when the jail stops.
.TP
\fBactioncheck\fR
the command ran before any other action. It aims to verify if the environment is still ok.
.TP
\fBactionban\fR
command(s) that bans the IP address after \fBmaxretry\fR log lines matches within last \fBfindtime\fR seconds.
.TP
\fBactionunban\fR
command(s) that unbans the IP address after \fBbantime\fR.
Commands specified in the [Definition] section are executed through a system shell so shell redirection and process control is allowed. The commands should
return 0, otherwise error would be logged. Moreover if \fBactioncheck\fR exits with non-0 status, it is taken as indication that firewall status has changed and fail2ban needs to reinitialize itself (i.e. issue \fBactionstop\fR and \fBactionstart\fR commands).
Tags are enclosed in <>. All the elements of [Init] are tags that are replaced in all action commands. Tags can be added by the
\fBfail2ban-client\fR using the setctag command.
More than a single command is allowed to be specified. Each command needs to be on a separate line and indented with whitespaces without blank lines. The following example defines
two commands to be executed.
actionban = iptables -I fail2ban-<name> --source <ip> -j DROP
echo ip=<ip>, match=<match>, time=<time> >> /var/log/fail2ban.log
.SS "Action Tags"
The following tags are substituted in the actionban, actionunban and actioncheck (when called before actionban/actionunban) commands.
.TP
\fBip\fR
An IPv4 ip address to be banned. e.g. 192.168.0.2
.TP
\fBfailures\fR
The number of times the failure occurred in the log file. e.g. 3
.TP
\fBtime\fR
The unix time of the ban. e.g. 1357508484
.TP
\fBmatches\fR
The concatenated string of the log file lines of the matches that generated the ban. Many characters interpreted by shell get escaped.
.SH FILTER FILES
Filter definitions are those in \fI/etc/fail2ban/filter.d/*.conf\fR and \fIfilter.d/*.local\fR.
These are used to identify failed authentication attempts in logs and to extract the host IP address (or hostname if \fBusedns\fR is \fBtrue\fR).
Like action files, filter files are ini files. The main section is the [Definition] section.
There are two filter definitions used in the [Definition] section:
.TP
\fBfailregex\fR
is the regex (\fBreg\fRular \fBex\fRpression) that will match failed attempts. The tag <HOST> is used as part of the regex and is itself a regex
for IPv4 addresses and hostnames. fail2ban will work out which one of these it actually is.
.TP
\fBignoreregex\fR
is the regex to identify log entries that should be ignored by fail2ban, even if they match failregex.
Using Python "string interpolation" mechanisms, other definitions are allowed and can later be used within other definitions as %(defnname)s. For example.
baduseragents = IE|wget
failregex = useragent=%(baduseragents)s
.PP
Filters can also have a section called [INCLUDES]. This is used to read other configuration files.
.TP
\fBbefore\fR
indicates that this file is read before the [Definition] section.
.TP
\fBafter\fR
indicates that this file is read after the [Definition] section.
.SH AUTHOR
Fail2ban was originally written by Cyril Jaquier <cyril.jaquier@fail2ban.org>.
At the moment it is maintained and further developed by Yaroslav O. Halchenko <debian@onerussian.com> and a number of contributors. See \fBTHANKS\fR file shipped with Fail2Ban for a full list.
.
Manual page written by Daniel Black and Yaroslav Halchenko.
.SH "REPORTING BUGS"
Report bugs to https://github.com/fail2ban/fail2ban/issues
.SH COPYRIGHT
Copyright \(co 2013 Daniel Black
.br
Copyright of modifications held by their respective authors.
Licensed under the GNU General Public License v2 (GPL).
.SH "SEE ALSO"
.br
fail2ban-server(1)

View File

@ -148,7 +148,7 @@ class BanManager:
def addBanTicket(self, ticket): def addBanTicket(self, ticket):
try: try:
self.__lock.acquire() self.__lock.acquire()
if not self.__inBanList(ticket): if not self._inBanList(ticket):
self.__banList.append(ticket) self.__banList.append(ticket)
self.__banTotal += 1 self.__banTotal += 1
return True return True
@ -177,7 +177,7 @@ class BanManager:
# @param ticket the ticket # @param ticket the ticket
# @return True if a ticket already exists # @return True if a ticket already exists
def __inBanList(self, ticket): def _inBanList(self, ticket):
for i in self.__banList: for i in self.__banList:
if ticket.getIP() == i.getIP(): if ticket.getIP() == i.getIP():
return True return True

View File

@ -50,8 +50,11 @@ class DateTemplate:
def getName(self): def getName(self):
return self.__name return self.__name
def setRegex(self, regex): def setRegex(self, regex, wordBegin=True):
self.__regex = regex.strip() regex = regex.strip()
if (wordBegin and not re.search(r'^\^', regex)):
regex = r'\b' + regex
self.__regex = regex
self.__cRegex = re.compile(regex) self.__cRegex = re.compile(regex)
def getRegex(self): def getRegex(self):
@ -158,6 +161,7 @@ class DateStrptime(DateTemplate):
"pattern" % (opattern, e)) "pattern" % (opattern, e))
if date[0] < 2000: if date[0] < 2000:
# There is probably no year field in the logs # There is probably no year field in the logs
# NOTE: Possibly makes week/year day incorrect
date[0] = MyTime.gmtime()[0] date[0] = MyTime.gmtime()[0]
# Bug fix for #1241756 # Bug fix for #1241756
# If the date is greater than the current time, we suppose # If the date is greater than the current time, we suppose
@ -166,10 +170,12 @@ class DateStrptime(DateTemplate):
logSys.debug( logSys.debug(
u"Correcting deduced year from %d to %d since %f > %f" % u"Correcting deduced year from %d to %d since %f > %f" %
(date[0], date[0]-1, time.mktime(date), MyTime.time())) (date[0], date[0]-1, time.mktime(date), MyTime.time()))
# NOTE: Possibly makes week/year day incorrect
date[0] -= 1 date[0] -= 1
elif date[1] == 1 and date[2] == 1: elif date[1] == 1 and date[2] == 1:
# If it is Jan 1st, it is either really Jan 1st or there # If it is Jan 1st, it is either really Jan 1st or there
# is neither month nor day in the log. # is neither month nor day in the log.
# NOTE: Possibly makes week/year day incorrect
date[1] = MyTime.gmtime()[1] date[1] = MyTime.gmtime()[1]
date[2] = MyTime.gmtime()[2] date[2] = MyTime.gmtime()[2]
return date return date
@ -180,7 +186,8 @@ class DateTai64n(DateTemplate):
def __init__(self): def __init__(self):
DateTemplate.__init__(self) DateTemplate.__init__(self)
# We already know the format for TAI64N # We already know the format for TAI64N
self.setRegex("@[0-9a-f]{24}") # yoh: we should not add an additional front anchor
self.setRegex("@[0-9a-f]{24}", wordBegin=False)
def getDate(self, line): def getDate(self, line):
date = None date = None

View File

@ -105,9 +105,17 @@ class FailManager:
fData.setLastReset(unixTime) fData.setLastReset(unixTime)
fData.setLastTime(unixTime) fData.setLastTime(unixTime)
self.__failList[ip] = fData self.__failList[ip] = fData
logSys.debug("Currently have failures from %d IPs: %s"
% (len(self.__failList), self.__failList.keys()))
self.__failTotal += 1 self.__failTotal += 1
if logSys.getEffectiveLevel() <= logging.DEBUG:
# yoh: Since composing this list might be somewhat time consuming
# in case of having many active failures, it should be ran only
# if debug level is "low" enough
failures_summary = ', '.join(['%s:%d' % (k, v.getRetry())
for k,v in self.__failList.iteritems()])
logSys.debug("Total # of detected failures: %d. Current failures from %d IPs (IP:count): %s"
% (self.__failTotal, len(self.__failList), failures_summary))
finally: finally:
self.__lock.release() self.__lock.release()

View File

@ -63,16 +63,17 @@ class FilterPyinotify(FileFilter):
logSys.debug("Created FilterPyinotify") logSys.debug("Created FilterPyinotify")
def callback(self, event): def callback(self, event, origin=''):
logSys.debug("%sCallback for Event: %s", origin, event)
path = event.pathname path = event.pathname
if event.mask & pyinotify.IN_CREATE: if event.mask & pyinotify.IN_CREATE:
# skip directories altogether # skip directories altogether
if event.mask & pyinotify.IN_ISDIR: if event.mask & pyinotify.IN_ISDIR:
logSys.debug("Ignoring creation of directory %s" % path) logSys.debug("Ignoring creation of directory %s", path)
return return
# check if that is a file we care about # check if that is a file we care about
if not path in self.__watches: if not path in self.__watches:
logSys.debug("Ignoring creation of %s we do not monitor" % path) logSys.debug("Ignoring creation of %s we do not monitor", path)
return return
else: else:
# we need to substitute the watcher with a new one, so first # we need to substitute the watcher with a new one, so first
@ -104,8 +105,8 @@ class FilterPyinotify(FileFilter):
def _addFileWatcher(self, path): def _addFileWatcher(self, path):
wd = self.__monitor.add_watch(path, pyinotify.IN_MODIFY) wd = self.__monitor.add_watch(path, pyinotify.IN_MODIFY)
self.__watches.update(wd) self.__watches.update(wd)
logSys.debug("Added file watcher for %s" % path) logSys.debug("Added file watcher for %s", path)
# process the file since we did get even # process the file since we did get even
self._process_file(path) self._process_file(path)
@ -114,7 +115,7 @@ class FilterPyinotify(FileFilter):
wd = self.__monitor.rm_watch(wdInt) wd = self.__monitor.rm_watch(wdInt)
if wd[wdInt]: if wd[wdInt]:
del self.__watches[path] del self.__watches[path]
logSys.debug("Removed file watcher for %s" % path) logSys.debug("Removed file watcher for %s", path)
return True return True
else: else:
return False return False
@ -130,7 +131,7 @@ class FilterPyinotify(FileFilter):
# we need to watch also the directory for IN_CREATE # we need to watch also the directory for IN_CREATE
self.__watches.update( self.__watches.update(
self.__monitor.add_watch(path_dir, pyinotify.IN_CREATE)) self.__monitor.add_watch(path_dir, pyinotify.IN_CREATE))
logSys.debug("Added monitor for the parent directory %s" % path_dir) logSys.debug("Added monitor for the parent directory %s", path_dir)
self._addFileWatcher(path) self._addFileWatcher(path)
@ -151,7 +152,7 @@ class FilterPyinotify(FileFilter):
# since there is no other monitored file under this directory # since there is no other monitored file under this directory
wdInt = self.__watches.pop(path_dir) wdInt = self.__watches.pop(path_dir)
_ = self.__monitor.rm_watch(wdInt) _ = self.__monitor.rm_watch(wdInt)
logSys.debug("Removed monitor for the parent directory %s" % path_dir) logSys.debug("Removed monitor for the parent directory %s", path_dir)
## ##
@ -165,7 +166,7 @@ class FilterPyinotify(FileFilter):
self.__notifier = pyinotify.ThreadedNotifier(self.__monitor, self.__notifier = pyinotify.ThreadedNotifier(self.__monitor,
ProcessPyinotify(self)) ProcessPyinotify(self))
self.__notifier.start() self.__notifier.start()
logSys.debug("pyinotifier started for %s." % self.jail.getName()) logSys.debug("pyinotifier started for %s.", self.jail.getName())
# TODO: verify that there is nothing really to be done for # TODO: verify that there is nothing really to be done for
# idle jails # idle jails
return True return True
@ -201,5 +202,4 @@ class ProcessPyinotify(pyinotify.ProcessEvent):
# just need default, since using mask on watch to limit events # just need default, since using mask on watch to limit events
def process_default(self, event): def process_default(self, event):
logSys.debug("Callback for Event: %s" % event) self.__FileFilter.callback(event, origin='Default ')
self.__FileFilter.callback(event)

View File

@ -385,7 +385,7 @@ class Server:
try: try:
handler.flush() handler.flush()
handler.close() handler.close()
except ValueError: except (ValueError, KeyError):
if sys.version_info >= (2,6): if sys.version_info >= (2,6):
raise raise
# is known to be thrown after logging was shutdown once # is known to be thrown after logging was shutdown once

View File

@ -3,3 +3,11 @@ install-purelib=/usr/share/fail2ban
[sdist] [sdist]
formats=bztar formats=bztar
[bdist_rpm]
release = 1
packager = Yaroslav Halchenko <debian@onerussian.com>, Daniel Black <grooverdan@users.sourceforge.net>
doc_files = DEVELOP
README
THANKS
doc/run-rootless.txt

View File

@ -67,6 +67,9 @@ setup(
), ),
('/var/run/fail2ban', ('/var/run/fail2ban',
'' ''
),
('/usr/share/doc/fail2ban',
['README', 'DEVELOP', 'doc/run-rootless.txt']
) )
] ]
) )
@ -82,13 +85,7 @@ elements = {
"/usr/bin/": "/usr/bin/":
[ [
"fail2ban.py" "fail2ban.py"
], ],
"/usr/lib/fail2ban/firewall/":
[
"iptables.py",
"ipfwadm.py",
"ipfw.py"
],
"/usr/lib/fail2ban/": "/usr/lib/fail2ban/":
[ [
"version.py", "version.py",

View File

@ -49,11 +49,11 @@ class AddFailure(unittest.TestCase):
self.assertFalse(self.__banManager.addBanTicket(self.__ticket)) self.assertFalse(self.__banManager.addBanTicket(self.__ticket))
self.assertEqual(self.__banManager.size(), 1) self.assertEqual(self.__banManager.size(), 1)
def _testInListOK(self): def testInListOK(self):
ticket = BanTicket('193.168.0.128', 1167605999.0) ticket = BanTicket('193.168.0.128', 1167605999.0)
self.assertTrue(self.__banManager.inBanList(ticket)) self.assertTrue(self.__banManager._inBanList(ticket))
def _testInListNOK(self): def testInListNOK(self):
ticket = BanTicket('111.111.1.111', 1167605999.0) ticket = BanTicket('111.111.1.111', 1167605999.0)
self.assertFalse(self.__banManager.inBanList(ticket)) self.assertFalse(self.__banManager._inBanList(ticket))

View File

@ -17,28 +17,93 @@
# 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, 2011-2013 Yaroslav Halchenko"
# $Revision$
__author__ = "Cyril Jaquier"
__version__ = "$Revision$"
__date__ = "$Date$"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
import unittest import os, shutil, tempfile, unittest
from client.jailreader import JailReader
from client.configreader import ConfigReader from client.configreader import ConfigReader
from client.jailreader import JailReader
from client.filterreader import FilterReader from client.filterreader import FilterReader
from client.jailsreader import JailsReader
from client.configurator import Configurator
class JailReaderTest(unittest.TestCase): class ConfigReaderTest(unittest.TestCase):
def setUp(self): def setUp(self):
"""Call before every test case.""" """Call before every test case."""
self.d = tempfile.mkdtemp(prefix="f2b-temp")
self.c = ConfigReader(basedir=self.d)
def tearDown(self): def tearDown(self):
"""Call after every test case.""" """Call after every test case."""
shutil.rmtree(self.d)
def _write(self, fname, value):
# verify if we don't need to create .d directory
if os.path.sep in fname:
d = os.path.dirname(fname)
d_ = os.path.join(self.d, d)
if not os.path.exists(d_):
os.makedirs(d_)
open("%s/%s" % (self.d, fname), "w").write("""
[section]
option = %s
""" % value)
def _remove(self, fname):
os.unlink("%s/%s" % (self.d, fname))
self.assertTrue(self.c.read('c')) # we still should have some
def _getoption(self, f='c'):
self.assertTrue(self.c.read(f)) # we got some now
return self.c.getOptions('section', [("int", 'option')])['option']
def testInaccessibleFile(self):
f = os.path.join(self.d, "d.conf") # inaccessible file
self._write('d.conf', 0)
self.assertEqual(self._getoption('d'), 0)
os.chmod(f, 0)
self.assertFalse(self.c.read('d')) # should not be readable BUT present
def testOptionalDotDDir(self):
self.assertFalse(self.c.read('c')) # nothing is there yet
self._write("c.conf", "1")
self.assertEqual(self._getoption(), 1)
self._write("c.conf", "2") # overwrite
self.assertEqual(self._getoption(), 2)
self._write("c.local", "3") # add override in .local
self.assertEqual(self._getoption(), 3)
self._write("c.d/98.conf", "998") # add 1st override in .d/
self.assertEqual(self._getoption(), 998)
self._write("c.d/90.conf", "990") # add previously sorted override in .d/
self.assertEqual(self._getoption(), 998) # should stay the same
self._write("c.d/99.conf", "999") # now override in a way without sorting we possibly get a failure
self.assertEqual(self._getoption(), 999)
self._remove("c.d/99.conf")
self.assertEqual(self._getoption(), 998)
self._remove("c.d/98.conf")
self.assertEqual(self._getoption(), 990)
self._remove("c.d/90.conf")
self.assertEqual(self._getoption(), 3)
self._remove("c.conf") # we allow to stay without .conf
self.assertEqual(self._getoption(), 3)
self._write("c.conf", "1")
self._remove("c.local")
self.assertEqual(self._getoption(), 1)
class JailReaderTest(unittest.TestCase):
def testStockSSHJail(self):
jail = JailReader('ssh-iptables', basedir='config') # we are running tests from root project dir atm
self.assertTrue(jail.read())
self.assertTrue(jail.getOptions())
self.assertFalse(jail.isEnabled())
self.assertEqual(jail.getName(), 'ssh-iptables')
def testSplitAction(self): def testSplitAction(self):
action = "mail-whois[name=SSH]" action = "mail-whois[name=SSH]"
@ -81,3 +146,60 @@ class FilterReaderTest(unittest.TestCase):
filterReader.getOptions(None) filterReader.getOptions(None)
self.assertEquals(filterReader.convert(), output) self.assertEquals(filterReader.convert(), output)
class JailsReaderTest(unittest.TestCase):
def testProvidingBadBasedir(self):
if not os.path.exists('/XXX'):
reader = JailsReader(basedir='/XXX')
self.assertRaises(ValueError, reader.read)
def testReadStockJailConf(self):
jails = JailsReader(basedir='config') # 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()
# by default None of the jails is enabled and we get no
# commands to communicate to the server
self.assertEqual(comm_commands, [])
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
self.assertTrue(jails.read()) # opens fine
self.assertTrue(jails.getOptions()) # reads fine
comm_commands = jails.convert()
# by default we have lots of jails ;)
self.assertTrue(len(comm_commands))
# and we know even some of them by heart
for j in ['ssh-iptables', 'recidive']:
# by default we have 'auto' backend ATM
self.assertTrue(['add', j, 'auto'] in comm_commands)
# and warn on useDNS
self.assertTrue(['set', j, 'usedns', 'warn'] in comm_commands)
self.assertTrue(['start', j] in comm_commands)
# last commands should be the 'start' commands
self.assertEqual(comm_commands[-1][0], 'start')
# TODO: make sure that all of the jails have actions assigned,
# otherwise it makes little to no sense
def testConfigurator(self):
configurator = Configurator()
configurator.setBaseDir('config')
self.assertEqual(configurator.getBaseDir(), 'config')
configurator.readEarly()
opts = configurator.getEarlyOptions()
# our current default settings
self.assertEqual(opts['socket'], '/var/run/fail2ban/fail2ban.sock')
self.assertEqual(opts['pidfile'], '/var/run/fail2ban/fail2ban.pid')
# and if we force change configurator's fail2ban's baseDir
# there should be an error message (test visually ;) --
# otherwise just a code smoke test)
configurator._Configurator__jails.setBaseDir('/tmp')
self.assertEqual(configurator._Configurator__jails.getBaseDir(), '/tmp')
self.assertEqual(configurator.getBaseDir(), 'config')

View File

@ -45,36 +45,50 @@ class DateDetectorTest(unittest.TestCase):
log = "1138049999 [sshd] error: PAM: Authentication failure" log = "1138049999 [sshd] error: PAM: Authentication failure"
date = [2006, 1, 23, 21, 59, 59, 0, 23, 0] date = [2006, 1, 23, 21, 59, 59, 0, 23, 0]
dateUnix = 1138049999.0 dateUnix = 1138049999.0
self.assertEqual(self.__datedetector.getTime(log), date) self.assertEqual(self.__datedetector.getTime(log), date)
self.assertEqual(self.__datedetector.getUnixTime(log), dateUnix) self.assertEqual(self.__datedetector.getUnixTime(log), dateUnix)
def testGetTime(self): def testGetTime(self):
log = "Jan 23 21:59:59 [sshd] error: PAM: Authentication failure" log = "Jan 23 21:59:59 [sshd] error: PAM: Authentication failure"
date = [2005, 1, 23, 21, 59, 59, 1, 23, -1] date = [2005, 1, 23, 21, 59, 59, 6, 23, -1]
dateUnix = 1106513999.0 dateUnix = 1106513999.0
# yoh: testing only up to 6 elements, since the day of the week
self.assertEqual(self.__datedetector.getTime(log), date) # is not correctly determined atm, since year is not present
# in the log entry. Since this doesn't effect the operation
# of fail2ban -- we just ignore incorrect day of the week
self.assertEqual(self.__datedetector.getTime(log)[:6], date[:6])
self.assertEqual(self.__datedetector.getUnixTime(log), dateUnix) self.assertEqual(self.__datedetector.getUnixTime(log), dateUnix)
def testVariousTimes(self): def testVariousTimes(self):
"""Test detection of various common date/time formats f2b should understand """Test detection of various common date/time formats f2b should understand
""" """
date = [2005, 1, 23, 21, 59, 59, 1, 23, -1] date = [2005, 1, 23, 21, 59, 59, 6, 23, -1]
dateUnix = 1106513999.0 dateUnix = 1106513999.0
for sdate in ( for sdate in (
"Jan 23 21:59:59", "Jan 23 21:59:59",
"Sun Jan 23 21:59:59 2005",
"Sun Jan 23 21:59:59",
"2005/01/23 21:59:59",
"2005.01.23 21:59:59", "2005.01.23 21:59:59",
"23/01/2005 21:59:59", "23/01/2005 21:59:59",
"23/01/05 21:59:59",
"23/Jan/2005:21:59:59",
"01/23/2005:21:59:59",
"2005-01-23 21:59:59",
"23-Jan-2005 21:59:59",
"23-01-2005 21:59:59",
"01-23-2005 21:59:59.252", # reported on f2b, causes Feb29 fix to break "01-23-2005 21:59:59.252", # reported on f2b, causes Feb29 fix to break
"@4000000041f4104f00000000", # TAI64N
"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>",
): ):
log = sdate + "[sshd] error: PAM: Authentication failure" log = sdate + "[sshd] error: PAM: Authentication failure"
# exclude # exclude
# TODO (Yarik is confused): figure out why for above it is # yoh: on [:6] see in above test
# "1" as day of the week which would be Tue, although it
# was Sun
self.assertEqual(self.__datedetector.getTime(log)[:6], date[:6]) self.assertEqual(self.__datedetector.getTime(log)[:6], date[:6])
self.assertEqual(self.__datedetector.getUnixTime(log), dateUnix) self.assertEqual(self.__datedetector.getUnixTime(log), dateUnix)
@ -89,6 +103,27 @@ class DateDetectorTest(unittest.TestCase):
self.assertRaises(ValueError, self.__datedetector._appendTemplate, self.assertRaises(ValueError, self.__datedetector._appendTemplate,
self.__datedetector.getTemplates()[0]) self.__datedetector.getTemplates()[0])
def testFullYearMatch_gh130(self):
# see https://github.com/fail2ban/fail2ban/pull/130
# yoh: unfortunately this test is not really effective to reproduce the
# situation but left in place to assure consistent behavior
m1 = [2012, 10, 11, 2, 37, 17]
self.assertEqual(
self.__datedetector.getTime('2012/10/11 02:37:17 [error] 18434#0')[:6],
m1)
self.__datedetector.sortTemplate()
# confuse it with year being at the end
for i in xrange(10):
self.assertEqual(
self.__datedetector.getTime('11/10/2012 02:37:17 [error] 18434#0')[:6],
m1)
self.__datedetector.sortTemplate()
# and now back to the original
self.assertEqual(
self.__datedetector.getTime('2012/10/11 02:37:17 [error] 18434#0')[:6],
m1)
# def testDefaultTempate(self): # def testDefaultTempate(self):
# self.__datedetector.setDefaultRegex("^\S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2}") # self.__datedetector.setDefaultRegex("^\S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2}")
# self.__datedetector.setDefaultPattern("%b %d %H:%M:%S") # self.__datedetector.setDefaultPattern("%b %d %H:%M:%S")

View File

@ -0,0 +1,3 @@
# per https://github.com/fail2ban/fail2ban/issues/125
# and https://github.com/fail2ban/fail2ban/issues/126
Feb 21 09:21:54 xxx postfix/smtpd[14398]: NOQUEUE: reject: RCPT from example.com[192.0.43.10]: 450 4.7.1 : Helo command rejected: Host not found; from=<> to=<> proto=ESMTP helo=

View File

@ -0,0 +1,17 @@
# yoh: Kept original apache log lines as well, just in case they might come useful
# for (testing) multiline regular expressions which would further constraint
# SOGo log lines
Mar 24 08:58:32 sogod [26818]: <0x0xb8537990[LDAPSource]> <NSException: 0xb87c3008> NAME:LDAPException REASON:operation bind failed: Invalid credentials (0x31) INFO:{login = "uid=hack0r,ou=users,dc=mail,dc=example,dc=org"; }
Mar 24 08:58:32 sogod [26818]: SOGoRootPage Login from '173.194.44.31' for user 'hack0r' might not have worked - password policy: 65535 grace: -1 expire: -1 bound: 0
173.194.44.31 - - [24/Mar/2013:08:58:32 GMT] "POST /SOGo/connect HTTP/1.1" 403 34/38 0.311 - - 2M
Mar 24 08:58:40 sogod [26818]: <0x0xb8537990[LDAPSource]> <NSException: 0xb87bb8d8> NAME:LDAPException REASON:operation bind failed: Invalid credentials (0x31) INFO:{login = "uid=kiddy,ou=users,dc=mail,dc=example,dc=org"; }
Mar 24 08:58:40 sogod [26818]: SOGoRootPage Login from '173.194.44.31' for user 'kiddy' might not have worked - password policy: 65535 grace: -1 expire: -1 bound: 0
173.194.44.31 - - [24/Mar/2013:08:58:40 GMT] "POST /SOGo/connect HTTP/1.1" 403 34/37 0.007 - - 32K
Mar 24 08:58:50 sogod [26818]: <0x0xb8537990[LDAPSource]> <NSException: 0xb87c27f8> NAME:LDAPException REASON:operation bind failed: Invalid credentials (0x31) INFO:{login = "uid=plsBanMe,ou=users,dc=mail,dc=example,dc=org"; }
Mar 24 08:58:50 sogod [26818]: SOGoRootPage Login from '173.194.44.31' for user 'plsBanMe' might not have worked - password policy: 65535 grace: -1 expire: -1 bound: 0
173.194.44.31 - - [24/Mar/2013:08:58:50 GMT] "POST /SOGo/connect HTTP/1.1" 403 34/40 0.008 - - 0
Mar 24 08:58:59 sogod [26818]: <0x0xb8537990[LDAPSource]> <NSException: 0xb87be830> NAME:LDAPException REASON:operation bind failed: Invalid credentials (0x31) INFO:{login = "uid=root,ou=users,dc=mail,dc=example,dc=org"; }
Mar 24 08:58:59 sogod [26818]: SOGoRootPage Login from '173.194.44.31' for user 'root' might not have worked - password policy: 65535 grace: -1 expire: -1 bound: 0
173.194.44.31 - - [24/Mar/2013:08:58:59 GMT] "POST /SOGo/connect HTTP/1.1" 403 34/36 0.007 - - 0
Mar 24 08:59:04 sogod [26818]: <0x0xb8537990[LDAPSource]> <NSException: 0xb87bc088> NAME:LDAPException REASON:operation bind failed: Invalid credentials (0x31) INFO:{login = "uid=admin,ou=users,dc=mail,dc=example,dc=org"; }
Mar 24 08:59:04 sogod [26818]: SOGoRootPage Login from '173.194.44.31' for user 'admin' might not have worked - password policy: 65535 grace: -1 expire: -1 bound: 0

View File

@ -22,6 +22,7 @@
__copyright__ = "Copyright (c) 2004 Cyril Jaquier; 2012 Yaroslav Halchenko" __copyright__ = "Copyright (c) 2004 Cyril Jaquier; 2012 Yaroslav Halchenko"
__license__ = "GPL" __license__ = "GPL"
from __builtin__ import open as fopen
import unittest import unittest
import os import os
import sys import sys
@ -38,6 +39,20 @@ from server.failmanager import FailManagerEmpty
# Useful helpers # Useful helpers
# #
# yoh: per Steven Hiscocks's insight while troubleshooting
# https://github.com/fail2ban/fail2ban/issues/103#issuecomment-15542836
# adding a sufficiently large buffer might help to guarantee that
# writes happen atomically.
def open(*args):
"""Overload built in open so we could assure sufficiently large buffer
Explicit .flush would be needed to assure that changes leave the buffer
"""
if len(args) == 2:
# ~50kB buffer should be sufficient for all tests here.
args = args + (50000,)
return fopen(*args)
def _killfile(f, name): def _killfile(f, name):
try: try:
f.close() f.close()
@ -48,6 +63,11 @@ def _killfile(f, name):
except: except:
pass pass
# there might as well be the .bak file
if os.path.exists(name + '.bak'):
_killfile(None, name + '.bak')
def _sleep_4_poll(): def _sleep_4_poll():
"""PollFilter relies on file timestamps - so we might need to """PollFilter relies on file timestamps - so we might need to
sleep to guarantee that they differ sleep to guarantee that they differ
@ -105,20 +125,23 @@ def _copy_lines_between_files(fin, fout, n=None, skip=0, mode='a', terminal_line
time.sleep(1) time.sleep(1)
if isinstance(fin, str): # pragma: no branch - only used with str in test cases if isinstance(fin, str): # pragma: no branch - only used with str in test cases
fin = open(fin, 'r') fin = open(fin, 'r')
if isinstance(fout, str):
fout = open(fout, mode)
# Skip # Skip
for i in xrange(skip): for i in xrange(skip):
_ = fin.readline() _ = fin.readline()
# Read/Write # Read
i = 0 i = 0
lines = []
while n is None or i < n: while n is None or i < n:
l = fin.readline() l = fin.readline()
if terminal_line is not None and l == terminal_line: if terminal_line is not None and l == terminal_line:
break break
fout.write(l) lines.append(l)
fout.flush()
i += 1 i += 1
# Write: all at once and flush
if isinstance(fout, str):
fout = open(fout, mode)
fout.write('\n'.join(lines))
fout.flush()
# to give other threads possibly some time to crunch # to give other threads possibly some time to crunch
time.sleep(0.1) time.sleep(0.1)
return fout return fout
@ -324,11 +347,17 @@ def get_monitor_failures_testcase(Filter_):
"""Generator of TestCase's for different filters/backends """Generator of TestCase's for different filters/backends
""" """
# add Filter_'s name so we could easily identify bad cows
testclass_name = tempfile.mktemp(
'fail2ban', 'monitorfailures_%s' % (Filter_.__name__,))
class MonitorFailures(unittest.TestCase): class MonitorFailures(unittest.TestCase):
count = 0
def setUp(self): def setUp(self):
"""Call before every test case.""" """Call before every test case."""
self.filter = self.name = 'NA' self.filter = self.name = 'NA'
_, self.name = tempfile.mkstemp('fail2ban', 'monitorfailures') self.name = '%s-%d' % (testclass_name, self.count)
MonitorFailures.count += 1 # so we have unique filenames across tests
self.file = open(self.name, 'a') self.file = open(self.name, 'a')
self.jail = DummyJail() self.jail = DummyJail()
self.filter = Filter_(self.jail) self.filter = Filter_(self.jail)
@ -351,12 +380,9 @@ def get_monitor_failures_testcase(Filter_):
self.filter.join() # wait for the thread to terminate self.filter.join() # wait for the thread to terminate
#print "D: KILLING THE FILE" #print "D: KILLING THE FILE"
_killfile(self.file, self.name) _killfile(self.file, self.name)
#time.sleep(0.2) # Give FS time to ack the removal
pass pass
def __str__(self): # pragma: no cover - will only show up if unexpected exception is thrown
return "MonitorFailures%s(%s)" \
% (Filter_, hasattr(self, 'name') and self.name or 'tempfile')
def isFilled(self, delay=2.): def isFilled(self, delay=2.):
"""Wait up to `delay` sec to assure that it was modified or not """Wait up to `delay` sec to assure that it was modified or not
""" """
@ -379,7 +405,7 @@ def get_monitor_failures_testcase(Filter_):
return not self.isFilled(delay) return not self.isFilled(delay)
def assert_correct_last_attempt(self, failures, count=None): def assert_correct_last_attempt(self, failures, count=None):
self.assertTrue(self.isFilled(10)) # give Filter a chance to react self.assertTrue(self.isFilled(20)) # give Filter a chance to react
_assert_correct_last_attempt(self, self.jail, failures, count=count) _assert_correct_last_attempt(self, self.jail, failures, count=count)
@ -431,9 +457,10 @@ def get_monitor_failures_testcase(Filter_):
# if we move file into a new location while it has been open already # if we move file into a new location while it has been open already
self.file = _copy_lines_between_files(GetFailures.FILENAME_01, self.name, self.file = _copy_lines_between_files(GetFailures.FILENAME_01, self.name,
n=14, mode='w') n=14, mode='w')
self.assertTrue(self.isEmpty(2)) # Poll might need more time
self.assertTrue(self.isEmpty(4 + int(isinstance(self.filter, FilterPoll))*2))
self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan) self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan)
self.assertEqual(self.filter.failManager.getFailTotal(), 2) # Fails with Poll from time to time self.assertEqual(self.filter.failManager.getFailTotal(), 2)
# move aside, but leaving the handle still open... # move aside, but leaving the handle still open...
os.rename(self.name, self.name + '.bak') os.rename(self.name, self.name + '.bak')
@ -488,7 +515,8 @@ def get_monitor_failures_testcase(Filter_):
# yoh: not sure why count here is not 9... TODO # yoh: not sure why count here is not 9... TODO
self.assert_correct_last_attempt(GetFailures.FAILURES_01)#, count=9) self.assert_correct_last_attempt(GetFailures.FAILURES_01)#, count=9)
MonitorFailures.__name__ = "MonitorFailures<%s>(%s)" \
% (Filter_.__name__, testclass_name) # 'tempfile')
return MonitorFailures return MonitorFailures

View File

@ -50,27 +50,29 @@ class StartStop(unittest.TestCase):
time.sleep(1) time.sleep(1)
self.__server.stopJail(name) self.__server.stopJail(name)
class TestServer(Server):
def setLogLevel(self, *args, **kwargs):
pass
def setLogTarget(self, *args, **kwargs):
pass
class Transmitter(unittest.TestCase): class TransmitterBase(unittest.TestCase):
def setUp(self): def setUp(self):
"""Call before every test case.""" """Call before every test case."""
self.__server = Server() self.transm = self.server._Server__transm
self.__transm = self.__server._Server__transm
self.__server.setLogTarget("/dev/null")
self.__server.setLogLevel(0)
sock_fd, sock_name = tempfile.mkstemp('fail2ban.sock', 'transmitter') sock_fd, sock_name = tempfile.mkstemp('fail2ban.sock', 'transmitter')
os.close(sock_fd) os.close(sock_fd)
pidfile_fd, pidfile_name = tempfile.mkstemp( pidfile_fd, pidfile_name = tempfile.mkstemp(
'fail2ban.pid', 'transmitter') 'fail2ban.pid', 'transmitter')
os.close(pidfile_fd) os.close(pidfile_fd)
self.__server.start(sock_name, pidfile_name, force=False) self.server.start(sock_name, pidfile_name, force=False)
self.jailName = "TestJail1" self.jailName = "TestJail1"
self.__server.addJail(self.jailName, "auto") self.server.addJail(self.jailName, "auto")
def tearDown(self): def tearDown(self):
"""Call after every test case.""" """Call after every test case."""
self.__server.quit() self.server.quit()
def setGetTest(self, cmd, inValue, outValue=None, jail=None): def setGetTest(self, cmd, inValue, outValue=None, jail=None):
setCmd = ["set", cmd, inValue] setCmd = ["set", cmd, inValue]
@ -81,8 +83,8 @@ class Transmitter(unittest.TestCase):
if outValue is None: if outValue is None:
outValue = inValue outValue = inValue
self.assertEqual(self.__transm.proceed(setCmd), (0, outValue)) self.assertEqual(self.transm.proceed(setCmd), (0, outValue))
self.assertEqual(self.__transm.proceed(getCmd), (0, outValue)) self.assertEqual(self.transm.proceed(getCmd), (0, outValue))
def setGetTestNOK(self, cmd, inValue, jail=None): def setGetTestNOK(self, cmd, inValue, jail=None):
setCmd = ["set", cmd, inValue] setCmd = ["set", cmd, inValue]
@ -92,30 +94,30 @@ class Transmitter(unittest.TestCase):
getCmd.insert(1, jail) getCmd.insert(1, jail)
# Get initial value before trying invalid value # Get initial value before trying invalid value
initValue = self.__transm.proceed(getCmd)[1] initValue = self.transm.proceed(getCmd)[1]
self.assertEqual(self.__transm.proceed(setCmd)[0], 1) self.assertEqual(self.transm.proceed(setCmd)[0], 1)
# Check after failed set that value is same as previous # Check after failed set that value is same as previous
self.assertEqual(self.__transm.proceed(getCmd), (0, initValue)) self.assertEqual(self.transm.proceed(getCmd), (0, initValue))
def jailAddDelTest(self, cmd, values, jail): def jailAddDelTest(self, cmd, values, jail):
cmdAdd = "add" + cmd cmdAdd = "add" + cmd
cmdDel = "del" + cmd cmdDel = "del" + cmd
self.assertEqual( self.assertEqual(
self.__transm.proceed(["get", jail, cmd]), (0, [])) self.transm.proceed(["get", jail, cmd]), (0, []))
for n, value in enumerate(values): for n, value in enumerate(values):
self.assertEqual( self.assertEqual(
self.__transm.proceed(["set", jail, cmdAdd, value]), self.transm.proceed(["set", jail, cmdAdd, value]),
(0, values[:n+1])) (0, values[:n+1]))
self.assertEqual( self.assertEqual(
self.__transm.proceed(["get", jail, cmd]), self.transm.proceed(["get", jail, cmd]),
(0, values[:n+1])) (0, values[:n+1]))
for n, value in enumerate(values): for n, value in enumerate(values):
self.assertEqual( self.assertEqual(
self.__transm.proceed(["set", jail, cmdDel, value]), self.transm.proceed(["set", jail, cmdDel, value]),
(0, values[n+1:])) (0, values[n+1:]))
self.assertEqual( self.assertEqual(
self.__transm.proceed(["get", jail, cmd]), self.transm.proceed(["get", jail, cmd]),
(0, values[n+1:])) (0, values[n+1:]))
def jailAddDelRegexTest(self, cmd, inValues, outValues, jail): def jailAddDelRegexTest(self, cmd, inValues, outValues, jail):
@ -126,107 +128,94 @@ class Transmitter(unittest.TestCase):
outValues = inValues outValues = inValues
self.assertEqual( self.assertEqual(
self.__transm.proceed(["get", jail, cmd]), (0, [])) self.transm.proceed(["get", jail, cmd]), (0, []))
for n, value in enumerate(inValues): for n, value in enumerate(inValues):
self.assertEqual( self.assertEqual(
self.__transm.proceed(["set", jail, cmdAdd, value]), self.transm.proceed(["set", jail, cmdAdd, value]),
(0, outValues[:n+1])) (0, outValues[:n+1]))
self.assertEqual( self.assertEqual(
self.__transm.proceed(["get", jail, cmd]), self.transm.proceed(["get", jail, cmd]),
(0, outValues[:n+1])) (0, outValues[:n+1]))
for n, value in enumerate(inValues): for n, value in enumerate(inValues):
self.assertEqual( self.assertEqual(
self.__transm.proceed(["set", jail, cmdDel, 0]), # First item self.transm.proceed(["set", jail, cmdDel, 0]), # First item
(0, outValues[n+1:])) (0, outValues[n+1:]))
self.assertEqual( self.assertEqual(
self.__transm.proceed(["get", jail, cmd]), self.transm.proceed(["get", jail, cmd]),
(0, outValues[n+1:])) (0, outValues[n+1:]))
class Transmitter(TransmitterBase):
def setUp(self):
self.server = TestServer()
super(Transmitter, self).setUp()
def testStopServer(self): def testStopServer(self):
self.assertEqual(self.__transm.proceed(["stop"]), (0, None)) self.assertEqual(self.transm.proceed(["stop"]), (0, None))
def testPing(self): def testPing(self):
self.assertEqual(self.__transm.proceed(["ping"]), (0, "pong")) self.assertEqual(self.transm.proceed(["ping"]), (0, "pong"))
def testSleep(self): def testSleep(self):
t0 = time.time() t0 = time.time()
self.assertEqual(self.__transm.proceed(["sleep", "1"]), (0, None)) self.assertEqual(self.transm.proceed(["sleep", "1"]), (0, None))
t1 = time.time() t1 = time.time()
# Approx 1 second delay # Approx 1 second delay
self.assertAlmostEqual(t1 - t0, 1, places=2) self.assertAlmostEqual(t1 - t0, 1, places=2)
def testLogTarget(self):
logTargets = []
for _ in xrange(3):
tmpFile = tempfile.mkstemp("fail2ban", "transmitter")
logTargets.append(tmpFile[1])
os.close(tmpFile[0])
for logTarget in logTargets:
self.setGetTest("logtarget", logTarget)
# If path is invalid, do not change logtarget
value = "/this/path/should/not/exist"
self.setGetTestNOK("logtarget", value)
self.__transm.proceed(["set", "/dev/null"])
for logTarget in logTargets:
os.remove(logTarget)
def testLogLevel(self):
self.setGetTest("loglevel", "4", 4)
self.setGetTest("loglevel", "2", 2)
self.setGetTest("loglevel", "-1", -1)
self.setGetTestNOK("loglevel", "Bird")
def testAddJail(self): def testAddJail(self):
jail2 = "TestJail2" jail2 = "TestJail2"
jail3 = "TestJail3" jail3 = "TestJail3"
jail4 = "TestJail4" jail4 = "TestJail4"
self.assertEqual( self.assertEqual(
self.__transm.proceed(["add", jail2, "polling"]), (0, jail2)) self.transm.proceed(["add", jail2, "polling"]), (0, jail2))
self.assertEqual(self.__transm.proceed(["add", jail3]), (0, jail3)) self.assertEqual(self.transm.proceed(["add", jail3]), (0, jail3))
self.assertEqual( self.assertEqual(
self.__transm.proceed(["add", jail4, "invalid backend"])[0], 1) self.transm.proceed(["add", jail4, "invalid backend"])[0], 1)
self.assertEqual( self.assertEqual(
self.__transm.proceed(["add", jail4, "auto"]), (0, jail4)) self.transm.proceed(["add", jail4, "auto"]), (0, jail4))
# Duplicate Jail # Duplicate Jail
self.assertEqual( self.assertEqual(
self.__transm.proceed(["add", self.jailName, "polling"])[0], 1) self.transm.proceed(["add", self.jailName, "polling"])[0], 1)
# All name is reserved # All name is reserved
self.assertEqual( self.assertEqual(
self.__transm.proceed(["add", "all", "polling"])[0], 1) self.transm.proceed(["add", "all", "polling"])[0], 1)
def testStartStopJail(self): def testStartStopJail(self):
self.assertEqual( self.assertEqual(
self.__transm.proceed(["start", self.jailName]), (0, None)) self.transm.proceed(["start", self.jailName]), (0, None))
time.sleep(1) time.sleep(1)
self.assertEqual( self.assertEqual(
self.__transm.proceed(["stop", self.jailName]), (0, None)) self.transm.proceed(["stop", self.jailName]), (0, None))
self.assertRaises( self.assertRaises(
UnknownJailException, self.__server.isAlive, self.jailName) UnknownJailException, self.server.isAlive, self.jailName)
def testStartStopAllJail(self): def testStartStopAllJail(self):
self.__server.addJail("TestJail2", "auto") self.server.addJail("TestJail2", "auto")
self.assertEqual( self.assertEqual(
self.__transm.proceed(["start", self.jailName]), (0, None)) self.transm.proceed(["start", self.jailName]), (0, None))
self.assertEqual( self.assertEqual(
self.__transm.proceed(["start", "TestJail2"]), (0, None)) self.transm.proceed(["start", "TestJail2"]), (0, None))
self.assertEqual(self.__transm.proceed(["stop", "all"]), (0, None)) # yoh: workaround for gh-146. I still think that there is some
# race condition and missing locking somewhere, but for now
# giving it a small delay reliably helps to proceed with tests
time.sleep(0.1)
self.assertEqual(self.transm.proceed(["stop", "all"]), (0, None))
time.sleep(1) time.sleep(1)
self.assertRaises( self.assertRaises(
UnknownJailException, self.__server.isAlive, self.jailName) UnknownJailException, self.server.isAlive, self.jailName)
self.assertRaises( self.assertRaises(
UnknownJailException, self.__server.isAlive, "TestJail2") UnknownJailException, self.server.isAlive, "TestJail2")
def testJailIdle(self): def testJailIdle(self):
self.assertEqual( self.assertEqual(
self.__transm.proceed(["set", self.jailName, "idle", "on"]), self.transm.proceed(["set", self.jailName, "idle", "on"]),
(0, True)) (0, True))
self.assertEqual( self.assertEqual(
self.__transm.proceed(["set", self.jailName, "idle", "off"]), self.transm.proceed(["set", self.jailName, "idle", "off"]),
(0, False)) (0, False))
self.assertEqual( self.assertEqual(
self.__transm.proceed(["set", self.jailName, "idle", "CAT"])[0], self.transm.proceed(["set", self.jailName, "idle", "CAT"])[0],
1) 1)
def testJailFindTime(self): def testJailFindTime(self):
@ -249,28 +238,28 @@ class Transmitter(unittest.TestCase):
# Safe default should be "no" # Safe default should be "no"
value = "Fish" value = "Fish"
self.assertEqual( self.assertEqual(
self.__transm.proceed(["set", self.jailName, "usedns", value]), self.transm.proceed(["set", self.jailName, "usedns", value]),
(0, "no")) (0, "no"))
def testJailBanIP(self): def testJailBanIP(self):
self.__server.startJail(self.jailName) # Jail must be started self.server.startJail(self.jailName) # Jail must be started
self.assertEqual( self.assertEqual(
self.__transm.proceed(["set", self.jailName, "banip", "127.0.0.1"]), self.transm.proceed(["set", self.jailName, "banip", "127.0.0.1"]),
(0, "127.0.0.1")) (0, "127.0.0.1"))
time.sleep(1) # Give chance to ban time.sleep(1) # Give chance to ban
self.assertEqual( self.assertEqual(
self.__transm.proceed(["set", self.jailName, "banip", "Badger"]), self.transm.proceed(["set", self.jailName, "banip", "Badger"]),
(0, "Badger")) #NOTE: Is IP address validated? Is DNS Lookup done? (0, "Badger")) #NOTE: Is IP address validated? Is DNS Lookup done?
time.sleep(1) # Give chance to ban time.sleep(1) # Give chance to ban
# Unban IP # Unban IP
self.assertEqual( self.assertEqual(
self.__transm.proceed( self.transm.proceed(
["set", self.jailName, "unbanip", "127.0.0.1"]), ["set", self.jailName, "unbanip", "127.0.0.1"]),
(0, "127.0.0.1")) (0, "127.0.0.1"))
# Unban IP which isn't banned # Unban IP which isn't banned
self.assertEqual( self.assertEqual(
self.__transm.proceed( self.transm.proceed(
["set", self.jailName, "unbanip", "192.168.1.1"])[0],1) ["set", self.jailName, "unbanip", "192.168.1.1"])[0],1)
def testJailMaxRetry(self): def testJailMaxRetry(self):
@ -292,22 +281,22 @@ class Transmitter(unittest.TestCase):
# Try duplicates # Try duplicates
value = "testcases/files/testcase04.log" value = "testcases/files/testcase04.log"
self.assertEqual( self.assertEqual(
self.__transm.proceed(["set", self.jailName, "addlogpath", value]), self.transm.proceed(["set", self.jailName, "addlogpath", value]),
(0, [value])) (0, [value]))
# Will silently ignore duplicate # Will silently ignore duplicate
self.assertEqual( self.assertEqual(
self.__transm.proceed(["set", self.jailName, "addlogpath", value]), self.transm.proceed(["set", self.jailName, "addlogpath", value]),
(0, [value])) (0, [value]))
self.assertEqual( self.assertEqual(
self.__transm.proceed(["get", self.jailName, "logpath"]), self.transm.proceed(["get", self.jailName, "logpath"]),
(0, [value])) (0, [value]))
self.assertEqual( self.assertEqual(
self.__transm.proceed(["set", self.jailName, "dellogpath", value]), self.transm.proceed(["set", self.jailName, "dellogpath", value]),
(0, [])) (0, []))
# Invalid file # Invalid file
value = "this_file_shouldn't_exist" value = "this_file_shouldn't_exist"
result = self.__transm.proceed( result = self.transm.proceed(
["set", self.jailName, "addlogpath", value]) ["set", self.jailName, "addlogpath", value])
self.assertTrue(isinstance(result[1], IOError)) self.assertTrue(isinstance(result[1], IOError))
@ -325,18 +314,18 @@ class Transmitter(unittest.TestCase):
# Try duplicates # Try duplicates
value = "127.0.0.1" value = "127.0.0.1"
self.assertEqual( self.assertEqual(
self.__transm.proceed(["set", self.jailName, "addignoreip", value]), self.transm.proceed(["set", self.jailName, "addignoreip", value]),
(0, [value])) (0, [value]))
# Will allow duplicate # Will allow duplicate
#NOTE: Should duplicates be allowed, or silent ignore like logpath? #NOTE: Should duplicates be allowed, or silent ignore like logpath?
self.assertEqual( self.assertEqual(
self.__transm.proceed(["set", self.jailName, "addignoreip", value]), self.transm.proceed(["set", self.jailName, "addignoreip", value]),
(0, [value, value])) (0, [value, value]))
self.assertEqual( self.assertEqual(
self.__transm.proceed(["get", self.jailName, "ignoreip"]), self.transm.proceed(["get", self.jailName, "ignoreip"]),
(0, [value, value])) (0, [value, value]))
self.assertEqual( self.assertEqual(
self.__transm.proceed(["set", self.jailName, "delignoreip", value]), self.transm.proceed(["set", self.jailName, "delignoreip", value]),
(0, [value])) (0, [value]))
def testJailRegex(self): def testJailRegex(self):
@ -355,11 +344,11 @@ class Transmitter(unittest.TestCase):
) )
self.assertEqual( self.assertEqual(
self.__transm.proceed( self.transm.proceed(
["set", self.jailName, "addfailregex", "No host regex"])[0], ["set", self.jailName, "addfailregex", "No host regex"])[0],
1) 1)
self.assertEqual( self.assertEqual(
self.__transm.proceed( self.transm.proceed(
["set", self.jailName, "addfailregex", 654])[0], ["set", self.jailName, "addfailregex", 654])[0],
1) 1)
@ -379,25 +368,25 @@ class Transmitter(unittest.TestCase):
) )
self.assertEqual( self.assertEqual(
self.__transm.proceed( self.transm.proceed(
["set", self.jailName, "addignoreregex", "Invalid [regex"])[0], ["set", self.jailName, "addignoreregex", "Invalid [regex"])[0],
1) 1)
self.assertEqual( self.assertEqual(
self.__transm.proceed( self.transm.proceed(
["set", self.jailName, "addignoreregex", 50])[0], ["set", self.jailName, "addignoreregex", 50])[0],
1) 1)
def testStatus(self): def testStatus(self):
jails = [self.jailName] jails = [self.jailName]
self.assertEqual(self.__transm.proceed(["status"]), self.assertEqual(self.transm.proceed(["status"]),
(0, [('Number of jail', len(jails)), ('Jail list', ", ".join(jails))])) (0, [('Number of jail', len(jails)), ('Jail list', ", ".join(jails))]))
self.__server.addJail("TestJail2", "auto") self.server.addJail("TestJail2", "auto")
jails.append("TestJail2") jails.append("TestJail2")
self.assertEqual(self.__transm.proceed(["status"]), self.assertEqual(self.transm.proceed(["status"]),
(0, [('Number of jail', len(jails)), ('Jail list', ", ".join(jails))])) (0, [('Number of jail', len(jails)), ('Jail list', ", ".join(jails))]))
def testJailStatus(self): def testJailStatus(self):
self.assertEqual(self.__transm.proceed(["status", self.jailName]), self.assertEqual(self.transm.proceed(["status", self.jailName]),
(0, (0,
[ [
('filter', [ ('filter', [
@ -432,54 +421,90 @@ class Transmitter(unittest.TestCase):
] ]
self.assertEqual( self.assertEqual(
self.__transm.proceed(["set", self.jailName, "addaction", action]), self.transm.proceed(["set", self.jailName, "addaction", action]),
(0, action)) (0, action))
self.assertEqual( self.assertEqual(
self.__transm.proceed(["get", self.jailName, "addaction", action]), self.transm.proceed(["get", self.jailName, "addaction", action]),
(0, action)) (0, action))
for cmd, value in zip(cmdList, cmdValueList): for cmd, value in zip(cmdList, cmdValueList):
self.assertEqual( self.assertEqual(
self.__transm.proceed( self.transm.proceed(
["set", self.jailName, cmd, action, value]), ["set", self.jailName, cmd, action, value]),
(0, value)) (0, value))
for cmd, value in zip(cmdList, cmdValueList): for cmd, value in zip(cmdList, cmdValueList):
self.assertEqual( self.assertEqual(
self.__transm.proceed(["get", self.jailName, cmd, action]), self.transm.proceed(["get", self.jailName, cmd, action]),
(0, value)) (0, value))
self.assertEqual( self.assertEqual(
self.__transm.proceed( self.transm.proceed(
["set", self.jailName, "setcinfo", action, "KEY", "VALUE"]), ["set", self.jailName, "setcinfo", action, "KEY", "VALUE"]),
(0, "VALUE")) (0, "VALUE"))
self.assertEqual( self.assertEqual(
self.__transm.proceed( self.transm.proceed(
["get", self.jailName, "cinfo", action, "KEY"]), ["get", self.jailName, "cinfo", action, "KEY"]),
(0, "VALUE")) (0, "VALUE"))
self.assertEqual( self.assertEqual(
self.__transm.proceed( self.transm.proceed(
["get", self.jailName, "cinfo", action, "InvalidKey"])[0], ["get", self.jailName, "cinfo", action, "InvalidKey"])[0],
1) 1)
self.assertEqual( self.assertEqual(
self.__transm.proceed( self.transm.proceed(
["set", self.jailName, "delcinfo", action, "KEY"]), ["set", self.jailName, "delcinfo", action, "KEY"]),
(0, None)) (0, None))
self.assertEqual( self.assertEqual(
self.__transm.proceed(["set", self.jailName, "delaction", action]), self.transm.proceed(["set", self.jailName, "delaction", action]),
(0, None)) (0, None))
self.assertEqual( self.assertEqual(
self.__transm.proceed( self.transm.proceed(
["set", self.jailName, "delaction", "Doesn't exist"])[0],1) ["set", self.jailName, "delaction", "Doesn't exist"])[0],1)
def testNOK(self): def testNOK(self):
self.assertEqual(self.__transm.proceed(["INVALID", "COMMAND"])[0],1) self.assertEqual(self.transm.proceed(["INVALID", "COMMAND"])[0],1)
def testSetNOK(self): def testSetNOK(self):
self.assertEqual( self.assertEqual(
self.__transm.proceed(["set", "INVALID", "COMMAND"])[0],1) self.transm.proceed(["set", "INVALID", "COMMAND"])[0],1)
def testGetNOK(self): def testGetNOK(self):
self.assertEqual( self.assertEqual(
self.__transm.proceed(["get", "INVALID", "COMMAND"])[0],1) self.transm.proceed(["get", "INVALID", "COMMAND"])[0],1)
def testStatusNOK(self): def testStatusNOK(self):
self.assertEqual( self.assertEqual(
self.__transm.proceed(["status", "INVALID", "COMMAND"])[0],1) self.transm.proceed(["status", "INVALID", "COMMAND"])[0],1)
class TransmitterLogging(TransmitterBase):
def setUp(self):
self.server = Server()
self.server.setLogTarget("/dev/null")
self.server.setLogLevel(0)
super(TransmitterLogging, self).setUp()
def testLogTarget(self):
logTargets = []
for _ in xrange(3):
tmpFile = tempfile.mkstemp("fail2ban", "transmitter")
logTargets.append(tmpFile[1])
os.close(tmpFile[0])
for logTarget in logTargets:
self.setGetTest("logtarget", logTarget)
# If path is invalid, do not change logtarget
value = "/this/path/should/not/exist"
self.setGetTestNOK("logtarget", value)
self.transm.proceed(["set", "/dev/null"])
for logTarget in logTargets:
os.remove(logTarget)
self.setGetTest("logtarget", "STDOUT")
self.setGetTest("logtarget", "STDERR")
self.setGetTest("logtarget", "SYSLOG")
def testLogLevel(self):
self.setGetTest("loglevel", "4", 4)
self.setGetTest("loglevel", "2", 2)
self.setGetTest("loglevel", "-1", -1)
self.setGetTest("loglevel", "0", 0)
self.setGetTestNOK("loglevel", "Bird")

View File

@ -0,0 +1,82 @@
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
# vi: set ft=python sts=4 ts=4 sw=4 noet :
# This file is part of Fail2Ban.
#
# Fail2Ban 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.
#
# Fail2Ban 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 Fail2Ban; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# Author: Steven Hiscocks
#
# $Revision$
__author__ = "Steven Hiscocks"
__version__ = "$Revision$"
__date__ = "$Date$"
__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
class Socket(unittest.TestCase):
def setUp(self):
"""Call before every test case."""
self.server = AsyncServer(self)
sock_fd, sock_name = tempfile.mkstemp('fail2ban.sock', 'socket')
os.close(sock_fd)
os.remove(sock_name)
self.sock_name = sock_name
def tearDown(self):
"""Call after every test case."""
@staticmethod
def proceed(message):
"""Test transmitter proceed method which just returns first arg"""
return message
def testSocket(self):
serverThread = threading.Thread(
target=self.server.start, args=(self.sock_name, False))
serverThread.daemon = True
serverThread.start()
time.sleep(1)
client = CSocket(self.sock_name)
testMessage = ["A", "test", "message"]
self.assertEqual(client.send(testMessage), testMessage)
self.server.stop()
serverThread.join(1)
self.assertFalse(os.path.exists(self.sock_name))
def testSocketForce(self):
open(self.sock_name, 'w').close() # Create sock file
# Try to start without force
self.assertRaises(
AsyncServerException, self.server.start, self.sock_name, False)
# Try agin with force set
serverThread = threading.Thread(
target=self.server.start, args=(self.sock_name, True))
serverThread.daemon = True
serverThread.start()
time.sleep(1)
self.server.stop()
serverThread.join(1)
self.assertFalse(os.path.exists(self.sock_name))

101
testcases/utils.py Normal file
View File

@ -0,0 +1,101 @@
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
# vi: set ft=python sts=4 ts=4 sw=4 noet :
# This file is part of Fail2Ban.
#
# Fail2Ban 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.
#
# Fail2Ban 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 Fail2Ban; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
__author__ = "Yaroslav Halchenko"
__copyright__ = "Copyright (c) 2013 Yaroslav Halchenko"
__license__ = "GPL"
import logging, os, re, traceback
from os.path import basename, dirname
#
# Following "traceback" functions are adopted from PyMVPA distributed
# under MIT/Expat and copyright by PyMVPA developers (i.e. me and
# Michael). Hereby I re-license derivative work on these pieces under GPL
# to stay in line with the main Fail2Ban license
#
def mbasename(s):
"""Custom function to include directory name if filename is too common
Also strip .py at the end
"""
base = basename(s)
if base.endswith('.py'):
base = base[:-3]
if base in set(['base', '__init__']):
base = basename(dirname(s)) + '.' + base
return base
class TraceBack(object):
"""Customized traceback to be included in debug messages
"""
def __init__(self, compress=False):
"""Initialize TrackBack metric
Parameters
----------
compress : bool
if True then prefix common with previous invocation gets
replaced with ...
"""
self.__prev = ""
self.__compress = compress
def __call__(self):
ftb = traceback.extract_stack(limit=100)[:-2]
entries = [[mbasename(x[0]), str(x[1])] for x in ftb]
entries = [ e for e in entries
if not e[0] in ['unittest', 'logging.__init__' ]]
# lets make it more consize
entries_out = [entries[0]]
for entry in entries[1:]:
if entry[0] == entries_out[-1][0]:
entries_out[-1][1] += ',%s' % entry[1]
else:
entries_out.append(entry)
sftb = '>'.join(['%s:%s' % (mbasename(x[0]),
x[1]) for x in entries_out])
if self.__compress:
# lets remove part which is common with previous invocation
prev_next = sftb
common_prefix = os.path.commonprefix((self.__prev, sftb))
common_prefix2 = re.sub('>[^>]*$', '', common_prefix)
if common_prefix2 != "":
sftb = '...' + sftb[len(common_prefix2):]
self.__prev = prev_next
return sftb
class FormatterWithTraceBack(logging.Formatter):
"""Custom formatter which expands %(tb) and %(tbc) with tracebacks
TODO: might need locking in case of compressed tracebacks
"""
def __init__(self, fmt, *args, **kwargs):
logging.Formatter.__init__(self, fmt=fmt, *args, **kwargs)
compress = '%(tbc)s' in fmt
self._tb = TraceBack(compress=compress)
def format(self, record):
record.tbc = record.tb = self._tb()
return logging.Formatter.format(self, record)