Merge branch '0.9' into distro-paths-gh-315

pull/625/head
Daniel Black 2014-03-02 15:17:21 +11:00
commit 2d45becb0e
49 changed files with 838 additions and 448 deletions

View File

@ -12,7 +12,7 @@ before_install:
install:
- pip install pyinotify
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then sudo apt-get install -qq python-gamin; cp /usr/share/pyshared/gamin.py /usr/lib/pyshared/python2.7/_gamin.so $VIRTUAL_ENV/lib/python2.7/site-packages/; fi
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then pip install -q coveralls; fi
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then cd ..; pip install -q coveralls; cd -; fi
script:
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then coverage run --rcfile=.travis_coveragerc setup.py test; else python setup.py test; fi
after_success:

View File

@ -90,6 +90,7 @@ ver. 0.8.13 (2014/XX/XXX) - maintenance-only-from-now-on
- New Features:
- filter nagios - detects unauthorized access to the nrpe daemon (Ivo Truxa)
- filter sendmail-{auth,reject} (jserrachinha and cepheid666 and fab23).
- Enhancements:
- filter pureftpd - added all translations of "Authentication failed for

View File

@ -202,6 +202,11 @@ config/filter.d/postfix.conf
config/filter.d/proftpd.conf
config/filter.d/pure-ftpd.conf
config/filter.d/qmail.conf
config/filter.d/pam-generic.conf
config/filter.d/php-url-fopen.conf
config/filter.d/postfix-sasl.conf
config/filter.d/sendmail-auth.conf
config/filter.d/sendmail-reject.conf
config/filter.d/sieve.conf
config/filter.d/solid-pop3d.conf
config/filter.d/squid.conf

3
THANKS
View File

@ -21,6 +21,7 @@ Bas van den Dikkenberg
Beau Raines
Bill Heaton
Carlos Alberto Lopez Perez
cepheid666
Christian Rauch
Christophe Carles
Christoph Haas
@ -51,6 +52,7 @@ Jonathan Lanning
Jonathan Underwood
Joël Bertrand
JP Espinosa
jserrachinha
Justin Shore
Kévin Drapel
kjohnsonecl
@ -99,5 +101,6 @@ Yaroslav Halchenko
Winston Smith
ykimon
Yehuda Katz
Zbigniew Jędrzejewski-Szmek
zugeschmiert
Zurd

View File

@ -325,7 +325,7 @@ class Fail2banClient:
if verbose <= 0:
logSys.setLevel(logging.ERROR)
elif verbose == 1:
logSys.setLevel(logging.WARN)
logSys.setLevel(logging.WARNING)
elif verbose == 2:
logSys.setLevel(logging.INFO)
else:

View File

@ -131,7 +131,7 @@ Report bugs to https://github.com/fail2ban/fail2ban/issues
"\"systemd-journal\" only"),
Option('-l', "--log-level", type="choice",
dest="log_level",
choices=('heavydebug', 'debug', 'info', 'warning', 'error', 'fatal'),
choices=('heavydebug', 'debug', 'info', 'notice', 'warning', 'error', 'critical'),
default=None,
help="Log level for the Fail2Ban logger to use"),
Option("-v", "--verbose", action='store_true',
@ -476,9 +476,9 @@ if __name__ == "__main__":
logSys.setLevel(getattr(logging, opts.log_level.upper()))
else: # pragma: no cover
# 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 critical' which would be silent
# unless error occurs
logSys.setLevel(getattr(logging, 'FATAL'))
logSys.setLevel(getattr(logging, 'CRITICAL'))
# Add the default logging handler
stdout = logging.StreamHandler(sys.stdout)

View File

@ -48,7 +48,7 @@ def get_opt_parser():
p.add_options([
Option('-l', "--log-level", type="choice",
dest="log_level",
choices=('heavydebug', 'debug', 'info', 'warning', 'error', 'fatal'),
choices=('heavydebug', 'debug', 'info', 'notice', 'warning', 'error', 'critical'),
default=None,
help="Log level for the logger to use during running tests"),
Option('-n', "--no-network", action="store_true",
@ -75,9 +75,10 @@ logSys = logging.getLogger("fail2ban")
verbosity = {'heavydebug': 4,
'debug': 3,
'info': 2,
'notice': 2,
'warning': 1,
'error': 1,
'fatal': 0,
'critical': 0,
None: 1}[opts.log_level]
if opts.log_level is not None: # pragma: no cover
@ -85,9 +86,9 @@ if opts.log_level is not None: # pragma: no cover
logSys.setLevel(getattr(logging, opts.log_level.upper()))
else: # pragma: no cover
# 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 critical' which would be silent
# unless error occurs
logSys.setLevel(getattr(logging, 'FATAL'))
logSys.setLevel(getattr(logging, 'CRITICAL'))
# Add the default logging handler
stdout = logging.StreamHandler(sys.stdout)
@ -111,7 +112,7 @@ logSys.addHandler(stdout)
#
# Let know the version
#
if not opts.log_level or opts.log_level != 'fatal': # pragma: no cover
if not opts.log_level or opts.log_level != 'critical': # pragma: no cover
print("Fail2ban %s test suite. Python %s. Please wait..." \
% (version, str(sys.version).replace('\n', '')))

View File

@ -1,17 +1,16 @@
# Fail2Ban configuration file
#
# Author: Russell Odom <russ@gloomytrousers.co.uk>
# Author: Russell Odom <russ@gloomytrousers.co.uk>, Daniel Black
# Sends a complaint e-mail to addresses listed in the whois record for an
# offending IP address.
# This uses the https://abusix.com/contactdb.html to lookup abuse contacts.
#
# DEPENDANCIES:
# This requires the dig command from bind-utils
#
# You should provide the <logpath> in the jail config - lines from the log
# matching the given IP address will be provided in the complaint as evidence.
#
# Note that we will try to use e-mail addresses that are most likely to be abuse
# addresses (based on various keywords). If they aren't found we fall back on
# any other addresses found in the whois record, with a few exceptions.
# If no addresses are found, no e-mail is sent.
#
# WARNING
# -------
#
@ -55,10 +54,10 @@ actioncheck =
# Tags: See jail.conf(5) man page
# 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 = oifs=${IFS}; IFS=.;SEP_IP=( <ip> ); set -- ${SEP_IP}; ADDRESSES=$(dig +short -t txt -q $4.$3.$2.$1.abuse-contacts.abusix.org); IFS=${oifs}
IP=<ip>
if [ ! -z "$ADDRESSES" ]; then
(printf %%b "<message>\n"; date '+Note: Local timezone is %%z (%%Z)'; grep -E '(^|[^0-9])<ip>([^0-9]|$)' <logpath>) | <mailcmd> "Abuse from <ip>" <mailargs> $ADDRESSES
(printf %%b "<message>\n"; date '+Note: Local timezone is %%z (%%Z)'; grep -E '(^|[^0-9])<ip>([^0-9]|$)' <logpath>) | <mailcmd> "Abuse from <ip>" <mailargs> ${ADDRESSES//,/\" \"}
fi
# Option: actionunban
@ -70,7 +69,7 @@ actionban = ADDRESSES=`whois <ip> | perl -e 'while (<STDIN>) { next if /^changed
actionunban =
[Init]
message = Dear Sir/Madam,\n\nWe have detected abuse from the IP address $IP, which according to a whois lookup is on your network. We would appreciate if you would investigate and take action as appropriate.\n\nLog lines are given below, but please ask if you require any further information.\n\n(If you are not the correct person to contact about this please accept our apologies - your e-mail address was extracted from the whois record by an automated process. This mail was generated by Fail2Ban.)\n
message = Dear Sir/Madam,\n\nWe have detected abuse from the IP address $IP, which according to a abusix.com is on your network. We would appreciate if you would investigate and take action as appropriate.\n\nLog lines are given below, but please ask if you require any further information.\n\n(If you are not the correct person to contact about this please accept our apologies - your e-mail address was extracted from the whois record by an automated process.)\n\n This mail was generated by Fail2Ban.\nThe recipient address of this report was provided by the Abuse Contact DB by abusix.com. abusix.com does not maintain the content of the database. All information which we pass out, derives from the RIR databases and is processed for ease of use. If you want to change or report non working abuse contacts please contact the appropriate RIR. If you have any further question, contact abusix.com directly via email (info@abusix.com). Information about the Abuse Contact Database can be found here: https://abusix.com/global-reporting/abuse-contact-db\nabusix.com is neither responsible nor liable for the content or accuracy of this message.\n
# Path to the log files which contain relevant lines for the abuser IP
#

View File

@ -33,13 +33,14 @@ before = iptables-blocktype.conf
# own rules. The 3600 second timeout is independent and acts as a
# safeguard in case the fail2ban process dies unexpectedly. The
# shorter of the two timeouts actually matters.
actionstart = iptables -I INPUT -m recent --update --seconds 3600 --name f2b-<name> -j <blocktype>
actionstart = if [ `id -u` -eq 0 ];then iptables -I INPUT -m recent --update --seconds 3600 --name f2b-<name> -j <blocktype>;fi
# Option: actionstop
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD
#
actionstop = echo / > /proc/net/xt_recent/f2b-<name>
if [ `id -u` -eq 0 ];then iptables -D INPUT -m recent --update --seconds 3600 --name f2b-<name> -j <blocktype>;fi
# Option: actioncheck
# Notes.: command executed once before each actionban command

View File

@ -121,7 +121,7 @@ class SMTPAction(ActionBase):
self.matches = matches
self.message_values = CallingMap(
jailname = self._jail.getName(), # Doesn't change
jailname = self._jail.name,
hostname = socket.gethostname,
bantime = self._jail.actions.getBanTime,
)

View File

@ -38,18 +38,17 @@ actionstop =
actioncheck =
actionban = oifs=${IFS}; IFS=.;SEP_IP=( <ip> ); set -- ${SEP_IP} ;ADDRESSES=$(dig +short -t txt -q $4.$3.$2.$1.abuse-contacts.abusix.org); IFS=${oifs}
actionban = oifs=${IFS}; IFS=.;SEP_IP=( <ip> ); set -- ${SEP_IP}; ADDRESSES=$(dig +short -t txt -q $4.$3.$2.$1.abuse-contacts.abusix.org); IFS=${oifs}
IP=<ip>
FROM=<sender>
SERVICE=<service>
FAILURES=<failures>
MATCHES='<matches>'
REPORTID=<time>@`uname -n`
TLP=<tlp>
PORT=<port>
DATE=`LC_TIME=C date -u --date=@<time> +"%%a, %%d %%h %%Y %%T +0000"`
if [ ! -z "$ADDRESSES" ]; then
(printf -- %%b "<header>\n<message>\n<report>\n${MATCHES}\n";
(printf -- %%b "<header>\n<message>\n<report>\n";
date '+Note: Local timezone is %%z (%%Z)';
printf -- %%b "<ipmatches>\n\n<footer>") | <mailcmd> <mailargs> ${ADDRESSES//,/\" \"}
fi

View File

@ -6,20 +6,22 @@
# file, but provide customizations in fail2ban.local file, e.g.:
#
# [Definition]
# loglevel = 4
# loglevel = DEBUG
#
[Definition]
# Option: loglevel
# Notes.: Set the log level output.
# 1 = ERROR
# 2 = WARN
# 3 = INFO
# 4 = DEBUG
# Values: [ NUM ] Default: 1
# CRITICAL
# ERROR
# WARNING
# NOTICE
# INFO
# DEBUG
# Values: [ LEVEL ] Default: ERROR
#
loglevel = 3
loglevel = INFO
# Option: logtarget
# Notes.: Set the log target. This could be a file, SYSLOG, STDERR or STDOUT.

View File

@ -0,0 +1,18 @@
# Fail2Ban filter for sendmail authentication failures
#
[INCLUDES]
before = common.conf
[Definition]
_daemon = (?:sm-(mta|acceptingconnections))
failregex = ^%(__prefix_line)s\w{14}: (\S+ )?\[<HOST>\]( \(may be forged\))?: possible SMTP attack: command=AUTH, count=\d+$
ignoreregex =
# DEV Notes:
#
# Author: Daniel Black

View File

@ -0,0 +1,34 @@
# Fail2Ban filter for sendmail spam/relay type failures
#
# Some of the below failregex will only work properly, when the following
# options are set in the .mc file (see your Sendmail documentation on how
# to modify it and generate the corresponding .cf file):
#
# FEATURE(`delay_checks')
# FEATURE(`greet_pause', `500')
# FEATURE(`ratecontrol', `nodelay', `terminate')
# FEATURE(`conncontrol', `nodelay', `terminate')
#
# ratecontrol and conncontrol also need corresponding options ClientRate:
# and ClientConn: in the access file, see documentation for ratecontrol and
# conncontrol in the sendmail/cf/README file.
[INCLUDES]
before = common.conf
[Definition]
_daemon = (?:sm-(mta|acceptingconnections))
failregex = ^%(__prefix_line)s\w{14}: ruleset=check_rcpt, arg1=(?P<email><\S+@\S+>), relay=(\S+ )?\[<HOST>\]( \(may be forged\))?, reject=(550 5\.7\.1 (?P=email)\.\.\. Relaying denied\. (IP name possibly forged \[(\d+\.){3}\d+\]|Proper authentication required\.|IP name lookup failed \[(\d+\.){3}\d+\])|553 5\.1\.8 (?P=email)\.\.\. Domain of sender address \S+ does not exist|550 5\.[71]\.1 (?P=email)\.\.\. (Rejected: .*|User unknown))$
^%(__prefix_line)sruleset=check_relay, arg1=(?P<dom>\S+), arg2=<HOST>, relay=((?P=dom) )?\[(\d+\.){3}\d+\]( \(may be forged\))?, reject=421 4\.3\.2 (Connection rate limit exceeded\.|Too many open connections\.)$
^%(__prefix_line)s\w{14}: rejecting commands from (\S+ )?\[<HOST>\] due to pre-greeting traffic after \d+ seconds$
^%(__prefix_line)s\w{14}: (\S+ )?\[<HOST>\]: ((?i)expn|vrfy) \S+ \[rejected\]$
ignoreregex =
# DEV Notes:
#
# Author: Daniel Black and Fabian Wenk

View File

@ -561,6 +561,18 @@ port = smtp,465,submission
logpath = %(postfix_log)s
[sendmail-auth]
port = submission,465,smtp
logpath = /var/log/mail.log
[sendmail-reject]
port = smtp
logpath = /var/log/mail.log
[qmail-rbl]
filter = qmail

View File

@ -74,12 +74,6 @@ further configuration. To run not as root, further setup is necessary:
with <name> suitably replaced.
- suppress actionstart for iptables-xt_recent-echo action by creating an override file
iptables-xt_recent-echo.local to accompany iptables-xt_recent-echo.conf with
[Definition]
actionstart =
- Permissions:
make sure that configuration files under /etc/fail2ban are readable by

View File

@ -24,7 +24,47 @@ __author__ = "Cyril Jaquier"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import logging
import logging.handlers
# Custom debug level
logging.HEAVYDEBUG = 5
"""
Below derived from:
https://mail.python.org/pipermail/tutor/2007-August/056243.html
"""
logging.NOTICE = logging.INFO + 5
logging.addLevelName(logging.NOTICE, 'NOTICE')
# define a new logger function for notice
# this is exactly like existing info, critical, debug...etc
def _Logger_notice(self, msg, *args, **kwargs):
"""
Log 'msg % args' with severity 'NOTICE'.
To pass exception information, use the keyword argument exc_info with
a true value, e.g.
logger.notice("Houston, we have a %s", "major disaster", exc_info=1)
"""
if self.isEnabledFor(logging.NOTICE):
self._log(logging.NOTICE, msg, args, **kwargs)
logging.Logger.notice = _Logger_notice
# define a new root level notice function
# this is exactly like existing info, critical, debug...etc
def _root_notice(msg, *args, **kwargs):
"""
Log a message with severity 'NOTICE' on the root logger.
"""
if len(logging.root.handlers) == 0:
logging.basicConfig()
logging.root.notice(msg, *args, **kwargs)
# make the notice root level function known
logging.notice = _root_notice
# add NOTICE to the priority map of all the levels
logging.handlers.SysLogHandler.priority_map['NOTICE'] = 'notice'

View File

@ -67,28 +67,23 @@ class Beautifier:
msg = "logs: " + response
elif inC[0:1] == ['status']:
if len(inC) > 1:
# Create IP list
ipList = ""
for ip in response[1][1][2][1]:
ipList += ip + " "
# Creates file list.
fileList = ""
for f in response[0][1][2][1]:
fileList += f + " "
# Display information
msg = "Status for the jail: " + inC[1] + "\n"
msg = msg + "|- " + response[0][0] + "\n"
msg = msg + "| |- " + response[0][1][2][0] + ":\t" + fileList + "\n"
msg = msg + "| |- " + response[0][1][0][0] + ":\t" + `response[0][1][0][1]` + "\n"
msg = msg + "| `- " + response[0][1][1][0] + ":\t" + `response[0][1][1][1]` + "\n"
msg = msg + "`- " + response[1][0] + "\n"
msg = msg + " |- " + response[1][1][0][0] + ":\t" + `response[1][1][0][1]` + "\n"
msg = msg + " | `- " + response[1][1][2][0] + ":\t" + ipList + "\n"
msg = msg + " `- " + response[1][1][1][0] + ":\t" + `response[1][1][1][1]`
msg = ["Status for the jail: %s" % inC[1]]
for n, res1 in enumerate(response):
prefix1 = "`-" if n == len(response) - 1 else "|-"
msg.append("%s %s" % (prefix1, res1[0]))
prefix1 = " " if n == len(response) - 1 else "| "
for m, res2 in enumerate(res1[1]):
prefix2 = prefix1 + ("`-" if m == len(res1[1]) - 1 else "|-")
val = " ".join(res2[1]) if isinstance(res2[1], list) else res2[1]
msg.append("%s %s:\t%s" % (prefix2, res2[0], val))
else:
msg = "Status\n"
msg = msg + "|- " + response[0][0] + ":\t" + `response[0][1]` + "\n"
msg = msg + "`- " + response[1][0] + ":\t\t" + response[1][1]
msg = ["Status"]
for n, res1 in enumerate(response):
prefix1 = "`-" if n == len(response) - 1 else "|-"
val = " ".join(res1[1]) if isinstance(res1[1], list) else res1[1]
msg.append("%s %s:\t%s" % (prefix1, res1[0], val))
msg = "\n".join(msg)
elif inC[1] == "logtarget":
msg = "Current logging target is:\n"
msg = msg + "`- " + response

View File

@ -54,7 +54,7 @@ class ConfigReader(SafeConfigParserWithIncludes):
raise ValueError("Base configuration directory %s does not exist "
% self._basedir)
basename = os.path.join(self._basedir, filename)
logSys.debug("Reading configs for %s under %s " % (basename, self._basedir))
logSys.info("Reading configs for %s under %s " % (basename, self._basedir))
config_files = [ basename + ".conf" ]
# possible further customizations under a .conf.d directory

View File

@ -45,7 +45,7 @@ class Fail2banReader(ConfigReader):
return ConfigReader.getOptions(self, "Definition", opts)
def getOptions(self):
opts = [["int", "loglevel", 1],
opts = [["int", "loglevel", "INFO" ],
["string", "logtarget", "STDERR"],
["string", "dbfile", "/var/lib/fail2ban/fail2ban.sqlite3"],
["int", "dbpurgeage", 86400]]

View File

@ -39,7 +39,7 @@ protocol = [
["ping", "tests if the server is alive"],
["help", "return this output"],
['', "LOGGING", ""],
["set loglevel <LEVEL>", "sets logging level to <LEVEL>. 0 is minimal, 4 is debug"],
["set loglevel <LEVEL>", "sets logging level to <LEVEL>. Levels: CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG"],
["get loglevel", "gets the logging level"],
["set logtarget <TARGET>", "sets logging target to <TARGET>. Can be STDOUT, STDERR, SYSLOG or a file"],
["get logtarget", "gets logging target"],

View File

@ -96,11 +96,28 @@ class ActionBase(object):
place to create a Python based action for Fail2Ban. This class can
be inherited from to ease implementation.
Required methods:
- __init__(jail, name)
- start()
- stop()
- ban(aInfo)
- unban(aInfo)
- __init__(jail, name)
- start()
- stop()
- ban(aInfo)
- unban(aInfo)
Called when action is created, but before the jail/actions is
started. This should carry out necessary methods to initialise
the action but not "start" the action.
Parameters
----------
jail : Jail
The jail in which the action belongs to.
name : str
Name assigned to the action.
Notes
-----
Any additional arguments specified in `jail.conf` or passed
via `fail2ban-client` will be passed as keyword arguments.
"""
__metaclass__ = ABCMeta
@ -118,24 +135,6 @@ class ActionBase(object):
return True
def __init__(self, jail, name):
"""Initialise action.
Called when action is created, but before the jail/actions is
started. This should carry out necessary methods to initialise
the action but not "start" the action.
Parameters
----------
jail : Jail
The jail in which the action belongs to.
name : str
Name assigned to the action.
Notes
-----
Any additional arguments specified in `jail.conf` or passed
via `fail2ban-client` will be passed as keyword arguments.
"""
self._jail = jail
self._name = name
self._logSys = logging.getLogger(
@ -177,22 +176,27 @@ class CommandAction(ActionBase):
"""A action which executes OS shell commands.
This is the default type of action which Fail2Ban uses.
Default sets all commands for actions as empty string, such
no command is executed.
Parameters
----------
jail : Jail
The jail in which the action belongs to.
name : str
Name assigned to the action.
Attributes
----------
actionban
actionstart
actionstop
actionunban
timeout
"""
def __init__(self, jail, name):
"""Initialise action.
Default sets all commands for actions as empty string, such
no command is executed.
Parameters
----------
jail : Jail
The jail in which the action belongs to.
name : str
Name assigned to the action.
"""
super(CommandAction, self).__init__(jail, name)
self.timeout = 60
## Command executed in order to initialize the system.
@ -482,7 +486,7 @@ class CommandAction(ActionBase):
self.stop()
self.start()
if not self.executeCmd(checkCmd, self.timeout):
self._logSys.fatal("Unable to restore environment")
self._logSys.critical("Unable to restore environment")
return False
# Replace tags

View File

@ -49,16 +49,27 @@ class Actions(JailThread, Mapping):
Mapping type, and the `add` method must be used to add new actions.
This class also starts and stops the actions, and fetches bans from
the jail executing these bans via the actions.
Parameters
----------
jail: Jail
The jail of which the actions belongs to.
Attributes
----------
daemon
ident
name
status
active : bool
Control the state of the thread.
idle : bool
Control the idle state of the thread.
sleeptime : int
The time the thread sleeps for in the loop.
"""
def __init__(self, jail):
"""Initialise an empty Actions instance.
Parameters
----------
jail: Jail
The jail of which the actions belongs to.
"""
JailThread.__init__(self)
## The jail which contains this action.
self._jail = jail
@ -192,30 +203,29 @@ class Actions(JailThread, Mapping):
bool
True when the thread exits nicely.
"""
self.setActive(True)
for name, action in self._actions.iteritems():
try:
action.start()
except Exception as e:
logSys.error("Failed to start jail '%s' action '%s': %s",
self._jail.getName(), name, e)
while self._isActive():
if not self.getIdle():
#logSys.debug(self._jail.getName() + ": action")
self._jail.name, name, e)
while self.active:
if not self.idle:
#logSys.debug(self._jail.name + ": action")
ret = self.__checkBan()
if not ret:
self.__checkUnBan()
time.sleep(self.getSleepTime())
time.sleep(self.sleeptime)
else:
time.sleep(self.getSleepTime())
time.sleep(self.sleeptime)
self.__flushBan()
for name, action in self._actions.iteritems():
try:
action.stop()
except Exception as e:
logSys.error("Failed to stop jail '%s' action '%s': %s",
self._jail.getName(), name, e)
logSys.debug(self._jail.getName() + ": action terminated")
self._jail.name, name, e)
logSys.debug(self._jail.name + ": action terminated")
return True
def __checkBan(self):
@ -237,31 +247,31 @@ class Actions(JailThread, Mapping):
aInfo["failures"] = bTicket.getAttempt()
aInfo["time"] = bTicket.getTime()
aInfo["matches"] = "\n".join(bTicket.getMatches())
if self._jail.getDatabase() is not None:
if self._jail.database is not None:
aInfo["ipmatches"] = lambda: "\n".join(
self._jail.getDatabase().getBansMerged(
self._jail.database.getBansMerged(
ip=bTicket.getIP()).getMatches())
aInfo["ipjailmatches"] = lambda: "\n".join(
self._jail.getDatabase().getBansMerged(
self._jail.database.getBansMerged(
ip=bTicket.getIP(), jail=self._jail).getMatches())
aInfo["ipfailures"] = lambda: "\n".join(
self._jail.getDatabase().getBansMerged(
self._jail.database.getBansMerged(
ip=bTicket.getIP()).getAttempt())
aInfo["ipjailfailures"] = lambda: "\n".join(
self._jail.getDatabase().getBansMerged(
self._jail.database.getBansMerged(
ip=bTicket.getIP(), jail=self._jail).getAttempt())
if self.__banManager.addBanTicket(bTicket):
logSys.warning("[%s] Ban %s" % (self._jail.getName(), aInfo["ip"]))
logSys.notice("[%s] Ban %s" % (self._jail.name, aInfo["ip"]))
for name, action in self._actions.iteritems():
try:
action.ban(aInfo)
except Exception as e:
logSys.error(
"Failed to execute ban jail '%s' action '%s': %s",
self._jail.getName(), name, e)
self._jail.name, name, e)
return True
else:
logSys.info("[%s] %s already banned" % (self._jail.getName(),
logSys.notice("[%s] %s already banned" % (self._jail.name,
aInfo["ip"]))
return False
@ -298,28 +308,20 @@ class Actions(JailThread, Mapping):
aInfo["failures"] = ticket.getAttempt()
aInfo["time"] = ticket.getTime()
aInfo["matches"] = "".join(ticket.getMatches())
logSys.warning("[%s] Unban %s" % (self._jail.getName(), aInfo["ip"]))
logSys.notice("[%s] Unban %s" % (self._jail.name, aInfo["ip"]))
for name, action in self._actions.iteritems():
try:
action.unban(aInfo)
except Exception as e:
logSys.error(
"Failed to execute unban jail '%s' action '%s': %s",
self._jail.getName(), name, e)
self._jail.name, name, e)
@property
def status(self):
"""Get the status of the filter.
Get some informations about the filter state such as the total
number of failures.
Returns
-------
list
List of tuple pairs, each containing a description and value
for general status information.
"""Status of active bans, and total ban counts.
"""
ret = [("Currently banned", self.__banManager.size()),
("Total banned", self.__banManager.getBanTotal()),
("IP list", self.__banManager.getBanList())]
("Banned IP list", self.__banManager.getBanList())]
return ret

View File

@ -58,6 +58,35 @@ def commitandrollback(f):
return wrapper
class Fail2BanDb(object):
"""Fail2Ban database for storing persistent data.
This allows after Fail2Ban is restarted to reinstated bans and
to continue monitoring logs from the same point.
This will either create a new Fail2Ban database, connect to an
existing, and if applicable upgrade the schema in the process.
Parameters
----------
filename : str
File name for SQLite3 database, which will be created if
doesn't already exist.
purgeAge : int
Purge age in seconds, used to remove old bans from
database during purge.
Raises
------
sqlite3.OperationalError
Error connecting/creating a SQLite3 database.
RuntimeError
If exisiting database fails to update to new schema.
Attributes
----------
filename
purgeage
"""
__version__ = 2
# Note all _TABLE_* strings must end in ';' for py26 compatibility
_TABLE_fail2banDb = "CREATE TABLE fail2banDb(version INTEGER);"
@ -130,21 +159,30 @@ class Fail2BanDb(object):
logSys.error( "Database update failed to acheive version '%i'"
": updated from '%i' to '%i'",
Fail2BanDb.__version__, version, newversion)
raise Exception('Failed to fully update')
raise RuntimeError('Failed to fully update')
finally:
cur.close()
def getFilename(self):
@property
def filename(self):
"""File name of SQLite3 database file.
"""
return self._dbFilename
def getPurgeAge(self):
@property
def purgeage(self):
"""Purge age in seconds.
"""
return self._purgeAge
def setPurgeAge(self, value):
@purgeage.setter
def purgeage(self, value):
self._purgeAge = int(value)
@commitandrollback
def createDb(self, cur):
"""Creates a new database, called during initialisation.
"""
# Version info
cur.executescript(Fail2BanDb._TABLE_fail2banDb)
cur.execute("INSERT INTO fail2banDb(version) VALUES(?)",
@ -161,8 +199,13 @@ class Fail2BanDb(object):
@commitandrollback
def updateDb(self, cur, version):
self.dbBackupFilename = self._dbFilename + '.' + time.strftime('%Y%m%d-%H%M%S', MyTime.gmtime())
shutil.copyfile(self._dbFilename, self.dbBackupFilename)
"""Update an existing database, called during initialisation.
A timestamped backup is also created prior to attempting the update.
"""
self._dbBackupFilename = self.filename + '.' + time.strftime('%Y%m%d-%H%M%S', MyTime.gmtime())
shutil.copyfile(self.filename, self._dbBackupFilename)
logSys.info("Database backup created: %s", self._dbBackupFilename)
if version > Fail2BanDb.__version__:
raise NotImplementedError(
"Attempt to travel to future version of database ...how did you get here??")
@ -182,36 +225,73 @@ class Fail2BanDb(object):
@commitandrollback
def addJail(self, cur, jail):
"""Adds a jail to the database.
Parameters
----------
jail : Jail
Jail to be added to the database.
"""
cur.execute(
"INSERT OR REPLACE INTO jails(name, enabled) VALUES(?, 1)",
(jail.getName(),))
def delJail(self, jail):
return self.delJailName(jail.getName())
(jail.name,))
@commitandrollback
def delJailName(self, cur, name):
def delJail(self, cur, jail):
"""Deletes a jail from the database.
Parameters
----------
jail : Jail
Jail to be removed from the database.
"""
# Will be deleted by purge as appropriate
cur.execute(
"UPDATE jails SET enabled=0 WHERE name=?", (name, ))
"UPDATE jails SET enabled=0 WHERE name=?", (jail.name, ))
@commitandrollback
def delAllJails(self, cur):
"""Deletes all jails from the database.
"""
# Will be deleted by purge as appropriate
cur.execute("UPDATE jails SET enabled=0")
@commitandrollback
def getJailNames(self, cur):
"""Get name of jails in database.
Currently only used for testing purposes.
Returns
-------
set
Set of jail names.
"""
cur.execute("SELECT name FROM jails")
return set(row[0] for row in cur.fetchmany())
@commitandrollback
def addLog(self, cur, jail, container):
"""Adds a log to the database.
Parameters
----------
jail : Jail
Jail that log is being monitored by.
container : FileContainer
File container of the log file being added.
Returns
-------
int
If log was already present in database, value of last position
in the log file; else `None`
"""
lastLinePos = None
cur.execute(
"SELECT firstlinemd5, lastfilepos FROM logs "
"WHERE jail=? AND path=?",
(jail.getName(), container.getFileName()))
(jail.name, container.getFileName()))
try:
firstLineMD5, lastLinePos = cur.fetchone()
except TypeError:
@ -220,7 +300,7 @@ class Fail2BanDb(object):
cur.execute(
"INSERT OR REPLACE INTO logs(jail, path, firstlinemd5, lastfilepos) "
"VALUES(?, ?, ?, ?)",
(jail.getName(), container.getFileName(),
(jail.name, container.getFileName(),
container.getHash(), container.getPos()))
if container.getHash() != firstLineMD5:
lastLinePos = None
@ -228,16 +308,39 @@ class Fail2BanDb(object):
@commitandrollback
def getLogPaths(self, cur, jail=None):
"""Gets all the log paths from the database.
Currently only for testing purposes.
Parameters
----------
jail : Jail
If specified, will only reutrn logs belonging to the jail.
Returns
-------
set
Set of log paths.
"""
query = "SELECT path FROM logs"
queryArgs = []
if jail is not None:
query += " WHERE jail=?"
queryArgs.append(jail.getName())
queryArgs.append(jail.name)
cur.execute(query, queryArgs)
return set(row[0] for row in cur.fetchmany())
@commitandrollback
def updateLog(self, cur, *args, **kwargs):
"""Updates hash and last position in log file.
Parameters
----------
jail : Jail
Jail of which the log file belongs to.
container : FileContainer
File container of the log file being updated.
"""
self._updateLog(cur, *args, **kwargs)
def _updateLog(self, cur, jail, container):
@ -245,15 +348,24 @@ class Fail2BanDb(object):
"UPDATE logs SET firstlinemd5=?, lastfilepos=? "
"WHERE jail=? AND path=?",
(container.getHash(), container.getPos(),
jail.getName(), container.getFileName()))
jail.name, container.getFileName()))
@commitandrollback
def addBan(self, cur, jail, ticket):
"""Add a ban to the database.
Parameters
----------
jail : Jail
Jail in which the ban has occured.
ticket : BanTicket
Ticket of the ban to be added.
"""
self._bansMergedCache = {}
#TODO: Implement data parts once arbitrary match keys completed
cur.execute(
"INSERT INTO bans(jail, ip, timeofban, data) VALUES(?, ?, ?, ?)",
(jail.getName(), ticket.getIP(), ticket.getTime(),
(jail.name, ticket.getIP(), ticket.getTime(),
{"matches": ticket.getMatches(),
"failures": ticket.getAttempt()}))
@ -264,7 +376,7 @@ class Fail2BanDb(object):
if jail is not None:
query += " AND jail=?"
queryArgs.append(jail.getName())
queryArgs.append(jail.name)
if bantime is not None:
query += " AND timeofban > ?"
queryArgs.append(MyTime.time() - bantime)
@ -276,6 +388,23 @@ class Fail2BanDb(object):
return cur.execute(query, queryArgs)
def getBans(self, **kwargs):
"""Get bans from the database.
Parameters
----------
jail : Jail
Jail that the ban belongs to. Default `None`; all jails.
bantime : int
Ban time in seconds, such that bans returned would still be
valid now. Default `None`; no limit.
ip : str
IP Address to filter bans by. Default `None`; all IPs.
Returns
-------
list
List of `Ticket`s for bans stored in database.
"""
tickets = []
for ip, timeofban, data in self._getBans(**kwargs):
#TODO: Implement data parts once arbitrary match keys completed
@ -284,7 +413,27 @@ class Fail2BanDb(object):
return tickets
def getBansMerged(self, ip, jail=None, **kwargs):
cacheKey = ip if jail is None else "%s|%s" % (ip, jail.getName())
"""Get bans from the database, merged into single ticket.
This is the same as `getBans`, but bans merged into single
ticket.
Parameters
----------
jail : Jail
Jail that the ban belongs to. Default `None`; all jails.
bantime : int
Ban time in seconds, such that bans returned would still be
valid now. Default `None`; no limit.
ip : str
IP Address to filter bans by. Default `None`; all IPs.
Returns
-------
Ticket
Single ticket representing bans stored in database.
"""
cacheKey = ip if jail is None else "%s|%s" % (ip, jail.name)
if cacheKey in self._bansMergedCache:
return self._bansMergedCache[cacheKey]
matches = []
@ -300,6 +449,8 @@ class Fail2BanDb(object):
@commitandrollback
def purge(self, cur):
"""Purge old bans, jails and log files from database.
"""
self._bansMergedCache = {}
cur.execute(
"DELETE FROM bans WHERE timeofban < ?",

View File

@ -31,11 +31,13 @@ logSys = logging.getLogger(__name__)
class DateDetector(object):
"""Manages one or more date templates to find a date within a log line.
Attributes
----------
templates
"""
def __init__(self):
"""Initialise the date detector.
"""
self.__lock = Lock()
self.__templates = list()
self.__known_names = set()

View File

@ -41,11 +41,14 @@ class DateTemplate(object):
This is an not functional abstract class which other templates should
inherit from.
Attributes
----------
name
regex
"""
def __init__(self):
"""Initialise the date template.
"""
self._name = ""
self._regex = ""
self._cRegex = None
@ -123,11 +126,14 @@ class DateEpoch(DateTemplate):
This includes Unix timestamps which appear at start of a line, optionally
within square braces (nsd), or on SELinux audit log lines.
Attributes
----------
name
regex
"""
def __init__(self):
"""Initialise the date template.
"""
DateTemplate.__init__(self)
self.regex = "(?:^|(?P<square>(?<=^\[))|(?P<selinux>(?<=audit\()))\d{10}(?:\.\d{3,6})?(?(selinux)(?=:\d+\))(?(square)(?=\])))"
@ -152,6 +158,19 @@ class DateEpoch(DateTemplate):
return None
class DatePatternRegex(DateTemplate):
"""Date template, with regex/pattern
Parameters
----------
pattern : str
Sets the date templates pattern.
Attributes
----------
name
regex
pattern
"""
_patternRE = r"%%(%%|[%s])" % "".join(timeRE.keys())
_patternName = {
'a': "DAY", 'A': "DAYNAME", 'b': "MON", 'B': "MONTH", 'd': "Day",
@ -159,17 +178,10 @@ class DatePatternRegex(DateTemplate):
'M': "Minute", 'p': "AMPM", 'S': "Second", 'U': "Yearweek",
'w': "Weekday", 'W': "Yearweek", 'y': 'Year2', 'Y': "Year", '%': "%",
'z': "Zone offset", 'f': "Microseconds", 'Z': "Zone name"}
for key in set(timeRE) - set(_patternName): # may not have them all...
_patternName[key] = "%%%s" % key
for _key in set(timeRE) - set(_patternName): # may not have them all...
_patternName[_key] = "%%%s" % _key
def __init__(self, pattern=None):
"""Initialise date template, with optional regex/pattern
Parameters
----------
pattern : str
Sets the date templates pattern.
"""
super(DatePatternRegex, self).__init__()
self._pattern = None
if pattern is not None:
@ -229,11 +241,14 @@ class DatePatternRegex(DateTemplate):
class DateTai64n(DateTemplate):
"""A date template which matches TAI64N formate timestamps.
Attributes
----------
name
regex
"""
def __init__(self):
"""Initialise the date template.
"""
DateTemplate.__init__(self)
# We already know the format for TAI64N
# yoh: we should not add an additional front anchor

View File

@ -357,6 +357,9 @@ class Filter(JailThread):
# IP address without CIDR mask
if len(s) == 1:
s.insert(1, '32')
elif "." in s[1]: # 255.255.255.0 style mask
s[1] = len(re.search(
"(?<=b)1+", bin(DNSUtils.addr2bin(s[1]))).group())
s[1] = long(s[1])
try:
a = DNSUtils.cidr(s[0], s[1])
@ -416,9 +419,9 @@ class Filter(JailThread):
% (unixTime, MyTime.time(), self.getFindTime()))
break
if self.inIgnoreIPList(ip):
logSys.debug("Ignore %s" % ip)
logSys.info("[%s] Ignore %s" % (self.jail.name, ip))
continue
logSys.debug("Found %s" % ip)
logSys.info("[%s] Found %s" % (self.jail.name, ip))
## print "D: Adding a ticket for %s" % ((ip, unixTime, [line]),)
self.failManager.addFailure(FailTicket(ip, unixTime, lines))
@ -497,7 +500,7 @@ class Filter(JailThread):
else:
continue
if date is None:
logSys.debug(
logSys.warning(
"Found a match for %r but no valid date/time "
"found for %r. Please try setting a custom "
"date pattern (see man page jail.conf(5)). "
@ -527,15 +530,10 @@ class Filter(JailThread):
logSys.error(e)
return failList
##
# Get the status of the filter.
#
# Get some informations about the filter state such as the total
# number of failures.
# @return a list with tuple
@property
def status(self):
"""Status of failures detected by filter.
"""
ret = [("Currently failed", self.failManager.size()),
("Total failed", self.failManager.getFailTotal())]
return ret
@ -559,7 +557,7 @@ class FileFilter(Filter):
logSys.error(path + " already exists")
else:
container = FileContainer(path, self.getLogEncoding(), tail)
db = self.jail.getDatabase()
db = self.jail.database
if db is not None:
lastpos = db.addLog(self.jail, container)
if lastpos and not tail:
@ -583,7 +581,7 @@ class FileFilter(Filter):
for log in self.__logPath:
if log.getFileName() == path:
self.__logPath.remove(log)
db = self.jail.getDatabase()
db = self.jail.database
if db is not None:
db.updateLog(self.jail, log)
logSys.info("Removed logfile = %s" % path)
@ -679,18 +677,21 @@ class FileFilter(Filter):
# might occur leading at least to tests failures.
while has_content:
line = container.readline()
if not line or not self._isActive():
if not line or not self.active:
# The jail reached the bottom or has been stopped
break
self.processLineAndAdd(line)
container.close()
db = self.jail.getDatabase()
db = self.jail.database
if db is not None:
db.updateLog(self.jail, container)
return True
@property
def status(self):
ret = Filter.status(self)
"""Status of Filter plus files being monitored.
"""
ret = super(FileFilter, self).status
path = [m.getFileName() for m in self.getLogPath()]
ret.append(("File list", path))
return ret
@ -776,7 +777,7 @@ class FileContainer:
## sys.stdout.flush()
# Compare hash and inode
if self.__hash != myHash or self.__ino != stats.st_ino:
logSys.debug("Log rotation detected for %s" % self.__filename)
logSys.info("Log rotation detected for %s" % self.__filename)
self.__hash = myHash
self.__ino = stats.st_ino
self.__pos = 0

View File

@ -109,16 +109,15 @@ class FilterGamin(FileFilter):
# @return True when the thread exits nicely
def run(self):
self.setActive(True)
# Gamin needs a loop to collect and dispatch events
while self._isActive():
if not self.getIdle():
while self.active:
if not self.idle:
# We cannot block here because we want to be able to
# exit.
if self.monitor.event_pending():
self.monitor.handle_events()
time.sleep(self.getSleepTime())
logSys.debug(self.jail.getName() + ": filter terminated")
time.sleep(self.sleeptime)
logSys.debug(self.jail.name + ": filter terminated")
return True

View File

@ -82,12 +82,11 @@ class FilterPoll(FileFilter):
# @return True when the thread exits nicely
def run(self):
self.setActive(True)
while self._isActive():
while self.active:
if logSys.getEffectiveLevel() <= 6:
logSys.log(6, "Woke up idle=%s with %d files monitored",
self.getIdle(), len(self.getLogPath()))
if not self.getIdle():
self.idle, len(self.getLogPath()))
if not self.idle:
# Get file modification
for container in self.getLogPath():
filename = container.getFileName()
@ -104,11 +103,11 @@ class FilterPoll(FileFilter):
self.failManager.cleanup(MyTime.time())
self.dateDetector.sortTemplate()
self.__modified = False
time.sleep(self.getSleepTime())
time.sleep(self.sleeptime)
else:
time.sleep(self.getSleepTime())
time.sleep(self.sleeptime)
logSys.debug(
(self.jail is not None and self.jail.getName() or "jailless") +
(self.jail is not None and self.jail.name or "jailless") +
" filter terminated")
return True
@ -143,7 +142,7 @@ class FilterPoll(FileFilter):
if self.__file404Cnt[filename] > 2:
logSys.warning("Too many errors. Setting the jail idle")
if self.jail is not None:
self.jail.setIdle(True)
self.jail.idle = True
else:
logSys.warning("No jail is assigned to %s" % self)
self.__file404Cnt[filename] = 0

View File

@ -168,11 +168,10 @@ class FilterPyinotify(FileFilter):
# loop is necessary
def run(self):
self.setActive(True)
self.__notifier = pyinotify.ThreadedNotifier(self.__monitor,
ProcessPyinotify(self))
self.__notifier.start()
logSys.debug("pyinotifier started for %s.", self.jail.getName())
logSys.debug("pyinotifier started for %s.", self.jail.name)
# TODO: verify that there is nothing really to be done for
# idle jails
return True

View File

@ -211,7 +211,6 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
# handover to FailManager
def run(self):
self.setActive(True)
# Seek to now - findtime in journal
start_time = datetime.datetime.now() - \
@ -224,9 +223,9 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
except OSError:
pass # Reading failure, so safe to ignore
while self._isActive():
if not self.getIdle():
while self._isActive():
while self.active:
if not self.idle:
while self.active:
try:
logentry = self.__journal.get_next()
except OSError:
@ -247,20 +246,14 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
except FailManagerEmpty:
self.failManager.cleanup(MyTime.time())
self.__modified = False
self.__journal.wait(self.getSleepTime())
logSys.debug((self.jail is not None and self.jail.getName()
self.__journal.wait(self.sleeptime)
logSys.debug((self.jail is not None and self.jail.name
or "jailless") +" filter terminated")
return True
##
# Get the status of the filter.
#
# Get some informations about the filter state such as the total
# number of failures.
# @return a list with tuple
@property
def status(self):
ret = JournalFilter.status(self)
ret = super(FilterSystemd, self).status
ret.append(("Journal matches",
[" + ".join(" ".join(match) for match in self.__matches)]))
return ret

View File

@ -31,6 +31,31 @@ from .actions import Actions
logSys = logging.getLogger(__name__)
class Jail:
"""Fail2Ban jail, which manages a filter and associated actions.
The class handles the initialisation of a filter, and actions. It's
role is then to act as an interface between the filter and actions,
passing bans detected by the filter, for the actions to then act upon.
Parameters
----------
name : str
Name assigned to the jail.
backend : str
Backend to be used for filter. "auto" will attempt to pick
the most preferred backend method. Default: "auto"
db : Fail2BanDb
Fail2Ban persistent database instance. Default: `None`
Attributes
----------
name
database
filter
actions
idle
status
"""
#Known backends. Each backend should have corresponding __initBackend method
# yoh: stored in a list instead of a tuple since only
@ -39,14 +64,19 @@ class Jail:
def __init__(self, name, backend = "auto", db=None):
self.__db = db
self.setName(name)
# 26 based on iptable chain name limit of 30 less len('f2b-')
if len(name) >= 26:
logSys.warning("Jail name %r might be too long and some commands "
"might not function correctly. Please shorten"
% name)
self.__name = name
self.__queue = Queue.Queue()
self.__filter = None
logSys.info("Creating new jail '%s'" % self.__name)
logSys.info("Creating new jail '%s'" % self.name)
self._setBackend(backend)
def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, self.__name)
return "%s(%r)" % (self.__class__.__name__, self.name)
def _setBackend(self, backend):
backend = backend.lower() # to assure consistent matching
@ -78,51 +108,49 @@ class Jail:
"Backend %r failed to initialize due to %s" % (b, e))
# log error since runtime error message isn't printed, INVALID COMMAND
logSys.error(
"Failed to initialize any backend for Jail %r" % self.__name)
"Failed to initialize any backend for Jail %r" % self.name)
raise RuntimeError(
"Failed to initialize any backend for Jail %r" % self.__name)
"Failed to initialize any backend for Jail %r" % self.name)
def _initPolling(self):
logSys.info("Jail '%s' uses poller" % self.__name)
logSys.info("Jail '%s' uses poller" % self.name)
from filterpoll import FilterPoll
self.__filter = FilterPoll(self)
def _initGamin(self):
# Try to import gamin
import gamin
logSys.info("Jail '%s' uses Gamin" % self.__name)
logSys.info("Jail '%s' uses Gamin" % self.name)
from filtergamin import FilterGamin
self.__filter = FilterGamin(self)
def _initPyinotify(self):
# Try to import pyinotify
import pyinotify
logSys.info("Jail '%s' uses pyinotify" % self.__name)
logSys.info("Jail '%s' uses pyinotify" % self.name)
from filterpyinotify import FilterPyinotify
self.__filter = FilterPyinotify(self)
def _initSystemd(self): # pragma: systemd no cover
# Try to import systemd
import systemd
logSys.info("Jail '%s' uses systemd" % self.__name)
logSys.info("Jail '%s' uses systemd" % self.name)
from filtersystemd import FilterSystemd
self.__filter = FilterSystemd(self)
def setName(self, name):
# 26 based on iptable chain name limit of 30 less len('f2b-')
if len(name) >= 26:
logSys.warning("Jail name %r might be too long and some commands "
"might not function correctly. Please shorten"
% name)
self.__name = name
def getName(self):
@property
def name(self):
"""Name of jail.
"""
return self.__name
def getDatabase(self):
@property
def database(self):
"""The database used to store persistent data for the jail.
"""
return self.__db
@property
def filter(self):
"""The filter which the jail is using to monitor log files.
@ -134,50 +162,71 @@ class Jail:
"""Actions object used to manage actions for jail.
"""
return self.__actions
@property
def idle(self):
"""A boolean indicating whether jail is idle.
"""
return self.filter.idle or self.actions.idle
@idle.setter
def idle(self, value):
self.filter.idle = value
self.actions.idle = value
@property
def status(self):
"""The status of the jail.
"""
return [
("Filter", self.filter.status),
("Actions", self.actions.status),
]
def putFailTicket(self, ticket):
"""Add a fail ticket to the jail.
Used by filter to add a failure for banning.
"""
self.__queue.put(ticket)
if self.__db is not None:
self.__db.addBan(self, ticket)
if self.database is not None:
self.database.addBan(self, ticket)
def getFailTicket(self):
"""Get a fail ticket from the jail.
Used by actions to get a failure for banning.
"""
try:
return self.__queue.get(False)
except Queue.Empty:
return False
def start(self):
self.__filter.start()
self.__actions.start()
"""Start the jail, by starting filter and actions threads.
Once stated, also queries the persistent database to reinstate
any valid bans.
"""
self.filter.start()
self.actions.start()
# Restore any previous valid bans from the database
if self.__db is not None:
for ticket in self.__db.getBans(
jail=self, bantime=self.__actions.getBanTime()):
if self.database is not None:
for ticket in self.database.getBans(
jail=self, bantime=self.actions.getBanTime()):
self.__queue.put(ticket)
logSys.info("Jail '%s' started" % self.__name)
logSys.info("Jail '%s' started" % self.name)
def stop(self):
self.__filter.stop()
self.__actions.stop()
self.__filter.join()
self.__actions.join()
logSys.info("Jail '%s' stopped" % self.__name)
def isAlive(self):
isAlive0 = self.__filter.isAlive()
isAlive1 = self.__actions.isAlive()
return isAlive0 or isAlive1
def setIdle(self, value):
self.__filter.setIdle(value)
self.__actions.setIdle(value)
def getIdle(self):
return self.__filter.getIdle() or self.__actions.getIdle()
def getStatus(self):
fStatus = self.__filter.status()
aStatus = self.__actions.status()
ret = [("filter", fStatus),
("action", aStatus)]
return ret
"""Stop the jail, by stopping filter and actions threads.
"""
self.filter.stop()
self.actions.stop()
self.filter.join()
self.actions.join()
logSys.info("Jail '%s' stopped" % self.name)
def is_alive(self):
"""Check jail "is_alive" by checking filter and actions threads.
"""
return self.filter.is_alive() or self.actions.is_alive()

View File

@ -39,8 +39,6 @@ class Jails(Mapping):
"""
def __init__(self):
"""Initialise an empty Jails instance.
"""
self.__lock = Lock()
self._jails = dict()
@ -56,6 +54,8 @@ class Jails(Mapping):
The name of the jail.
backend : str
The backend to use.
db : Fail2BanDb
Fail2Ban's persistent database instance.
Raises
------

View File

@ -25,94 +25,53 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
from threading import Thread
import logging
# Gets the instance of the logger.
logSys = logging.getLogger(__name__)
from abc import abstractproperty, abstractmethod
class JailThread(Thread):
##
# Constructor.
#
# Initialize the filter object with default values.
# @param jail the jail object
"""Abstract class for threading elements in Fail2Ban.
Attributes
----------
daemon
ident
name
status
active : bool
Control the state of the thread.
idle : bool
Control the idle state of the thread.
sleeptime : int
The time the thread sleeps for in the loop.
"""
def __init__(self):
Thread.__init__(self)
super(JailThread, self).__init__()
## Control the state of the thread.
self.__isRunning = False
self.active = False
## Control the idle state of the thread.
self.__isIdle = False
self.idle = False
## The time the thread sleeps in the loop.
self.__sleepTime = 1
##
# Set the time that the thread sleeps.
#
# This value could also be called "polling time". A value of 1 is a
# good one. This unit is "second"
# @param value the polling time (second)
def setSleepTime(self, value):
self.__sleepTime = value
logSys.info("Set sleeptime %s" % value)
##
# Get the time that the thread sleeps.
#
# @return the polling time
def getSleepTime(self):
return self.__sleepTime
##
# Set the idle flag.
#
# This flag stops the check of the log file.
# @param value boolean value
def setIdle(self, value):
self.__isIdle = value
##
# Get the idle state.
#
# @return the idle state
def getIdle(self):
return self.__isIdle
##
# Stop the thread.
#
# Stop the exection of the thread and quit.
def stop(self):
self.__isRunning = False
##
# Set the isRunning flag.
#
# @param value True if the thread is running
def setActive(self, value):
self.__isRunning = value
##
# Check if the thread is active.
#
# Check if the filter thread is running.
# @return True if the thread is running
def _isActive(self):
return self.__isRunning
##
# Get the status of the thread
#
# Get some informations about the thread. This is an abstract method.
# @return a list with tuple
def status(self):
self.sleeptime = 1
@abstractproperty
def status(self): # pragma: no cover - abstract
"""Abstract - Should provide status information.
"""
pass
def start(self):
"""Sets active flag and starts thread.
"""
self.active = True
super(JailThread, self).start()
def stop(self):
"""Sets `active` property to False, to flag run method to return.
"""
self.active = False
@abstractmethod
def run(self): # pragma: no cover - absract
"""Abstract - Called when thread starts, thread stops when returns.
"""
pass

View File

@ -51,7 +51,7 @@ class Server:
self.__logLevel = None
self.__logTarget = None
# Set logging level
self.setLogLevel(3)
self.setLogLevel("INFO")
self.setLogTarget("STDOUT")
def __sigTERMhandler(self, signum, frame):
@ -125,14 +125,14 @@ class Server:
self.__db.addJail(self.__jails[name])
def delJail(self, name):
del self.__jails[name]
if self.__db is not None:
self.__db.delJailName(name)
self.__db.delJail(self.__jails[name])
del self.__jails[name]
def startJail(self, name):
try:
self.__lock.acquire()
if not self.isAlive(name):
if not self.__jails[name].is_alive():
self.__jails[name].start()
finally:
self.__lock.release()
@ -141,7 +141,7 @@ class Server:
logSys.debug("Stopping jail %s" % name)
try:
self.__lock.acquire()
if self.isAlive(name):
if self.__jails[name].is_alive():
self.__jails[name].stop()
self.delJail(name)
finally:
@ -155,16 +155,13 @@ class Server:
self.stopJail(jail)
finally:
self.__lock.release()
def isAlive(self, name):
return self.__jails[name].isAlive()
def setIdleJail(self, name, value):
self.__jails[name].setIdle(value)
self.__jails[name].idle = value
return True
def getIdleJail(self, name):
return self.__jails[name].getIdle()
return self.__jails[name].idle
# Filter
def addIgnoreIP(self, name, ip):
@ -316,35 +313,30 @@ class Server:
self.__lock.release()
def statusJail(self, name):
return self.__jails[name].getStatus()
return self.__jails[name].status
# Logging
##
# Set the logging level.
#
# Incrementing the value gives more messages.
# 0 = FATAL
# 1 = ERROR
# 2 = WARNING
# 3 = INFO
# 4 = DEBUG
# CRITICAL
# ERROR
# WARNING
# NOTICE
# INFO
# DEBUG
# @param value the level
def setLogLevel(self, value):
try:
self.__loggingLock.acquire()
self.__logLevel = value
logLevel = logging.DEBUG
if value == 0:
logLevel = logging.FATAL
elif value == 1:
logLevel = logging.ERROR
elif value == 2:
logLevel = logging.WARNING
elif value == 3:
logLevel = logging.INFO
logging.getLogger(__name__).parent.parent.setLevel(logLevel)
logging.getLogger(__name__).parent.parent.setLevel(
getattr(logging, value.upper()))
except AttributeError:
raise ValueError("Invalid log level")
else:
self.__logLevel = value.upper()
finally:
self.__loggingLock.release()

View File

@ -107,7 +107,7 @@ class Transmitter:
name = command[0]
# Logging
if name == "loglevel":
value = int(command[1])
value = command[1]
self.__server.setLogLevel(value)
return self.__server.getLogLevel()
elif name == "logtarget":
@ -123,14 +123,14 @@ class Transmitter:
if db is None:
return None
else:
return db.getFilename()
return db.filename
elif name == "dbpurgeage":
db = self.__server.getDatabase()
if db is None:
return None
else:
db.setPurgeAge(command[1])
return db.getPurgeAge()
db.purgeage = command[1]
return db.purgeage
# Jail
elif command[1] == "idle":
if command[2] == "on":
@ -265,13 +265,13 @@ class Transmitter:
if db is None:
return None
else:
return db.getFilename()
return db.filename
elif name == "dbpurgeage":
db = self.__server.getDatabase()
if db is None:
return None
else:
return db.getPurgeAge()
return db.purgeage
# Filter
elif command[1] == "logpath":
return self.__server.getLogPath(name)

View File

@ -77,7 +77,7 @@ class SMTPActionTest(unittest.TestCase):
self.assertEqual(self.smtpd.mailfrom, "fail2ban")
self.assertEqual(self.smtpd.rcpttos, ["root"])
self.assertTrue(
"Subject: [Fail2Ban] %s: started" % self.jail.getName()
"Subject: [Fail2Ban] %s: started" % self.jail.name
in self.smtpd.data)
def testStop(self):
@ -86,7 +86,7 @@ class SMTPActionTest(unittest.TestCase):
self.assertEqual(self.smtpd.rcpttos, ["root"])
self.assertTrue(
"Subject: [Fail2Ban] %s: stopped" %
self.jail.getName() in self.smtpd.data)
self.jail.name in self.smtpd.data)
def testBan(self):
aInfo = {
@ -102,7 +102,7 @@ class SMTPActionTest(unittest.TestCase):
self.assertEqual(self.smtpd.rcpttos, ["root"])
self.assertTrue(
"Subject: [Fail2Ban] %s: banned %s" %
(self.jail.getName(), aInfo['ip']) in self.smtpd.data)
(self.jail.name, aInfo['ip']) in self.smtpd.data)
self.assertTrue(
"%i attempts" % aInfo['failures'] in self.smtpd.data)

View File

@ -84,8 +84,8 @@ class ExecuteActions(LogCaptureTestCase):
self.__actions.stop()
self.__actions.join()
self.assertEqual(self.__actions.status(),[("Currently banned", 0 ),
("Total banned", 0 ), ("IP list", [] )])
self.assertEqual(self.__actions.status,[("Currently banned", 0 ),
("Total banned", 0 ), ("Banned IP list", [] )])
def testAddActionPython(self):

View File

@ -559,7 +559,7 @@ class JailsReaderTest(LogCaptureTestCase):
[['set', 'dbfile',
'/var/lib/fail2ban/fail2ban.sqlite3'],
['set', 'dbpurgeage', 86400],
['set', 'loglevel', 3],
['set', 'loglevel', "INFO"],
['set', 'logtarget', '/var/log/fail2ban.log']])
# and if we force change configurator's fail2ban's baseDir

View File

@ -47,7 +47,7 @@ class DatabaseTest(unittest.TestCase):
os.remove(self.dbFilename)
def testGetFilename(self):
self.assertEqual(self.dbFilename, self.db.getFilename())
self.assertEqual(self.dbFilename, self.db.filename)
def testCreateInvalidPath(self):
self.assertRaises(
@ -61,7 +61,7 @@ class DatabaseTest(unittest.TestCase):
self.db = Fail2BanDb(self.dbFilename)
# and check jail of same name still present
self.assertTrue(
self.jail.getName() in self.db.getJailNames(),
self.jail.name in self.db.getJailNames(),
"Jail not retained in Db after disconnect reconnect.")
def testUpdateDb(self):
@ -74,13 +74,13 @@ class DatabaseTest(unittest.TestCase):
self.assertEqual(self.db.updateDb(Fail2BanDb.__version__), Fail2BanDb.__version__)
self.assertRaises(NotImplementedError, self.db.updateDb, Fail2BanDb.__version__ + 1)
os.remove(self.db.dbBackupFilename)
os.remove(self.db._dbBackupFilename)
def testAddJail(self):
self.jail = DummyJail()
self.db.addJail(self.jail)
self.assertTrue(
self.jail.getName() in self.db.getJailNames(),
self.jail.name in self.db.getJailNames(),
"Jail not added to database")
def testAddLog(self):

View File

@ -32,6 +32,8 @@ class DummyJail(object):
def __init__(self):
self.lock = Lock()
self.queue = []
self.idle = False
self.database = None
self.actions = Actions(self)
def __len__(self):
@ -58,15 +60,6 @@ class DummyJail(object):
finally:
self.lock.release()
def setIdle(self, value):
pass
def getIdle(self):
pass
def getName(self):
@property
def name(self):
return "DummyJail #%s with %d tickets" % (id(self), len(self))
def getDatabase(self):
return None

View File

@ -0,0 +1,12 @@
# failJSON: { "time": "2005-02-16T23:33:20", "match": true , "host": "190.5.230.178" }
Feb 16 23:33:20 smtp1 sm-mta[5133]: s1GNXHYB005133: [190.5.230.178]: possible SMTP attack: command=AUTH, count=5
# failJSON: { "time": "2005-02-16T23:40:36", "match": true , "host": "75.176.164.191" }
Feb 16 23:40:36 smtp1 sm-mta[5178]: s1GNeNqe005178: cpe-075-176-164-191.sc.res.rr.com [75.176.164.191]: possible SMTP attack: command=AUTH, count=5
# failJSON: { "time": "2005-02-24T12:10:15", "match": true , "host": "211.75.6.133" }
Feb 24 12:10:15 kismet sm-acceptingconnections[32053]: s1OHA28u032053: 211-75-6-133.HINET-IP.hinet.net [211.75.6.133]: possible SMTP attack: command=AUTH, count=6
# failJSON: { "time": "2005-02-24T13:00:17", "match": true , "host": "95.70.241.192" }
Feb 24 13:00:17 kismet sm-acceptingconnections[1499]: s1OHxxSn001499: 192.241.70.95.dsl.static.turk.net [95.70.241.192] (may be forged): possible SMTP attack: command=AUTH, count=6

View File

@ -0,0 +1,67 @@
# failJSON: { "time": "2005-02-25T03:01:10", "match": true , "host": "128.68.136.133" }
Feb 25 03:01:10 kismet sm-acceptingconnections[27713]: s1P819mk027713: ruleset=check_rcpt, arg1=<asservnew@freemailhost.ru>, relay=128-68-136-133.broadband.corbina.ru [128.68.136.133], reject=550 5.7.1 <asservnew@freemailhost.ru>... Relaying denied. Proper authentication required.
# failJSON: { "time": "2005-02-23T21:36:14", "match": true , "host": "80.253.155.119" }
Feb 23 21:36:14 petermurray sm-mta[22248]: s1NLaDQT022248: ruleset=check_rcpt, arg1=<dautareuk2@hotmail.it>, relay=int0.client.access.azadnet.net [80.253.155.119] (may be forged), reject=550 5.7.1 <dautareuk2@hotmail.it>... Relaying denied. IP name possibly forged [80.253.155.119]
# failJSON: { "time": "2005-02-24T07:33:59", "match": true , "host": "118.161.66.57" }
Feb 24 07:33:59 petermurray sm-mta[21134]: s1O7XtZJ021134: ruleset=check_rcpt, arg1=<sanjinn232@yahoo.com.tw>, relay=118-161-66-57.dynamic.hinet.net [118.161.66.57], reject=550 5.7.1 <sanjinn232@yahoo.com.tw>... Relaying denied. Proper authentication required.
# failJSON: { "time": "2005-02-23T07:57:28", "match": true , "host": "2.180.185.27" }
Feb 23 07:57:28 petermurray sm-mta[6519]: s1N7vR47006519: ruleset=check_rcpt, arg1=<camila.pinto@camilopinto.pt>, relay=[2.180.185.27], reject=553 5.1.8 <camila.pinto@camilopinto.pt>... Domain of sender address camila.pinto@andrewweitzman.com does not exist
# failJSON: { "time": "2005-02-23T14:13:08", "match": true , "host": "85.60.238.161" }
Feb 23 14:13:08 petermurray sm-mta[17126]: s1NED81M017126: ruleset=check_rcpt, arg1=<anabelaalvesd@camilopinto.pt>, relay=161.pool85-60-238.dynamic.orange.es [85.60.238.161], reject=553 5.1.8 <anabelaalvesd@camilopinto.pt>... Domain of sender address anabelaalvesd@dsldevice.lan does not exist
# failJSON: { "time": "2005-02-24T05:07:40", "match": true , "host": "202.53.73.138" }
Feb 24 05:07:40 petermurray sm-mta[716]: s1O57c6H000716: ruleset=check_rcpt, arg1=<camilo_pinto@camilopinto.pt>, relay=202.53.73.138.nettlinx.com [202.53.73.138] (may be forged), reject=553 5.1.8 <camilo_pinto@camilopinto.pt>... Domain of sender address root@srv.montserv.com does not exist
# failJSON: { "time": "2005-02-23T07:00:08", "match": true , "host": "151.232.63.226" }
Feb 23 07:00:08 petermurray sm-mta[3992]: s1N706jo003992: ruleset=check_rcpt, arg1=<joaofr@camilopinto.pt>, relay=[151.232.63.226], reject=550 5.7.1 <joaofr@camilopinto.pt>... Rejected: 151.232.63.226 listed at sbl-xbl.spamhaus.org
# failJSON: { "time": "2005-02-23T04:36:21", "match": true , "host": "74.137.127.206" }
Feb 23 04:36:21 kismet sm-acceptingconnections[12603]: s1N9aKAw012603: ruleset=check_rcpt, arg1=<user@host.com>, relay=74-137-127-206.dhcp.insightbb.com [74.137.127.206], reject=550 5.7.1 <user@host.com>... Rejected: IP in SpamCop blacklist, see: http://spamcop.net/bl.shtml?74.137.127.206
# failJSON: { "time": "2005-02-23T04:38:57", "match": true , "host": "203.229.186.250" }
Feb 23 04:38:57 kismet sm-acceptingconnections[16772]: s1N9csSZ016772: ruleset=check_rcpt, arg1=<user@host.com>, relay=[203.229.186.250], reject=550 5.7.1 <user@host.com>... Rejected: IP in Barracuda RBL, see: http://www.barracudacentral.org/reputation?ip=203.229.186.250
# failJSON: { "time": "2005-02-23T06:06:04", "match": true , "host": "186.54.117.93" }
Feb 23 06:06:04 kismet sm-acceptingconnections[18622]: s1NB63Bp018622: ruleset=check_rcpt, arg1=<user@host.com>, relay=r186-54-117-93.dialup.adsl.anteldata.net.uy [186.54.117.93], reject=550 5.7.1 <user@host.com>... Rejected: IP in SpamHaus PBL, see http://www.spamhaus.org/query/bl?ip=186.54.117.93
# failJSON: { "time": "2005-02-24T01:46:44", "match": true , "host": "217.21.54.82" }
Feb 24 01:46:44 petermurray sm-mta[24422]: ruleset=check_relay, arg1=leased-line-54-82.telecom.by, arg2=217.21.54.82, relay=leased-line-54-82.telecom.by [217.21.54.82], reject=421 4.3.2 Connection rate limit exceeded.
# failJSON: { "time": "2005-02-27T15:49:07", "match": true , "host": "189.30.205.74" }
Feb 27 15:49:07 batman sm-mta[88390]: ruleset=check_relay, arg1=189-30-205-74.paebv701.dsl.brasiltelecom.net.br, arg2=189.30.205.74, relay=189-30-205-74.paebv701.dsl.brasiltelecom.net.br [189.30.205.74], reject=421 4.3.2 Too many open connections.
# failJSON: { "time": "2005-02-19T18:01:50", "match": true , "host": "196.213.73.146" }
Feb 19 18:01:50 batman sm-mta[78152]: ruleset=check_relay, arg1=[196.213.73.146], arg2=196.213.73.146, relay=[196.213.73.146], reject=421 4.3.2 Connection rate limit exceeded.
# failJSON: { "time": "2005-02-27T10:53:06", "match": true , "host": "209.15.212.253" }
Feb 27 10:53:06 batman sm-mta[44307]: s1R9r60D044307: rejecting commands from [209.15.212.253] due to pre-greeting traffic after 0 seconds
# failJSON: { "time": "2005-02-27T15:44:18", "match": true , "host": "41.204.78.137" }
Feb 27 15:44:18 batman sm-mta[87838]: s1REiHdq087838: ruleset=check_rcpt, arg1=<gert-jan@t-online.ch>, relay=[41.204.78.137], reject=550 5.7.1 <gert-jan@t-online.ch>... Relaying denied. IP name lookup failed [41.204.78.137]
# failJSON: { "time": "2005-02-27T15:49:02", "match": true , "host": "189.30.205.74" }
Feb 27 15:49:02 batman sm-mta[88377]: s1REn1un088377: ruleset=check_rcpt, arg1=<non-existing-user@example.com>, relay=189-30-205-74.paebv701.dsl.brasiltelecom.net.br [189.30.205.74], reject=550 5.1.1 <non-existing-user@example.com>... User unknown
# failJSON: { "time": "2005-02-27T22:44:42", "match": true , "host": "123.69.106.50" }
Feb 27 22:44:42 batman sm-mta[30972]: s1RLieRP030972: ruleset=check_rcpt, arg1=<existing-user@example.com>, relay=[123.69.106.50], reject=553 5.1.8 <existing-user@example.com>... Domain of sender address lf@ibuv.net does not exist
# failJSON: { "time": "2005-02-23T21:18:47", "match": true , "host": "76.72.174.70" }
Feb 23 21:18:47 batman sm-mta[93301]: s1NKIkZa093301: [76.72.174.70]: EXPN root [rejected]
# failJSON: { "time": "2005-02-13T01:16:50", "match": true , "host": "217.193.142.180" }
Feb 13 01:16:50 batman sm-mta[25815]: s1D0GoSs025815: [217.193.142.180]: expn info [rejected]
# failJSON: { "time": "2005-02-22T14:02:44", "match": true , "host": "24.73.201.194" }
Feb 22 14:02:44 batman sm-mta[4030]: s1MD2hsd004030: rrcs-24-73-201-194.se.biz.rr.com [24.73.201.194]: EXPN root [rejected]
# failJSON: { "time": "2005-02-13T01:16:50", "match": true , "host": "217.193.142.180" }
Feb 13 01:16:50 batman sm-mta[25815]: s1D0GoSs025815: [217.193.142.180]: vrfy info [rejected]
# failJSON: { "time": "2005-02-22T14:02:44", "match": true , "host": "24.73.201.194" }
Feb 22 14:02:44 batman sm-mta[4030]: s1MD2hsd004030: rrcs-24-73-201-194.se.biz.rr.com [24.73.201.194]: VRFY root [rejected]

View File

@ -236,6 +236,15 @@ class IgnoreIP(LogCaptureTestCase):
self.assertFalse(self.filter.inIgnoreIPList('192.168.1.255'))
self.assertFalse(self.filter.inIgnoreIPList('192.168.0.255'))
def testIgnoreIPMask(self):
self.filter.addIgnoreIP('192.168.1.0/255.255.255.128')
self.assertTrue(self.filter.inIgnoreIPList('192.168.1.0'))
self.assertTrue(self.filter.inIgnoreIPList('192.168.1.1'))
self.assertTrue(self.filter.inIgnoreIPList('192.168.1.127'))
self.assertFalse(self.filter.inIgnoreIPList('192.168.1.128'))
self.assertFalse(self.filter.inIgnoreIPList('192.168.1.255'))
self.assertFalse(self.filter.inIgnoreIPList('192.168.0.255'))
def testIgnoreInProcessLine(self):
setUpMyTime()
self.filter.addIgnoreIP('192.168.1.0/25')
@ -316,7 +325,7 @@ class LogFileMonitor(LogCaptureTestCase):
self.file = open(self.name, 'a')
self.filter = FilterPoll(DummyJail())
self.filter.addLogPath(self.name)
self.filter.setActive(True)
self.filter.active = True
self.filter.addFailRegex("(?:(?:Authentication failure|Failed [-/\w+]+) for(?: [iI](?:llegal|nvalid) user)?|[Ii](?:llegal|nvalid) user|ROOT LOGIN REFUSED) .*(?: from|FROM) <HOST>")
def tearDown(self):
@ -457,7 +466,7 @@ def get_monitor_failures_testcase(Filter_):
self.jail = DummyJail()
self.filter = Filter_(self.jail)
self.filter.addLogPath(self.name)
self.filter.setActive(True)
self.filter.active = True
self.filter.addFailRegex("(?:(?:Authentication failure|Failed [-/\w+]+) for(?: [iI](?:llegal|nvalid) user)?|[Ii](?:llegal|nvalid) user|ROOT LOGIN REFUSED) .*(?: from|FROM) <HOST>")
self.filter.start()
# If filter is polling it would sleep a bit to guarantee that
@ -678,7 +687,7 @@ def get_monitor_failures_journal_testcase(Filter_): # pragma: systemd no cover
"TEST_UUID=%s" % self.test_uuid])
self.journal_fields = {
'TEST_FIELD': "1", 'TEST_UUID': self.test_uuid}
self.filter.setActive(True)
self.filter.active = True
self.filter.addFailRegex("(?:(?:Authentication failure|Failed [-/\w+]+) for(?: [iI](?:llegal|nvalid) user)?|[Ii](?:llegal|nvalid) user|ROOT LOGIN REFUSED) .*(?: from|FROM) <HOST>")
self.filter.start()
@ -795,7 +804,7 @@ class GetFailures(unittest.TestCase):
setUpMyTime()
self.jail = DummyJail()
self.filter = FileFilter(self.jail)
self.filter.setActive(True)
self.filter.active = True
# TODO Test this
#self.filter.setTimeRegex("\S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2}")
#self.filter.setTimePattern("%b %d %H:%M:%S")
@ -886,7 +895,7 @@ class GetFailures(unittest.TestCase):
('warn', output_yes)):
jail = DummyJail()
filter_ = FileFilter(jail, useDns=useDns)
filter_.setActive(True)
filter_.active = True
filter_.failManager.setMaxRetry(1) # we might have just few failures
filter_.addLogPath(GetFailures.FILENAME_USEDNS)

View File

@ -45,7 +45,7 @@ class FilterSamplesRegex(unittest.TestCase):
def setUp(self):
"""Call before every test case."""
self.filter = Filter(None)
self.filter.setActive(True)
self.filter.active = True
setUpMyTime()

View File

@ -227,8 +227,7 @@ class Transmitter(TransmitterBase):
time.sleep(1)
self.assertEqual(
self.transm.proceed(["stop", self.jailName]), (0, None))
self.assertRaises(
UnknownJailException, self.server.isAlive, self.jailName)
self.assertTrue(self.jailName not in self.server._Server__jails)
def testStartStopAllJail(self):
self.server.addJail("TestJail2", "auto")
@ -242,10 +241,8 @@ class Transmitter(TransmitterBase):
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")
self.assertTrue(self.jailName not in self.server._Server__jails)
self.assertTrue("TestJail2" not in self.server._Server__jails)
def testJailIdle(self):
self.assertEqual(
@ -482,15 +479,15 @@ class Transmitter(TransmitterBase):
self.assertEqual(self.transm.proceed(["status", self.jailName]),
(0,
[
('filter', [
('Filter', [
('Currently failed', 0),
('Total failed', 0),
('File list', [])]
),
('action', [
('Actions', [
('Currently banned', 0),
('Total banned', 0),
('IP list', [])]
('Banned IP list', [])]
)
]
)
@ -686,7 +683,7 @@ class TransmitterLogging(TransmitterBase):
def setUp(self):
self.server = Server()
self.server.setLogTarget("/dev/null")
self.server.setLogLevel(0)
self.server.setLogLevel("CRITICAL")
super(TransmitterLogging, self).setUp()
def testLogTarget(self):
@ -711,12 +708,14 @@ class TransmitterLogging(TransmitterBase):
self.setGetTest("logtarget", "SYSLOG")
def testLogLevel(self):
self.setGetTest("loglevel", "4", 4)
self.setGetTest("loglevel", "3", 3)
self.setGetTest("loglevel", "2", 2)
self.setGetTest("loglevel", "1", 1)
self.setGetTest("loglevel", "-1", -1)
self.setGetTest("loglevel", "0", 0)
self.setGetTest("loglevel", "HEAVYDEBUG")
self.setGetTest("loglevel", "DEBUG")
self.setGetTest("loglevel", "INFO")
self.setGetTest("loglevel", "NOTICE")
self.setGetTest("loglevel", "WARNING")
self.setGetTest("loglevel", "ERROR")
self.setGetTest("loglevel", "CRITICAL")
self.setGetTest("loglevel", "cRiTiCaL", "CRITICAL")
self.setGetTestNOK("loglevel", "Bird")
def testFlushLogs(self):
@ -724,7 +723,7 @@ class TransmitterLogging(TransmitterBase):
try:
f, fn = tempfile.mkstemp("fail2ban.log")
os.close(f)
self.server.setLogLevel(2)
self.server.setLogLevel("WARNING")
self.assertEqual(self.transm.proceed(["set", "logtarget", fn]), (0, fn))
l = logging.getLogger('fail2ban.server.server').parent.parent
l.warning("Before file moved")
@ -774,7 +773,7 @@ class JailTests(unittest.TestCase):
# Just a smoke test for now
longname = "veryveryverylongname"
jail = Jail(longname)
self.assertEqual(jail.getName(), longname)
self.assertEqual(jail.name, longname)
class RegexTests(unittest.TestCase):

View File

@ -19,6 +19,15 @@
__fail2ban_jails () {
"$1" status 2>/dev/null | awk -F"\t+" '/Jail list/{print $2}' | sed 's/, / /g'
}
__fail2ban_jail_actions () {
"$1" get "$2" actions 2>/dev/null | sed -n '$s/\([^,]\+\),\?/\1/gp'
}
__fail2ban_jail_action_properties () {
"$1" get "$2" actionproperties "$3" 2>/dev/null | sed -n '$s/\([^,]\+\),\?/\1/gp'
}
__fail2ban_jail_action_methods () {
"$1" get "$2" actionmethods "$3" 2>/dev/null | sed -n '$s/\([^,]\+\),\?/\1/gp'
}
_fail2ban () {
local cur prev words cword
@ -50,7 +59,7 @@ _fail2ban () {
_filedir
return 0
elif [[ "$1" == *"fail2ban-client" ]];then
local cmd jail
local cmd jail action
case $prev in
"$1")
COMPREPLY=( $( compgen -W \
@ -71,7 +80,7 @@ _fail2ban () {
;;
*)
if [[ "${words[$cword-2]}" == "add" ]];then
COMPREPLY=( $( compgen -W "auto polling gamin pyinotify" -- "$cur" ) )
COMPREPLY=( $( compgen -W "auto polling gamin pyinotify systemd" -- "$cur" ) )
return 0
elif [[ "${words[$cword-2]}" == "set" || "${words[$cword-2]}" == "get" ]];then
cmd="${words[cword-2]}"
@ -80,6 +89,11 @@ _fail2ban () {
cmd="${words[$cword-3]}"
jail="${words[$cword-2]}"
# Handle in section below
elif [[ "${words[$cword-4]}" == "set" || "${words[$cword-4]}" == "get" && ${words[$cword-2]} == action* ]];then
cmd="${words[$cword-4]}"
jail="${words[$cword-3]}"
action="${words[$cword-1]}"
# Handle in section below
fi
;;
esac
@ -88,7 +102,7 @@ _fail2ban () {
case $prev in
loglevel)
if [[ "$cmd" == "set" ]];then
COMPREPLY=( $( compgen -W "0 1 2 3 4" -- "$cur" ) )
COMPREPLY=( $( compgen -W "CRITICAL ERROR WARNING NOTICE INFO DEBUG" -- "$cur" ) )
fi
return 0
;;
@ -106,6 +120,25 @@ _fail2ban () {
return 0
;;
esac
elif [[ -n "$jail" && -n "$action" ]];then
case ${words[$cwords-3]} in
action)
COMPREPLY=( $( compgen -W \
"$( __fail2ban_jail_action_properties "$1" "$jail" "$action")" \
-- "$cur" ) )
if [[ "$cmd" == "set" ]];then
COMPREPLY+=( $(compgen -W "$(__fail2ban_jail_action_methods "$1" "$jail" "$action")" -- "$cur" ) )
fi
return 0
;;
esac
elif [[ -n "$jail" && $prev == action* ]];then
case $prev in
action|actionproperties|actionmethods)
COMPREPLY=( $(compgen -W "$(__fail2ban_jail_actions "$1" "$jail")" -- "$cur" ) )
return 0
;;
esac
elif [[ -n "$jail" && "$cmd" == "set" ]];then
case $prev in
addlogpath)

View File

@ -107,7 +107,7 @@ These files have one section, [Definition].
The items that can be set are:
.TP
.B loglevel
verbosity level of log output: 1 = ERROR, 2 = WARN, 3 = INFO, 4 = DEBUG. Default: 1
verbosity level of log output: CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG. Default: ERROR
.TP
.B logtarget
log target: filename, SYSLOG, STDERR or STDOUT. Default: STDERR . Only a single log target can be specified.
@ -134,7 +134,15 @@ filename(s) of the log files to be monitored. Globs -- paths containing * and ?
Ensure syslog or the program that generates the log file isn't configured to compress repeated log messages to "\fI*last message repeated 5 time*s\fR" otherwise it will fail to detect. This is called \fIRepeatedMsgReduction\fR in rsyslog and should be \fIOff\fR.
.TP
.B action
action(s) from \fI/etc/fail2ban/action.d/\fR without the \fI.conf\fR/\fI.local\fR extension. Arguments can be passed to actions to override the default values from the [Init] section in the action file. Arguments are specified by [name=value,name2=value]. Values can also be quoted. More that one action can be specified (in separate lines).
action(s) from \fI/etc/fail2ban/action.d/\fR without the \fI.conf\fR/\fI.local\fR extension. Arguments can be passed to actions to override the default values from the [Init] section in the action file. Arguments are specified by:
.RS
.RS
[name=value,name2=value,name3="values,values"]
.RE
Values can also be quoted (required when value includes a ","). More that one action can be specified (in separate lines).
.RE
.TP
.B ignoreip
list of IPs not to ban. They can include a CIDR mask too.

View File

@ -51,7 +51,7 @@ if setuptools and "test" in sys.argv:
hdlr.setFormatter(fmt)
logSys.addHandler(hdlr)
if set(["-q", "--quiet"]) & set(sys.argv):
logSys.setLevel(logging.FATAL)
logSys.setLevel(logging.CRITICAL)
warnings.simplefilter("ignore")
sys.warnoptions.append("ignore")
elif set(["-v", "--verbose"]) & set(sys.argv):