mirror of https://github.com/fail2ban/fail2ban
Merge branch '0.9' into distro-paths-gh-315
commit
2d45becb0e
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
5
MANIFEST
5
MANIFEST
|
@ -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
3
THANKS
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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', '')))
|
||||
|
||||
|
|
|
@ -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
|
||||
#
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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"],
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 < ?",
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
------
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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]
|
||||
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
2
setup.py
2
setup.py
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue