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:
|
install:
|
||||||
- pip install pyinotify
|
- 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 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:
|
script:
|
||||||
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then coverage run --rcfile=.travis_coveragerc setup.py test; else python setup.py test; fi
|
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then coverage run --rcfile=.travis_coveragerc setup.py test; else python setup.py test; fi
|
||||||
after_success:
|
after_success:
|
||||||
|
|
|
@ -90,6 +90,7 @@ ver. 0.8.13 (2014/XX/XXX) - maintenance-only-from-now-on
|
||||||
|
|
||||||
- New Features:
|
- New Features:
|
||||||
- filter nagios - detects unauthorized access to the nrpe daemon (Ivo Truxa)
|
- filter nagios - detects unauthorized access to the nrpe daemon (Ivo Truxa)
|
||||||
|
- filter sendmail-{auth,reject} (jserrachinha and cepheid666 and fab23).
|
||||||
|
|
||||||
- Enhancements:
|
- Enhancements:
|
||||||
- filter pureftpd - added all translations of "Authentication failed for
|
- 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/proftpd.conf
|
||||||
config/filter.d/pure-ftpd.conf
|
config/filter.d/pure-ftpd.conf
|
||||||
config/filter.d/qmail.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/sieve.conf
|
||||||
config/filter.d/solid-pop3d.conf
|
config/filter.d/solid-pop3d.conf
|
||||||
config/filter.d/squid.conf
|
config/filter.d/squid.conf
|
||||||
|
|
3
THANKS
3
THANKS
|
@ -21,6 +21,7 @@ Bas van den Dikkenberg
|
||||||
Beau Raines
|
Beau Raines
|
||||||
Bill Heaton
|
Bill Heaton
|
||||||
Carlos Alberto Lopez Perez
|
Carlos Alberto Lopez Perez
|
||||||
|
cepheid666
|
||||||
Christian Rauch
|
Christian Rauch
|
||||||
Christophe Carles
|
Christophe Carles
|
||||||
Christoph Haas
|
Christoph Haas
|
||||||
|
@ -51,6 +52,7 @@ Jonathan Lanning
|
||||||
Jonathan Underwood
|
Jonathan Underwood
|
||||||
Joël Bertrand
|
Joël Bertrand
|
||||||
JP Espinosa
|
JP Espinosa
|
||||||
|
jserrachinha
|
||||||
Justin Shore
|
Justin Shore
|
||||||
Kévin Drapel
|
Kévin Drapel
|
||||||
kjohnsonecl
|
kjohnsonecl
|
||||||
|
@ -99,5 +101,6 @@ Yaroslav Halchenko
|
||||||
Winston Smith
|
Winston Smith
|
||||||
ykimon
|
ykimon
|
||||||
Yehuda Katz
|
Yehuda Katz
|
||||||
|
Zbigniew Jędrzejewski-Szmek
|
||||||
zugeschmiert
|
zugeschmiert
|
||||||
Zurd
|
Zurd
|
||||||
|
|
|
@ -325,7 +325,7 @@ class Fail2banClient:
|
||||||
if verbose <= 0:
|
if verbose <= 0:
|
||||||
logSys.setLevel(logging.ERROR)
|
logSys.setLevel(logging.ERROR)
|
||||||
elif verbose == 1:
|
elif verbose == 1:
|
||||||
logSys.setLevel(logging.WARN)
|
logSys.setLevel(logging.WARNING)
|
||||||
elif verbose == 2:
|
elif verbose == 2:
|
||||||
logSys.setLevel(logging.INFO)
|
logSys.setLevel(logging.INFO)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -131,7 +131,7 @@ Report bugs to https://github.com/fail2ban/fail2ban/issues
|
||||||
"\"systemd-journal\" only"),
|
"\"systemd-journal\" only"),
|
||||||
Option('-l', "--log-level", type="choice",
|
Option('-l', "--log-level", type="choice",
|
||||||
dest="log_level",
|
dest="log_level",
|
||||||
choices=('heavydebug', 'debug', 'info', 'warning', 'error', 'fatal'),
|
choices=('heavydebug', 'debug', 'info', 'notice', 'warning', 'error', 'critical'),
|
||||||
default=None,
|
default=None,
|
||||||
help="Log level for the Fail2Ban logger to use"),
|
help="Log level for the Fail2Ban logger to use"),
|
||||||
Option("-v", "--verbose", action='store_true',
|
Option("-v", "--verbose", action='store_true',
|
||||||
|
@ -476,9 +476,9 @@ if __name__ == "__main__":
|
||||||
logSys.setLevel(getattr(logging, opts.log_level.upper()))
|
logSys.setLevel(getattr(logging, opts.log_level.upper()))
|
||||||
else: # pragma: no cover
|
else: # pragma: no cover
|
||||||
# suppress the logging but it would leave unittests' progress dots
|
# suppress the logging but it would leave unittests' progress dots
|
||||||
# ticking, unless like with '-l fatal' which would be silent
|
# ticking, unless like with '-l critical' which would be silent
|
||||||
# unless error occurs
|
# unless error occurs
|
||||||
logSys.setLevel(getattr(logging, 'FATAL'))
|
logSys.setLevel(getattr(logging, 'CRITICAL'))
|
||||||
|
|
||||||
# Add the default logging handler
|
# Add the default logging handler
|
||||||
stdout = logging.StreamHandler(sys.stdout)
|
stdout = logging.StreamHandler(sys.stdout)
|
||||||
|
|
|
@ -48,7 +48,7 @@ def get_opt_parser():
|
||||||
p.add_options([
|
p.add_options([
|
||||||
Option('-l', "--log-level", type="choice",
|
Option('-l', "--log-level", type="choice",
|
||||||
dest="log_level",
|
dest="log_level",
|
||||||
choices=('heavydebug', 'debug', 'info', 'warning', 'error', 'fatal'),
|
choices=('heavydebug', 'debug', 'info', 'notice', 'warning', 'error', 'critical'),
|
||||||
default=None,
|
default=None,
|
||||||
help="Log level for the logger to use during running tests"),
|
help="Log level for the logger to use during running tests"),
|
||||||
Option('-n', "--no-network", action="store_true",
|
Option('-n', "--no-network", action="store_true",
|
||||||
|
@ -75,9 +75,10 @@ logSys = logging.getLogger("fail2ban")
|
||||||
verbosity = {'heavydebug': 4,
|
verbosity = {'heavydebug': 4,
|
||||||
'debug': 3,
|
'debug': 3,
|
||||||
'info': 2,
|
'info': 2,
|
||||||
|
'notice': 2,
|
||||||
'warning': 1,
|
'warning': 1,
|
||||||
'error': 1,
|
'error': 1,
|
||||||
'fatal': 0,
|
'critical': 0,
|
||||||
None: 1}[opts.log_level]
|
None: 1}[opts.log_level]
|
||||||
|
|
||||||
if opts.log_level is not None: # pragma: no cover
|
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()))
|
logSys.setLevel(getattr(logging, opts.log_level.upper()))
|
||||||
else: # pragma: no cover
|
else: # pragma: no cover
|
||||||
# suppress the logging but it would leave unittests' progress dots
|
# suppress the logging but it would leave unittests' progress dots
|
||||||
# ticking, unless like with '-l fatal' which would be silent
|
# ticking, unless like with '-l critical' which would be silent
|
||||||
# unless error occurs
|
# unless error occurs
|
||||||
logSys.setLevel(getattr(logging, 'FATAL'))
|
logSys.setLevel(getattr(logging, 'CRITICAL'))
|
||||||
|
|
||||||
# Add the default logging handler
|
# Add the default logging handler
|
||||||
stdout = logging.StreamHandler(sys.stdout)
|
stdout = logging.StreamHandler(sys.stdout)
|
||||||
|
@ -111,7 +112,7 @@ logSys.addHandler(stdout)
|
||||||
#
|
#
|
||||||
# Let know the version
|
# 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..." \
|
print("Fail2ban %s test suite. Python %s. Please wait..." \
|
||||||
% (version, str(sys.version).replace('\n', '')))
|
% (version, str(sys.version).replace('\n', '')))
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
# Fail2Ban configuration file
|
# 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
|
# Sends a complaint e-mail to addresses listed in the whois record for an
|
||||||
# offending IP address.
|
# 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
|
# 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.
|
# 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
|
# WARNING
|
||||||
# -------
|
# -------
|
||||||
#
|
#
|
||||||
|
@ -55,10 +54,10 @@ actioncheck =
|
||||||
# Tags: See jail.conf(5) man page
|
# Tags: See jail.conf(5) man page
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionban = ADDRESSES=`whois <ip> | perl -e 'while (<STDIN>) { next if /^changed|@(ripe|apnic)\.net/io; $m += (/abuse|trouble:|report|spam|security/io?3:0); if (/([a-z0-9_\-\.+]+@[a-z0-9\-]+(\.[[a-z0-9\-]+)+)/io) { while (s/([a-z0-9_\-\.+]+@[a-z0-9\-]+(\.[[a-z0-9\-]+)+)//io) { if ($m) { $a{lc($1)}=$m } else { $b{lc($1)}=$m } } $m=0 } else { $m && --$m } } if (%%a) {print join(",",keys(%%a))} else {print join(",",keys(%%b))}'`
|
actionban = 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>
|
IP=<ip>
|
||||||
if [ ! -z "$ADDRESSES" ]; then
|
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
|
fi
|
||||||
|
|
||||||
# Option: actionunban
|
# Option: actionunban
|
||||||
|
@ -70,7 +69,7 @@ actionban = ADDRESSES=`whois <ip> | perl -e 'while (<STDIN>) { next if /^changed
|
||||||
actionunban =
|
actionunban =
|
||||||
|
|
||||||
[Init]
|
[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
|
# 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
|
# own rules. The 3600 second timeout is independent and acts as a
|
||||||
# safeguard in case the fail2ban process dies unexpectedly. The
|
# safeguard in case the fail2ban process dies unexpectedly. The
|
||||||
# shorter of the two timeouts actually matters.
|
# 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
|
# Option: actionstop
|
||||||
# Notes.: command executed once at the end of Fail2Ban
|
# Notes.: command executed once at the end of Fail2Ban
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionstop = echo / > /proc/net/xt_recent/f2b-<name>
|
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
|
# Option: actioncheck
|
||||||
# Notes.: command executed once before each actionban command
|
# Notes.: command executed once before each actionban command
|
||||||
|
|
|
@ -121,7 +121,7 @@ class SMTPAction(ActionBase):
|
||||||
self.matches = matches
|
self.matches = matches
|
||||||
|
|
||||||
self.message_values = CallingMap(
|
self.message_values = CallingMap(
|
||||||
jailname = self._jail.getName(), # Doesn't change
|
jailname = self._jail.name,
|
||||||
hostname = socket.gethostname,
|
hostname = socket.gethostname,
|
||||||
bantime = self._jail.actions.getBanTime,
|
bantime = self._jail.actions.getBanTime,
|
||||||
)
|
)
|
||||||
|
|
|
@ -38,18 +38,17 @@ actionstop =
|
||||||
|
|
||||||
actioncheck =
|
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>
|
IP=<ip>
|
||||||
FROM=<sender>
|
FROM=<sender>
|
||||||
SERVICE=<service>
|
SERVICE=<service>
|
||||||
FAILURES=<failures>
|
FAILURES=<failures>
|
||||||
MATCHES='<matches>'
|
|
||||||
REPORTID=<time>@`uname -n`
|
REPORTID=<time>@`uname -n`
|
||||||
TLP=<tlp>
|
TLP=<tlp>
|
||||||
PORT=<port>
|
PORT=<port>
|
||||||
DATE=`LC_TIME=C date -u --date=@<time> +"%%a, %%d %%h %%Y %%T +0000"`
|
DATE=`LC_TIME=C date -u --date=@<time> +"%%a, %%d %%h %%Y %%T +0000"`
|
||||||
if [ ! -z "$ADDRESSES" ]; then
|
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)';
|
date '+Note: Local timezone is %%z (%%Z)';
|
||||||
printf -- %%b "<ipmatches>\n\n<footer>") | <mailcmd> <mailargs> ${ADDRESSES//,/\" \"}
|
printf -- %%b "<ipmatches>\n\n<footer>") | <mailcmd> <mailargs> ${ADDRESSES//,/\" \"}
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -6,20 +6,22 @@
|
||||||
# file, but provide customizations in fail2ban.local file, e.g.:
|
# file, but provide customizations in fail2ban.local file, e.g.:
|
||||||
#
|
#
|
||||||
# [Definition]
|
# [Definition]
|
||||||
# loglevel = 4
|
# loglevel = DEBUG
|
||||||
#
|
#
|
||||||
|
|
||||||
[Definition]
|
[Definition]
|
||||||
|
|
||||||
# Option: loglevel
|
# Option: loglevel
|
||||||
# Notes.: Set the log level output.
|
# Notes.: Set the log level output.
|
||||||
# 1 = ERROR
|
# CRITICAL
|
||||||
# 2 = WARN
|
# ERROR
|
||||||
# 3 = INFO
|
# WARNING
|
||||||
# 4 = DEBUG
|
# NOTICE
|
||||||
# Values: [ NUM ] Default: 1
|
# INFO
|
||||||
|
# DEBUG
|
||||||
|
# Values: [ LEVEL ] Default: ERROR
|
||||||
#
|
#
|
||||||
loglevel = 3
|
loglevel = INFO
|
||||||
|
|
||||||
# Option: logtarget
|
# Option: logtarget
|
||||||
# Notes.: Set the log target. This could be a file, SYSLOG, STDERR or STDOUT.
|
# Notes.: Set the log target. This could be a file, SYSLOG, STDERR or STDOUT.
|
||||||
|
|
|
@ -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
|
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]
|
[qmail-rbl]
|
||||||
|
|
||||||
filter = qmail
|
filter = qmail
|
||||||
|
|
|
@ -74,12 +74,6 @@ further configuration. To run not as root, further setup is necessary:
|
||||||
|
|
||||||
with <name> suitably replaced.
|
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:
|
- Permissions:
|
||||||
|
|
||||||
make sure that configuration files under /etc/fail2ban are readable by
|
make sure that configuration files under /etc/fail2ban are readable by
|
||||||
|
|
|
@ -24,7 +24,47 @@ __author__ = "Cyril Jaquier"
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import logging
|
import logging.handlers
|
||||||
|
|
||||||
# Custom debug level
|
# Custom debug level
|
||||||
logging.HEAVYDEBUG = 5
|
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
|
msg = "logs: " + response
|
||||||
elif inC[0:1] == ['status']:
|
elif inC[0:1] == ['status']:
|
||||||
if len(inC) > 1:
|
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
|
# Display information
|
||||||
msg = "Status for the jail: " + inC[1] + "\n"
|
msg = ["Status for the jail: %s" % inC[1]]
|
||||||
msg = msg + "|- " + response[0][0] + "\n"
|
for n, res1 in enumerate(response):
|
||||||
msg = msg + "| |- " + response[0][1][2][0] + ":\t" + fileList + "\n"
|
prefix1 = "`-" if n == len(response) - 1 else "|-"
|
||||||
msg = msg + "| |- " + response[0][1][0][0] + ":\t" + `response[0][1][0][1]` + "\n"
|
msg.append("%s %s" % (prefix1, res1[0]))
|
||||||
msg = msg + "| `- " + response[0][1][1][0] + ":\t" + `response[0][1][1][1]` + "\n"
|
prefix1 = " " if n == len(response) - 1 else "| "
|
||||||
msg = msg + "`- " + response[1][0] + "\n"
|
for m, res2 in enumerate(res1[1]):
|
||||||
msg = msg + " |- " + response[1][1][0][0] + ":\t" + `response[1][1][0][1]` + "\n"
|
prefix2 = prefix1 + ("`-" if m == len(res1[1]) - 1 else "|-")
|
||||||
msg = msg + " | `- " + response[1][1][2][0] + ":\t" + ipList + "\n"
|
val = " ".join(res2[1]) if isinstance(res2[1], list) else res2[1]
|
||||||
msg = msg + " `- " + response[1][1][1][0] + ":\t" + `response[1][1][1][1]`
|
msg.append("%s %s:\t%s" % (prefix2, res2[0], val))
|
||||||
else:
|
else:
|
||||||
msg = "Status\n"
|
msg = ["Status"]
|
||||||
msg = msg + "|- " + response[0][0] + ":\t" + `response[0][1]` + "\n"
|
for n, res1 in enumerate(response):
|
||||||
msg = msg + "`- " + response[1][0] + ":\t\t" + response[1][1]
|
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":
|
elif inC[1] == "logtarget":
|
||||||
msg = "Current logging target is:\n"
|
msg = "Current logging target is:\n"
|
||||||
msg = msg + "`- " + response
|
msg = msg + "`- " + response
|
||||||
|
|
|
@ -54,7 +54,7 @@ class ConfigReader(SafeConfigParserWithIncludes):
|
||||||
raise ValueError("Base configuration directory %s does not exist "
|
raise ValueError("Base configuration directory %s does not exist "
|
||||||
% self._basedir)
|
% self._basedir)
|
||||||
basename = os.path.join(self._basedir, filename)
|
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" ]
|
config_files = [ basename + ".conf" ]
|
||||||
|
|
||||||
# possible further customizations under a .conf.d directory
|
# possible further customizations under a .conf.d directory
|
||||||
|
|
|
@ -45,7 +45,7 @@ class Fail2banReader(ConfigReader):
|
||||||
return ConfigReader.getOptions(self, "Definition", opts)
|
return ConfigReader.getOptions(self, "Definition", opts)
|
||||||
|
|
||||||
def getOptions(self):
|
def getOptions(self):
|
||||||
opts = [["int", "loglevel", 1],
|
opts = [["int", "loglevel", "INFO" ],
|
||||||
["string", "logtarget", "STDERR"],
|
["string", "logtarget", "STDERR"],
|
||||||
["string", "dbfile", "/var/lib/fail2ban/fail2ban.sqlite3"],
|
["string", "dbfile", "/var/lib/fail2ban/fail2ban.sqlite3"],
|
||||||
["int", "dbpurgeage", 86400]]
|
["int", "dbpurgeage", 86400]]
|
||||||
|
|
|
@ -39,7 +39,7 @@ protocol = [
|
||||||
["ping", "tests if the server is alive"],
|
["ping", "tests if the server is alive"],
|
||||||
["help", "return this output"],
|
["help", "return this output"],
|
||||||
['', "LOGGING", ""],
|
['', "LOGGING", ""],
|
||||||
["set loglevel <LEVEL>", "sets logging level to <LEVEL>. 0 is minimal, 4 is debug"],
|
["set loglevel <LEVEL>", "sets logging level to <LEVEL>. Levels: CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG"],
|
||||||
["get loglevel", "gets the logging level"],
|
["get loglevel", "gets the logging level"],
|
||||||
["set logtarget <TARGET>", "sets logging target to <TARGET>. Can be STDOUT, STDERR, SYSLOG or a file"],
|
["set logtarget <TARGET>", "sets logging target to <TARGET>. Can be STDOUT, STDERR, SYSLOG or a file"],
|
||||||
["get logtarget", "gets logging target"],
|
["get logtarget", "gets logging target"],
|
||||||
|
|
|
@ -96,11 +96,28 @@ class ActionBase(object):
|
||||||
place to create a Python based action for Fail2Ban. This class can
|
place to create a Python based action for Fail2Ban. This class can
|
||||||
be inherited from to ease implementation.
|
be inherited from to ease implementation.
|
||||||
Required methods:
|
Required methods:
|
||||||
- __init__(jail, name)
|
|
||||||
- start()
|
- __init__(jail, name)
|
||||||
- stop()
|
- start()
|
||||||
- ban(aInfo)
|
- stop()
|
||||||
- unban(aInfo)
|
- 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
|
__metaclass__ = ABCMeta
|
||||||
|
|
||||||
|
@ -118,24 +135,6 @@ class ActionBase(object):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def __init__(self, jail, name):
|
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._jail = jail
|
||||||
self._name = name
|
self._name = name
|
||||||
self._logSys = logging.getLogger(
|
self._logSys = logging.getLogger(
|
||||||
|
@ -177,22 +176,27 @@ class CommandAction(ActionBase):
|
||||||
"""A action which executes OS shell commands.
|
"""A action which executes OS shell commands.
|
||||||
|
|
||||||
This is the default type of action which Fail2Ban uses.
|
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):
|
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)
|
super(CommandAction, self).__init__(jail, name)
|
||||||
self.timeout = 60
|
self.timeout = 60
|
||||||
## Command executed in order to initialize the system.
|
## Command executed in order to initialize the system.
|
||||||
|
@ -482,7 +486,7 @@ class CommandAction(ActionBase):
|
||||||
self.stop()
|
self.stop()
|
||||||
self.start()
|
self.start()
|
||||||
if not self.executeCmd(checkCmd, self.timeout):
|
if not self.executeCmd(checkCmd, self.timeout):
|
||||||
self._logSys.fatal("Unable to restore environment")
|
self._logSys.critical("Unable to restore environment")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Replace tags
|
# Replace tags
|
||||||
|
|
|
@ -49,16 +49,27 @@ class Actions(JailThread, Mapping):
|
||||||
Mapping type, and the `add` method must be used to add new actions.
|
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
|
This class also starts and stops the actions, and fetches bans from
|
||||||
the jail executing these bans via the actions.
|
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):
|
def __init__(self, jail):
|
||||||
"""Initialise an empty Actions instance.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
jail: Jail
|
|
||||||
The jail of which the actions belongs to.
|
|
||||||
"""
|
|
||||||
JailThread.__init__(self)
|
JailThread.__init__(self)
|
||||||
## The jail which contains this action.
|
## The jail which contains this action.
|
||||||
self._jail = jail
|
self._jail = jail
|
||||||
|
@ -192,30 +203,29 @@ class Actions(JailThread, Mapping):
|
||||||
bool
|
bool
|
||||||
True when the thread exits nicely.
|
True when the thread exits nicely.
|
||||||
"""
|
"""
|
||||||
self.setActive(True)
|
|
||||||
for name, action in self._actions.iteritems():
|
for name, action in self._actions.iteritems():
|
||||||
try:
|
try:
|
||||||
action.start()
|
action.start()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logSys.error("Failed to start jail '%s' action '%s': %s",
|
logSys.error("Failed to start jail '%s' action '%s': %s",
|
||||||
self._jail.getName(), name, e)
|
self._jail.name, name, e)
|
||||||
while self._isActive():
|
while self.active:
|
||||||
if not self.getIdle():
|
if not self.idle:
|
||||||
#logSys.debug(self._jail.getName() + ": action")
|
#logSys.debug(self._jail.name + ": action")
|
||||||
ret = self.__checkBan()
|
ret = self.__checkBan()
|
||||||
if not ret:
|
if not ret:
|
||||||
self.__checkUnBan()
|
self.__checkUnBan()
|
||||||
time.sleep(self.getSleepTime())
|
time.sleep(self.sleeptime)
|
||||||
else:
|
else:
|
||||||
time.sleep(self.getSleepTime())
|
time.sleep(self.sleeptime)
|
||||||
self.__flushBan()
|
self.__flushBan()
|
||||||
for name, action in self._actions.iteritems():
|
for name, action in self._actions.iteritems():
|
||||||
try:
|
try:
|
||||||
action.stop()
|
action.stop()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logSys.error("Failed to stop jail '%s' action '%s': %s",
|
logSys.error("Failed to stop jail '%s' action '%s': %s",
|
||||||
self._jail.getName(), name, e)
|
self._jail.name, name, e)
|
||||||
logSys.debug(self._jail.getName() + ": action terminated")
|
logSys.debug(self._jail.name + ": action terminated")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def __checkBan(self):
|
def __checkBan(self):
|
||||||
|
@ -237,31 +247,31 @@ class Actions(JailThread, Mapping):
|
||||||
aInfo["failures"] = bTicket.getAttempt()
|
aInfo["failures"] = bTicket.getAttempt()
|
||||||
aInfo["time"] = bTicket.getTime()
|
aInfo["time"] = bTicket.getTime()
|
||||||
aInfo["matches"] = "\n".join(bTicket.getMatches())
|
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(
|
aInfo["ipmatches"] = lambda: "\n".join(
|
||||||
self._jail.getDatabase().getBansMerged(
|
self._jail.database.getBansMerged(
|
||||||
ip=bTicket.getIP()).getMatches())
|
ip=bTicket.getIP()).getMatches())
|
||||||
aInfo["ipjailmatches"] = lambda: "\n".join(
|
aInfo["ipjailmatches"] = lambda: "\n".join(
|
||||||
self._jail.getDatabase().getBansMerged(
|
self._jail.database.getBansMerged(
|
||||||
ip=bTicket.getIP(), jail=self._jail).getMatches())
|
ip=bTicket.getIP(), jail=self._jail).getMatches())
|
||||||
aInfo["ipfailures"] = lambda: "\n".join(
|
aInfo["ipfailures"] = lambda: "\n".join(
|
||||||
self._jail.getDatabase().getBansMerged(
|
self._jail.database.getBansMerged(
|
||||||
ip=bTicket.getIP()).getAttempt())
|
ip=bTicket.getIP()).getAttempt())
|
||||||
aInfo["ipjailfailures"] = lambda: "\n".join(
|
aInfo["ipjailfailures"] = lambda: "\n".join(
|
||||||
self._jail.getDatabase().getBansMerged(
|
self._jail.database.getBansMerged(
|
||||||
ip=bTicket.getIP(), jail=self._jail).getAttempt())
|
ip=bTicket.getIP(), jail=self._jail).getAttempt())
|
||||||
if self.__banManager.addBanTicket(bTicket):
|
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():
|
for name, action in self._actions.iteritems():
|
||||||
try:
|
try:
|
||||||
action.ban(aInfo)
|
action.ban(aInfo)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logSys.error(
|
logSys.error(
|
||||||
"Failed to execute ban jail '%s' action '%s': %s",
|
"Failed to execute ban jail '%s' action '%s': %s",
|
||||||
self._jail.getName(), name, e)
|
self._jail.name, name, e)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
logSys.info("[%s] %s already banned" % (self._jail.getName(),
|
logSys.notice("[%s] %s already banned" % (self._jail.name,
|
||||||
aInfo["ip"]))
|
aInfo["ip"]))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -298,28 +308,20 @@ class Actions(JailThread, Mapping):
|
||||||
aInfo["failures"] = ticket.getAttempt()
|
aInfo["failures"] = ticket.getAttempt()
|
||||||
aInfo["time"] = ticket.getTime()
|
aInfo["time"] = ticket.getTime()
|
||||||
aInfo["matches"] = "".join(ticket.getMatches())
|
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():
|
for name, action in self._actions.iteritems():
|
||||||
try:
|
try:
|
||||||
action.unban(aInfo)
|
action.unban(aInfo)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logSys.error(
|
logSys.error(
|
||||||
"Failed to execute unban jail '%s' action '%s': %s",
|
"Failed to execute unban jail '%s' action '%s': %s",
|
||||||
self._jail.getName(), name, e)
|
self._jail.name, name, e)
|
||||||
|
|
||||||
|
@property
|
||||||
def status(self):
|
def status(self):
|
||||||
"""Get the status of the filter.
|
"""Status of active bans, and total ban counts.
|
||||||
|
|
||||||
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.
|
|
||||||
"""
|
"""
|
||||||
ret = [("Currently banned", self.__banManager.size()),
|
ret = [("Currently banned", self.__banManager.size()),
|
||||||
("Total banned", self.__banManager.getBanTotal()),
|
("Total banned", self.__banManager.getBanTotal()),
|
||||||
("IP list", self.__banManager.getBanList())]
|
("Banned IP list", self.__banManager.getBanList())]
|
||||||
return ret
|
return ret
|
||||||
|
|
|
@ -58,6 +58,35 @@ def commitandrollback(f):
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
class Fail2BanDb(object):
|
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
|
__version__ = 2
|
||||||
# Note all _TABLE_* strings must end in ';' for py26 compatibility
|
# Note all _TABLE_* strings must end in ';' for py26 compatibility
|
||||||
_TABLE_fail2banDb = "CREATE TABLE fail2banDb(version INTEGER);"
|
_TABLE_fail2banDb = "CREATE TABLE fail2banDb(version INTEGER);"
|
||||||
|
@ -130,21 +159,30 @@ class Fail2BanDb(object):
|
||||||
logSys.error( "Database update failed to acheive version '%i'"
|
logSys.error( "Database update failed to acheive version '%i'"
|
||||||
": updated from '%i' to '%i'",
|
": updated from '%i' to '%i'",
|
||||||
Fail2BanDb.__version__, version, newversion)
|
Fail2BanDb.__version__, version, newversion)
|
||||||
raise Exception('Failed to fully update')
|
raise RuntimeError('Failed to fully update')
|
||||||
finally:
|
finally:
|
||||||
cur.close()
|
cur.close()
|
||||||
|
|
||||||
def getFilename(self):
|
@property
|
||||||
|
def filename(self):
|
||||||
|
"""File name of SQLite3 database file.
|
||||||
|
"""
|
||||||
return self._dbFilename
|
return self._dbFilename
|
||||||
|
|
||||||
def getPurgeAge(self):
|
@property
|
||||||
|
def purgeage(self):
|
||||||
|
"""Purge age in seconds.
|
||||||
|
"""
|
||||||
return self._purgeAge
|
return self._purgeAge
|
||||||
|
|
||||||
def setPurgeAge(self, value):
|
@purgeage.setter
|
||||||
|
def purgeage(self, value):
|
||||||
self._purgeAge = int(value)
|
self._purgeAge = int(value)
|
||||||
|
|
||||||
@commitandrollback
|
@commitandrollback
|
||||||
def createDb(self, cur):
|
def createDb(self, cur):
|
||||||
|
"""Creates a new database, called during initialisation.
|
||||||
|
"""
|
||||||
# Version info
|
# Version info
|
||||||
cur.executescript(Fail2BanDb._TABLE_fail2banDb)
|
cur.executescript(Fail2BanDb._TABLE_fail2banDb)
|
||||||
cur.execute("INSERT INTO fail2banDb(version) VALUES(?)",
|
cur.execute("INSERT INTO fail2banDb(version) VALUES(?)",
|
||||||
|
@ -161,8 +199,13 @@ class Fail2BanDb(object):
|
||||||
|
|
||||||
@commitandrollback
|
@commitandrollback
|
||||||
def updateDb(self, cur, version):
|
def updateDb(self, cur, version):
|
||||||
self.dbBackupFilename = self._dbFilename + '.' + time.strftime('%Y%m%d-%H%M%S', MyTime.gmtime())
|
"""Update an existing database, called during initialisation.
|
||||||
shutil.copyfile(self._dbFilename, self.dbBackupFilename)
|
|
||||||
|
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__:
|
if version > Fail2BanDb.__version__:
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
"Attempt to travel to future version of database ...how did you get here??")
|
"Attempt to travel to future version of database ...how did you get here??")
|
||||||
|
@ -182,36 +225,73 @@ class Fail2BanDb(object):
|
||||||
|
|
||||||
@commitandrollback
|
@commitandrollback
|
||||||
def addJail(self, cur, jail):
|
def addJail(self, cur, jail):
|
||||||
|
"""Adds a jail to the database.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
jail : Jail
|
||||||
|
Jail to be added to the database.
|
||||||
|
"""
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"INSERT OR REPLACE INTO jails(name, enabled) VALUES(?, 1)",
|
"INSERT OR REPLACE INTO jails(name, enabled) VALUES(?, 1)",
|
||||||
(jail.getName(),))
|
(jail.name,))
|
||||||
|
|
||||||
def delJail(self, jail):
|
|
||||||
return self.delJailName(jail.getName())
|
|
||||||
|
|
||||||
@commitandrollback
|
@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
|
# Will be deleted by purge as appropriate
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"UPDATE jails SET enabled=0 WHERE name=?", (name, ))
|
"UPDATE jails SET enabled=0 WHERE name=?", (jail.name, ))
|
||||||
|
|
||||||
@commitandrollback
|
@commitandrollback
|
||||||
def delAllJails(self, cur):
|
def delAllJails(self, cur):
|
||||||
|
"""Deletes all jails from the database.
|
||||||
|
"""
|
||||||
# Will be deleted by purge as appropriate
|
# Will be deleted by purge as appropriate
|
||||||
cur.execute("UPDATE jails SET enabled=0")
|
cur.execute("UPDATE jails SET enabled=0")
|
||||||
|
|
||||||
@commitandrollback
|
@commitandrollback
|
||||||
def getJailNames(self, cur):
|
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")
|
cur.execute("SELECT name FROM jails")
|
||||||
return set(row[0] for row in cur.fetchmany())
|
return set(row[0] for row in cur.fetchmany())
|
||||||
|
|
||||||
@commitandrollback
|
@commitandrollback
|
||||||
def addLog(self, cur, jail, container):
|
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
|
lastLinePos = None
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"SELECT firstlinemd5, lastfilepos FROM logs "
|
"SELECT firstlinemd5, lastfilepos FROM logs "
|
||||||
"WHERE jail=? AND path=?",
|
"WHERE jail=? AND path=?",
|
||||||
(jail.getName(), container.getFileName()))
|
(jail.name, container.getFileName()))
|
||||||
try:
|
try:
|
||||||
firstLineMD5, lastLinePos = cur.fetchone()
|
firstLineMD5, lastLinePos = cur.fetchone()
|
||||||
except TypeError:
|
except TypeError:
|
||||||
|
@ -220,7 +300,7 @@ class Fail2BanDb(object):
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"INSERT OR REPLACE INTO logs(jail, path, firstlinemd5, lastfilepos) "
|
"INSERT OR REPLACE INTO logs(jail, path, firstlinemd5, lastfilepos) "
|
||||||
"VALUES(?, ?, ?, ?)",
|
"VALUES(?, ?, ?, ?)",
|
||||||
(jail.getName(), container.getFileName(),
|
(jail.name, container.getFileName(),
|
||||||
container.getHash(), container.getPos()))
|
container.getHash(), container.getPos()))
|
||||||
if container.getHash() != firstLineMD5:
|
if container.getHash() != firstLineMD5:
|
||||||
lastLinePos = None
|
lastLinePos = None
|
||||||
|
@ -228,16 +308,39 @@ class Fail2BanDb(object):
|
||||||
|
|
||||||
@commitandrollback
|
@commitandrollback
|
||||||
def getLogPaths(self, cur, jail=None):
|
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"
|
query = "SELECT path FROM logs"
|
||||||
queryArgs = []
|
queryArgs = []
|
||||||
if jail is not None:
|
if jail is not None:
|
||||||
query += " WHERE jail=?"
|
query += " WHERE jail=?"
|
||||||
queryArgs.append(jail.getName())
|
queryArgs.append(jail.name)
|
||||||
cur.execute(query, queryArgs)
|
cur.execute(query, queryArgs)
|
||||||
return set(row[0] for row in cur.fetchmany())
|
return set(row[0] for row in cur.fetchmany())
|
||||||
|
|
||||||
@commitandrollback
|
@commitandrollback
|
||||||
def updateLog(self, cur, *args, **kwargs):
|
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)
|
self._updateLog(cur, *args, **kwargs)
|
||||||
|
|
||||||
def _updateLog(self, cur, jail, container):
|
def _updateLog(self, cur, jail, container):
|
||||||
|
@ -245,15 +348,24 @@ class Fail2BanDb(object):
|
||||||
"UPDATE logs SET firstlinemd5=?, lastfilepos=? "
|
"UPDATE logs SET firstlinemd5=?, lastfilepos=? "
|
||||||
"WHERE jail=? AND path=?",
|
"WHERE jail=? AND path=?",
|
||||||
(container.getHash(), container.getPos(),
|
(container.getHash(), container.getPos(),
|
||||||
jail.getName(), container.getFileName()))
|
jail.name, container.getFileName()))
|
||||||
|
|
||||||
@commitandrollback
|
@commitandrollback
|
||||||
def addBan(self, cur, jail, ticket):
|
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 = {}
|
self._bansMergedCache = {}
|
||||||
#TODO: Implement data parts once arbitrary match keys completed
|
#TODO: Implement data parts once arbitrary match keys completed
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"INSERT INTO bans(jail, ip, timeofban, data) VALUES(?, ?, ?, ?)",
|
"INSERT INTO bans(jail, ip, timeofban, data) VALUES(?, ?, ?, ?)",
|
||||||
(jail.getName(), ticket.getIP(), ticket.getTime(),
|
(jail.name, ticket.getIP(), ticket.getTime(),
|
||||||
{"matches": ticket.getMatches(),
|
{"matches": ticket.getMatches(),
|
||||||
"failures": ticket.getAttempt()}))
|
"failures": ticket.getAttempt()}))
|
||||||
|
|
||||||
|
@ -264,7 +376,7 @@ class Fail2BanDb(object):
|
||||||
|
|
||||||
if jail is not None:
|
if jail is not None:
|
||||||
query += " AND jail=?"
|
query += " AND jail=?"
|
||||||
queryArgs.append(jail.getName())
|
queryArgs.append(jail.name)
|
||||||
if bantime is not None:
|
if bantime is not None:
|
||||||
query += " AND timeofban > ?"
|
query += " AND timeofban > ?"
|
||||||
queryArgs.append(MyTime.time() - bantime)
|
queryArgs.append(MyTime.time() - bantime)
|
||||||
|
@ -276,6 +388,23 @@ class Fail2BanDb(object):
|
||||||
return cur.execute(query, queryArgs)
|
return cur.execute(query, queryArgs)
|
||||||
|
|
||||||
def getBans(self, **kwargs):
|
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 = []
|
tickets = []
|
||||||
for ip, timeofban, data in self._getBans(**kwargs):
|
for ip, timeofban, data in self._getBans(**kwargs):
|
||||||
#TODO: Implement data parts once arbitrary match keys completed
|
#TODO: Implement data parts once arbitrary match keys completed
|
||||||
|
@ -284,7 +413,27 @@ class Fail2BanDb(object):
|
||||||
return tickets
|
return tickets
|
||||||
|
|
||||||
def getBansMerged(self, ip, jail=None, **kwargs):
|
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:
|
if cacheKey in self._bansMergedCache:
|
||||||
return self._bansMergedCache[cacheKey]
|
return self._bansMergedCache[cacheKey]
|
||||||
matches = []
|
matches = []
|
||||||
|
@ -300,6 +449,8 @@ class Fail2BanDb(object):
|
||||||
|
|
||||||
@commitandrollback
|
@commitandrollback
|
||||||
def purge(self, cur):
|
def purge(self, cur):
|
||||||
|
"""Purge old bans, jails and log files from database.
|
||||||
|
"""
|
||||||
self._bansMergedCache = {}
|
self._bansMergedCache = {}
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"DELETE FROM bans WHERE timeofban < ?",
|
"DELETE FROM bans WHERE timeofban < ?",
|
||||||
|
|
|
@ -31,11 +31,13 @@ logSys = logging.getLogger(__name__)
|
||||||
|
|
||||||
class DateDetector(object):
|
class DateDetector(object):
|
||||||
"""Manages one or more date templates to find a date within a log line.
|
"""Manages one or more date templates to find a date within a log line.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
templates
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialise the date detector.
|
|
||||||
"""
|
|
||||||
self.__lock = Lock()
|
self.__lock = Lock()
|
||||||
self.__templates = list()
|
self.__templates = list()
|
||||||
self.__known_names = set()
|
self.__known_names = set()
|
||||||
|
|
|
@ -41,11 +41,14 @@ class DateTemplate(object):
|
||||||
|
|
||||||
This is an not functional abstract class which other templates should
|
This is an not functional abstract class which other templates should
|
||||||
inherit from.
|
inherit from.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
name
|
||||||
|
regex
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialise the date template.
|
|
||||||
"""
|
|
||||||
self._name = ""
|
self._name = ""
|
||||||
self._regex = ""
|
self._regex = ""
|
||||||
self._cRegex = None
|
self._cRegex = None
|
||||||
|
@ -123,11 +126,14 @@ class DateEpoch(DateTemplate):
|
||||||
|
|
||||||
This includes Unix timestamps which appear at start of a line, optionally
|
This includes Unix timestamps which appear at start of a line, optionally
|
||||||
within square braces (nsd), or on SELinux audit log lines.
|
within square braces (nsd), or on SELinux audit log lines.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
name
|
||||||
|
regex
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialise the date template.
|
|
||||||
"""
|
|
||||||
DateTemplate.__init__(self)
|
DateTemplate.__init__(self)
|
||||||
self.regex = "(?:^|(?P<square>(?<=^\[))|(?P<selinux>(?<=audit\()))\d{10}(?:\.\d{3,6})?(?(selinux)(?=:\d+\))(?(square)(?=\])))"
|
self.regex = "(?:^|(?P<square>(?<=^\[))|(?P<selinux>(?<=audit\()))\d{10}(?:\.\d{3,6})?(?(selinux)(?=:\d+\))(?(square)(?=\])))"
|
||||||
|
|
||||||
|
@ -152,6 +158,19 @@ class DateEpoch(DateTemplate):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
class DatePatternRegex(DateTemplate):
|
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())
|
_patternRE = r"%%(%%|[%s])" % "".join(timeRE.keys())
|
||||||
_patternName = {
|
_patternName = {
|
||||||
'a': "DAY", 'A': "DAYNAME", 'b': "MON", 'B': "MONTH", 'd': "Day",
|
'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",
|
'M': "Minute", 'p': "AMPM", 'S': "Second", 'U': "Yearweek",
|
||||||
'w': "Weekday", 'W': "Yearweek", 'y': 'Year2', 'Y': "Year", '%': "%",
|
'w': "Weekday", 'W': "Yearweek", 'y': 'Year2', 'Y': "Year", '%': "%",
|
||||||
'z': "Zone offset", 'f': "Microseconds", 'Z': "Zone name"}
|
'z': "Zone offset", 'f': "Microseconds", 'Z': "Zone name"}
|
||||||
for key in set(timeRE) - set(_patternName): # may not have them all...
|
for _key in set(timeRE) - set(_patternName): # may not have them all...
|
||||||
_patternName[key] = "%%%s" % key
|
_patternName[_key] = "%%%s" % _key
|
||||||
|
|
||||||
def __init__(self, pattern=None):
|
def __init__(self, pattern=None):
|
||||||
"""Initialise date template, with optional regex/pattern
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
pattern : str
|
|
||||||
Sets the date templates pattern.
|
|
||||||
"""
|
|
||||||
super(DatePatternRegex, self).__init__()
|
super(DatePatternRegex, self).__init__()
|
||||||
self._pattern = None
|
self._pattern = None
|
||||||
if pattern is not None:
|
if pattern is not None:
|
||||||
|
@ -229,11 +241,14 @@ class DatePatternRegex(DateTemplate):
|
||||||
|
|
||||||
class DateTai64n(DateTemplate):
|
class DateTai64n(DateTemplate):
|
||||||
"""A date template which matches TAI64N formate timestamps.
|
"""A date template which matches TAI64N formate timestamps.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
name
|
||||||
|
regex
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialise the date template.
|
|
||||||
"""
|
|
||||||
DateTemplate.__init__(self)
|
DateTemplate.__init__(self)
|
||||||
# We already know the format for TAI64N
|
# We already know the format for TAI64N
|
||||||
# yoh: we should not add an additional front anchor
|
# yoh: we should not add an additional front anchor
|
||||||
|
|
|
@ -357,6 +357,9 @@ class Filter(JailThread):
|
||||||
# IP address without CIDR mask
|
# IP address without CIDR mask
|
||||||
if len(s) == 1:
|
if len(s) == 1:
|
||||||
s.insert(1, '32')
|
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])
|
s[1] = long(s[1])
|
||||||
try:
|
try:
|
||||||
a = DNSUtils.cidr(s[0], s[1])
|
a = DNSUtils.cidr(s[0], s[1])
|
||||||
|
@ -416,9 +419,9 @@ class Filter(JailThread):
|
||||||
% (unixTime, MyTime.time(), self.getFindTime()))
|
% (unixTime, MyTime.time(), self.getFindTime()))
|
||||||
break
|
break
|
||||||
if self.inIgnoreIPList(ip):
|
if self.inIgnoreIPList(ip):
|
||||||
logSys.debug("Ignore %s" % ip)
|
logSys.info("[%s] Ignore %s" % (self.jail.name, ip))
|
||||||
continue
|
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]),)
|
## print "D: Adding a ticket for %s" % ((ip, unixTime, [line]),)
|
||||||
self.failManager.addFailure(FailTicket(ip, unixTime, lines))
|
self.failManager.addFailure(FailTicket(ip, unixTime, lines))
|
||||||
|
|
||||||
|
@ -497,7 +500,7 @@ class Filter(JailThread):
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
if date is None:
|
if date is None:
|
||||||
logSys.debug(
|
logSys.warning(
|
||||||
"Found a match for %r but no valid date/time "
|
"Found a match for %r but no valid date/time "
|
||||||
"found for %r. Please try setting a custom "
|
"found for %r. Please try setting a custom "
|
||||||
"date pattern (see man page jail.conf(5)). "
|
"date pattern (see man page jail.conf(5)). "
|
||||||
|
@ -527,15 +530,10 @@ class Filter(JailThread):
|
||||||
logSys.error(e)
|
logSys.error(e)
|
||||||
return failList
|
return failList
|
||||||
|
|
||||||
|
@property
|
||||||
##
|
|
||||||
# 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
|
|
||||||
|
|
||||||
def status(self):
|
def status(self):
|
||||||
|
"""Status of failures detected by filter.
|
||||||
|
"""
|
||||||
ret = [("Currently failed", self.failManager.size()),
|
ret = [("Currently failed", self.failManager.size()),
|
||||||
("Total failed", self.failManager.getFailTotal())]
|
("Total failed", self.failManager.getFailTotal())]
|
||||||
return ret
|
return ret
|
||||||
|
@ -559,7 +557,7 @@ class FileFilter(Filter):
|
||||||
logSys.error(path + " already exists")
|
logSys.error(path + " already exists")
|
||||||
else:
|
else:
|
||||||
container = FileContainer(path, self.getLogEncoding(), tail)
|
container = FileContainer(path, self.getLogEncoding(), tail)
|
||||||
db = self.jail.getDatabase()
|
db = self.jail.database
|
||||||
if db is not None:
|
if db is not None:
|
||||||
lastpos = db.addLog(self.jail, container)
|
lastpos = db.addLog(self.jail, container)
|
||||||
if lastpos and not tail:
|
if lastpos and not tail:
|
||||||
|
@ -583,7 +581,7 @@ class FileFilter(Filter):
|
||||||
for log in self.__logPath:
|
for log in self.__logPath:
|
||||||
if log.getFileName() == path:
|
if log.getFileName() == path:
|
||||||
self.__logPath.remove(log)
|
self.__logPath.remove(log)
|
||||||
db = self.jail.getDatabase()
|
db = self.jail.database
|
||||||
if db is not None:
|
if db is not None:
|
||||||
db.updateLog(self.jail, log)
|
db.updateLog(self.jail, log)
|
||||||
logSys.info("Removed logfile = %s" % path)
|
logSys.info("Removed logfile = %s" % path)
|
||||||
|
@ -679,18 +677,21 @@ class FileFilter(Filter):
|
||||||
# might occur leading at least to tests failures.
|
# might occur leading at least to tests failures.
|
||||||
while has_content:
|
while has_content:
|
||||||
line = container.readline()
|
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
|
# The jail reached the bottom or has been stopped
|
||||||
break
|
break
|
||||||
self.processLineAndAdd(line)
|
self.processLineAndAdd(line)
|
||||||
container.close()
|
container.close()
|
||||||
db = self.jail.getDatabase()
|
db = self.jail.database
|
||||||
if db is not None:
|
if db is not None:
|
||||||
db.updateLog(self.jail, container)
|
db.updateLog(self.jail, container)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
def status(self):
|
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()]
|
path = [m.getFileName() for m in self.getLogPath()]
|
||||||
ret.append(("File list", path))
|
ret.append(("File list", path))
|
||||||
return ret
|
return ret
|
||||||
|
@ -776,7 +777,7 @@ class FileContainer:
|
||||||
## sys.stdout.flush()
|
## sys.stdout.flush()
|
||||||
# Compare hash and inode
|
# Compare hash and inode
|
||||||
if self.__hash != myHash or self.__ino != stats.st_ino:
|
if self.__hash != myHash or self.__ino != stats.st_ino:
|
||||||
logSys.debug("Log rotation detected for %s" % self.__filename)
|
logSys.info("Log rotation detected for %s" % self.__filename)
|
||||||
self.__hash = myHash
|
self.__hash = myHash
|
||||||
self.__ino = stats.st_ino
|
self.__ino = stats.st_ino
|
||||||
self.__pos = 0
|
self.__pos = 0
|
||||||
|
|
|
@ -109,16 +109,15 @@ class FilterGamin(FileFilter):
|
||||||
# @return True when the thread exits nicely
|
# @return True when the thread exits nicely
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.setActive(True)
|
|
||||||
# Gamin needs a loop to collect and dispatch events
|
# Gamin needs a loop to collect and dispatch events
|
||||||
while self._isActive():
|
while self.active:
|
||||||
if not self.getIdle():
|
if not self.idle:
|
||||||
# We cannot block here because we want to be able to
|
# We cannot block here because we want to be able to
|
||||||
# exit.
|
# exit.
|
||||||
if self.monitor.event_pending():
|
if self.monitor.event_pending():
|
||||||
self.monitor.handle_events()
|
self.monitor.handle_events()
|
||||||
time.sleep(self.getSleepTime())
|
time.sleep(self.sleeptime)
|
||||||
logSys.debug(self.jail.getName() + ": filter terminated")
|
logSys.debug(self.jail.name + ": filter terminated")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -82,12 +82,11 @@ class FilterPoll(FileFilter):
|
||||||
# @return True when the thread exits nicely
|
# @return True when the thread exits nicely
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.setActive(True)
|
while self.active:
|
||||||
while self._isActive():
|
|
||||||
if logSys.getEffectiveLevel() <= 6:
|
if logSys.getEffectiveLevel() <= 6:
|
||||||
logSys.log(6, "Woke up idle=%s with %d files monitored",
|
logSys.log(6, "Woke up idle=%s with %d files monitored",
|
||||||
self.getIdle(), len(self.getLogPath()))
|
self.idle, len(self.getLogPath()))
|
||||||
if not self.getIdle():
|
if not self.idle:
|
||||||
# Get file modification
|
# Get file modification
|
||||||
for container in self.getLogPath():
|
for container in self.getLogPath():
|
||||||
filename = container.getFileName()
|
filename = container.getFileName()
|
||||||
|
@ -104,11 +103,11 @@ class FilterPoll(FileFilter):
|
||||||
self.failManager.cleanup(MyTime.time())
|
self.failManager.cleanup(MyTime.time())
|
||||||
self.dateDetector.sortTemplate()
|
self.dateDetector.sortTemplate()
|
||||||
self.__modified = False
|
self.__modified = False
|
||||||
time.sleep(self.getSleepTime())
|
time.sleep(self.sleeptime)
|
||||||
else:
|
else:
|
||||||
time.sleep(self.getSleepTime())
|
time.sleep(self.sleeptime)
|
||||||
logSys.debug(
|
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")
|
" filter terminated")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -143,7 +142,7 @@ class FilterPoll(FileFilter):
|
||||||
if self.__file404Cnt[filename] > 2:
|
if self.__file404Cnt[filename] > 2:
|
||||||
logSys.warning("Too many errors. Setting the jail idle")
|
logSys.warning("Too many errors. Setting the jail idle")
|
||||||
if self.jail is not None:
|
if self.jail is not None:
|
||||||
self.jail.setIdle(True)
|
self.jail.idle = True
|
||||||
else:
|
else:
|
||||||
logSys.warning("No jail is assigned to %s" % self)
|
logSys.warning("No jail is assigned to %s" % self)
|
||||||
self.__file404Cnt[filename] = 0
|
self.__file404Cnt[filename] = 0
|
||||||
|
|
|
@ -168,11 +168,10 @@ class FilterPyinotify(FileFilter):
|
||||||
# loop is necessary
|
# loop is necessary
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.setActive(True)
|
|
||||||
self.__notifier = pyinotify.ThreadedNotifier(self.__monitor,
|
self.__notifier = pyinotify.ThreadedNotifier(self.__monitor,
|
||||||
ProcessPyinotify(self))
|
ProcessPyinotify(self))
|
||||||
self.__notifier.start()
|
self.__notifier.start()
|
||||||
logSys.debug("pyinotifier started for %s.", self.jail.getName())
|
logSys.debug("pyinotifier started for %s.", self.jail.name)
|
||||||
# TODO: verify that there is nothing really to be done for
|
# TODO: verify that there is nothing really to be done for
|
||||||
# idle jails
|
# idle jails
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -211,7 +211,6 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
|
||||||
# handover to FailManager
|
# handover to FailManager
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.setActive(True)
|
|
||||||
|
|
||||||
# Seek to now - findtime in journal
|
# Seek to now - findtime in journal
|
||||||
start_time = datetime.datetime.now() - \
|
start_time = datetime.datetime.now() - \
|
||||||
|
@ -224,9 +223,9 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
|
||||||
except OSError:
|
except OSError:
|
||||||
pass # Reading failure, so safe to ignore
|
pass # Reading failure, so safe to ignore
|
||||||
|
|
||||||
while self._isActive():
|
while self.active:
|
||||||
if not self.getIdle():
|
if not self.idle:
|
||||||
while self._isActive():
|
while self.active:
|
||||||
try:
|
try:
|
||||||
logentry = self.__journal.get_next()
|
logentry = self.__journal.get_next()
|
||||||
except OSError:
|
except OSError:
|
||||||
|
@ -247,20 +246,14 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
|
||||||
except FailManagerEmpty:
|
except FailManagerEmpty:
|
||||||
self.failManager.cleanup(MyTime.time())
|
self.failManager.cleanup(MyTime.time())
|
||||||
self.__modified = False
|
self.__modified = False
|
||||||
self.__journal.wait(self.getSleepTime())
|
self.__journal.wait(self.sleeptime)
|
||||||
logSys.debug((self.jail is not None and self.jail.getName()
|
logSys.debug((self.jail is not None and self.jail.name
|
||||||
or "jailless") +" filter terminated")
|
or "jailless") +" filter terminated")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
##
|
@property
|
||||||
# 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
|
|
||||||
|
|
||||||
def status(self):
|
def status(self):
|
||||||
ret = JournalFilter.status(self)
|
ret = super(FilterSystemd, self).status
|
||||||
ret.append(("Journal matches",
|
ret.append(("Journal matches",
|
||||||
[" + ".join(" ".join(match) for match in self.__matches)]))
|
[" + ".join(" ".join(match) for match in self.__matches)]))
|
||||||
return ret
|
return ret
|
||||||
|
|
|
@ -31,6 +31,31 @@ from .actions import Actions
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
||||||
class Jail:
|
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
|
#Known backends. Each backend should have corresponding __initBackend method
|
||||||
# yoh: stored in a list instead of a tuple since only
|
# 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):
|
def __init__(self, name, backend = "auto", db=None):
|
||||||
self.__db = db
|
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.__queue = Queue.Queue()
|
||||||
self.__filter = None
|
self.__filter = None
|
||||||
logSys.info("Creating new jail '%s'" % self.__name)
|
logSys.info("Creating new jail '%s'" % self.name)
|
||||||
self._setBackend(backend)
|
self._setBackend(backend)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "%s(%r)" % (self.__class__.__name__, self.__name)
|
return "%s(%r)" % (self.__class__.__name__, self.name)
|
||||||
|
|
||||||
def _setBackend(self, backend):
|
def _setBackend(self, backend):
|
||||||
backend = backend.lower() # to assure consistent matching
|
backend = backend.lower() # to assure consistent matching
|
||||||
|
@ -78,51 +108,49 @@ class Jail:
|
||||||
"Backend %r failed to initialize due to %s" % (b, e))
|
"Backend %r failed to initialize due to %s" % (b, e))
|
||||||
# log error since runtime error message isn't printed, INVALID COMMAND
|
# log error since runtime error message isn't printed, INVALID COMMAND
|
||||||
logSys.error(
|
logSys.error(
|
||||||
"Failed to initialize any backend for Jail %r" % self.__name)
|
"Failed to initialize any backend for Jail %r" % self.name)
|
||||||
raise RuntimeError(
|
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):
|
def _initPolling(self):
|
||||||
logSys.info("Jail '%s' uses poller" % self.__name)
|
logSys.info("Jail '%s' uses poller" % self.name)
|
||||||
from filterpoll import FilterPoll
|
from filterpoll import FilterPoll
|
||||||
self.__filter = FilterPoll(self)
|
self.__filter = FilterPoll(self)
|
||||||
|
|
||||||
def _initGamin(self):
|
def _initGamin(self):
|
||||||
# Try to import gamin
|
# Try to import gamin
|
||||||
import gamin
|
import gamin
|
||||||
logSys.info("Jail '%s' uses Gamin" % self.__name)
|
logSys.info("Jail '%s' uses Gamin" % self.name)
|
||||||
from filtergamin import FilterGamin
|
from filtergamin import FilterGamin
|
||||||
self.__filter = FilterGamin(self)
|
self.__filter = FilterGamin(self)
|
||||||
|
|
||||||
def _initPyinotify(self):
|
def _initPyinotify(self):
|
||||||
# Try to import pyinotify
|
# Try to import pyinotify
|
||||||
import pyinotify
|
import pyinotify
|
||||||
logSys.info("Jail '%s' uses pyinotify" % self.__name)
|
logSys.info("Jail '%s' uses pyinotify" % self.name)
|
||||||
from filterpyinotify import FilterPyinotify
|
from filterpyinotify import FilterPyinotify
|
||||||
self.__filter = FilterPyinotify(self)
|
self.__filter = FilterPyinotify(self)
|
||||||
|
|
||||||
def _initSystemd(self): # pragma: systemd no cover
|
def _initSystemd(self): # pragma: systemd no cover
|
||||||
# Try to import systemd
|
# Try to import systemd
|
||||||
import systemd
|
import systemd
|
||||||
logSys.info("Jail '%s' uses systemd" % self.__name)
|
logSys.info("Jail '%s' uses systemd" % self.name)
|
||||||
from filtersystemd import FilterSystemd
|
from filtersystemd import FilterSystemd
|
||||||
self.__filter = FilterSystemd(self)
|
self.__filter = FilterSystemd(self)
|
||||||
|
|
||||||
def setName(self, name):
|
@property
|
||||||
# 26 based on iptable chain name limit of 30 less len('f2b-')
|
def name(self):
|
||||||
if len(name) >= 26:
|
"""Name of jail.
|
||||||
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):
|
|
||||||
return self.__name
|
return self.__name
|
||||||
|
|
||||||
def getDatabase(self):
|
@property
|
||||||
|
def database(self):
|
||||||
|
"""The database used to store persistent data for the jail.
|
||||||
|
"""
|
||||||
return self.__db
|
return self.__db
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def filter(self):
|
def filter(self):
|
||||||
"""The filter which the jail is using to monitor log files.
|
"""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.
|
"""Actions object used to manage actions for jail.
|
||||||
"""
|
"""
|
||||||
return self.__actions
|
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):
|
def putFailTicket(self, ticket):
|
||||||
|
"""Add a fail ticket to the jail.
|
||||||
|
|
||||||
|
Used by filter to add a failure for banning.
|
||||||
|
"""
|
||||||
self.__queue.put(ticket)
|
self.__queue.put(ticket)
|
||||||
if self.__db is not None:
|
if self.database is not None:
|
||||||
self.__db.addBan(self, ticket)
|
self.database.addBan(self, ticket)
|
||||||
|
|
||||||
def getFailTicket(self):
|
def getFailTicket(self):
|
||||||
|
"""Get a fail ticket from the jail.
|
||||||
|
|
||||||
|
Used by actions to get a failure for banning.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
return self.__queue.get(False)
|
return self.__queue.get(False)
|
||||||
except Queue.Empty:
|
except Queue.Empty:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self.__filter.start()
|
"""Start the jail, by starting filter and actions threads.
|
||||||
self.__actions.start()
|
|
||||||
|
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
|
# Restore any previous valid bans from the database
|
||||||
if self.__db is not None:
|
if self.database is not None:
|
||||||
for ticket in self.__db.getBans(
|
for ticket in self.database.getBans(
|
||||||
jail=self, bantime=self.__actions.getBanTime()):
|
jail=self, bantime=self.actions.getBanTime()):
|
||||||
self.__queue.put(ticket)
|
self.__queue.put(ticket)
|
||||||
logSys.info("Jail '%s' started" % self.__name)
|
logSys.info("Jail '%s' started" % self.name)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self.__filter.stop()
|
"""Stop the jail, by stopping filter and actions threads.
|
||||||
self.__actions.stop()
|
"""
|
||||||
self.__filter.join()
|
self.filter.stop()
|
||||||
self.__actions.join()
|
self.actions.stop()
|
||||||
logSys.info("Jail '%s' stopped" % self.__name)
|
self.filter.join()
|
||||||
|
self.actions.join()
|
||||||
def isAlive(self):
|
logSys.info("Jail '%s' stopped" % self.name)
|
||||||
isAlive0 = self.__filter.isAlive()
|
|
||||||
isAlive1 = self.__actions.isAlive()
|
def is_alive(self):
|
||||||
return isAlive0 or isAlive1
|
"""Check jail "is_alive" by checking filter and actions threads.
|
||||||
|
"""
|
||||||
def setIdle(self, value):
|
return self.filter.is_alive() or self.actions.is_alive()
|
||||||
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
|
|
||||||
|
|
|
@ -39,8 +39,6 @@ class Jails(Mapping):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialise an empty Jails instance.
|
|
||||||
"""
|
|
||||||
self.__lock = Lock()
|
self.__lock = Lock()
|
||||||
self._jails = dict()
|
self._jails = dict()
|
||||||
|
|
||||||
|
@ -56,6 +54,8 @@ class Jails(Mapping):
|
||||||
The name of the jail.
|
The name of the jail.
|
||||||
backend : str
|
backend : str
|
||||||
The backend to use.
|
The backend to use.
|
||||||
|
db : Fail2BanDb
|
||||||
|
Fail2Ban's persistent database instance.
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
------
|
------
|
||||||
|
|
|
@ -25,94 +25,53 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
import logging
|
from abc import abstractproperty, abstractmethod
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
|
||||||
logSys = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
class JailThread(Thread):
|
class JailThread(Thread):
|
||||||
|
"""Abstract class for threading elements in Fail2Ban.
|
||||||
##
|
|
||||||
# Constructor.
|
Attributes
|
||||||
#
|
----------
|
||||||
# Initialize the filter object with default values.
|
daemon
|
||||||
# @param jail the jail object
|
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):
|
def __init__(self):
|
||||||
Thread.__init__(self)
|
super(JailThread, self).__init__()
|
||||||
## Control the state of the thread.
|
## Control the state of the thread.
|
||||||
self.__isRunning = False
|
self.active = False
|
||||||
## Control the idle state of the thread.
|
## Control the idle state of the thread.
|
||||||
self.__isIdle = False
|
self.idle = False
|
||||||
## The time the thread sleeps in the loop.
|
## The time the thread sleeps in the loop.
|
||||||
self.__sleepTime = 1
|
self.sleeptime = 1
|
||||||
|
|
||||||
##
|
@abstractproperty
|
||||||
# Set the time that the thread sleeps.
|
def status(self): # pragma: no cover - abstract
|
||||||
#
|
"""Abstract - Should provide status information.
|
||||||
# This value could also be called "polling time". A value of 1 is a
|
"""
|
||||||
# good one. This unit is "second"
|
pass
|
||||||
# @param value the polling time (second)
|
|
||||||
|
def start(self):
|
||||||
def setSleepTime(self, value):
|
"""Sets active flag and starts thread.
|
||||||
self.__sleepTime = value
|
"""
|
||||||
logSys.info("Set sleeptime %s" % value)
|
self.active = True
|
||||||
|
super(JailThread, self).start()
|
||||||
##
|
|
||||||
# Get the time that the thread sleeps.
|
def stop(self):
|
||||||
#
|
"""Sets `active` property to False, to flag run method to return.
|
||||||
# @return the polling time
|
"""
|
||||||
|
self.active = False
|
||||||
def getSleepTime(self):
|
|
||||||
return self.__sleepTime
|
@abstractmethod
|
||||||
|
def run(self): # pragma: no cover - absract
|
||||||
##
|
"""Abstract - Called when thread starts, thread stops when returns.
|
||||||
# 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):
|
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -51,7 +51,7 @@ class Server:
|
||||||
self.__logLevel = None
|
self.__logLevel = None
|
||||||
self.__logTarget = None
|
self.__logTarget = None
|
||||||
# Set logging level
|
# Set logging level
|
||||||
self.setLogLevel(3)
|
self.setLogLevel("INFO")
|
||||||
self.setLogTarget("STDOUT")
|
self.setLogTarget("STDOUT")
|
||||||
|
|
||||||
def __sigTERMhandler(self, signum, frame):
|
def __sigTERMhandler(self, signum, frame):
|
||||||
|
@ -125,14 +125,14 @@ class Server:
|
||||||
self.__db.addJail(self.__jails[name])
|
self.__db.addJail(self.__jails[name])
|
||||||
|
|
||||||
def delJail(self, name):
|
def delJail(self, name):
|
||||||
del self.__jails[name]
|
|
||||||
if self.__db is not None:
|
if self.__db is not None:
|
||||||
self.__db.delJailName(name)
|
self.__db.delJail(self.__jails[name])
|
||||||
|
del self.__jails[name]
|
||||||
|
|
||||||
def startJail(self, name):
|
def startJail(self, name):
|
||||||
try:
|
try:
|
||||||
self.__lock.acquire()
|
self.__lock.acquire()
|
||||||
if not self.isAlive(name):
|
if not self.__jails[name].is_alive():
|
||||||
self.__jails[name].start()
|
self.__jails[name].start()
|
||||||
finally:
|
finally:
|
||||||
self.__lock.release()
|
self.__lock.release()
|
||||||
|
@ -141,7 +141,7 @@ class Server:
|
||||||
logSys.debug("Stopping jail %s" % name)
|
logSys.debug("Stopping jail %s" % name)
|
||||||
try:
|
try:
|
||||||
self.__lock.acquire()
|
self.__lock.acquire()
|
||||||
if self.isAlive(name):
|
if self.__jails[name].is_alive():
|
||||||
self.__jails[name].stop()
|
self.__jails[name].stop()
|
||||||
self.delJail(name)
|
self.delJail(name)
|
||||||
finally:
|
finally:
|
||||||
|
@ -155,16 +155,13 @@ class Server:
|
||||||
self.stopJail(jail)
|
self.stopJail(jail)
|
||||||
finally:
|
finally:
|
||||||
self.__lock.release()
|
self.__lock.release()
|
||||||
|
|
||||||
def isAlive(self, name):
|
|
||||||
return self.__jails[name].isAlive()
|
|
||||||
|
|
||||||
def setIdleJail(self, name, value):
|
def setIdleJail(self, name, value):
|
||||||
self.__jails[name].setIdle(value)
|
self.__jails[name].idle = value
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def getIdleJail(self, name):
|
def getIdleJail(self, name):
|
||||||
return self.__jails[name].getIdle()
|
return self.__jails[name].idle
|
||||||
|
|
||||||
# Filter
|
# Filter
|
||||||
def addIgnoreIP(self, name, ip):
|
def addIgnoreIP(self, name, ip):
|
||||||
|
@ -316,35 +313,30 @@ class Server:
|
||||||
self.__lock.release()
|
self.__lock.release()
|
||||||
|
|
||||||
def statusJail(self, name):
|
def statusJail(self, name):
|
||||||
return self.__jails[name].getStatus()
|
return self.__jails[name].status
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
|
|
||||||
##
|
##
|
||||||
# Set the logging level.
|
# Set the logging level.
|
||||||
#
|
#
|
||||||
# Incrementing the value gives more messages.
|
# CRITICAL
|
||||||
# 0 = FATAL
|
# ERROR
|
||||||
# 1 = ERROR
|
# WARNING
|
||||||
# 2 = WARNING
|
# NOTICE
|
||||||
# 3 = INFO
|
# INFO
|
||||||
# 4 = DEBUG
|
# DEBUG
|
||||||
# @param value the level
|
# @param value the level
|
||||||
|
|
||||||
def setLogLevel(self, value):
|
def setLogLevel(self, value):
|
||||||
try:
|
try:
|
||||||
self.__loggingLock.acquire()
|
self.__loggingLock.acquire()
|
||||||
self.__logLevel = value
|
logging.getLogger(__name__).parent.parent.setLevel(
|
||||||
logLevel = logging.DEBUG
|
getattr(logging, value.upper()))
|
||||||
if value == 0:
|
except AttributeError:
|
||||||
logLevel = logging.FATAL
|
raise ValueError("Invalid log level")
|
||||||
elif value == 1:
|
else:
|
||||||
logLevel = logging.ERROR
|
self.__logLevel = value.upper()
|
||||||
elif value == 2:
|
|
||||||
logLevel = logging.WARNING
|
|
||||||
elif value == 3:
|
|
||||||
logLevel = logging.INFO
|
|
||||||
logging.getLogger(__name__).parent.parent.setLevel(logLevel)
|
|
||||||
finally:
|
finally:
|
||||||
self.__loggingLock.release()
|
self.__loggingLock.release()
|
||||||
|
|
||||||
|
|
|
@ -107,7 +107,7 @@ class Transmitter:
|
||||||
name = command[0]
|
name = command[0]
|
||||||
# Logging
|
# Logging
|
||||||
if name == "loglevel":
|
if name == "loglevel":
|
||||||
value = int(command[1])
|
value = command[1]
|
||||||
self.__server.setLogLevel(value)
|
self.__server.setLogLevel(value)
|
||||||
return self.__server.getLogLevel()
|
return self.__server.getLogLevel()
|
||||||
elif name == "logtarget":
|
elif name == "logtarget":
|
||||||
|
@ -123,14 +123,14 @@ class Transmitter:
|
||||||
if db is None:
|
if db is None:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return db.getFilename()
|
return db.filename
|
||||||
elif name == "dbpurgeage":
|
elif name == "dbpurgeage":
|
||||||
db = self.__server.getDatabase()
|
db = self.__server.getDatabase()
|
||||||
if db is None:
|
if db is None:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
db.setPurgeAge(command[1])
|
db.purgeage = command[1]
|
||||||
return db.getPurgeAge()
|
return db.purgeage
|
||||||
# Jail
|
# Jail
|
||||||
elif command[1] == "idle":
|
elif command[1] == "idle":
|
||||||
if command[2] == "on":
|
if command[2] == "on":
|
||||||
|
@ -265,13 +265,13 @@ class Transmitter:
|
||||||
if db is None:
|
if db is None:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return db.getFilename()
|
return db.filename
|
||||||
elif name == "dbpurgeage":
|
elif name == "dbpurgeage":
|
||||||
db = self.__server.getDatabase()
|
db = self.__server.getDatabase()
|
||||||
if db is None:
|
if db is None:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return db.getPurgeAge()
|
return db.purgeage
|
||||||
# Filter
|
# Filter
|
||||||
elif command[1] == "logpath":
|
elif command[1] == "logpath":
|
||||||
return self.__server.getLogPath(name)
|
return self.__server.getLogPath(name)
|
||||||
|
|
|
@ -77,7 +77,7 @@ class SMTPActionTest(unittest.TestCase):
|
||||||
self.assertEqual(self.smtpd.mailfrom, "fail2ban")
|
self.assertEqual(self.smtpd.mailfrom, "fail2ban")
|
||||||
self.assertEqual(self.smtpd.rcpttos, ["root"])
|
self.assertEqual(self.smtpd.rcpttos, ["root"])
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
"Subject: [Fail2Ban] %s: started" % self.jail.getName()
|
"Subject: [Fail2Ban] %s: started" % self.jail.name
|
||||||
in self.smtpd.data)
|
in self.smtpd.data)
|
||||||
|
|
||||||
def testStop(self):
|
def testStop(self):
|
||||||
|
@ -86,7 +86,7 @@ class SMTPActionTest(unittest.TestCase):
|
||||||
self.assertEqual(self.smtpd.rcpttos, ["root"])
|
self.assertEqual(self.smtpd.rcpttos, ["root"])
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
"Subject: [Fail2Ban] %s: stopped" %
|
"Subject: [Fail2Ban] %s: stopped" %
|
||||||
self.jail.getName() in self.smtpd.data)
|
self.jail.name in self.smtpd.data)
|
||||||
|
|
||||||
def testBan(self):
|
def testBan(self):
|
||||||
aInfo = {
|
aInfo = {
|
||||||
|
@ -102,7 +102,7 @@ class SMTPActionTest(unittest.TestCase):
|
||||||
self.assertEqual(self.smtpd.rcpttos, ["root"])
|
self.assertEqual(self.smtpd.rcpttos, ["root"])
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
"Subject: [Fail2Ban] %s: banned %s" %
|
"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(
|
self.assertTrue(
|
||||||
"%i attempts" % aInfo['failures'] in self.smtpd.data)
|
"%i attempts" % aInfo['failures'] in self.smtpd.data)
|
||||||
|
|
||||||
|
|
|
@ -84,8 +84,8 @@ class ExecuteActions(LogCaptureTestCase):
|
||||||
|
|
||||||
self.__actions.stop()
|
self.__actions.stop()
|
||||||
self.__actions.join()
|
self.__actions.join()
|
||||||
self.assertEqual(self.__actions.status(),[("Currently banned", 0 ),
|
self.assertEqual(self.__actions.status,[("Currently banned", 0 ),
|
||||||
("Total banned", 0 ), ("IP list", [] )])
|
("Total banned", 0 ), ("Banned IP list", [] )])
|
||||||
|
|
||||||
|
|
||||||
def testAddActionPython(self):
|
def testAddActionPython(self):
|
||||||
|
|
|
@ -559,7 +559,7 @@ class JailsReaderTest(LogCaptureTestCase):
|
||||||
[['set', 'dbfile',
|
[['set', 'dbfile',
|
||||||
'/var/lib/fail2ban/fail2ban.sqlite3'],
|
'/var/lib/fail2ban/fail2ban.sqlite3'],
|
||||||
['set', 'dbpurgeage', 86400],
|
['set', 'dbpurgeage', 86400],
|
||||||
['set', 'loglevel', 3],
|
['set', 'loglevel', "INFO"],
|
||||||
['set', 'logtarget', '/var/log/fail2ban.log']])
|
['set', 'logtarget', '/var/log/fail2ban.log']])
|
||||||
|
|
||||||
# and if we force change configurator's fail2ban's baseDir
|
# and if we force change configurator's fail2ban's baseDir
|
||||||
|
|
|
@ -47,7 +47,7 @@ class DatabaseTest(unittest.TestCase):
|
||||||
os.remove(self.dbFilename)
|
os.remove(self.dbFilename)
|
||||||
|
|
||||||
def testGetFilename(self):
|
def testGetFilename(self):
|
||||||
self.assertEqual(self.dbFilename, self.db.getFilename())
|
self.assertEqual(self.dbFilename, self.db.filename)
|
||||||
|
|
||||||
def testCreateInvalidPath(self):
|
def testCreateInvalidPath(self):
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
|
@ -61,7 +61,7 @@ class DatabaseTest(unittest.TestCase):
|
||||||
self.db = Fail2BanDb(self.dbFilename)
|
self.db = Fail2BanDb(self.dbFilename)
|
||||||
# and check jail of same name still present
|
# and check jail of same name still present
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
self.jail.getName() in self.db.getJailNames(),
|
self.jail.name in self.db.getJailNames(),
|
||||||
"Jail not retained in Db after disconnect reconnect.")
|
"Jail not retained in Db after disconnect reconnect.")
|
||||||
|
|
||||||
def testUpdateDb(self):
|
def testUpdateDb(self):
|
||||||
|
@ -74,13 +74,13 @@ class DatabaseTest(unittest.TestCase):
|
||||||
|
|
||||||
self.assertEqual(self.db.updateDb(Fail2BanDb.__version__), Fail2BanDb.__version__)
|
self.assertEqual(self.db.updateDb(Fail2BanDb.__version__), Fail2BanDb.__version__)
|
||||||
self.assertRaises(NotImplementedError, self.db.updateDb, Fail2BanDb.__version__ + 1)
|
self.assertRaises(NotImplementedError, self.db.updateDb, Fail2BanDb.__version__ + 1)
|
||||||
os.remove(self.db.dbBackupFilename)
|
os.remove(self.db._dbBackupFilename)
|
||||||
|
|
||||||
def testAddJail(self):
|
def testAddJail(self):
|
||||||
self.jail = DummyJail()
|
self.jail = DummyJail()
|
||||||
self.db.addJail(self.jail)
|
self.db.addJail(self.jail)
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
self.jail.getName() in self.db.getJailNames(),
|
self.jail.name in self.db.getJailNames(),
|
||||||
"Jail not added to database")
|
"Jail not added to database")
|
||||||
|
|
||||||
def testAddLog(self):
|
def testAddLog(self):
|
||||||
|
|
|
@ -32,6 +32,8 @@ class DummyJail(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.lock = Lock()
|
self.lock = Lock()
|
||||||
self.queue = []
|
self.queue = []
|
||||||
|
self.idle = False
|
||||||
|
self.database = None
|
||||||
self.actions = Actions(self)
|
self.actions = Actions(self)
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
|
@ -58,15 +60,6 @@ class DummyJail(object):
|
||||||
finally:
|
finally:
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
|
|
||||||
def setIdle(self, value):
|
@property
|
||||||
pass
|
def name(self):
|
||||||
|
|
||||||
def getIdle(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def getName(self):
|
|
||||||
return "DummyJail #%s with %d tickets" % (id(self), len(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.1.255'))
|
||||||
self.assertFalse(self.filter.inIgnoreIPList('192.168.0.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):
|
def testIgnoreInProcessLine(self):
|
||||||
setUpMyTime()
|
setUpMyTime()
|
||||||
self.filter.addIgnoreIP('192.168.1.0/25')
|
self.filter.addIgnoreIP('192.168.1.0/25')
|
||||||
|
@ -316,7 +325,7 @@ class LogFileMonitor(LogCaptureTestCase):
|
||||||
self.file = open(self.name, 'a')
|
self.file = open(self.name, 'a')
|
||||||
self.filter = FilterPoll(DummyJail())
|
self.filter = FilterPoll(DummyJail())
|
||||||
self.filter.addLogPath(self.name)
|
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.addFailRegex("(?:(?:Authentication failure|Failed [-/\w+]+) for(?: [iI](?:llegal|nvalid) user)?|[Ii](?:llegal|nvalid) user|ROOT LOGIN REFUSED) .*(?: from|FROM) <HOST>")
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
@ -457,7 +466,7 @@ def get_monitor_failures_testcase(Filter_):
|
||||||
self.jail = DummyJail()
|
self.jail = DummyJail()
|
||||||
self.filter = Filter_(self.jail)
|
self.filter = Filter_(self.jail)
|
||||||
self.filter.addLogPath(self.name)
|
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.addFailRegex("(?:(?:Authentication failure|Failed [-/\w+]+) for(?: [iI](?:llegal|nvalid) user)?|[Ii](?:llegal|nvalid) user|ROOT LOGIN REFUSED) .*(?: from|FROM) <HOST>")
|
||||||
self.filter.start()
|
self.filter.start()
|
||||||
# If filter is polling it would sleep a bit to guarantee that
|
# 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])
|
"TEST_UUID=%s" % self.test_uuid])
|
||||||
self.journal_fields = {
|
self.journal_fields = {
|
||||||
'TEST_FIELD': "1", 'TEST_UUID': self.test_uuid}
|
'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.addFailRegex("(?:(?:Authentication failure|Failed [-/\w+]+) for(?: [iI](?:llegal|nvalid) user)?|[Ii](?:llegal|nvalid) user|ROOT LOGIN REFUSED) .*(?: from|FROM) <HOST>")
|
||||||
self.filter.start()
|
self.filter.start()
|
||||||
|
|
||||||
|
@ -795,7 +804,7 @@ class GetFailures(unittest.TestCase):
|
||||||
setUpMyTime()
|
setUpMyTime()
|
||||||
self.jail = DummyJail()
|
self.jail = DummyJail()
|
||||||
self.filter = FileFilter(self.jail)
|
self.filter = FileFilter(self.jail)
|
||||||
self.filter.setActive(True)
|
self.filter.active = True
|
||||||
# TODO Test this
|
# TODO Test this
|
||||||
#self.filter.setTimeRegex("\S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2}")
|
#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")
|
#self.filter.setTimePattern("%b %d %H:%M:%S")
|
||||||
|
@ -886,7 +895,7 @@ class GetFailures(unittest.TestCase):
|
||||||
('warn', output_yes)):
|
('warn', output_yes)):
|
||||||
jail = DummyJail()
|
jail = DummyJail()
|
||||||
filter_ = FileFilter(jail, useDns=useDns)
|
filter_ = FileFilter(jail, useDns=useDns)
|
||||||
filter_.setActive(True)
|
filter_.active = True
|
||||||
filter_.failManager.setMaxRetry(1) # we might have just few failures
|
filter_.failManager.setMaxRetry(1) # we might have just few failures
|
||||||
|
|
||||||
filter_.addLogPath(GetFailures.FILENAME_USEDNS)
|
filter_.addLogPath(GetFailures.FILENAME_USEDNS)
|
||||||
|
|
|
@ -45,7 +45,7 @@ class FilterSamplesRegex(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Call before every test case."""
|
"""Call before every test case."""
|
||||||
self.filter = Filter(None)
|
self.filter = Filter(None)
|
||||||
self.filter.setActive(True)
|
self.filter.active = True
|
||||||
|
|
||||||
setUpMyTime()
|
setUpMyTime()
|
||||||
|
|
||||||
|
|
|
@ -227,8 +227,7 @@ class Transmitter(TransmitterBase):
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(["stop", self.jailName]), (0, None))
|
self.transm.proceed(["stop", self.jailName]), (0, None))
|
||||||
self.assertRaises(
|
self.assertTrue(self.jailName not in self.server._Server__jails)
|
||||||
UnknownJailException, self.server.isAlive, self.jailName)
|
|
||||||
|
|
||||||
def testStartStopAllJail(self):
|
def testStartStopAllJail(self):
|
||||||
self.server.addJail("TestJail2", "auto")
|
self.server.addJail("TestJail2", "auto")
|
||||||
|
@ -242,10 +241,8 @@ class Transmitter(TransmitterBase):
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
self.assertEqual(self.transm.proceed(["stop", "all"]), (0, None))
|
self.assertEqual(self.transm.proceed(["stop", "all"]), (0, None))
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
self.assertRaises(
|
self.assertTrue(self.jailName not in self.server._Server__jails)
|
||||||
UnknownJailException, self.server.isAlive, self.jailName)
|
self.assertTrue("TestJail2" not in self.server._Server__jails)
|
||||||
self.assertRaises(
|
|
||||||
UnknownJailException, self.server.isAlive, "TestJail2")
|
|
||||||
|
|
||||||
def testJailIdle(self):
|
def testJailIdle(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
@ -482,15 +479,15 @@ class Transmitter(TransmitterBase):
|
||||||
self.assertEqual(self.transm.proceed(["status", self.jailName]),
|
self.assertEqual(self.transm.proceed(["status", self.jailName]),
|
||||||
(0,
|
(0,
|
||||||
[
|
[
|
||||||
('filter', [
|
('Filter', [
|
||||||
('Currently failed', 0),
|
('Currently failed', 0),
|
||||||
('Total failed', 0),
|
('Total failed', 0),
|
||||||
('File list', [])]
|
('File list', [])]
|
||||||
),
|
),
|
||||||
('action', [
|
('Actions', [
|
||||||
('Currently banned', 0),
|
('Currently banned', 0),
|
||||||
('Total banned', 0),
|
('Total banned', 0),
|
||||||
('IP list', [])]
|
('Banned IP list', [])]
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -686,7 +683,7 @@ class TransmitterLogging(TransmitterBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.server = Server()
|
self.server = Server()
|
||||||
self.server.setLogTarget("/dev/null")
|
self.server.setLogTarget("/dev/null")
|
||||||
self.server.setLogLevel(0)
|
self.server.setLogLevel("CRITICAL")
|
||||||
super(TransmitterLogging, self).setUp()
|
super(TransmitterLogging, self).setUp()
|
||||||
|
|
||||||
def testLogTarget(self):
|
def testLogTarget(self):
|
||||||
|
@ -711,12 +708,14 @@ class TransmitterLogging(TransmitterBase):
|
||||||
self.setGetTest("logtarget", "SYSLOG")
|
self.setGetTest("logtarget", "SYSLOG")
|
||||||
|
|
||||||
def testLogLevel(self):
|
def testLogLevel(self):
|
||||||
self.setGetTest("loglevel", "4", 4)
|
self.setGetTest("loglevel", "HEAVYDEBUG")
|
||||||
self.setGetTest("loglevel", "3", 3)
|
self.setGetTest("loglevel", "DEBUG")
|
||||||
self.setGetTest("loglevel", "2", 2)
|
self.setGetTest("loglevel", "INFO")
|
||||||
self.setGetTest("loglevel", "1", 1)
|
self.setGetTest("loglevel", "NOTICE")
|
||||||
self.setGetTest("loglevel", "-1", -1)
|
self.setGetTest("loglevel", "WARNING")
|
||||||
self.setGetTest("loglevel", "0", 0)
|
self.setGetTest("loglevel", "ERROR")
|
||||||
|
self.setGetTest("loglevel", "CRITICAL")
|
||||||
|
self.setGetTest("loglevel", "cRiTiCaL", "CRITICAL")
|
||||||
self.setGetTestNOK("loglevel", "Bird")
|
self.setGetTestNOK("loglevel", "Bird")
|
||||||
|
|
||||||
def testFlushLogs(self):
|
def testFlushLogs(self):
|
||||||
|
@ -724,7 +723,7 @@ class TransmitterLogging(TransmitterBase):
|
||||||
try:
|
try:
|
||||||
f, fn = tempfile.mkstemp("fail2ban.log")
|
f, fn = tempfile.mkstemp("fail2ban.log")
|
||||||
os.close(f)
|
os.close(f)
|
||||||
self.server.setLogLevel(2)
|
self.server.setLogLevel("WARNING")
|
||||||
self.assertEqual(self.transm.proceed(["set", "logtarget", fn]), (0, fn))
|
self.assertEqual(self.transm.proceed(["set", "logtarget", fn]), (0, fn))
|
||||||
l = logging.getLogger('fail2ban.server.server').parent.parent
|
l = logging.getLogger('fail2ban.server.server').parent.parent
|
||||||
l.warning("Before file moved")
|
l.warning("Before file moved")
|
||||||
|
@ -774,7 +773,7 @@ class JailTests(unittest.TestCase):
|
||||||
# Just a smoke test for now
|
# Just a smoke test for now
|
||||||
longname = "veryveryverylongname"
|
longname = "veryveryverylongname"
|
||||||
jail = Jail(longname)
|
jail = Jail(longname)
|
||||||
self.assertEqual(jail.getName(), longname)
|
self.assertEqual(jail.name, longname)
|
||||||
|
|
||||||
class RegexTests(unittest.TestCase):
|
class RegexTests(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,15 @@
|
||||||
__fail2ban_jails () {
|
__fail2ban_jails () {
|
||||||
"$1" status 2>/dev/null | awk -F"\t+" '/Jail list/{print $2}' | sed 's/, / /g'
|
"$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 () {
|
_fail2ban () {
|
||||||
local cur prev words cword
|
local cur prev words cword
|
||||||
|
@ -50,7 +59,7 @@ _fail2ban () {
|
||||||
_filedir
|
_filedir
|
||||||
return 0
|
return 0
|
||||||
elif [[ "$1" == *"fail2ban-client" ]];then
|
elif [[ "$1" == *"fail2ban-client" ]];then
|
||||||
local cmd jail
|
local cmd jail action
|
||||||
case $prev in
|
case $prev in
|
||||||
"$1")
|
"$1")
|
||||||
COMPREPLY=( $( compgen -W \
|
COMPREPLY=( $( compgen -W \
|
||||||
|
@ -71,7 +80,7 @@ _fail2ban () {
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
if [[ "${words[$cword-2]}" == "add" ]];then
|
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
|
return 0
|
||||||
elif [[ "${words[$cword-2]}" == "set" || "${words[$cword-2]}" == "get" ]];then
|
elif [[ "${words[$cword-2]}" == "set" || "${words[$cword-2]}" == "get" ]];then
|
||||||
cmd="${words[cword-2]}"
|
cmd="${words[cword-2]}"
|
||||||
|
@ -80,6 +89,11 @@ _fail2ban () {
|
||||||
cmd="${words[$cword-3]}"
|
cmd="${words[$cword-3]}"
|
||||||
jail="${words[$cword-2]}"
|
jail="${words[$cword-2]}"
|
||||||
# Handle in section below
|
# 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
|
fi
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
@ -88,7 +102,7 @@ _fail2ban () {
|
||||||
case $prev in
|
case $prev in
|
||||||
loglevel)
|
loglevel)
|
||||||
if [[ "$cmd" == "set" ]];then
|
if [[ "$cmd" == "set" ]];then
|
||||||
COMPREPLY=( $( compgen -W "0 1 2 3 4" -- "$cur" ) )
|
COMPREPLY=( $( compgen -W "CRITICAL ERROR WARNING NOTICE INFO DEBUG" -- "$cur" ) )
|
||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
|
@ -106,6 +120,25 @@ _fail2ban () {
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
esac
|
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
|
elif [[ -n "$jail" && "$cmd" == "set" ]];then
|
||||||
case $prev in
|
case $prev in
|
||||||
addlogpath)
|
addlogpath)
|
||||||
|
|
|
@ -107,7 +107,7 @@ These files have one section, [Definition].
|
||||||
The items that can be set are:
|
The items that can be set are:
|
||||||
.TP
|
.TP
|
||||||
.B loglevel
|
.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
|
.TP
|
||||||
.B logtarget
|
.B logtarget
|
||||||
log target: filename, SYSLOG, STDERR or STDOUT. Default: STDERR . Only a single log target can be specified.
|
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.
|
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
|
.TP
|
||||||
.B action
|
.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
|
.TP
|
||||||
.B ignoreip
|
.B ignoreip
|
||||||
list of IPs not to ban. They can include a CIDR mask too.
|
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)
|
hdlr.setFormatter(fmt)
|
||||||
logSys.addHandler(hdlr)
|
logSys.addHandler(hdlr)
|
||||||
if set(["-q", "--quiet"]) & set(sys.argv):
|
if set(["-q", "--quiet"]) & set(sys.argv):
|
||||||
logSys.setLevel(logging.FATAL)
|
logSys.setLevel(logging.CRITICAL)
|
||||||
warnings.simplefilter("ignore")
|
warnings.simplefilter("ignore")
|
||||||
sys.warnoptions.append("ignore")
|
sys.warnoptions.append("ignore")
|
||||||
elif set(["-v", "--verbose"]) & set(sys.argv):
|
elif set(["-v", "--verbose"]) & set(sys.argv):
|
||||||
|
|
Loading…
Reference in New Issue