Merge commit '0.8.8-145-g72b0647' into debian

* commit '0.8.8-145-g72b0647': (114 commits)
  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
  ENH: adding ability to incorporate tracebacks into log lines while running tests
  ENH: FailManager -- improve log message to report total # of detected failures as well
  BF: allow to wait longer for FilterPoll in test_move_file
  ENH: elaborated debug log message about already detected failures
  BF: Remove custom __str__ for MonitorFailures and just adjust __name__ of the generated class
  DOC: Added suggested by @beilber description of .d/ + added I formatting to all filenames
  ENH: increase timeout to 20 sec from 10 sec in assert_correct_last_attempt
  BF: fixing up for handling of TAI64N timestamps and adding some unittest for prev commit (not effective much though)
  An example of failed logins against sogo
  Update sogo-auth.conf
  Added Daniel and Steven to THANKS
  My improvements to manpages
  PKG: change email that I want in RPMs
  ...
pull/808/head
Yaroslav Halchenko 2013-03-27 11:19:46 -04:00
commit 6c4aeaedc5
91 changed files with 2110 additions and 554 deletions

4
.coveragerc Normal file
View File

@ -0,0 +1,4 @@
[run]
branch = True
omit = /usr*

7
.gitignore vendored
View File

@ -1 +1,8 @@
*~ *~
build
dist
*.pyc
htmlcov
.coverage
*.orig
*.rej

132
DEVELOP
View File

@ -24,14 +24,100 @@ Request feature. You can find more details on the Fail2Ban wiki
Testing Testing
======= =======
Existing tests can be run by executing `fail2ban-testcases`. Existing tests can be run by executing `fail2ban-testcases`. This has options
like --log-level that will probably be useful. `fail2ban-testcases --help` for
full options.
Test cases should cover all usual cases, all exception cases and all inside
/ outside boundary conditions.
Test cases should cover all branches. The coverage tool will help identify
missing branches. Also see http://nedbatchelder.com/code/coverage/branch.html
for more details.
Install the package python-coverage to visualise your test coverage. Run the
following (note: on Debian-based systems, the script is called
`python-coverage`):
coverage run fail2ban-testcases
coverage html
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
complete. Try to ensure tests cover as many independent paths through the
code.
Manual Execution. To run in a development environment do:
./fail2ban-client -c config/ -s /tmp/f2b.sock -i start
some quick commands:
status
add test pyinotify
status test
set test addaction iptables
set test actionban iptables echo <ip> <cidr> >> /tmp/ban
set test actionunban iptables echo <ip> <cidr> >> /tmp/unban
get test actionban iptables
get test actionunban iptables
set test banip 192.168.2.2
status test
Documentation about creating tests (when tests are required and some guidelines
for creating good tests) will be added soon.
Coding Standards Coding Standards
================ ================
Coming Soon.
Style
-----
Please use tabs for now. Keep to 80 columns, at least for readable text.
Tests
-----
Add tests. They should test all the code you add in a meaning way.
Coverage
--------
Test coverage should always increase as you add code.
You may use "# pragma: no cover" in the code for branches of code that support
older versions on python. For all other uses of "pragma: no cover" or
"pragma: no branch" document the reason why its not covered. "I haven't written
a test case" isn't a sufficient reason.
Documentation
-------------
Ensure this documentation is up to date after changes. Also ensure that the man
pages still are accurate. Ensure that there is sufficient documentation for
your new features to be used.
Bugs
----
Remove them and don't add any more.
Git
---
Use the following tags in your commit messages:
'BF:' for bug 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
--------------
If you add an action.d/*.conf file also add a example in config/jail.conf
with enabled=false and maxretry=5 for ssh.
Design Design
@ -127,12 +213,14 @@ FileContainer
.__pos .__pos
Keeps the position pointer Keeps the position pointer
dnsutils.py
~~~~~~~~~~~
DNSUtils DNSUtils
Utility class for DNS and IP handling Utility class for DNS and IP handling
RF-Note: convert to functions within a separate submodule
filter*.py filter*.py
~~~~~~~~~~ ~~~~~~~~~~
@ -156,3 +244,35 @@ action.py
~~~~~~~~~ ~~~~~~~~~
Takes care about executing start/check/ban/unban/stop commands Takes care about executing start/check/ban/unban/stop commands
Releasing
=========
# Ensure the version is correct in ./common/version.py
# Add/finalize the corresponding entry in the ChangeLog
# Update man pages
(cd man ; ./generate-man )
git commit -m 'update man pages for release' man/*
# Make sure the tests pass
./fail2ban-testcases-all
# Prepare/upload source and rpm binary distributions
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.

View File

@ -3,6 +3,8 @@ ChangeLog
TODO TODO
THANKS THANKS
COPYING COPYING
DEVELOP
doc/run-rootless.txt
fail2ban-client fail2ban-client
fail2ban-server fail2ban-server
fail2ban-testcases fail2ban-testcases
@ -40,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
@ -48,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
@ -55,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
@ -86,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
@ -108,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

4
README
View File

@ -91,5 +91,5 @@ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details. 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 You should have received a copy of the GNU General Public License along with
Fail2Ban; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Fail2Ban; if not, write to the Free Software Foundation, Inc., 51 Franklin
Suite 330, Boston, MA 02111-1307 USA Street, Fifth Floor, Boston, MA 02110, USA

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

7
TODO
View File

@ -13,9 +13,12 @@ Legend:
# partially done # partially done
* done * done
- Removed relative imports - Run tests though all filters/examples files - (see sshd example file) as unit
test
- Cleanup fail2ban-client and fail2ban-server. Move code to server/ and client/ * Removed relative imports
* 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

@ -17,20 +17,14 @@
# 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, 2013- Yaroslav Halchenko"
# $Revision$
__author__ = "Cyril Jaquier"
__version__ = "$Revision$"
__date__ = "$Date$"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
from server.jails import UnknownJailException
from server.jails import DuplicateJailException
import logging import logging
from common.exceptions import UnknownJailException, DuplicateJailException
# Gets the instance of the logger. # Gets the instance of the logger.
logSys = logging.getLogger("fail2ban.client.config") logSys = logging.getLogger("fail2ban.client.config")

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
@ -36,33 +36,61 @@ logSys = logging.getLogger("fail2ban.client.config")
class ConfigReader(SafeConfigParserWithIncludes): class ConfigReader(SafeConfigParserWithIncludes):
BASE_DIRECTORY = "/etc/fail2ban/" DEFAULT_BASEDIR = '/etc/fail2ban'
def __init__(self): def __init__(self, basedir=None):
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 + '/' if not (os.path.exists(basedir) and os.access(basedir, os.R_OK | os.X_OK)):
setBaseDir = staticmethod(setBaseDir) raise ValueError("Base configuration directory %s either does not exist "
"or is not accessible" % basedir)
self._basedir = basedir.rstrip('/')
#@staticmethod def getBaseDir(self):
def getBaseDir(): return self._basedir
return ConfigReader.BASE_DIRECTORY
getBaseDir = staticmethod(getBaseDir)
def read(self, filename): def read(self, filename):
basename = ConfigReader.BASE_DIRECTORY + filename basename = os.path.join(self._basedir, filename)
logSys.debug("Reading " + basename) logSys.debug("Reading configs for %s under %s " % (basename, self._basedir))
bConf = basename + ".conf" config_files = [ basename + ".conf",
bLocal = basename + ".local" basename + ".local" ]
if os.path.exists(bConf) or os.path.exists(bLocal):
SafeConfigParserWithIncludes.read(self, [bConf, bLocal]) # 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
## ##
@ -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

@ -35,14 +35,15 @@ 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")
def getEarlyOptions(self): def getEarlyOptions(self):
opts = [["string", "socket", "/tmp/fail2ban.sock"]] opts = [["string", "socket", "/tmp/fail2ban.sock"],
["string", "pidfile", "/var/run/fail2ban/fail2ban.pid"]]
return ConfigReader.getOptions(self, "Definition", opts) return ConfigReader.getOptions(self, "Definition", opts)
def getOptions(self): def getOptions(self):

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"],
@ -75,7 +76,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)
@ -86,8 +88,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)
@ -96,8 +100,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):
@ -142,12 +148,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
@ -172,6 +186,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:

36
common/exceptions.py Normal file
View File

@ -0,0 +1,36 @@
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
# vi: set ft=python sts=4 ts=4 sw=4 noet :
"""Fail2Ban exceptions used by both client and server
"""
# 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__ = "Cyril Jaquier, Yaroslav Halchenko"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2012 Yaroslav Halchenko"
__license__ = "GPL"
#
# Jails
#
class DuplicateJailException(Exception):
pass
class UnknownJailException(Exception):
pass

View File

@ -40,6 +40,7 @@ protocol = [
["stop", "stops all jails and terminate the server"], ["stop", "stops all jails and terminate the server"],
["status", "gets the current status of the server"], ["status", "gets the current status of the server"],
["ping", "tests if the server is alive"], ["ping", "tests if the server is alive"],
["help", "return this output"],
['', "LOGGING", ""], ['', "LOGGING", ""],
["set loglevel <LEVEL>", "sets logging level to <LEVEL>. 0 is minimal, 4 is debug"], ["set loglevel <LEVEL>", "sets logging level to <LEVEL>. 0 is minimal, 4 is debug"],
["get loglevel", "gets the logging level"], ["get loglevel", "gets the logging level"],
@ -90,6 +91,7 @@ protocol = [
["get <JAIL> actioncheck <ACT>", "gets the check command for the action <ACT> for <JAIL>"], ["get <JAIL> actioncheck <ACT>", "gets the check command for the action <ACT> for <JAIL>"],
["get <JAIL> actionban <ACT>", "gets the ban command for the action <ACT> for <JAIL>"], ["get <JAIL> actionban <ACT>", "gets the ban command for the action <ACT> for <JAIL>"],
["get <JAIL> actionunban <ACT>", "gets the unban command for the action <ACT> for <JAIL>"], ["get <JAIL> actionunban <ACT>", "gets the unban command for the action <ACT> for <JAIL>"],
["get <JAIL> cinfo <ACT> <KEY>", "gets the value for <KEY> for the action <ACT> for <JAIL>"],
] ]
## ##

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
@ -116,7 +112,7 @@ actionunban = if [ -f <tmpfile>.first ]; then
port = ??? port = ???
# Option: userid # Option: userid
# Notes.: Your DSheild user ID. Should be provided either in the jail config or # Notes.: Your DShield user ID. Should be provided either in the jail config or
# in a .local file. # in a .local file.
# Register at https://secure.dshield.org/register.html # Register at https://secure.dshield.org/register.html
# Values: [ NUM ] Default: 0 # Values: [ NUM ] Default: 0
@ -124,13 +120,13 @@ port = ???
userid = 0 userid = 0
# Option: myip # Option: myip
# Notes.: TThe target IP for the attack (your public IP). Should be provided # Notes.: The target IP for the attack (your public IP). Should be provided
# either in the jail config or in a .local file unless your PUBLIC IP # either in the jail config or in a .local file unless your PUBLIC IP
# is the first IP assigned to eth0 # is the first IP assigned to eth0
# Values: [ an IP address ] Default: Tries to find the IP address of eth0, # Values: [ an IP address ] Default: Tries to find the IP address of eth0,
# which in most cases will be a private IP, and therefore incorrect # which in most cases will be a private IP, and therefore incorrect
# #
myip = `ip -4 addr show dev eth0 | grep inet | head -1 | sed -r 's/.*inet ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}).*/\1/'` myip = `ip -4 addr show dev eth0 | grep inet | head -n 1 | sed -r 's/.*inet ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}).*/\1/'`
# Option: protocol # Option: protocol
# Notes.: The protocol over which the attack is happening # Notes.: The protocol over which the attack is happening
@ -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,16 +42,14 @@ 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
[Init] [Init]
# Defaut name of the chain # Default name of the chain
# #
name = default name = default

View File

@ -0,0 +1,71 @@
# Fail2Ban configuration file
#
# Author: Daniel Black
#
# This is for ipset protocol 4 (ipset v4.2). If you have a later version
# of ipset try to use the iptables-ipset-proto6.conf as it does some things
# nicer.
#
# This requires the program ipset which is normally in package called ipset.
#
# IPset was a feature introduced in the linux kernel 2.6.39 and 3.0.0 kernels.
#
# If you are running on an older kernel you make need to patch in external
# modules.
#
# On Debian machines this can be done with:
#
# apt-get install ipset xtables-addons-source
# module-assistant auto-install xtables-addons
[Definition]
# Option: actionstart
# Notes.: command executed once at the start of Fail2Ban.
# Values: CMD
#
actionstart = ipset --create fail2ban-<name> iphash
iptables -I INPUT -p <protocol> -m multiport --dports <port> -m set --match-set fail2ban-<name> src -j DROP
# Option: actionstop
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD
#
actionstop = iptables -D INPUT -p <protocol> -m multiport --dports <port> -m set --match-set fail2ban-<name> src -j DROP
ipset --flush fail2ban-<name>
ipset --destroy fail2ban-<name>
# Option: actionban
# Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: See jail.conf(5) man page
# Values: CMD
#
actionban = ipset --test fail2ban-<name> <ip> || ipset --add fail2ban-<name> <ip>
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: See jail.conf(5) man page
# Values: CMD
#
actionunban = ipset --test fail2ban-<name> <ip> && ipset --del fail2ban-<name> <ip>
[Init]
# Default name of the ipset
#
name = default
# Option: port
# Notes.: specifies port to monitor
# Values: [ NUM | STRING ] Default: ssh
#
port = ssh
# Option: protocol
# Notes.: internally used by config reader for interpolations.
# Values: [ tcp | udp | icmp | all ] Default: tcp
#
protocol = tcp

View File

@ -0,0 +1,78 @@
# Fail2Ban configuration file
#
# Author: Daniel Black
#
# This is for ipset protocol 6 (and hopefully later) (ipset v6.14).
# Use ipset -V to see the protocol and version. Version 4 should use
# iptables-ipset-proto4.conf.
#
# This requires the program ipset which is normally in package called ipset.
#
# IPset was a feature introduced in the linux kernel 2.6.39 and 3.0.0 kernels.
#
# If you are running on an older kernel you make need to patch in external
# modules.
#
# On Debian machines this can be done with:
#
# apt-get install ipset xtables-addons-source
# module-assistant auto-install xtables-addons
[Definition]
# Option: actionstart
# Notes.: command executed once at the start of Fail2Ban.
# Values: CMD
#
actionstart = ipset create fail2ban-<name> hash:ip timeout <bantime>
iptables -I INPUT -p <protocol> -m multiport --dports <port> -m set --match-set fail2ban-<name> src -j DROP
# Option: actionstop
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD
#
actionstop = iptables -D INPUT -p <protocol> -m multiport --dports <port> -m set --match-set fail2ban-<name> src -j DROP
ipset flush fail2ban-<name>
ipset destroy fail2ban-<name>
# Option: actionban
# Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: See jail.conf(5) man page
# Values: CMD
#
actionban = ipset add fail2ban-<name> <ip> timeout <bantime> -exist
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: See jail.conf(5) man page
# Values: CMD
#
actionunban = ipset del fail2ban-<name> <ip> -exist
[Init]
# Default name of the ipset
#
name = default
# Option: port
# Notes.: specifies port to monitor
# Values: [ NUM | STRING ] Default: ssh
#
port = ssh
# Option: protocol
# Notes.: internally used by config reader for interpolations.
# Values: [ tcp | udp | icmp | all ] Default: tcp
#
protocol = tcp
# Option: bantime
# Notes: specifies the bantime in seconds (handled internally rather than by fail2ban)
# Values: [ NUM ] Default: 600
bantime = 600

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,16 +50,14 @@ 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
[Init] [Init]
# Defaut name of the chain # Default name of the chain
# #
name = default name = default

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,16 +40,14 @@ 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
[Init] [Init]
# Defaut name of the chain # Default name of the chain
# #
name = default name = default

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,16 +42,14 @@ 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
[Init] [Init]
# Defaut name of the chain # Default name of the chain
# #
name = default name = default

View File

@ -11,7 +11,7 @@
# Notes.: command executed once at the start of Fail2Ban. # Notes.: command executed once at the start of Fail2Ban.
# Values: CMD # Values: CMD
# #
# Changing iptables rules requires root priviledges. If fail2ban is # Changing iptables rules requires root privileges. If fail2ban is
# configured to run as root, firewall setup can be performed by # configured to run as root, firewall setup can be performed by
# fail2ban automatically. However, if fail2ban is configured to run as # fail2ban automatically. However, if fail2ban is configured to run as
# a normal user, the configuration must be done by some other means # a normal user, the configuration must be done by some other means
@ -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,16 +54,14 @@ 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>
[Init] [Init]
# Defaut name of the chain # Default name of the chain
# #
name = default name = default

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,16 +40,14 @@ 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
[Init] [Init]
# Defaut name of the chain # Default name of the chain
# #
name = default name = default

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,16 +50,14 @@ 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 =
[Init] [Init]
# Defaut name of the chain # Default name of the chain
# #
name = default name = default

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,16 +48,14 @@ 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 =
[Init] [Init]
# Defaut name of the chain # Default name of the chain
# #
name = default name = default

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,16 +46,14 @@ 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 =
[Init] [Init]
# Defaut name of the chain # Default name of the chain
# #
name = default name = default

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 =
@ -102,13 +98,13 @@ mnwlogin =
mnwpass = mnwpass =
# Option: myip # Option: myip
# Notes.: TThe target IP for the attack (your public IP). Should be overridden # Notes.: The target IP for the attack (your public IP). Should be overridden
# either in the jail config or in a .local file unless your PUBLIC IP # either in the jail config or in a .local file unless your PUBLIC IP
# is the first IP assigned to eth0 # is the first IP assigned to eth0
# Values: [ an IP address ] Default: Tries to find the IP address of eth0, # Values: [ an IP address ] Default: Tries to find the IP address of eth0,
# which in most cases will be a private IP, and therefore incorrect # which in most cases will be a private IP, and therefore incorrect
# #
myip = `ip -4 addr show dev eth0 | grep inet | head -1 | sed -r 's/.*inet ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}).*/\1/'` myip = `ip -4 addr show dev eth0 | grep inet | head -n 1 | sed -r 's/.*inet ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}).*/\1/'`
# Option: protocol # Option: protocol
# Notes.: The protocol over which the attack is happening # Notes.: The protocol over which the attack is happening

View File

@ -0,0 +1,25 @@
# Fail2Ban configuration file
#
# Author: Michael Gebetsroither
#
# This is for blocking whole hosts through blackhole routes.
#
# PRO:
# - Works on all kernel versions and as no compatibility problems (back to debian lenny and WAY further).
# - It's FAST for very large numbers of blocked ips.
# - It's FAST because it Blocks traffic before it enters common iptables chains used for filtering.
# - It's per host, ideal as action against ssh password bruteforcing to block further attack attempts.
# - No additional software required beside iproute/iproute2
#
# CON:
# - Blocking is per IP and NOT per service, but ideal as action against ssh password bruteforcing hosts
[Definition]
actionban = ip route add <type> <ip>
actionunban = ip route del <type> <ip>
# Type of blocking
#
# Type can be blackhole, unreachable and prohibit. Unreachable and prohibit correspond to the ICMP reject messages.
type = blackhole

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,16 +72,14 @@ 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 =
[Init] [Init]
# Defaut name of the chain # Default name of the chain
# #
name = default name = default

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>
@ -57,23 +55,21 @@ actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip>
Here are more information about <ip>:\n Here are more information about <ip>:\n
`/usr/bin/whois <ip>`\n\n `/usr/bin/whois <ip>`\n\n
Lines containing IP:<ip> in <logpath>\n Lines containing IP:<ip> in <logpath>\n
`/bin/grep '\<<ip>\>' <logpath>`\n\n `grep '\<<ip>\>' <logpath>`\n\n
Regards,\n Regards,\n
Fail2Ban" | /usr/sbin/sendmail -f <sender> <dest> Fail2Ban" | /usr/sbin/sendmail -f <sender> <dest>
# 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 =
[Init] [Init]
# Defaut name of the chain # Default name of the chain
# #
name = default name = default

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,16 +60,14 @@ 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 =
[Init] [Init]
# Defaut name of the chain # Default name of the chain
# #
name = default name = default

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,16 +58,14 @@ 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 =
[Init] [Init]
# Defaut name of the chain # Default name of the chain
# #
name = default name = default

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

@ -24,6 +24,10 @@ loglevel = 3
# Option: logtarget # Option: logtarget
# Notes.: Set the log target. This could be a file, SYSLOG, STDERR or STDOUT. # Notes.: Set the log target. This could be a file, SYSLOG, STDERR or STDOUT.
# Only one log target can be specified. # Only one log target can be specified.
# If you change logtarget from the default value and you are
# using logrotate -- also adjust or disable rotation in the
# corresponding configuration file
# (e.g. /etc/logrotate.d/fail2ban on Debian systems)
# Values: STDOUT STDERR SYSLOG file Default: /var/log/fail2ban.log # Values: STDOUT STDERR SYSLOG file Default: /var/log/fail2ban.log
# #
logtarget = /var/log/fail2ban.log logtarget = /var/log/fail2ban.log
@ -36,3 +40,10 @@ logtarget = /var/log/fail2ban.log
# #
socket = /var/run/fail2ban/fail2ban.sock socket = /var/run/fail2ban/fail2ban.sock
# Option: pidfile
# Notes.: Set the PID file. This is used to store the process ID of the
# fail2ban server.
# Values: FILE Default: /var/run/fail2ban/fail2ban.sock
#
pidfile = /var/run/fail2ban/fail2ban.pid

View File

@ -14,7 +14,8 @@
# (?:::f{4,6}:)?(?P<host>[\w\-.^_]+) # (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
# Values: TEXT # Values: TEXT
# #
failregex = .*(?:pop3-login|imap-login):.*(?:Authentication failure|Aborted login \(auth failed|Aborted login \(tried to use disabled|Disconnected \(auth failed).*rip=(?P<host>\S*),.* failregex = .*(?:pop3-login|imap-login):.*(?:Authentication failure|Aborted login \(auth failed|Aborted login \(tried to use disabled|Disconnected \(auth failed).*\s+rip=(?P<host>\S*),.*
pam.*dovecot.*(?:authentication failure).*\s+rhost=<HOST>(?:\s+user=.*)?\s*$
# Option: ignoreregex # Option: ignoreregex
# Notes.: regex to ignore. If this regex matches, the line is ignored. # Notes.: regex to ignore. If this regex matches, the line is ignored.

View File

@ -15,6 +15,7 @@
# Values: TEXT # Values: TEXT
# #
failregex = \[<HOST>\] .*(?:rejected by local_scan|Unrouteable address) failregex = \[<HOST>\] .*(?:rejected by local_scan|Unrouteable address)
login authenticator failed for .* \[<HOST>\]: 535 Incorrect authentication data \(set_id=.*\)\s*$
# Option: ignoreregex # Option: ignoreregex
# Notes.: regex to ignore. If this regex matches, the line is ignored. # Notes.: regex to ignore. If this regex matches, the line is ignored.

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,22 @@
# Fail2Ban configuration file for roundcube web server
#
# Author: Teodor Micu & Yaroslav Halchenko
#
#
[Definition]
# Option: failregex
# Notes.: regex to match the password failure messages in the logfile. The
# host must be matched by a group named "host". The tag "<HOST>" can
# be used for standard IP/hostname matching and is only an alias for
# (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
# Values: TEXT
#
failregex = FAILED login for .*. from <HOST>\s*$
# Option: ignoreregex
# Notes.: regex to ignore. If this regex matches, the line is ignored.
# Values: TEXT
#
ignoreregex =

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

@ -2,11 +2,17 @@
# #
# Author: Yaroslav Halchenko # Author: Yaroslav Halchenko
# #
# $Revision$
# [INCLUDES]
# Read common prefixes. If any customizations available -- read them from
# common.local
before = common.conf
[Definition] [Definition]
_daemon = sshd
# Option: failregex # Option: failregex
# Notes.: regex to match the password failures messages in the logfile. The # Notes.: regex to match the password failures messages in the logfile. The
# host must be matched by a group named "host". The tag "<HOST>" can # host must be matched by a group named "host". The tag "<HOST>" can
@ -14,7 +20,7 @@
# (?:::f{4,6}:)?(?P<host>[\w\-.^_]+) # (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
# Values: TEXT # Values: TEXT
# #
failregex = sshd(?:\[\d+\])?: Did not receive identification string from <HOST>$ failregex = ^%(__prefix_line)sDid not receive identification string from <HOST>\s*$
# Option: ignoreregex # Option: ignoreregex
# Notes.: regex to ignore. If this regex matches, the line is ignored. # Notes.: regex to ignore. If this regex matches, the line is ignored.

View File

@ -30,7 +30,6 @@ failregex = ^%(__prefix_line)s(?:error: PAM: )?Authentication failure for .* fro
^%(__prefix_line)s[iI](?:llegal|nvalid) user .* from <HOST>\s*$ ^%(__prefix_line)s[iI](?:llegal|nvalid) user .* from <HOST>\s*$
^%(__prefix_line)sUser .+ from <HOST> not allowed because not listed in AllowUsers\s*$ ^%(__prefix_line)sUser .+ from <HOST> not allowed because not listed in AllowUsers\s*$
^%(__prefix_line)sUser .+ from <HOST> not allowed because listed in DenyUsers\s*$ ^%(__prefix_line)sUser .+ from <HOST> not allowed because listed in DenyUsers\s*$
^%(__prefix_line)s(?:pam_unix\(sshd:auth\):\s)?authentication failure; logname=\S* uid=\S* euid=\S* tty=\S* ruser=\S* rhost=<HOST>(?:\s+user=.*)?\s*$
^%(__prefix_line)srefused connect from \S+ \(<HOST>\)\s*$ ^%(__prefix_line)srefused connect from \S+ \(<HOST>\)\s*$
^%(__prefix_line)sUser .+ from <HOST> not allowed because none of user's groups are listed in AllowGroups\s*$ ^%(__prefix_line)sUser .+ from <HOST> not allowed because none of user's groups are listed in AllowGroups\s*$

View File

@ -18,8 +18,8 @@
# (?:::f{4,6}:)?(?P<host>[\w\-.^_]+) # (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
# Values: TEXT # Values: TEXT
# #
failregex = webmin.* Non-existent login as .+ from <HOST>$ failregex = webmin.* Non-existent login as .+ from <HOST>\s*$
webmin.* Invalid login as .+ from <HOST>$ webmin.* Invalid login as .+ from <HOST>\s*$
# Option: ignoreregex # Option: ignoreregex
# Notes.: regex to ignore. If this regex matches, the line is ignored. # Notes.: regex to ignore. If this regex matches, the line is ignored.

View File

@ -101,6 +101,37 @@ action = hostsdeny
ignoreregex = for myuser from ignoreregex = for myuser from
logpath = /var/log/sshd.log logpath = /var/log/sshd.log
# Here we use blackhole routes for not requiring any additional kernel support
# to store large volumes of banned IPs
[ssh-route]
enabled = false
filter = sshd
action = route
logpath = /var/log/sshd.log
maxretry = 5
# Here we use a combination of Netfilter/Iptables and IPsets
# for storing large volumes of banned IPs
#
# IPset comes in two versions. See ipset -V for which one to use
# requires the ipset package and kernel support.
[ssh-iptables-ipset4]
enabled = false
filter = sshd
action = iptables-ipset-proto4[name=SSH, port=ssh, protocol=tcp]
logpath = /var/log/sshd.log
maxretry = 5
[ssh-iptables-ipset6]
enabled = false
filter = sshd
action = iptables-ipset-proto6[name=SSH, port=ssh, protocol=tcp, bantime=600]
logpath = /var/log/sshd.log
maxretry = 5
# This jail demonstrates the use of wildcards in "logpath". # This jail demonstrates the use of wildcards in "logpath".
# Moreover, it is possible to give other files on a new line. # Moreover, it is possible to give other files on a new line.
@ -172,6 +203,29 @@ action = shorewall
sendmail[name=Postfix, dest=you@example.com] sendmail[name=Postfix, dest=you@example.com]
logpath = /var/log/apache2/error_log logpath = /var/log/apache2/error_log
# Monitor roundcube server
[roundcube-iptables]
enabled = false
filter = roundcube-auth
action = iptables[name=RoundCube, port="http,https"]
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.
@ -201,7 +255,7 @@ logpath = /var/log/lighttpd/error.log
maxretry = 2 maxretry = 2
# Same as above for mod_auth # Same as above for mod_auth
# It catches wrong authentifications # It catches wrong authentications
[lighttpd-auth] [lighttpd-auth]

View File

@ -1,7 +1,7 @@
Fail2ban normally requires root priviledges to insert iptables rules Fail2ban normally requires root privileges to insert iptables rules
through calls to /sbin/iptables and also to read the logfiles. through calls to /sbin/iptables and also to read the logfiles.
Fail2ban can run as an unpriviledged user provided that those two Fail2ban can run as an unprivileged user provided that those two
capabilites are preserved. The idea is to run fail2ban as a normal capabilities are preserved. The idea is to run fail2ban as a normal
user (e.g. fail2ban) who belongs to a group which is allowed to read user (e.g. fail2ban) who belongs to a group which is allowed to read
logfiles. The user should also be allowed to write to logfiles. The user should also be allowed to write to
/proc/net/xt_recent/fail2ban-<name> (name is specified in the iptables /proc/net/xt_recent/fail2ban-<name> (name is specified in the iptables
@ -20,14 +20,14 @@ Another way to use xt_recent is by inserting the rules by writing to
action. Files in /proc/net/xt_recent/ are protected by normal action. Files in /proc/net/xt_recent/ are protected by normal
filesystem rules, so can be chown'ed and chmod'ed to be writable by a filesystem rules, so can be chown'ed and chmod'ed to be writable by a
certain user. After the necessary iptables rules are inserted (which certain user. After the necessary iptables rules are inserted (which
requires root priviledges), blacklisting can be perfomed by an requires root privileges), blacklisting can be performed by an
unpriviledged user. unprivileged user.
Using fail2ban with xt_recent allows smarter filtering than normal Using fail2ban with xt_recent allows smarter filtering than normal
iptables rules with the xt_recent module can provide. iptables rules with the xt_recent module can provide.
The disadvantage is that fail2ban cannot perform the setup by itself, The disadvantage is that fail2ban cannot perform the setup by itself,
which would require the priviledge to call /sbin/iptables, and it must which would require the privilege to call /sbin/iptables, and it must
be done through other means. be done through other means.
The primary advantage is obvious: it's generally better to run The primary advantage is obvious: it's generally better to run
@ -46,7 +46,7 @@ some user and thus allow delisting IPs by helper administrators
without the ability to mess up other iptables rules. without the ability to mess up other iptables rules.
The xt_recent-echo jail can be used under the root user without The xt_recent-echo jail can be used under the root user without
further configuration. To run not as root, futher setup is necessary: further configuration. To run not as root, further setup is necessary:
- Create user: - Create user:

View File

@ -27,12 +27,13 @@ import getopt, time, shlex, socket
# Inserts our own modules path first in the list # Inserts our own modules path first in the list
# fix for bug #343821 # fix for bug #343821
if os.path.abspath(__file__).startswith('/usr/'): try:
# makes sense to use system-wide library iff -client is also under /usr/
sys.path.insert(1, "/usr/share/fail2ban")
# Now we can import our modules
from common.version import version from common.version import version
except ImportError, e:
sys.path.insert(1, "/usr/share/fail2ban")
from common.version import version
# Now we can import the rest of modules
from common.protocol import printFormatted from common.protocol import printFormatted
from client.csocket import CSocket from client.csocket import CSocket
from client.configurator import Configurator from client.configurator import Configurator
@ -61,6 +62,7 @@ class Fail2banClient:
self.__conf["verbose"] = 1 self.__conf["verbose"] = 1
self.__conf["interactive"] = False self.__conf["interactive"] = False
self.__conf["socket"] = None self.__conf["socket"] = None
self.__conf["pidfile"] = None
def dispVersion(self): def dispVersion(self):
print "Fail2Ban v" + version print "Fail2Ban v" + version
@ -83,6 +85,7 @@ class Fail2banClient:
print "Options:" print "Options:"
print " -c <DIR> configuration directory" print " -c <DIR> configuration directory"
print " -s <FILE> socket path" print " -s <FILE> socket path"
print " -p <FILE> pidfile path"
print " -d dump configuration. For debugging" print " -d dump configuration. For debugging"
print " -i interactive mode" print " -i interactive mode"
print " -v increase verbosity" print " -v increase verbosity"
@ -118,6 +121,8 @@ class Fail2banClient:
self.__conf["conf"] = opt[1] self.__conf["conf"] = opt[1]
elif opt[0] == "-s": elif opt[0] == "-s":
self.__conf["socket"] = opt[1] self.__conf["socket"] = opt[1]
elif opt[0] == "-p":
self.__conf["pidfile"] = opt[1]
elif opt[0] == "-d": elif opt[0] == "-d":
self.__conf["dump"] = True self.__conf["dump"] = True
elif opt[0] == "-v": elif opt[0] == "-v":
@ -180,8 +185,21 @@ class Fail2banClient:
# Do not continue if configuration is not 100% valid # Do not continue if configuration is not 100% valid
if not ret: if not ret:
return False return False
# verify that directory for the socket file exists
socket_dir = os.path.dirname(self.__conf["socket"])
if not os.path.exists(socket_dir):
logSys.error(
"There is no directory %s to contain the socket file %s."
% (socket_dir, self.__conf["socket"]))
return False
if not os.access(socket_dir, os.W_OK | os.X_OK):
logSys.error(
"Directory %s exists but not accessible for writing"
% (socket_dir,))
return False
# Start the server # Start the server
self.__startServerAsync(self.__conf["socket"], self.__startServerAsync(self.__conf["socket"],
self.__conf["pidfile"],
self.__conf["force"]) self.__conf["force"])
try: try:
# Wait for the server to start # Wait for the server to start
@ -190,10 +208,10 @@ class Fail2banClient:
self.__processCmd(self.__stream, False) self.__processCmd(self.__stream, False)
return True return True
except ServerExecutionException: except ServerExecutionException:
logSys.error("Could not start server. Maybe an old " + logSys.error("Could not start server. Maybe an old "
"socket file is still present. Try to " + "socket file is still present. Try to "
"remove " + self.__conf["socket"] + ". If " + "remove " + self.__conf["socket"] + ". If "
"you used fail2ban-client to start the " + "you used fail2ban-client to start the "
"server, adding the -x option will do it") "server, adding the -x option will do it")
return False return False
elif len(cmd) == 1 and cmd[0] == "reload": elif len(cmd) == 1 and cmd[0] == "reload":
@ -230,7 +248,7 @@ class Fail2banClient:
# #
# Start the Fail2ban server in daemon mode. # Start the Fail2ban server in daemon mode.
def __startServerAsync(self, socket, force = False): def __startServerAsync(self, socket, pidfile, force = False):
# Forks the current process. # Forks the current process.
pid = os.fork() pid = os.fork()
if pid == 0: if pid == 0:
@ -241,22 +259,26 @@ class Fail2banClient:
# Set the socket path. # Set the socket path.
args.append("-s") args.append("-s")
args.append(socket) args.append(socket)
# Set the pidfile
args.append("-p")
args.append(pidfile)
# Force the execution if needed. # Force the execution if needed.
if force: if force:
args.append("-x") args.append("-x")
try: try:
# Use the current directory. # Use the current directory.
exe = os.path.abspath(os.path.join(sys.path[0], self.SERVER)) exe = os.path.abspath(os.path.join(sys.path[0], self.SERVER))
logSys.debug("Starting %r with args %r" % (exe, args))
os.execv(exe, args) os.execv(exe, args)
except OSError: except OSError:
try: try:
# Use the PATH env. # Use the PATH env.
logSys.warning("Initial start attempt failed. Starting %r with the same args" % (self.SERVER,))
os.execvp(self.SERVER, args) os.execvp(self.SERVER, args)
except OSError: except OSError:
print "Could not find %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
@ -275,7 +297,7 @@ class Fail2banClient:
delta = -1 delta = -1
elif pos < 2: elif pos < 2:
delta = 1 delta = 1
# The server has 30 secondes to start. # The server has 30 seconds to start.
if cnt >= 300: if cnt >= 300:
if self.__conf["verbose"] > 1: if self.__conf["verbose"] > 1:
sys.stdout.write('\n') sys.stdout.write('\n')
@ -296,7 +318,7 @@ class Fail2banClient:
# Reads the command line options. # Reads the command line options.
try: try:
cmdOpts = 'hc:s:xdviqV' cmdOpts = 'hc:s:p:xdviqV'
cmdLongOpts = ['help', 'version'] cmdLongOpts = ['help', 'version']
optList, args = getopt.getopt(self.__argv[1:], cmdOpts, cmdLongOpts) optList, args = getopt.getopt(self.__argv[1:], cmdOpts, cmdLongOpts)
except getopt.GetoptError: except getopt.GetoptError:
@ -327,9 +349,11 @@ class Fail2banClient:
# Set socket path # Set socket path
self.__configurator.readEarly() self.__configurator.readEarly()
socket = self.__configurator.getEarlyOptions() conf = self.__configurator.getEarlyOptions()
if self.__conf["socket"] == None: if self.__conf["socket"] == None:
self.__conf["socket"] = socket["socket"] self.__conf["socket"] = conf["socket"]
if self.__conf["pidfile"] == None:
self.__conf["pidfile"] = conf["pidfile"]
logSys.info("Using socket file " + self.__conf["socket"]) logSys.info("Using socket file " + self.__conf["socket"])
if self.__conf["dump"]: if self.__conf["dump"]:
@ -356,7 +380,9 @@ class Fail2banClient:
if cmd == "exit" or cmd == "quit": if cmd == "exit" or cmd == "quit":
# Exit # Exit
return True return True
if not cmd == "": if cmd == "help":
self.dispUsage()
elif not cmd == "":
self.__processCommand(shlex.split(cmd)) self.__processCommand(shlex.split(cmd))
except (EOFError, KeyboardInterrupt): except (EOFError, KeyboardInterrupt):
print print
@ -394,7 +420,7 @@ class Fail2banClient:
class ServerExecutionException(Exception): class ServerExecutionException(Exception):
pass pass
if __name__ == "__main__": if __name__ == "__main__": # pragma: no cover - can't test main
client = Fail2banClient() client = Fail2banClient()
# Exit with correct return value # Exit with correct return value
if client.start(sys.argv): if client.start(sys.argv):

View File

@ -1,4 +1,7 @@
#!/usr/bin/python #!/usr/bin/python
# 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. # This file is part of Fail2Ban.
# #
# Fail2Ban is free software; you can redistribute it and/or modify # Fail2Ban is free software; you can redistribute it and/or modify
@ -23,13 +26,14 @@ import getopt, sys, time, logging, os
# Inserts our own modules path first in the list # Inserts our own modules path first in the list
# fix for bug #343821 # fix for bug #343821
if os.path.abspath(__file__).startswith('/usr/'): try:
# makes sense to use system-wide library iff -regex is also under /usr/ from common.version import version
except ImportError, e:
sys.path.insert(1, "/usr/share/fail2ban") sys.path.insert(1, "/usr/share/fail2ban")
from common.version import version
from client.configparserinc import SafeConfigParserWithIncludes from client.configparserinc import SafeConfigParserWithIncludes
from ConfigParser import NoOptionError, NoSectionError, MissingSectionHeaderError from ConfigParser import NoOptionError, NoSectionError, MissingSectionHeaderError
from common.version import version
from server.filter import Filter from server.filter import Filter
from server.failregex import RegexException from server.failregex import RegexException
@ -43,6 +47,10 @@ class RegexStat:
self.__failregex = failregex self.__failregex = failregex
self.__ipList = list() self.__ipList = list()
def __str__(self):
return "%s(%r) %d failed: %s" \
% (self.__class__, self.__failregex, self.__stats, self.__ipList)
def inc(self): def inc(self):
self.__stats += 1 self.__stats += 1

View File

@ -26,11 +26,12 @@ import getopt, sys, logging, os
# Inserts our own modules path first in the list # Inserts our own modules path first in the list
# fix for bug #343821 # fix for bug #343821
if os.path.abspath(__file__).startswith('/usr/'): try:
# makes sense to use system-wide library iff -server is also under /usr/
sys.path.insert(1, "/usr/share/fail2ban")
from common.version import version from common.version import version
except ImportError, e:
sys.path.insert(1, "/usr/share/fail2ban")
from common.version import version
from server.server import Server from server.server import Server
# Gets the instance of the logger. # Gets the instance of the logger.
@ -53,6 +54,7 @@ class Fail2banServer:
self.__conf["background"] = True self.__conf["background"] = True
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"
def dispVersion(self): def dispVersion(self):
print "Fail2Ban v" + version print "Fail2Ban v" + version
@ -80,6 +82,7 @@ class Fail2banServer:
print " -b start in background" print " -b start in background"
print " -f start in foreground" print " -f start in foreground"
print " -s <FILE> socket path" print " -s <FILE> socket path"
print " -p <FILE> pidfile path"
print " -x force execution of the server (remove socket file)" print " -x force execution of the server (remove socket file)"
print " -h, --help display this help message" print " -h, --help display this help message"
print " -V, --version print the version" print " -V, --version print the version"
@ -96,6 +99,8 @@ class Fail2banServer:
self.__conf["background"] = False self.__conf["background"] = False
if opt[0] == "-s": if opt[0] == "-s":
self.__conf["socket"] = opt[1] self.__conf["socket"] = opt[1]
if opt[0] == "-p":
self.__conf["pidfile"] = opt[1]
if opt[0] == "-x": if opt[0] == "-x":
self.__conf["force"] = True self.__conf["force"] = True
if opt[0] in ["-h", "--help"]: if opt[0] in ["-h", "--help"]:
@ -111,7 +116,7 @@ class Fail2banServer:
# Reads the command line options. # Reads the command line options.
try: try:
cmdOpts = 'bfs:xhV' cmdOpts = 'bfs:p:xhV'
cmdLongOpts = ['help', 'version'] cmdLongOpts = ['help', 'version']
optList, args = getopt.getopt(self.__argv[1:], cmdOpts, cmdLongOpts) optList, args = getopt.getopt(self.__argv[1:], cmdOpts, cmdLongOpts)
except getopt.GetoptError: except getopt.GetoptError:
@ -122,7 +127,9 @@ class Fail2banServer:
try: try:
self.__server = Server(self.__conf["background"]) self.__server = Server(self.__conf["background"])
self.__server.start(self.__conf["socket"], self.__conf["force"]) self.__server.start(self.__conf["socket"],
self.__conf["pidfile"],
self.__conf["force"])
return True return True
except Exception, e: except Exception, e:
logSys.exception(e) logSys.exception(e)

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
@ -42,7 +45,7 @@ from optparse import OptionParser, Option
def get_opt_parser(): def get_opt_parser():
# use module docstring for help output # use module docstring for help output
p = OptionParser( p = OptionParser(
usage="%s [OPTIONS]\n" % sys.argv[0] + __doc__, usage="%s [OPTIONS] [regexps]\n" % sys.argv[0] + __doc__,
version="%prog " + version) version="%prog " + version)
p.add_options([ p.add_options([
@ -51,13 +54,20 @@ 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"),
Option('-n', "--no-network", action="store_true",
dest="no_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
parser = get_opt_parser() parser = get_opt_parser()
(opts, files) = parser.parse_args() (opts, regexps) = parser.parse_args()
assert(not len(files))
# #
# Logging # Logging
@ -72,10 +82,10 @@ verbosity = {'debug': 3,
'fatal': 0, 'fatal': 0,
None: 1}[opts.log_level] None: 1}[opts.log_level]
if opts.log_level is not None: if opts.log_level is not None: # pragma: no cover
# so we had explicit settings # so we had explicit settings
logSys.setLevel(getattr(logging, opts.log_level.upper())) logSys.setLevel(getattr(logging, opts.log_level.upper()))
else: else: # pragma: no cover
# suppress the logging but it would leave unittests' progress dots # suppress the logging but it would leave unittests' progress dots
# ticking, unless like with '-l fatal' which would be silent # ticking, unless like with '-l fatal' which would be silent
# unless error occurs # unless error occurs
@ -83,19 +93,27 @@ else:
# Add the default logging handler # Add the default logging handler
stdout = logging.StreamHandler(sys.stdout) stdout = logging.StreamHandler(sys.stdout)
# Custom log format for the verbose tests runs
if verbosity > 1:
stdout.setFormatter(logging.Formatter(' %(asctime)-15s %(thread)s %(message)s'))
else:
# just prefix with the space
stdout.setFormatter(logging.Formatter(' %(message)s'))
logSys.addHandler(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
if verbosity > 1: # pragma: no cover
stdout.setFormatter(Formatter(' %(asctime)-15s %(thread)s' + fmt))
else: # pragma: no cover
# just prefix with the space
stdout.setFormatter(Formatter(fmt))
logSys.addHandler(stdout)
# #
# Let know the version # Let know the version
# #
if not opts.log_level or opts.log_level != 'fatal': if not opts.log_level or opts.log_level != 'fatal': # pragma: no cover
print "Fail2ban %s test suite. Python %s. Please wait..." \ print "Fail2ban %s test suite. Python %s. Please wait..." \
% (version, str(sys.version).replace('\n', '')) % (version, str(sys.version).replace('\n', ''))
@ -103,23 +121,42 @@ if not opts.log_level or opts.log_level != 'fatal':
# #
# Gather the tests # Gather the tests
# #
if not len(regexps): # pragma: no cover
tests = unittest.TestSuite() tests = unittest.TestSuite()
else: # pragma: no cover
import re
class FilteredTestSuite(unittest.TestSuite):
_regexps = [re.compile(r) for r in regexps]
def addTest(self, suite):
suite_str = str(suite)
for r in self._regexps:
if r.search(suite_str):
super(FilteredTestSuite, self).addTest(suite)
return
tests = FilteredTestSuite()
# Server # Server
#tests.addTest(unittest.makeSuite(servertestcase.StartStop)) #tests.addTest(unittest.makeSuite(servertestcase.StartStop))
#tests.addTest(unittest.makeSuite(servertestcase.Transmitter)) tests.addTest(unittest.makeSuite(servertestcase.Transmitter))
tests.addTest(unittest.makeSuite(actiontestcase.ExecuteAction)) tests.addTest(unittest.makeSuite(actiontestcase.ExecuteAction))
# FailManager # FailManager
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.JailsReaderTest))
# CSocket and AsyncServer
tests.addTest(unittest.makeSuite(sockettestcase.Socket))
# Filter # Filter
if not opts.no_network:
tests.addTest(unittest.makeSuite(filtertestcase.IgnoreIP)) tests.addTest(unittest.makeSuite(filtertestcase.IgnoreIP))
tests.addTest(unittest.makeSuite(filtertestcase.LogFile)) tests.addTest(unittest.makeSuite(filtertestcase.LogFile))
tests.addTest(unittest.makeSuite(filtertestcase.LogFileMonitor)) tests.addTest(unittest.makeSuite(filtertestcase.LogFileMonitor))
if not opts.no_network:
tests.addTest(unittest.makeSuite(filtertestcase.GetFailures)) tests.addTest(unittest.makeSuite(filtertestcase.GetFailures))
tests.addTest(unittest.makeSuite(filtertestcase.DNSUtilsTests)) tests.addTest(unittest.makeSuite(filtertestcase.DNSUtilsTests))
tests.addTest(unittest.makeSuite(filtertestcase.JailTests)) tests.addTest(unittest.makeSuite(filtertestcase.JailTests))
@ -140,19 +177,22 @@ filters = [FilterPoll] # always available
try: try:
from server.filtergamin import FilterGamin from server.filtergamin import FilterGamin
filters.append(FilterGamin) filters.append(FilterGamin)
except Exception, e: except Exception, e: # pragma: no cover
print "I: Skipping gamin backend testing. Got exception '%s'" % e print "I: Skipping gamin backend testing. Got exception '%s'" % e
try: try:
from server.filterpyinotify import FilterPyinotify from server.filterpyinotify import FilterPyinotify
filters.append(FilterPyinotify) filters.append(FilterPyinotify)
except Exception, e: except Exception, e: # pragma: no cover
print "I: Skipping pyinotify backend testing. Got exception '%s'" % e print "I: Skipping pyinotify backend testing. Got exception '%s'" % e
for Filter_ in filters: for Filter_ in filters:
tests.addTest(unittest.makeSuite( tests.addTest(unittest.makeSuite(
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
@ -170,7 +210,7 @@ try:
tests_results = testRunner.run(tests) tests_results = testRunner.run(tests)
finally: finally: # pragma: no cover
# Just for the sake of it reset the TZ # Just for the sake of it reset the TZ
# yoh: move all this into setup/teardown methods within tests # yoh: move all this into setup/teardown methods within tests
os.environ.pop('TZ') os.environ.pop('TZ')
@ -178,5 +218,5 @@ finally:
os.environ['TZ'] = old_TZ os.environ['TZ'] = old_TZ
time.tzset() time.tzset()
if not tests_results.wasSuccessful(): if not tests_results.wasSuccessful(): # pragma: no cover
sys.exit(1) sys.exit(1)

View File

@ -49,5 +49,5 @@ details.
You should have received a copy of the GNU General Public You should have received a copy of the GNU General Public
License along with Fail2Ban; if not, write to the Free License along with Fail2Ban; if not, write to the Free
Software Foundation, Inc., 59 Temple Place, Suite 330, Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02111-1307 USA Boston, MA 02110, USA

View File

@ -1,12 +1,12 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.36. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.10.
.TH FAIL2BAN-CLIENT "1" "March 2008" "fail2ban-client v0.8.2" "User Commands" .TH FAIL2BAN-CLIENT "1" "March 2013" "fail2ban-client v0.8.8" "User Commands"
.SH NAME .SH NAME
fail2ban-client \- configure and control the server fail2ban-client \- configure and control the server
.SH SYNOPSIS .SH SYNOPSIS
.B fail2ban-client .B fail2ban-client
[\fIOPTIONS\fR] \fI<COMMAND>\fR [\fIOPTIONS\fR] \fI<COMMAND>\fR
.SH DESCRIPTION .SH DESCRIPTION
Fail2Ban v0.8.2 reads log file that contains password failure report Fail2Ban v0.8.8 reads log file that contains password failure report
and bans the corresponding IP addresses using firewall rules. and bans the corresponding IP addresses using firewall rules.
.SH OPTIONS .SH OPTIONS
.TP .TP
@ -16,6 +16,9 @@ configuration directory
\fB\-s\fR <FILE> \fB\-s\fR <FILE>
socket path socket path
.TP .TP
\fB\-p\fR <FILE>
pidfile path
.TP
\fB\-d\fR \fB\-d\fR
dump configuration. For debugging dump configuration. For debugging
.TP .TP
@ -110,7 +113,7 @@ adds <FILE> to the monitoring list
of <JAIL> of <JAIL>
.TP .TP
\fBset <JAIL> dellogpath <FILE>\fR \fBset <JAIL> dellogpath <FILE>\fR
removes <FILE> to the monitoring removes <FILE> from the monitoring
list of <JAIL> list of <JAIL>
.TP .TP
\fBset <JAIL> addfailregex <REGEX>\fR \fBset <JAIL> addfailregex <REGEX>\fR
@ -140,6 +143,15 @@ back for <JAIL>
sets the number of seconds <TIME> sets the number of seconds <TIME>
a host will be banned for <JAIL> a host will be banned for <JAIL>
.TP .TP
\fBset <JAIL> usedns <VALUE>\fR
sets the usedns mode for <JAIL>
.TP
\fBset <JAIL> banip <IP>\fR
manually Ban <IP> for <JAIL>
.TP
\fBset <JAIL> unbanip <IP>\fR
manually Unban <IP> in <JAIL>
.TP
\fBset <JAIL> maxretry <RETRY>\fR \fBset <JAIL> maxretry <RETRY>\fR
sets the number of failures sets the number of failures
<RETRY> before banning the host <RETRY> before banning the host
@ -191,14 +203,6 @@ files for <JAIL>
gets the list of ignored IP gets the list of ignored IP
addresses for <JAIL> addresses for <JAIL>
.TP .TP
\fBget <JAIL> timeregex\fR
gets the regular expression used
for the time detection for <JAIL>
.TP
\fBget <JAIL> timepattern\fR
gets the pattern used for the time
detection for <JAIL>
.TP
\fBget <JAIL> failregex\fR \fBget <JAIL> failregex\fR
gets the list of regular gets the list of regular
expressions which matches the expressions which matches the
@ -218,6 +222,9 @@ will look back for failures for
gets the time a host is banned for gets the time a host is banned for
<JAIL> <JAIL>
.TP .TP
\fBget <JAIL> usedns\fR
gets the usedns setting for <JAIL>
.TP
\fBget <JAIL> maxretry\fR \fBget <JAIL> maxretry\fR
gets the number of failures gets the number of failures
allowed for <JAIL> allowed for <JAIL>
@ -245,15 +252,19 @@ action <ACT> for <JAIL>
\fBget <JAIL> actionunban <ACT>\fR \fBget <JAIL> actionunban <ACT>\fR
gets the unban command for the gets the unban command for the
action <ACT> for <JAIL> action <ACT> for <JAIL>
.TP
\fBget <JAIL> cinfo <ACT> <KEY>\fR
gets the value for <KEY> for the
action <ACT> for <JAIL>
.SH FILES .SH FILES
\fI/etc/fail2ban/*\fR \fI/etc/fail2ban/*\fR
.SH AUTHOR .SH AUTHOR
Written by Cyril Jaquier <cyril.jaquier@fail2ban.org>. Written by Cyril Jaquier <cyril.jaquier@fail2ban.org>.
Many contributions by Yaroslav O. Halchenko <debian@onerussian.com>. Many contributions by Yaroslav O. Halchenko <debian@onerussian.com>.
.SH "REPORTING BUGS" .SH "REPORTING BUGS"
Report bugs on https://github.com/fail2ban/fail2ban/issues Report bugs to https://github.com/fail2ban/fail2ban/issues
.SH COPYRIGHT .SH COPYRIGHT
Copyright \(co 2004-2008 Cyril Jaquier Copyright \(co 2004\-2008 Cyril Jaquier, 2008\- Fail2Ban Contributors
.br .br
Copyright of modifications held by their respective authors. Copyright of modifications held by their respective authors.
Licensed under the GNU General Public License v2 (GPL). Licensed under the GNU General Public License v2 (GPL).

View File

@ -1,12 +1,12 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.36. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.10.
.TH FAIL2BAN-REGEX "1" "March 2008" "fail2ban-regex v0.8.2" "User Commands" .TH FAIL2BAN-REGEX "1" "March 2013" "fail2ban-regex v0.8.8" "User Commands"
.SH NAME .SH NAME
fail2ban-regex \- test Fail2ban "failregex" option fail2ban-regex \- test Fail2ban "failregex" option
.SH SYNOPSIS .SH SYNOPSIS
.B fail2ban-regex .B fail2ban-regex
[\fIOPTIONS\fR] \fI<LOG> <REGEX> \fR[\fIIGNOREREGEX\fR] [\fIOPTIONS\fR] \fI<LOG> <REGEX> \fR[\fIIGNOREREGEX\fR]
.SH DESCRIPTION .SH DESCRIPTION
Fail2Ban v0.8.2 reads log file that contains password failure report Fail2Ban v0.8.8 reads log file that contains password failure report
and bans the corresponding IP addresses using firewall rules. and bans the corresponding IP addresses using firewall rules.
.PP .PP
This tools can test regular expressions for "fail2ban". This tools can test regular expressions for "fail2ban".
@ -17,6 +17,9 @@ display this help message
.TP .TP
\fB\-V\fR, \fB\-\-version\fR \fB\-V\fR, \fB\-\-version\fR
print the version print the version
.TP
\fB\-v\fR, \fB\-\-verbose\fR
verbose output
.SH LOG .SH LOG
.TP .TP
\fBstring\fR \fBstring\fR
@ -42,9 +45,9 @@ path to a filter file (filter.d/sshd.conf)
Written by Cyril Jaquier <cyril.jaquier@fail2ban.org>. Written by Cyril Jaquier <cyril.jaquier@fail2ban.org>.
Many contributions by Yaroslav O. Halchenko <debian@onerussian.com>. Many contributions by Yaroslav O. Halchenko <debian@onerussian.com>.
.SH "REPORTING BUGS" .SH "REPORTING BUGS"
Report bugs on https://github.com/fail2ban/fail2ban/issues Report bugs to https://github.com/fail2ban/fail2ban/issues
.SH COPYRIGHT .SH COPYRIGHT
Copyright \(co 2004-2008 Cyril Jaquier Copyright \(co 2004\-2008 Cyril Jaquier
.br .br
Copyright of modifications held by their respective authors. Copyright of modifications held by their respective authors.
Licensed under the GNU General Public License v2 (GPL). Licensed under the GNU General Public License v2 (GPL).

View File

@ -1,12 +1,12 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.36. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.10.
.TH FAIL2BAN-SERVER "1" "March 2008" "fail2ban-server v0.8.2" "User Commands" .TH FAIL2BAN-SERVER "1" "March 2013" "fail2ban-server v0.8.8" "User Commands"
.SH NAME .SH NAME
fail2ban-server \- start the server fail2ban-server \- start the server
.SH SYNOPSIS .SH SYNOPSIS
.B fail2ban-server .B fail2ban-server
[\fIOPTIONS\fR] [\fIOPTIONS\fR]
.SH DESCRIPTION .SH DESCRIPTION
Fail2Ban v0.8.2 reads log file that contains password failure report Fail2Ban v0.8.8 reads log file that contains password failure report
and bans the corresponding IP addresses using firewall rules. and bans the corresponding IP addresses using firewall rules.
.PP .PP
Only use this command for debugging purpose. Start the server with Only use this command for debugging purpose. Start the server with
@ -23,6 +23,9 @@ start in foreground
\fB\-s\fR <FILE> \fB\-s\fR <FILE>
socket path socket path
.TP .TP
\fB\-p\fR <FILE>
pidfile path
.TP
\fB\-x\fR \fB\-x\fR
force execution of the server (remove socket file) force execution of the server (remove socket file)
.TP .TP
@ -35,9 +38,9 @@ print the version
Written by Cyril Jaquier <cyril.jaquier@fail2ban.org>. Written by Cyril Jaquier <cyril.jaquier@fail2ban.org>.
Many contributions by Yaroslav O. Halchenko <debian@onerussian.com>. Many contributions by Yaroslav O. Halchenko <debian@onerussian.com>.
.SH "REPORTING BUGS" .SH "REPORTING BUGS"
Report bugs on https://github.com/fail2ban/fail2ban/issues Report bugs to https://github.com/fail2ban/fail2ban/issues
.SH COPYRIGHT .SH COPYRIGHT
Copyright \(co 2004-2008 Cyril Jaquier Copyright \(co 2004\-2008 Cyril Jaquier, 2008\- Fail2Ban Contributors
.br .br
Copyright of modifications held by their respective authors. Copyright of modifications held by their respective authors.
Licensed under the GNU General Public License v2 (GPL). Licensed under the GNU General Public License v2 (GPL).

44
man/fail2ban.1 Normal file
View File

@ -0,0 +1,44 @@
.TH FAIL2BAN "1"
.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 "fail2ban" "jail.conf(5)"
.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
.d 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

@ -243,7 +243,7 @@ class Action:
return Action.executeCmd(stopCmd) return Action.executeCmd(stopCmd)
def escapeTag(tag): def escapeTag(tag):
for c in '\\#&;`|*?~<>^()[]{}$\n': for c in '\\#&;`|*?~<>^()[]{}$\n\'"':
if c in tag: if c in tag:
tag = tag.replace(c, '\\' + c) tag = tag.replace(c, '\\' + c)
return tag return tag
@ -277,8 +277,8 @@ class Action:
# Executes a command with preliminary checks and substitutions. # Executes a command with preliminary checks and substitutions.
# #
# Before executing any commands, executes the "check" command first # Before executing any commands, executes the "check" command first
# in order to check if prerequirements are met. If this check fails, # in order to check if pre-requirements are met. If this check fails,
# it tries to restore a sane environnement before executing the real # it tries to restore a sane environment before executing the real
# command. # command.
# Replaces "aInfo" and "cInfo" in the query too. # Replaces "aInfo" and "cInfo" in the query too.
# #

View File

@ -77,7 +77,8 @@ class Actions(JailThread):
for action in self.__actions: for action in self.__actions:
if action.getName() == name: if action.getName() == name:
self.__actions.remove(action) self.__actions.remove(action)
break return
raise KeyError("Invalid Action name: %s" % name)
## ##
# Returns an action. # Returns an action.
@ -91,7 +92,7 @@ class Actions(JailThread):
for action in self.__actions: for action in self.__actions:
if action.getName() == name: if action.getName() == name:
return action return action
raise KeyError raise KeyError("Invalid Action name")
## ##
# Returns the last defined action. # Returns the last defined action.
@ -131,7 +132,7 @@ class Actions(JailThread):
# Unban the IP. # Unban the IP.
self.__unBan(ticket) self.__unBan(ticket)
return ip return ip
return 'None' raise ValueError("IP %s is not banned" % ip)
## ##
# Main loop. # Main loop.

View File

@ -142,7 +142,7 @@ class AsyncServer(asyncore.dispatcher):
if sys.version_info >= (2, 6): # if python 2.6 or greater... if sys.version_info >= (2, 6): # if python 2.6 or greater...
logSys.debug("Detected Python 2.6 or greater. asyncore.loop() not using poll") logSys.debug("Detected Python 2.6 or greater. asyncore.loop() not using poll")
asyncore.loop(use_poll = False) # fixes the "Unexpected communication problem" issue on Python 2.6 and 3.0 asyncore.loop(use_poll = False) # fixes the "Unexpected communication problem" issue on Python 2.6 and 3.0
else: else: # pragma: no cover
logSys.debug("NOT Python 2.6/3.* - asyncore.loop() using poll") logSys.debug("NOT Python 2.6/3.* - asyncore.loop() using poll")
asyncore.loop(use_poll = True) asyncore.loop(use_poll = True)

View File

@ -40,6 +40,14 @@ class DateDetector:
def __init__(self): def __init__(self):
self.__lock = Lock() self.__lock = Lock()
self.__templates = list() self.__templates = list()
self.__known_names = set()
def _appendTemplate(self, template):
name = template.getName()
if name in self.__known_names:
raise ValueError("There is already a template with name %s" % name)
self.__known_names.add(name)
self.__templates.append(template)
def addDefaultTemplate(self): def addDefaultTemplate(self):
self.__lock.acquire() self.__lock.acquire()
@ -49,104 +57,104 @@ class DateDetector:
template.setName("MONTH Day Hour:Minute:Second") template.setName("MONTH Day Hour:Minute:Second")
template.setRegex("\S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2}") template.setRegex("\S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2}")
template.setPattern("%b %d %H:%M:%S") template.setPattern("%b %d %H:%M:%S")
self.__templates.append(template) self._appendTemplate(template)
# asctime # asctime
template = DateStrptime() template = DateStrptime()
template.setName("WEEKDAY MONTH Day Hour:Minute:Second Year") template.setName("WEEKDAY MONTH Day Hour:Minute:Second Year")
template.setRegex("\S{3} \S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2} \d{4}") template.setRegex("\S{3} \S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2} \d{4}")
template.setPattern("%a %b %d %H:%M:%S %Y") template.setPattern("%a %b %d %H:%M:%S %Y")
self.__templates.append(template) self._appendTemplate(template)
# asctime without year # asctime without year
template = DateStrptime() template = DateStrptime()
template.setName("WEEKDAY MONTH Day Hour:Minute:Second") template.setName("WEEKDAY MONTH Day Hour:Minute:Second")
template.setRegex("\S{3} \S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2}") template.setRegex("\S{3} \S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2}")
template.setPattern("%a %b %d %H:%M:%S") template.setPattern("%a %b %d %H:%M:%S")
self.__templates.append(template) self._appendTemplate(template)
# simple date # simple date
template = DateStrptime() template = DateStrptime()
template.setName("Year/Month/Day Hour:Minute:Second") template.setName("Year/Month/Day Hour:Minute:Second")
template.setRegex("\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}") template.setRegex("\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}")
template.setPattern("%Y/%m/%d %H:%M:%S") template.setPattern("%Y/%m/%d %H:%M:%S")
self.__templates.append(template) self._appendTemplate(template)
# simple date too (from x11vnc) # simple date too (from x11vnc)
template = DateStrptime() template = DateStrptime()
template.setName("Day/Month/Year Hour:Minute:Second") template.setName("Day/Month/Year Hour:Minute:Second")
template.setRegex("\d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2}") template.setRegex("\d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2}")
template.setPattern("%d/%m/%Y %H:%M:%S") template.setPattern("%d/%m/%Y %H:%M:%S")
self.__templates.append(template) self._appendTemplate(template)
# previous one but with year given by 2 digits # previous one but with year given by 2 digits
# (See http://bugs.debian.org/537610) # (See http://bugs.debian.org/537610)
template = DateStrptime() template = DateStrptime()
template.setName("Day/Month/Year Hour:Minute:Second") template.setName("Day/Month/Year2 Hour:Minute:Second")
template.setRegex("\d{2}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}") template.setRegex("\d{2}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}")
template.setPattern("%d/%m/%y %H:%M:%S") template.setPattern("%d/%m/%y %H:%M:%S")
self.__templates.append(template) self._appendTemplate(template)
# Apache format [31/Oct/2006:09:22:55 -0000] # Apache format [31/Oct/2006:09:22:55 -0000]
template = DateStrptime() template = DateStrptime()
template.setName("Day/MONTH/Year:Hour:Minute:Second") template.setName("Day/MONTH/Year:Hour:Minute:Second")
template.setRegex("\d{2}/\S{3}/\d{4}:\d{2}:\d{2}:\d{2}") template.setRegex("\d{2}/\S{3}/\d{4}:\d{2}:\d{2}:\d{2}")
template.setPattern("%d/%b/%Y:%H:%M:%S") template.setPattern("%d/%b/%Y:%H:%M:%S")
self.__templates.append(template) self._appendTemplate(template)
# CPanel 05/20/2008:01:57:39 # CPanel 05/20/2008:01:57:39
template = DateStrptime() template = DateStrptime()
template.setName("Month/Day/Year:Hour:Minute:Second") template.setName("Month/Day/Year:Hour:Minute:Second")
template.setRegex("\d{2}/\d{2}/\d{4}:\d{2}:\d{2}:\d{2}") template.setRegex("\d{2}/\d{2}/\d{4}:\d{2}:\d{2}:\d{2}")
template.setPattern("%m/%d/%Y:%H:%M:%S") template.setPattern("%m/%d/%Y:%H:%M:%S")
self.__templates.append(template) self._appendTemplate(template)
# Exim 2006-12-21 06:43:20 # Exim 2006-12-21 06:43:20
template = DateStrptime() template = DateStrptime()
template.setName("Year-Month-Day Hour:Minute:Second") template.setName("Year-Month-Day Hour:Minute:Second")
template.setRegex("\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}") template.setRegex("\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}")
template.setPattern("%Y-%m-%d %H:%M:%S") template.setPattern("%Y-%m-%d %H:%M:%S")
self.__templates.append(template) self._appendTemplate(template)
# custom for syslog-ng 2006.12.21 06:43:20 # custom for syslog-ng 2006.12.21 06:43:20
template = DateStrptime() template = DateStrptime()
template.setName("Year.Month.Day Hour:Minute:Second") template.setName("Year.Month.Day Hour:Minute:Second")
template.setRegex("\d{4}.\d{2}.\d{2} \d{2}:\d{2}:\d{2}") template.setRegex("\d{4}.\d{2}.\d{2} \d{2}:\d{2}:\d{2}")
template.setPattern("%Y.%m.%d %H:%M:%S") template.setPattern("%Y.%m.%d %H:%M:%S")
self.__templates.append(template) self._appendTemplate(template)
# named 26-Jul-2007 15:20:52.252 # named 26-Jul-2007 15:20:52.252
template = DateStrptime() template = DateStrptime()
template.setName("Day-MONTH-Year Hour:Minute:Second[.Millisecond]") template.setName("Day-MONTH-Year Hour:Minute:Second[.Millisecond]")
template.setRegex("\d{2}-\S{3}-\d{4} \d{2}:\d{2}:\d{2}") template.setRegex("\d{2}-\S{3}-\d{4} \d{2}:\d{2}:\d{2}")
template.setPattern("%d-%b-%Y %H:%M:%S") template.setPattern("%d-%b-%Y %H:%M:%S")
self.__templates.append(template) self._appendTemplate(template)
# 17-07-2008 17:23:25 # 17-07-2008 17:23:25
template = DateStrptime() template = DateStrptime()
template.setName("Day-Month-Year Hour:Minute:Second") template.setName("Day-Month-Year Hour:Minute:Second")
template.setRegex("\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2}") template.setRegex("\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2}")
template.setPattern("%d-%m-%Y %H:%M:%S") template.setPattern("%d-%m-%Y %H:%M:%S")
self.__templates.append(template) self._appendTemplate(template)
# 01-27-2012 16:22:44.252 # 01-27-2012 16:22:44.252
template = DateStrptime() template = DateStrptime()
template.setName("Month-Day-Year Hour:Minute:Second[.Millisecond]") template.setName("Month-Day-Year Hour:Minute:Second[.Millisecond]")
template.setRegex("\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2}") template.setRegex("\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2}")
template.setPattern("%m-%d-%Y %H:%M:%S") template.setPattern("%m-%d-%Y %H:%M:%S")
self.__templates.append(template) self._appendTemplate(template)
# TAI64N # TAI64N
template = DateTai64n() template = DateTai64n()
template.setName("TAI64N") template.setName("TAI64N")
self.__templates.append(template) self._appendTemplate(template)
# Epoch # Epoch
template = DateEpoch() template = DateEpoch()
template.setName("Epoch") template.setName("Epoch")
self.__templates.append(template) self._appendTemplate(template)
# ISO 8601 # ISO 8601
template = DateISO8601() template = DateISO8601()
template.setName("ISO 8601") template.setName("ISO 8601")
self.__templates.append(template) self._appendTemplate(template)
# Only time information in the log # Only time information in the log
template = DateStrptime() template = DateStrptime()
template.setName("Hour:Minute:Second") template.setName("Hour:Minute:Second")
template.setRegex("^\d{2}:\d{2}:\d{2}") template.setRegex("^\d{2}:\d{2}:\d{2}")
template.setPattern("%H:%M:%S") template.setPattern("%H:%M:%S")
self.__templates.append(template) self._appendTemplate(template)
# <09/16/08@05:03:30> # <09/16/08@05:03:30>
template = DateStrptime() template = DateStrptime()
template.setName("<Month/Day/Year@Hour:Minute:Second>") template.setName("<Month/Day/Year@Hour:Minute:Second>")
template.setRegex("^<\d{2}/\d{2}/\d{2}@\d{2}:\d{2}:\d{2}>") template.setRegex("^<\d{2}/\d{2}/\d{2}@\d{2}:\d{2}:\d{2}>")
template.setPattern("<%m/%d/%y@%H:%M:%S>") template.setPattern("<%m/%d/%y@%H:%M:%S>")
self.__templates.append(template) self._appendTemplate(template)
finally: finally:
self.__lock.release() self.__lock.release()
@ -158,7 +166,8 @@ class DateDetector:
try: try:
for template in self.__templates: for template in self.__templates:
match = template.matchDate(line) match = template.matchDate(line)
if not match == None: if not match is None:
logSys.debug("Matched time template %s" % template.getName())
return match return match
return None return None
finally: finally:
@ -170,8 +179,9 @@ class DateDetector:
for template in self.__templates: for template in self.__templates:
try: try:
date = template.getDate(line) date = template.getDate(line)
if date == None: if date is None:
continue continue
logSys.debug("Got time using template %s" % template.getName())
return date return date
except ValueError: except ValueError:
pass pass
@ -194,7 +204,8 @@ class DateDetector:
self.__lock.acquire() self.__lock.acquire()
try: try:
logSys.debug("Sorting the template list") logSys.debug("Sorting the template list")
self.__templates.sort(lambda x, y: cmp(x.getHits(), y.getHits())) self.__templates.sort(lambda x, y: cmp(x.getHits(), y.getHits()), reverse=True)
self.__templates.reverse() t = self.__templates[0]
logSys.debug("Winning template: %s with %d hits" % (t.getName(), t.getHits()))
finally: finally:
self.__lock.release() self.__lock.release()

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

@ -130,4 +130,4 @@ class FailRegex(Regex):
s = self._matchCache.string s = self._matchCache.string
r = self._matchCache.re r = self._matchCache.re
raise RegexException("No 'host' found in '%s' using '%s'" % (s, r)) raise RegexException("No 'host' found in '%s' using '%s'" % (s, r))
return host return str(host)

View File

@ -44,7 +44,7 @@ logSys = logging.getLogger("fail2ban.filter")
# Log reader class. # Log reader class.
# #
# This class reads a log file and detects login failures or anything else # This class reads a log file and detects login failures or anything else
# that matches a given regular expression. This class is instanciated by # that matches a given regular expression. This class is instantiated by
# a Jail object. # a Jail object.
class Filter(JailThread): class Filter(JailThread):
@ -93,6 +93,7 @@ class Filter(JailThread):
self.__failRegex.append(regex) self.__failRegex.append(regex)
except RegexException, e: except RegexException, e:
logSys.error(e) logSys.error(e)
raise e
def delFailRegex(self, index): def delFailRegex(self, index):
@ -117,7 +118,7 @@ class Filter(JailThread):
# Add the regular expression which matches the failure. # Add the regular expression which matches the failure.
# #
# The regular expression can also match any other pattern than failures # The regular expression can also match any other pattern than failures
# and thus can be used for many purporse. # and thus can be used for many purpose.
# @param value the regular expression # @param value the regular expression
def addIgnoreRegex(self, value): def addIgnoreRegex(self, value):
@ -126,6 +127,7 @@ class Filter(JailThread):
self.__ignoreRegex.append(regex) self.__ignoreRegex.append(regex)
except RegexException, e: except RegexException, e:
logSys.error(e) logSys.error(e)
raise e
def delIgnoreRegex(self, index): def delIgnoreRegex(self, index):
try: try:
@ -211,7 +213,7 @@ class Filter(JailThread):
# file has been modified and looks for failures. # file has been modified and looks for failures.
# @return True when the thread exits nicely # @return True when the thread exits nicely
def run(self): def run(self): # pragma: no cover
raise Exception("run() is abstract") raise Exception("run() is abstract")
## ##
@ -226,7 +228,7 @@ class Filter(JailThread):
self.failManager.addFailure(FailTicket(ip, unixTime)) self.failManager.addFailure(FailTicket(ip, unixTime))
# Perform the banning of the IP now. # Perform the banning of the IP now.
try: try: # pragma: no branch - exception is the only way out
while True: while True:
ticket = self.failManager.toBan() ticket = self.failManager.toBan()
self.jail.putFailTicket(ticket) self.jail.putFailTicket(ticket)
@ -373,7 +375,7 @@ class Filter(JailThread):
failList.append([ip, date]) failList.append([ip, date])
# We matched a regex, it is enough to stop. # We matched a regex, it is enough to stop.
break break
except RegexException, e: except RegexException, e: # pragma: no cover - unsure if reachable
logSys.error(e) logSys.error(e)
return failList return failList
@ -414,7 +416,7 @@ class FileFilter(Filter):
def _addLogPath(self, path): def _addLogPath(self, path):
# nothing to do by default # nothing to do by default
# to be overriden by backends # to be overridden by backends
pass pass
@ -433,7 +435,7 @@ class FileFilter(Filter):
def _delLogPath(self, path): def _delLogPath(self, path):
# nothing to do by default # nothing to do by default
# to be overriden by backends # to be overridden by backends
pass pass
## ##
@ -477,10 +479,19 @@ class FileFilter(Filter):
# Try to open log file. # Try to open log file.
try: try:
container.open() container.open()
except Exception, e: # see http://python.org/dev/peps/pep-3151/
except IOError, e:
logSys.error("Unable to open %s" % filename) logSys.error("Unable to open %s" % filename)
logSys.exception(e) logSys.exception(e)
return False return False
except OSError, e: # pragma: no cover - requires race condition to tigger this
logSys.error("Error opening %s" % filename)
logSys.exception(e)
return False
except OSError, e: # pragma: no cover - Requires implemention error in FileContainer to generate
logSys.error("Internal errror in FileContainer open method - please report as a bug to https://github.com/fail2ban/fail2ban/issues")
logSys.exception(e)
return False
while True: while True:
line = container.readline() line = container.readline()
@ -507,7 +518,7 @@ class FileFilter(Filter):
try: try:
import hashlib import hashlib
md5sum = hashlib.md5 md5sum = hashlib.md5
except ImportError: except ImportError: # pragma: no cover
# hashlib was introduced in Python 2.5. For compatibility with those # hashlib was introduced in Python 2.5. For compatibility with those
# elderly Pythons, import from md5 # elderly Pythons, import from md5
import md5 import md5
@ -550,7 +561,7 @@ class FileContainer:
stats = os.fstat(self.__handler.fileno()) stats = os.fstat(self.__handler.fileno())
# Compare hash and inode # Compare hash and inode
if self.__hash != myHash or self.__ino != stats.st_ino: if self.__hash != myHash or self.__ino != stats.st_ino:
logSys.info("Log rotation detected for %s" % self.__filename) logSys.debug("Log rotation detected for %s" % self.__filename)
self.__hash = myHash self.__hash = myHash
self.__ino = stats.st_ino self.__ino = stats.st_ino
self.__pos = 0 self.__pos = 0
@ -639,7 +650,7 @@ class DNSUtils:
ip = DNSUtils.dnsToIp(text) ip = DNSUtils.dnsToIp(text)
ipList.extend(ip) ipList.extend(ip)
if ip and useDns == "warn": if ip and useDns == "warn":
logSys.warning("Determined IP using DNS Reverse Lookup: %s = %s", logSys.warning("Determined IP using DNS Lookup: %s = %s",
text, ipList) text, ipList)
return ipList return ipList

View File

@ -39,7 +39,7 @@ logSys = logging.getLogger("fail2ban.filter")
# Log reader class. # Log reader class.
# #
# This class reads a log file and detects login failures or anything else # This class reads a log file and detects login failures or anything else
# that matches a given regular expression. This class is instanciated by # that matches a given regular expression. This class is instantiated by
# a Jail object. # a Jail object.
class FilterPoll(FileFilter): class FilterPoll(FileFilter):

View File

@ -63,12 +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
if event.mask & pyinotify.IN_ISDIR:
logSys.debug("Ignoring creation of directory %s", path)
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
@ -100,7 +105,7 @@ 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)
@ -110,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
@ -126,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)
@ -147,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)
## ##
@ -161,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
@ -197,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

@ -17,16 +17,11 @@
# 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, 2013- Yaroslav Halchenko"
# $Revision$
__author__ = "Cyril Jaquier"
__version__ = "$Revision$"
__date__ = "$Date$"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
from common.exceptions import DuplicateJailException, UnknownJailException
from jail import Jail from jail import Jail
from threading import Lock from threading import Lock
@ -160,9 +155,3 @@ class Jails:
finally: finally:
self.__lock.release() self.__lock.release()
class DuplicateJailException(Exception):
pass
class UnknownJailException(Exception):
pass

View File

@ -40,8 +40,6 @@ logSys = logging.getLogger("fail2ban.server")
class Server: class Server:
PID_FILE = "/var/run/fail2ban/fail2ban.pid"
def __init__(self, daemon = False): def __init__(self, daemon = False):
self.__loggingLock = Lock() self.__loggingLock = Lock()
self.__lock = RLock() self.__lock = RLock()
@ -59,7 +57,7 @@ class Server:
logSys.debug("Caught signal %d. Exiting" % signum) logSys.debug("Caught signal %d. Exiting" % signum)
self.quit() self.quit()
def start(self, sock, force = False): def start(self, sock, pidfile, force = False):
logSys.info("Starting Fail2ban v" + version.version) logSys.info("Starting Fail2ban v" + version.version)
# Install signal handlers # Install signal handlers
@ -79,8 +77,8 @@ class Server:
# Creates a PID file. # Creates a PID file.
try: try:
logSys.debug("Creating PID file %s" % Server.PID_FILE) logSys.debug("Creating PID file %s" % pidfile)
pidFile = open(Server.PID_FILE, 'w') pidFile = open(pidfile, 'w')
pidFile.write("%s\n" % os.getpid()) pidFile.write("%s\n" % os.getpid())
pidFile.close() pidFile.close()
except IOError, e: except IOError, e:
@ -94,17 +92,11 @@ class Server:
logSys.error("Could not start server: %s", e) logSys.error("Could not start server: %s", e)
# Removes the PID file. # Removes the PID file.
try: try:
logSys.debug("Remove PID file %s" % Server.PID_FILE) logSys.debug("Remove PID file %s" % pidfile)
os.remove(Server.PID_FILE) os.remove(pidfile)
except OSError, e: except OSError, e:
logSys.error("Unable to remove PID file: %s" % e) logSys.error("Unable to remove PID file: %s" % e)
logSys.info("Exiting Fail2ban") logSys.info("Exiting Fail2ban")
# Shutdowns the logging.
try:
self.__loggingLock.acquire()
logging.shutdown()
finally:
self.__loggingLock.release()
def quit(self): def quit(self):
# Stop communication first because if jail's unban action # Stop communication first because if jail's unban action
@ -114,9 +106,18 @@ class Server:
# are exiting) # are exiting)
# See https://github.com/fail2ban/fail2ban/issues/7 # See https://github.com/fail2ban/fail2ban/issues/7
self.__asyncServer.stop() self.__asyncServer.stop()
# Now stop all the jails # Now stop all the jails
self.stopAllJail() self.stopAllJail()
# Only now shutdown the logging.
try:
self.__loggingLock.acquire()
logging.shutdown()
finally:
self.__loggingLock.release()
def addJail(self, name, backend): def addJail(self, name, backend):
self.__jails.add(name, backend) self.__jails.add(name, backend)
@ -369,11 +370,20 @@ class Server:
logSys.error("Unable to log to " + target) logSys.error("Unable to log to " + target)
logSys.info("Logging to previous target " + self.__logTarget) logSys.info("Logging to previous target " + self.__logTarget)
return False return False
# Removes previous handlers # Removes previous handlers -- in reverse order since removeHandler
for handler in logging.getLogger("fail2ban").handlers: # alter the list in-place and that can confuses the iterable
# Closes the handler. for handler in logging.getLogger("fail2ban").handlers[::-1]:
# Remove the handler.
logging.getLogger("fail2ban").removeHandler(handler) logging.getLogger("fail2ban").removeHandler(handler)
# And try to close -- it might be closed already
try:
handler.flush()
handler.close() handler.close()
except (ValueError, KeyError):
if sys.version_info >= (2,6):
raise
# is known to be thrown after logging was shutdown once
# with older Pythons -- seems to be safe to ignore there
# tell the handler to use this format # tell the handler to use this format
hdlr.setFormatter(formatter) hdlr.setFormatter(formatter)
logging.getLogger("fail2ban").addHandler(hdlr) logging.getLogger("fail2ban").addHandler(hdlr)

View File

@ -112,14 +112,18 @@ class Transmitter:
return self.__server.getLogLevel() return self.__server.getLogLevel()
elif name == "logtarget": elif name == "logtarget":
value = command[1] value = command[1]
self.__server.setLogTarget(value) if self.__server.setLogTarget(value):
return self.__server.getLogTarget() return self.__server.getLogTarget()
else:
raise Exception("Failed to change log target")
# Jail # Jail
elif command[1] == "idle": elif command[1] == "idle":
if command[2] == "on": if command[2] == "on":
self.__server.setIdleJail(name, True) self.__server.setIdleJail(name, True)
elif command[2] == "off": elif command[2] == "off":
self.__server.setIdleJail(name, False) self.__server.setIdleJail(name, False)
else:
raise Exception("Invalid idle option, must be 'yes' or 'no'")
return self.__server.getIdleJail(name) return self.__server.getIdleJail(name)
# Filter # Filter
elif command[1] == "addignoreip": elif command[1] == "addignoreip":
@ -183,12 +187,13 @@ class Transmitter:
self.__server.addAction(name, value) self.__server.addAction(name, value)
return self.__server.getLastAction(name).getName() return self.__server.getLastAction(name).getName()
elif command[1] == "delaction": elif command[1] == "delaction":
value = command[2]
self.__server.delAction(name, value) self.__server.delAction(name, value)
return None return None
elif command[1] == "setcinfo": elif command[1] == "setcinfo":
act = command[2] act = command[2]
key = command[3] key = command[3]
value = command[4] value = " ".join(command[4:])
self.__server.setCInfo(name, act, key, value) self.__server.setCInfo(name, act, key, value)
return self.__server.getCInfo(name, act, key) return self.__server.getCInfo(name, act, key)
elif command[1] == "delcinfo": elif command[1] == "delcinfo":
@ -198,27 +203,27 @@ class Transmitter:
return None return None
elif command[1] == "actionstart": elif command[1] == "actionstart":
act = command[2] act = command[2]
value = command[3] value = " ".join(command[3:])
self.__server.setActionStart(name, act, value) self.__server.setActionStart(name, act, value)
return self.__server.getActionStart(name, act) return self.__server.getActionStart(name, act)
elif command[1] == "actionstop": elif command[1] == "actionstop":
act = command[2] act = command[2]
value = command[3] value = " ".join(command[3:])
self.__server.setActionStop(name, act, value) self.__server.setActionStop(name, act, value)
return self.__server.getActionStop(name, act) return self.__server.getActionStop(name, act)
elif command[1] == "actioncheck": elif command[1] == "actioncheck":
act = command[2] act = command[2]
value = command[3] value = " ".join(command[3:])
self.__server.setActionCheck(name, act, value) self.__server.setActionCheck(name, act, value)
return self.__server.getActionCheck(name, act) return self.__server.getActionCheck(name, act)
elif command[1] == "actionban": elif command[1] == "actionban":
act = command[2] act = command[2]
value = command[3] value = " ".join(command[3:])
self.__server.setActionBan(name, act, value) self.__server.setActionBan(name, act, value)
return self.__server.getActionBan(name, act) return self.__server.getActionBan(name, act)
elif command[1] == "actionunban": elif command[1] == "actionunban":
act = command[2] act = command[2]
value = command[3] value = " ".join(command[3:])
self.__server.setActionUnban(name, act, value) self.__server.setActionUnban(name, act, value)
return self.__server.getActionUnban(name, act) return self.__server.getActionUnban(name, act)
raise Exception("Invalid command (no set action or not yet implemented)") raise Exception("Invalid command (no set action or not yet implemented)")
@ -265,12 +270,16 @@ class Transmitter:
elif command[1] == "actionunban": elif command[1] == "actionunban":
act = command[2] act = command[2]
return self.__server.getActionUnban(name, act) return self.__server.getActionUnban(name, act)
elif command[1] == "cinfo":
act = command[2]
key = command[3]
return self.__server.getCInfo(name, act, key)
raise Exception("Invalid command (no get action or not yet implemented)") raise Exception("Invalid command (no get action or not yet implemented)")
def status(self, command): def status(self, command):
if len(command) == 0: if len(command) == 0:
return self.__server.status() return self.__server.status()
else: elif len(command) == 1:
name = command[0] name = command[0]
return self.__server.statusJail(name) return self.__server.statusJail(name)
raise Exception("Invalid command (no status)") raise Exception("Invalid command (no status)")

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']
) )
] ]
) )
@ -83,12 +86,6 @@ elements = {
[ [
"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

@ -17,26 +17,91 @@
# 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.configreader import ConfigReader
from client.jailreader import JailReader from client.jailreader import JailReader
from client.jailsreader import JailsReader
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]"
@ -44,3 +109,40 @@ class JailReaderTest(unittest.TestCase):
result = JailReader.splitAction(action) result = JailReader.splitAction(action)
self.assertEquals(expected, result) self.assertEquals(expected, result)
class JailsReaderTest(unittest.TestCase):
def testProvidingBadBasedir(self):
if not os.path.exists('/XXX'):
self.assertRaises(ValueError, JailsReader, basedir='/XXX')
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

View File

@ -51,33 +51,79 @@ class DateDetectorTest(unittest.TestCase):
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)
def testStableSortTemplate(self):
old_names = [x.getName() for x in self.__datedetector.getTemplates()]
self.__datedetector.sortTemplate()
# If there were no hits -- sorting should not change the order
for old_name, n in zip(old_names, self.__datedetector.getTemplates()):
self.assertEqual(old_name, n.getName()) # "Sort must be stable"
def testAllUniqueTemplateNames(self):
self.assertRaises(ValueError, self.__datedetector._appendTemplate,
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

@ -1 +1,8 @@
@400000004c91b044077a9e94 imap-login: Info: Aborted login (auth failed, 1 attempts): user=<martin@waschbuesch.de>, method=CRAM-MD5, rip=80.187.101.33, lip=80.254.129.240, TLS @400000004c91b044077a9e94 imap-login: Info: Aborted login (auth failed, 1 attempts): user=<martin@waschbuesch.de>, method=CRAM-MD5, rip=80.187.101.33, lip=80.254.129.240, TLS
@e040c6d8a3bfa62d358083300119c259cd44dcd0 dovecot-auth: pam_unix(dovecot:auth): authentication failure; logname= uid=0 euid=0 tty=dovecot ruser=web rhost=176.61.140.224
# Above example with injected rhost into ruser -- should not match for 1.2.3.4
@e040c6d8a3bfa62d358083300119c259cd44dcd0 dovecot-auth: pam_unix(dovecot:auth): authentication failure; logname= uid=0 euid=0 tty=dovecot ruser=rhost=1.2.3.4 rhost=192.0.43.10
@e040c6d8a3bfa62d358083300119c259cd44dcd0 dovecot-auth: pam_unix(dovecot:auth): authentication failure; logname= uid=0 euid=0 tty=dovecot ruser=root rhost=176.61.140.224 user=root
Dec 12 11:19:11 dunnart dovecot: pop3-login: Aborted login (tried to use disabled plaintext auth): rip=190.210.136.21, lip=113.212.99.193

View File

@ -0,0 +1,2 @@
# From IRC 2013-01-04
2013-01-04 17:03:46 login authenticator failed for rrcs-24-106-174-74.se.biz.rr.com ([192.168.2.33]) [24.106.174.74]: 535 Incorrect authentication data (set_id=brian)

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 @@
[22-Jan-2013 22:28:21 +0200]: FAILED login for user1 from 192.0.43.10

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

@ -0,0 +1,2 @@
# http://forums.powervps.com/showthread.php?t=1667
Jun 7 01:10:56 host sshd[5937]: Did not receive identification string from 69.61.56.114

View File

@ -0,0 +1,7 @@
#Webmin authentication failures from /var/log/auth.log
#1 User exists, bad password
Dec 13 08:15:18 sb1 webmin[25875]: Invalid login as root from 89.2.49.230
#2 User does not exists
Dec 12 23:14:19 sb1 webmin[22134]: Non-existent login as robert from 188.40.105.142

View File

@ -99,26 +99,29 @@ def _copy_lines_between_files(fin, fout, n=None, skip=0, mode='a', terminal_line
Returns open fout Returns open fout
""" """
if sys.version_info[:2] <= (2,4): if sys.version_info[:2] <= (2,4): # pragma: no cover
# on old Python st_mtime is int, so we should give at least 1 sec so # on old Python st_mtime is int, so we should give at least 1 sec so
# polling filter could detect the change # polling filter could detect the change
time.sleep(1) time.sleep(1)
if isinstance(fin, str): 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 +327,15 @@ def get_monitor_failures_testcase(Filter_):
"""Generator of TestCase's for different filters/backends """Generator of TestCase's for different filters/backends
""" """
_, testclass_name = tempfile.mkstemp('fail2ban', 'monitorfailures')
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 +358,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):
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 +383,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 +435,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(2 + int(isinstance(self.filter, FilterPoll))*4))
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 +493,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

@ -27,8 +27,9 @@ __date__ = "$Date$"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
import unittest, socket, time import unittest, socket, time, tempfile, os
from server.server import Server from server.server import Server
from common.exceptions import UnknownJailException
class StartStop(unittest.TestCase): class StartStop(unittest.TestCase):
@ -49,82 +50,461 @@ 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.__server.setLogLevel(0) sock_fd, sock_name = tempfile.mkstemp('fail2ban.sock', 'transmitter')
self.__server.start(False) os.close(sock_fd)
pidfile_fd, pidfile_name = tempfile.mkstemp(
'fail2ban.pid', 'transmitter')
os.close(pidfile_fd)
self.server.start(sock_name, pidfile_name, force=False)
self.jailName = "TestJail1"
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 testSetActionOK(self): def setGetTest(self, cmd, inValue, outValue=None, jail=None):
name = "TestCase" setCmd = ["set", cmd, inValue]
cmdList = [["add", name], getCmd = ["get", cmd]
["set", name, "actionstart", "Action Start"], if jail is not None:
["set", name, "actionstop", "Action Stop"], setCmd.insert(1, jail)
["set", name, "actioncheck", "Action Check"], getCmd.insert(1, jail)
["set", name, "actionban", "Action Ban"], if outValue is None:
["set", name, "actionunban", "Action Unban"], outValue = inValue
["quit"]]
outList = [(0, name), self.assertEqual(self.transm.proceed(setCmd), (0, outValue))
(0, 'Action Start'), self.assertEqual(self.transm.proceed(getCmd), (0, outValue))
(0, 'Action Stop'),
(0, 'Action Check'),
(0, 'Action Ban'),
(0, 'Action Unban'),
(0, None)]
cnt = 0 def setGetTestNOK(self, cmd, inValue, jail=None):
for cmd in cmdList: setCmd = ["set", cmd, inValue]
self.assertEqual(self.__server.transm.proceed(cmd), outList[cnt]) getCmd = ["get", cmd]
cnt += 1 if jail is not None:
setCmd.insert(1, jail)
getCmd.insert(1, jail)
def testSetActionNOK(self): # Get initial value before trying invalid value
name = "TestCase" initValue = self.transm.proceed(getCmd)[1]
cmdList = [["addd", name], self.assertEqual(self.transm.proceed(setCmd)[0], 1)
["set", name, "test"], # Check after failed set that value is same as previous
["prout prout", "Stop"], self.assertEqual(self.transm.proceed(getCmd), (0, initValue))
["fail2ban", "sucks"],
["set"],
["_/&%", "@*+%&"],
[" quit"]]
outList = [1, def jailAddDelTest(self, cmd, values, jail):
1, cmdAdd = "add" + cmd
1, cmdDel = "del" + cmd
1,
1,
1,
1]
cnt = 0 self.assertEqual(
for cmd in cmdList: self.transm.proceed(["get", jail, cmd]), (0, []))
msg = self.__server.transm.proceed(cmd) for n, value in enumerate(values):
self.assertEqual(msg[0], outList[cnt]) self.assertEqual(
cnt += 1 self.transm.proceed(["set", jail, cmdAdd, value]),
(0, values[:n+1]))
self.assertEqual(
self.transm.proceed(["get", jail, cmd]),
(0, values[:n+1]))
for n, value in enumerate(values):
self.assertEqual(
self.transm.proceed(["set", jail, cmdDel, value]),
(0, values[n+1:]))
self.assertEqual(
self.transm.proceed(["get", jail, cmd]),
(0, values[n+1:]))
def testJail(self): def jailAddDelRegexTest(self, cmd, inValues, outValues, jail):
name = "TestCase" cmdAdd = "add" + cmd
cmdList = [["add", name], cmdDel = "del" + cmd
["set", name, "logpath", "testcases/files/testcase01.log"],
["set", name, "timeregex", "\S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2}"],
["set", name, "timepattern", "%b %d %H:%M:%S"],
["set", name, "failregex", "Authentication failure"],
["start", name],
["stop", name],
["quit"]]
for cmd in cmdList: if outValues is None:
self.__server.transm.proceed(cmd) outValues = inValues
if cmd == ["start", name]:
time.sleep(2)
jail = self.__server.jails[name]
self.assertEqual(jail.getFilter().failManager.size(), 0)
self.assertEqual(jail.getAction().banManager.size(), 2)
self.assertEqual(
self.transm.proceed(["get", jail, cmd]), (0, []))
for n, value in enumerate(inValues):
self.assertEqual(
self.transm.proceed(["set", jail, cmdAdd, value]),
(0, outValues[:n+1]))
self.assertEqual(
self.transm.proceed(["get", jail, cmd]),
(0, outValues[:n+1]))
for n, value in enumerate(inValues):
self.assertEqual(
self.transm.proceed(["set", jail, cmdDel, 0]), # First item
(0, outValues[n+1:]))
self.assertEqual(
self.transm.proceed(["get", jail, cmd]),
(0, outValues[n+1:]))
class Transmitter(TransmitterBase):
def setUp(self):
self.server = TestServer()
super(Transmitter, self).setUp()
def testStopServer(self):
self.assertEqual(self.transm.proceed(["stop"]), (0, None))
def testPing(self):
self.assertEqual(self.transm.proceed(["ping"]), (0, "pong"))
def testSleep(self):
t0 = time.time()
self.assertEqual(self.transm.proceed(["sleep", "1"]), (0, None))
t1 = time.time()
# Approx 1 second delay
self.assertAlmostEqual(t1 - t0, 1, places=2)
def testAddJail(self):
jail2 = "TestJail2"
jail3 = "TestJail3"
jail4 = "TestJail4"
self.assertEqual(
self.transm.proceed(["add", jail2, "polling"]), (0, jail2))
self.assertEqual(self.transm.proceed(["add", jail3]), (0, jail3))
self.assertEqual(
self.transm.proceed(["add", jail4, "invalid backend"])[0], 1)
self.assertEqual(
self.transm.proceed(["add", jail4, "auto"]), (0, jail4))
# Duplicate Jail
self.assertEqual(
self.transm.proceed(["add", self.jailName, "polling"])[0], 1)
# All name is reserved
self.assertEqual(
self.transm.proceed(["add", "all", "polling"])[0], 1)
def testStartStopJail(self):
self.assertEqual(
self.transm.proceed(["start", self.jailName]), (0, None))
time.sleep(1)
self.assertEqual(
self.transm.proceed(["stop", self.jailName]), (0, None))
self.assertRaises(
UnknownJailException, self.server.isAlive, self.jailName)
def testStartStopAllJail(self):
self.server.addJail("TestJail2", "auto")
self.assertEqual(
self.transm.proceed(["start", self.jailName]), (0, None))
self.assertEqual(
self.transm.proceed(["start", "TestJail2"]), (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)
self.assertRaises(
UnknownJailException, self.server.isAlive, self.jailName)
self.assertRaises(
UnknownJailException, self.server.isAlive, "TestJail2")
def testJailIdle(self):
self.assertEqual(
self.transm.proceed(["set", self.jailName, "idle", "on"]),
(0, True))
self.assertEqual(
self.transm.proceed(["set", self.jailName, "idle", "off"]),
(0, False))
self.assertEqual(
self.transm.proceed(["set", self.jailName, "idle", "CAT"])[0],
1)
def testJailFindTime(self):
self.setGetTest("findtime", "120", 120, jail=self.jailName)
self.setGetTest("findtime", "60", 60, jail=self.jailName)
self.setGetTest("findtime", "-60", -60, jail=self.jailName)
self.setGetTestNOK("findtime", "Dog", jail=self.jailName)
def testJailBanTime(self):
self.setGetTest("bantime", "600", 600, jail=self.jailName)
self.setGetTest("bantime", "50", 50, jail=self.jailName)
self.setGetTest("bantime", "-50", -50, jail=self.jailName)
self.setGetTestNOK("bantime", "Cat", jail=self.jailName)
def testJailUseDNS(self):
self.setGetTest("usedns", "yes", jail=self.jailName)
self.setGetTest("usedns", "warn", jail=self.jailName)
self.setGetTest("usedns", "no", jail=self.jailName)
# Safe default should be "no"
value = "Fish"
self.assertEqual(
self.transm.proceed(["set", self.jailName, "usedns", value]),
(0, "no"))
def testJailBanIP(self):
self.server.startJail(self.jailName) # Jail must be started
self.assertEqual(
self.transm.proceed(["set", self.jailName, "banip", "127.0.0.1"]),
(0, "127.0.0.1"))
time.sleep(1) # Give chance to ban
self.assertEqual(
self.transm.proceed(["set", self.jailName, "banip", "Badger"]),
(0, "Badger")) #NOTE: Is IP address validated? Is DNS Lookup done?
time.sleep(1) # Give chance to ban
# Unban IP
self.assertEqual(
self.transm.proceed(
["set", self.jailName, "unbanip", "127.0.0.1"]),
(0, "127.0.0.1"))
# Unban IP which isn't banned
self.assertEqual(
self.transm.proceed(
["set", self.jailName, "unbanip", "192.168.1.1"])[0],1)
def testJailMaxRetry(self):
self.setGetTest("maxretry", "5", 5, jail=self.jailName)
self.setGetTest("maxretry", "2", 2, jail=self.jailName)
self.setGetTest("maxretry", "-2", -2, jail=self.jailName)
self.setGetTestNOK("maxretry", "Duck", jail=self.jailName)
def testJailLogPath(self):
self.jailAddDelTest(
"logpath",
[
"testcases/files/testcase01.log",
"testcases/files/testcase02.log",
"testcases/files/testcase03.log",
],
self.jailName
)
# Try duplicates
value = "testcases/files/testcase04.log"
self.assertEqual(
self.transm.proceed(["set", self.jailName, "addlogpath", value]),
(0, [value]))
# Will silently ignore duplicate
self.assertEqual(
self.transm.proceed(["set", self.jailName, "addlogpath", value]),
(0, [value]))
self.assertEqual(
self.transm.proceed(["get", self.jailName, "logpath"]),
(0, [value]))
self.assertEqual(
self.transm.proceed(["set", self.jailName, "dellogpath", value]),
(0, []))
# Invalid file
value = "this_file_shouldn't_exist"
result = self.transm.proceed(
["set", self.jailName, "addlogpath", value])
self.assertTrue(isinstance(result[1], IOError))
def testJailIgnoreIP(self):
self.jailAddDelTest(
"ignoreip",
[
"127.0.0.1",
"192.168.1.1",
"8.8.8.8",
],
self.jailName
)
# Try duplicates
value = "127.0.0.1"
self.assertEqual(
self.transm.proceed(["set", self.jailName, "addignoreip", value]),
(0, [value]))
# Will allow duplicate
#NOTE: Should duplicates be allowed, or silent ignore like logpath?
self.assertEqual(
self.transm.proceed(["set", self.jailName, "addignoreip", value]),
(0, [value, value]))
self.assertEqual(
self.transm.proceed(["get", self.jailName, "ignoreip"]),
(0, [value, value]))
self.assertEqual(
self.transm.proceed(["set", self.jailName, "delignoreip", value]),
(0, [value]))
def testJailRegex(self):
self.jailAddDelRegexTest("failregex",
[
"user john at <HOST>",
"Admin user login from <HOST>",
"failed attempt from <HOST> again",
],
[
"user john at (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)",
"Admin user login from (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)",
"failed attempt from (?:::f{4,6}:)?(?P<host>[\w\-.^_]+) again",
],
self.jailName
)
self.assertEqual(
self.transm.proceed(
["set", self.jailName, "addfailregex", "No host regex"])[0],
1)
self.assertEqual(
self.transm.proceed(
["set", self.jailName, "addfailregex", 654])[0],
1)
def testJailIgnoreRegex(self):
self.jailAddDelRegexTest("ignoreregex",
[
"user john",
"Admin user login from <HOST>",
"Dont match me!",
],
[
"user john",
"Admin user login from (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)",
"Dont match me!",
],
self.jailName
)
self.assertEqual(
self.transm.proceed(
["set", self.jailName, "addignoreregex", "Invalid [regex"])[0],
1)
self.assertEqual(
self.transm.proceed(
["set", self.jailName, "addignoreregex", 50])[0],
1)
def testStatus(self):
jails = [self.jailName]
self.assertEqual(self.transm.proceed(["status"]),
(0, [('Number of jail', len(jails)), ('Jail list', ", ".join(jails))]))
self.server.addJail("TestJail2", "auto")
jails.append("TestJail2")
self.assertEqual(self.transm.proceed(["status"]),
(0, [('Number of jail', len(jails)), ('Jail list', ", ".join(jails))]))
def testJailStatus(self):
self.assertEqual(self.transm.proceed(["status", self.jailName]),
(0,
[
('filter', [
('Currently failed', 0),
('Total failed', 0),
('File list', [])]
),
('action', [
('Currently banned', 0),
('Total banned', 0),
('IP list', [])]
)
]
)
)
def testAction(self):
action = "TestCaseAction"
cmdList = [
"actionstart",
"actionstop",
"actioncheck",
"actionban",
"actionunban",
]
cmdValueList = [
"Action Start",
"Action Stop",
"Action Check",
"Action Ban",
"Action Unban",
]
self.assertEqual(
self.transm.proceed(["set", self.jailName, "addaction", action]),
(0, action))
self.assertEqual(
self.transm.proceed(["get", self.jailName, "addaction", action]),
(0, action))
for cmd, value in zip(cmdList, cmdValueList):
self.assertEqual(
self.transm.proceed(
["set", self.jailName, cmd, action, value]),
(0, value))
for cmd, value in zip(cmdList, cmdValueList):
self.assertEqual(
self.transm.proceed(["get", self.jailName, cmd, action]),
(0, value))
self.assertEqual(
self.transm.proceed(
["set", self.jailName, "setcinfo", action, "KEY", "VALUE"]),
(0, "VALUE"))
self.assertEqual(
self.transm.proceed(
["get", self.jailName, "cinfo", action, "KEY"]),
(0, "VALUE"))
self.assertEqual(
self.transm.proceed(
["get", self.jailName, "cinfo", action, "InvalidKey"])[0],
1)
self.assertEqual(
self.transm.proceed(
["set", self.jailName, "delcinfo", action, "KEY"]),
(0, None))
self.assertEqual(
self.transm.proceed(["set", self.jailName, "delaction", action]),
(0, None))
self.assertEqual(
self.transm.proceed(
["set", self.jailName, "delaction", "Doesn't exist"])[0],1)
def testNOK(self):
self.assertEqual(self.transm.proceed(["INVALID", "COMMAND"])[0],1)
def testSetNOK(self):
self.assertEqual(
self.transm.proceed(["set", "INVALID", "COMMAND"])[0],1)
def testGetNOK(self):
self.assertEqual(
self.transm.proceed(["get", "INVALID", "COMMAND"])[0],1)
def testStatusNOK(self):
self.assertEqual(
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)