Merge branch 'enh-rel0.9.6' into debian

* enh-rel0.9.6: (60 commits)
  updated man pages
  ENH: prep for 0.9.6 release (as of tomorrow)
  BF: added missing entires into MANIFEST
  Update ChangeLog
  ChangeLog entry added + jail.conf review
  code review, makes the test cases workable, added dev-notes
  ChangeLog update
  `filter.d/apache-modsecurity.conf`   - fixed for newer version (one space, closes gh-1626) reviewed and optimized:   - non-greedy catch-all replaced for safer match   - unneeded catch-all anchoring removed   - non-capturing groups
  filter.d/dovecot.conf update: - fixes failregex, that ignores failures through some irrelevant info (closes #1623); - ignores whole additionally irrelevant info in anchored regex before fixed failure data `\((?:auth failed, \d+ attempts( in \d+ secs)?|tried to use (disabled|disallowed) \S+ auth)\)` - review, IPv6 compatibility fix, non-capturing groups
  Update jail.conf
  Use Fedora's backend-settings for openSUSE
  amend after code review of merge gh-1581
  Make changes and add test file
  Add Mongodb-auth filter and jail
  Update FILTERS
  filter.d/sshd.conf: Match 'Invalid user' with 'port \d*'
  ChangeLog entry added
  filter.d/sendmail-reject.conf: double space (should be by missing dns-host only) Closes #1578
  Update Changelog to reflect the new np.conf action
  Create npf.conf for the NPF packet filter
  ...
pull/1858/head
Yaroslav Halchenko 2016-12-09 09:37:33 -05:00
commit 623bb39ca6
68 changed files with 1146 additions and 319 deletions

View File

@ -41,6 +41,8 @@ script:
- if [[ "$F2B_PY_3" ]]; then coverage run bin/fail2ban-testcases; fi - if [[ "$F2B_PY_3" ]]; then coverage run bin/fail2ban-testcases; fi
# Use $VENV_BIN (not python) or else sudo will always run the system's python (2.7) # Use $VENV_BIN (not python) or else sudo will always run the system's python (2.7)
- sudo $VENV_BIN/pip install . - sudo $VENV_BIN/pip install .
# Doc files should get installed on Travis under Linux
- test -e /usr/share/doc/fail2ban/FILTERS
after_success: after_success:
- coveralls - coveralls
- codecov - codecov

View File

@ -6,13 +6,85 @@
Fail2Ban: Changelog Fail2Ban: Changelog
=================== ===================
ver. 0.9.5 (2016/07/15) - old-not-obsolete ver. 0.9.6 (2016/12/10) - stretch-is-coming
----------- -----------
0.9.x line is no longer heavily developed. If you are interested in 0.9.x line is no longer heavily developed. If you are interested in
new features (e.g. IPv6 support), please consider 0.10 branch and its new features (e.g. IPv6 support), please consider 0.10 branch and its
releases. releases.
### Fixes
* Misleading add resp. enable of (already available) jail in database, that
induced a subsequent error: last position of log file will be never retrieved (gh-795)
* Fixed a distribution related bug within testReadStockJailConfForceEnabled
(e.g. test-cases faults on Fedora, see gh-1353)
* Fixed pythonic filters and test scripts (running via wrong python version,
uses "fail2ban-python" now);
* Fixed test case "testSetupInstallRoot" for not default python version (also
using direct call, out of virtualenv);
* Fixed ambiguous wrong recognized date pattern resp. its optional parts (see gh-1512);
* FIPS compliant, use sha1 instead of md5 if it not allowed (see gh-1540)
* Monit config: scripting is not supported in path (gh-1556)
* `filter.d/apache-modsecurity.conf`
- Fixed for newer version (one space, gh-1626), optimized: non-greedy catch-all
replaced for safer match, unneeded catch-all anchoring removed, non-capturing
* `filter.d/asterisk.conf`
- Fixed to match different asterisk log prefix (source file: method:)
* `filter.d/dovecot.conf`
- Fixed failregex ignores failures through some not relevant info (gh-1623)
* `filter.d/ignorecommands/apache-fakegooglebot`
- Fixed error within apache-fakegooglebot, that will be called
with wrong python version (gh-1506)
* `filter.d/assp.conf`
- Extended failregex and test cases to handle ASSP V1 and V2 (gh-1494)
* `filter.d/postfix-sasl.conf`
- Allow for having no trailing space after 'failed:' (gh-1497)
* `filter.d/vsftpd.conf`
- Optional reason part in message after FAIL LOGIN (gh-1543)
* `filter.d/sendmail-reject.conf`
- removed mandatory double space (if dns-host available, gh-1579)
* filter.d/sshd.conf
- recognized "Failed publickey for" (gh-1477);
- optimized failregex to match all of "Failed any-method for ... from <HOST>" (gh-1479)
- eliminated possible complex injections (on user-name resp. auth-info, see gh-1479)
- optional port part after host (see gh-1533, gh-1581)
### New Features
* New Actions:
- `action.d/npf.conf` for NPF, the latest packet filter for NetBSD
* New Filters:
- `filter.d/mongodb-auth.conf` for MongoDB (document-oriented NoSQL database engine)
(gh-1586, gh-1606 and gh-1607)
### Enhancements
* DateTemplate regexp extended with the word-end boundary, additionally to
word-start boundary
* Introduces new command "fail2ban-python", as automatically created symlink to
python executable, where fail2ban currently installed (resp. its modules are located):
- allows to use the same version, fail2ban currently running, e.g. in
external scripts just via replace python with fail2ban-python:
```diff
-#!/usr/bin/env python
+#!/usr/bin/env fail2ban-python
```
- always the same pickle protocol
- the same (and also guaranteed available) fail2ban modules
- simplified stand-alone install, resp. stand-alone installation possibility
via setup (like gh-1487) is getting closer
* Several test cases rewritten using new methods assertIn, assertNotIn
* New forward compatibility method assertRaisesRegexp (normally python >= 2.7).
Methods assertIn, assertNotIn, assertRaisesRegexp, assertLogged, assertNotLogged
are test covered now
* Jail configuration extended with new syntax to pass options to the backend (see gh-1408),
examples:
- `backend = systemd[journalpath=/run/log/journal/machine-1]`
- `backend = systemd[journalfiles="/run/log/journal/machine-1/system.journal, /run/log/journal/machine-1/user.journal"]`
- `backend = systemd[journalflags=2]`
ver. 0.9.5 (2016/07/15) - old-not-obsolete
-----------
### Fixes ### Fixes
* `filter.d/monit.conf` * `filter.d/monit.conf`
- Extended failregex with new monit "access denied" version (gh-1355) - Extended failregex with new monit "access denied" version (gh-1355)

View File

@ -227,7 +227,7 @@ Regular expressions (failregex, ignoreregex) assume that the date/time has been
removed from the log line (this is just how fail2ban works internally ATM). removed from the log line (this is just how fail2ban works internally ATM).
If the format is like '<date...> error 1.2.3.4 is evil' then you need to match If the format is like '<date...> error 1.2.3.4 is evil' then you need to match
the < at the start so regex should be similar to '^<> <HOST> is evil$' using the <> at the start so regex should be similar to '^<> error <HOST> is evil$' using
<HOST> where the IP/domain name appears in the log line. <HOST> where the IP/domain name appears in the log line.
The following general rules apply to regular expressions: The following general rules apply to regular expressions:

View File

@ -33,12 +33,14 @@ config/action.d/iptables-new.conf
config/action.d/iptables-xt_recent-echo.conf config/action.d/iptables-xt_recent-echo.conf
config/action.d/mail-buffered.conf config/action.d/mail-buffered.conf
config/action.d/mail.conf config/action.d/mail.conf
config/action.d/mail-whois-common.conf
config/action.d/mail-whois.conf config/action.d/mail-whois.conf
config/action.d/mail-whois-lines.conf config/action.d/mail-whois-lines.conf
config/action.d/mynetwatchman.conf config/action.d/mynetwatchman.conf
config/action.d/nftables-allports.conf config/action.d/nftables-allports.conf
config/action.d/nftables-common.conf config/action.d/nftables-common.conf
config/action.d/nftables-multiport.conf config/action.d/nftables-multiport.conf
config/action.d/npf.conf
config/action.d/nsupdate.conf config/action.d/nsupdate.conf
config/action.d/osx-afctl.conf config/action.d/osx-afctl.conf
config/action.d/osx-ipfw.conf config/action.d/osx-ipfw.conf
@ -54,6 +56,7 @@ config/action.d/sendmail-whois-ipmatches.conf
config/action.d/sendmail-whois-lines.conf config/action.d/sendmail-whois-lines.conf
config/action.d/sendmail-whois-matches.conf config/action.d/sendmail-whois-matches.conf
config/action.d/shorewall.conf config/action.d/shorewall.conf
config/action.d/shorewall-ipset-proto6.conf
config/action.d/smtp.py config/action.d/smtp.py
config/action.d/symbiosis-blacklist-allports.conf config/action.d/symbiosis-blacklist-allports.conf
config/action.d/ufw.conf config/action.d/ufw.conf
@ -69,6 +72,7 @@ config/filter.d/apache-modsecurity.conf
config/filter.d/apache-nohome.conf config/filter.d/apache-nohome.conf
config/filter.d/apache-noscript.conf config/filter.d/apache-noscript.conf
config/filter.d/apache-overflows.conf config/filter.d/apache-overflows.conf
config/filter.d/apache-pass.conf
config/filter.d/apache-shellshock.conf config/filter.d/apache-shellshock.conf
config/filter.d/assp.conf config/filter.d/assp.conf
config/filter.d/asterisk.conf config/filter.d/asterisk.conf
@ -81,11 +85,13 @@ config/filter.d/cyrus-imap.conf
config/filter.d/directadmin.conf config/filter.d/directadmin.conf
config/filter.d/dovecot.conf config/filter.d/dovecot.conf
config/filter.d/dropbear.conf config/filter.d/dropbear.conf
config/filter.d/drupal-auth.conf
config/filter.d/ejabberd-auth.conf config/filter.d/ejabberd-auth.conf
config/filter.d/exim-common.conf config/filter.d/exim-common.conf
config/filter.d/exim.conf config/filter.d/exim.conf
config/filter.d/exim-spam.conf config/filter.d/exim-spam.conf
config/filter.d/freeswitch.conf config/filter.d/freeswitch.conf
config/filter.d/froxlor-auth.conf
config/filter.d/groupoffice.conf config/filter.d/groupoffice.conf
config/filter.d/gssftpd.conf config/filter.d/gssftpd.conf
config/filter.d/guacamole.conf config/filter.d/guacamole.conf
@ -95,6 +101,7 @@ config/filter.d/ignorecommands
config/filter.d/ignorecommands/apache-fakegooglebot config/filter.d/ignorecommands/apache-fakegooglebot
config/filter.d/kerio.conf config/filter.d/kerio.conf
config/filter.d/lighttpd-auth.conf config/filter.d/lighttpd-auth.conf
config/filter.d/mongodb-auth.conf
config/filter.d/monit.conf config/filter.d/monit.conf
config/filter.d/murmur.conf config/filter.d/murmur.conf
config/filter.d/mysqld-auth.conf config/filter.d/mysqld-auth.conf
@ -150,6 +157,7 @@ config/paths-opensuse.conf
config/paths-osx.conf config/paths-osx.conf
CONTRIBUTING.md CONTRIBUTING.md
COPYING COPYING
.coveragerc
DEVELOP DEVELOP
doc/run-rootless.txt doc/run-rootless.txt
fail2ban-2to3 fail2ban-2to3
@ -194,6 +202,7 @@ fail2ban/server/server.py
fail2ban/server/strptime.py fail2ban/server/strptime.py
fail2ban/server/ticket.py fail2ban/server/ticket.py
fail2ban/server/transmitter.py fail2ban/server/transmitter.py
fail2ban/setup.py
fail2ban-testcases-all fail2ban-testcases-all
fail2ban-testcases-all-python3 fail2ban-testcases-all-python3
fail2ban/tests/action_d/__init__.py fail2ban/tests/action_d/__init__.py
@ -205,6 +214,7 @@ fail2ban/tests/banmanagertestcase.py
fail2ban/tests/clientreadertestcase.py fail2ban/tests/clientreadertestcase.py
fail2ban/tests/config/action.d/brokenaction.conf fail2ban/tests/config/action.d/brokenaction.conf
fail2ban/tests/config/fail2ban.conf fail2ban/tests/config/fail2ban.conf
fail2ban/tests/config/filter.d/common.conf
fail2ban/tests/config/filter.d/simple.conf fail2ban/tests/config/filter.d/simple.conf
fail2ban/tests/config/filter.d/test.conf fail2ban/tests/config/filter.d/test.conf
fail2ban/tests/config/filter.d/test.local fail2ban/tests/config/filter.d/test.local
@ -256,6 +266,7 @@ fail2ban/tests/files/logs/apache-modsecurity
fail2ban/tests/files/logs/apache-nohome fail2ban/tests/files/logs/apache-nohome
fail2ban/tests/files/logs/apache-noscript fail2ban/tests/files/logs/apache-noscript
fail2ban/tests/files/logs/apache-overflows fail2ban/tests/files/logs/apache-overflows
fail2ban/tests/files/logs/apache-pass
fail2ban/tests/files/logs/apache-shellshock fail2ban/tests/files/logs/apache-shellshock
fail2ban/tests/files/logs/assp fail2ban/tests/files/logs/assp
fail2ban/tests/files/logs/asterisk fail2ban/tests/files/logs/asterisk
@ -269,10 +280,12 @@ fail2ban/tests/files/logs/cyrus-imap
fail2ban/tests/files/logs/directadmin fail2ban/tests/files/logs/directadmin
fail2ban/tests/files/logs/dovecot fail2ban/tests/files/logs/dovecot
fail2ban/tests/files/logs/dropbear fail2ban/tests/files/logs/dropbear
fail2ban/tests/files/logs/drupal-auth
fail2ban/tests/files/logs/ejabberd-auth fail2ban/tests/files/logs/ejabberd-auth
fail2ban/tests/files/logs/exim fail2ban/tests/files/logs/exim
fail2ban/tests/files/logs/exim-spam fail2ban/tests/files/logs/exim-spam
fail2ban/tests/files/logs/freeswitch fail2ban/tests/files/logs/freeswitch
fail2ban/tests/files/logs/froxlor-auth
fail2ban/tests/files/logs/groupoffice fail2ban/tests/files/logs/groupoffice
fail2ban/tests/files/logs/gssftpd fail2ban/tests/files/logs/gssftpd
fail2ban/tests/files/logs/guacamole fail2ban/tests/files/logs/guacamole
@ -280,6 +293,7 @@ fail2ban/tests/files/logs/haproxy-http-auth
fail2ban/tests/files/logs/horde fail2ban/tests/files/logs/horde
fail2ban/tests/files/logs/kerio fail2ban/tests/files/logs/kerio
fail2ban/tests/files/logs/lighttpd-auth fail2ban/tests/files/logs/lighttpd-auth
fail2ban/tests/files/logs/mongodb-auth
fail2ban/tests/files/logs/monit fail2ban/tests/files/logs/monit
fail2ban/tests/files/logs/murmur fail2ban/tests/files/logs/murmur
fail2ban/tests/files/logs/mysqld-auth fail2ban/tests/files/logs/mysqld-auth
@ -356,6 +370,8 @@ files/gentoo-confd
files/gentoo-initd files/gentoo-initd
files/ipmasq-ZZZzzz_fail2ban.rul files/ipmasq-ZZZzzz_fail2ban.rul
files/logwatch/fail2ban files/logwatch/fail2ban
files/logwatch/fail2ban-0.8.log
files/logwatch/fail2ban-0.9.log
files/macosx-initd files/macosx-initd
files/monit/fail2ban files/monit/fail2ban
files/nagios/check_fail2ban files/nagios/check_fail2ban
@ -373,8 +389,11 @@ man/fail2ban-regex.1
man/fail2ban-regex.h2m man/fail2ban-regex.h2m
man/fail2ban-server.1 man/fail2ban-server.1
man/fail2ban-server.h2m man/fail2ban-server.h2m
man/fail2ban-testcases.1
man/fail2ban-testcases.h2m
man/generate-man man/generate-man
man/jail.conf.5 man/jail.conf.5
.pylintrc
README.md README.md
README.Solaris README.Solaris
RELEASE RELEASE

View File

@ -2,7 +2,7 @@
/ _|__ _(_) |_ ) |__ __ _ _ _ / _|__ _(_) |_ ) |__ __ _ _ _
| _/ _` | | |/ /| '_ \/ _` | ' \ | _/ _` | | |/ /| '_ \/ _` | ' \
|_| \__,_|_|_/___|_.__/\__,_|_||_| |_| \__,_|_|_/___|_.__/\__,_|_||_|
v0.9.5 2016/07/15 v0.9.6 2016/12/10
## Fail2Ban: ban hosts that cause multiple authentication errors ## Fail2Ban: ban hosts that cause multiple authentication errors
@ -39,8 +39,8 @@ Optional:
To install, just do: To install, just do:
tar xvfj fail2ban-0.9.5.tar.bz2 tar xvfj fail2ban-0.9.6.tar.bz2
cd fail2ban-0.9.5 cd fail2ban-0.9.6
python setup.py install python setup.py install
This will install Fail2Ban into the python library directory. The executable This will install Fail2Ban into the python library directory. The executable

View File

@ -53,7 +53,7 @@ Preparation
or an alternative for comparison with previous release or an alternative for comparison with previous release
git diff 0.9.5 | grep -B2 'index 0000000..' | grep -B1 'new file mode' | sed -n -e '/^diff /s,.* b/,,gp' >> MANIFEST git diff 0.9.6 | grep -B2 'index 0000000..' | grep -B1 'new file mode' | sed -n -e '/^diff /s,.* b/,,gp' >> MANIFEST
sort MANIFEST | uniq | sponge MANIFEST sort MANIFEST | uniq | sponge MANIFEST
* Run:: * Run::
@ -70,7 +70,7 @@ Preparation
* clean up current directory:: * clean up current directory::
diff -rul --exclude \*.pyc . /tmp/fail2ban-0.9.5/ diff -rul --exclude \*.pyc . /tmp/fail2ban-0.9.6/
* Only differences should be files that you don't want distributed. * Only differences should be files that you don't want distributed.
@ -83,7 +83,7 @@ Preparation
* To generate a list of committers use e.g.:: * To generate a list of committers use e.g.::
git shortlog -sn 0.9.5.. | sed -e 's,^[ 0-9\t]*,,g' | tr '\n' '\|' | sed -e 's:|:, :g' git shortlog -sn 0.9.6.. | sed -e 's,^[ 0-9\t]*,,g' | tr '\n' '\|' | sed -e 's:|:, :g'
* Ensure the top of the ChangeLog has the right version and current date. * Ensure the top of the ChangeLog has the right version and current date.
* Ensure the top entry of the ChangeLog has the right version and current date. * Ensure the top entry of the ChangeLog has the right version and current date.
@ -106,7 +106,7 @@ Preparation
* Tag the release by using a signed (and annotated) tag. Cut/paste * Tag the release by using a signed (and annotated) tag. Cut/paste
release ChangeLog entry as tag annotation:: release ChangeLog entry as tag annotation::
git tag -s 0.9.5 git tag -s 0.9.6
Pre Release Pre Release
=========== ===========

1
THANKS
View File

@ -119,6 +119,7 @@ Thomas Mayer
Tom Pike Tom Pike
Tom Hendrikx Tom Hendrikx
Tomas Pihl Tomas Pihl
Thomas Skierlo (phaleas)
Tony Lawrence Tony Lawrence
Tomasz Ciolek Tomasz Ciolek
Tyler Tyler

View File

@ -176,7 +176,7 @@ class Fail2banClient:
if showRet: if showRet:
self.__logSocketError() self.__logSocketError()
return False return False
except Exception, e: except Exception as e:
if showRet: if showRet:
logSys.error(e) logSys.error(e)
return False return False
@ -429,7 +429,7 @@ class Fail2banClient:
elif not cmd == "": elif not cmd == "":
try: try:
self.__processCommand(shlex.split(cmd)) self.__processCommand(shlex.split(cmd))
except Exception, e: except Exception as e:
logSys.error(e) logSys.error(e)
except (EOFError, KeyboardInterrupt): except (EOFError, KeyboardInterrupt):
print print
@ -451,7 +451,7 @@ class Fail2banClient:
ret = self.__configurator.getOptions(jail) ret = self.__configurator.getOptions(jail)
self.__configurator.convertToProtocol() self.__configurator.convertToProtocol()
self.__stream = self.__configurator.getConfigStream() self.__stream = self.__configurator.getConfigStream()
except Exception, e: except Exception as e:
logSys.error("Failed during configuration: %s" % e) logSys.error("Failed during configuration: %s" % e)
ret = False ret = False
return ret return ret

View File

@ -127,7 +127,7 @@ class Fail2banServer:
self.__conf["pidfile"], self.__conf["pidfile"],
self.__conf["force"]) self.__conf["force"])
return True return True
except Exception, e: except Exception as e:
logSys.exception(e) logSys.exception(e)
self.__server.quit() self.__server.quit()
return False return False

View File

@ -39,10 +39,18 @@ from fail2ban.version import version
from fail2ban.tests.utils import gatherTests from fail2ban.tests.utils import gatherTests
from fail2ban.helpers import FormatterWithTraceBack, getLogger from fail2ban.helpers import FormatterWithTraceBack, getLogger
from fail2ban.setup import updatePyExec
from fail2ban.server.mytime import MyTime from fail2ban.server.mytime import MyTime
from optparse import OptionParser, Option from optparse import OptionParser, Option
# Update fail2ban-python env to current python version (where f2b-modules located/installed)
bindir = os.path.dirname(
# __file__ seems to be overwritten sometimes on some python versions (e.g. bug of 2.6 by running under cProfile, etc.):
sys.argv[0] if os.path.basename(sys.argv[0]) == 'fail2ban-testcases' else __file__
)
updatePyExec(bindir)
def get_opt_parser(): def get_opt_parser():
# use module docstring for help output # use module docstring for help output
p = OptionParser( p = OptionParser(

View File

@ -1,6 +1,6 @@
# Fail2ban reporting to badips.com # Fail2ban reporting to badips.com
# #
# Note: This reports and IP only and does not actually ban traffic. Use # Note: This reports an IP only and does not actually ban traffic. Use
# another action in the same jail if you want bans to occur. # another action in the same jail if you want bans to occur.
# #
# Set the category to the appropriate value before use. # Set the category to the appropriate value before use.

61
config/action.d/npf.conf Normal file
View File

@ -0,0 +1,61 @@
# Fail2Ban configuration file
#
# NetBSD npf ban/unban
#
# Author: Nils Ratusznik <nils@NetBSD.org>
# Based on pf.conf action file
#
[Definition]
# Option: actionstart
# Notes.: command executed once at the start of Fail2Ban.
# Values: CMD
#
# we don't enable NPF automatically, as it will be enabled elsewhere
actionstart =
# Option: actionstop
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD
#
# we don't disable NPF automatically either
actionstop =
# Option: actioncheck
# Notes.: command executed once before each actionban command
# Values: CMD
#
actioncheck =
# Option: actionban
# Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: <ip> IP address
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD
#
actionban = /sbin/npfctl table <tablename> add <ip>
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: <ip> IP address
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD
#
# note -r option used to remove matching rule
actionunban = /sbin/npfctl table <tablename> rem <ip>
[Init]
# Option: tablename
# Notes.: The pf table name.
# Values: [ STRING ]
#
tablename = fail2ban

View File

@ -10,9 +10,10 @@ before = apache-common.conf
[Definition] [Definition]
failregex = ^%(_apache_error_client)s ModSecurity: (\[.*?\] )*Access denied with code [45]\d\d.*$ failregex = ^%(_apache_error_client)s ModSecurity:\s+(?:\[(?:\w+ \"[^\"]*\"|[^\]]*)\]\s*)*Access denied with code [45]\d\d
ignoreregex = ignoreregex =
# https://github.com/SpiderLabs/ModSecurity/wiki/ModSecurity-2-Data-Formats # https://github.com/SpiderLabs/ModSecurity/wiki/ModSecurity-2-Data-Formats
# Author: Daniel Black # Author: Daniel Black
# Sergey G. Brester aka sebres (review, optimization)

View File

@ -1,24 +1,43 @@
# Fail2Ban filter for Anti-Spam SMTP Proxy Server also known as ASSP # Fail2Ban filter for Anti-Spam SMTP Proxy Server (ASSP)
# Filter works in theory for both ASSP V1 and V2. Recommended ASSP is V2.5.1 or later.
# Support for ASSP V1 ended in 2014 so if you are still running ASSP V1 an immediate upgrade is recommended.
# #
# Honmepage: http://www.magicvillage.de/~Fritz_Borgstedt/assp/0003D91C-8000001C/ # Homepage: http://sourceforge.net/projects/assp/
# ProjektSite: http://sourceforge.net/projects/assp/?source=directory # ProjectSite: http://sourceforge.net/projects/assp/?source=directory
# #
# #
[Definition] [Definition]
# Note: First three failregex matches below are for ASSP V1 with the remaining being designed for V2. Deleting the V1 regex is recommended but I left it in for compatibilty reasons.
__assp_actions = (?:dropping|refusing) __assp_actions = (?:dropping|refusing)
failregex = ^(:? \[SSL-out\])? <HOST> max sender authentication errors \(\d{,3}\) exceeded -- %(__assp_actions)s connection - after reply: \d{3} \d{1}\.\d{1}.\d{1} Error: authentication failed: \w+;$ failregex = ^(:? \[SSL-out\])? <HOST> max sender authentication errors \(\d{,3}\) exceeded -- %(__assp_actions)s connection - after reply: \d{3} \d{1}\.\d{1}.\d{1} Error: authentication failed: \w+;$
^(?: \[SSL-out\])? <HOST> SSL negotiation with client failed: SSL accept attempt failed with unknown error.*:unknown protocol;$ ^(?: \[SSL-out\])? <HOST> SSL negotiation with client failed: SSL accept attempt failed with unknown error.*:unknown protocol;$
^ Blocking <HOST> - too much AUTH errors \(\d{,3}\);$ ^ Blocking <HOST> - too much AUTH errors \(\d{,3}\);$
^\s*(?:[\w\-]+\s+)*(?:\[\S+\]\s+)*<HOST> (?:\<\S+@\S+\.\S+\> )*(?:to: \S+@\S+\.\S+ )*relay attempt blocked for(?: \(parsing\))?: \S+$
^\s*(?:[\w\-]+\s+)*(?:\[\S+\]\s+)*<HOST> \[SMTP Error\] 535 5\.7\.8 Error: authentication failed:\s+(?:\S+|Connection lost to authentication server|Invalid authentication mechanism|Invalid base64 data in continued response)?$
ignoreregex = ignoreregex =
# DEV Notes: # DEV Notes:
# V1 Examples matches:
# Apr-27-13 02:33:09 Blocking 217.194.197.97 - too much AUTH errors (41);
# Dec-29-12 17:10:31 [SSL-out] 200.247.87.82 SSL negotiation with client failed: SSL accept attempt failed with unknown errorerror:140760FC:SSL routines:SSL23_GET_CLIENT_HELLO:unknown protocol;
# Dec-30-12 04:01:47 [SSL-out] 81.82.232.66 max sender authentication errors (5) exceeded
# #
# Examples: Apr-27-13 02:33:09 Blocking 217.194.197.97 - too much AUTH errors (41); # V2 Examples matches:
# Dec-29-12 17:10:31 [SSL-out] 200.247.87.82 SSL negotiation with client failed: SSL accept attempt failed with unknown errorerror:140760FC:SSL routines:SSL23_GET_CLIENT_HELLO:unknown protocol; # Jul-29-16 16:49:52 m1-25391-06124 [Worker_1] [TLS-out] [RelayAttempt] 0.0.0.0 <user@example.com> to: user@example.org relay attempt blocked for: someone@example.org
# Dec-30-12 04:01:47 [SSL-out] 81.82.232.66 max sender authentication errors (5) exceeded # Jul-30-16 16:59:42 [Worker_1] [TLS-out] 0.0.0.0 [SMTP Error] 535 5.7.8 Error: authentication failed: UGFzc3dvcmQ6
# Jul-30-16 00:15:36 m1-52131-09651 [Worker_1] [TLS-out] 0.0.0.0 [SMTP Error] 535 5.7.8 Error: authentication failed: UGFzc3dvcmQ6
# Jul-31-16 06:45:59 [Worker_1] [TLS-in] [TLS-out] 0.0.0.0 [SMTP Error] 535 5.7.8 Error: authentication failed:
# Jan-05-16 08:38:49 m1-01129-09140 [Worker_1] [TLS-in] [TLS-out] [RelayAttempt] 0.0.0.0 <user@example.com> relay attempt blocked for (parsing): <user2@example>
# Jun-12-16 16:43:37 m1-64217-12013 [Worker_1] [TLS-in] [TLS-out] [RelayAttempt] 0.0.0.0 <user@example.com> to: user2@example.com relay attempt blocked for (parsing): <a.notheruser69@example.c>
# Jan-22-16 22:25:51 [Worker_1] [TLS-out] 0.0.0.0 [SMTP Error] 535 5.7.8 Error: authentication failed: Invalid authentication mechanism
# Mar-19-16 13:42:20 [Worker_1] [TLS-out] 0.0.0.0 [SMTP Error] 535 5.7.8 Error: authentication failed: Invalid base64 data in continued response
# Jul-18-16 16:54:21 [Worker_2] [TLS-out] 0.0.0.0 [SMTP Error] 535 5.7.8 Error: authentication failed: Connection lost to authentication server
# Jul-18-16 17:14:23 m1-76453-02949 [Worker_1] [TLS-out] 0.0.0.0 [SMTP Error] 535 5.7.8 Error: authentication failed: Connection lost to authentication server
# #
# Author: Enrico Labedzki (enrico.labedzki@deiwos.de) # Author: Enrico Labedzki (enrico.labedzki@deiwos.de)
# V2 Filters: Robert Hardy (rhardy@webcon.ca)

View File

@ -16,7 +16,7 @@ __pid_re = (?:\[\d+\])
iso8601 = \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+[+-]\d{4} iso8601 = \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+[+-]\d{4}
# All Asterisk log messages begin like this: # All Asterisk log messages begin like this:
log_prefix= (?:NOTICE|SECURITY|WARNING)%(__pid_re)s:?(?:\[C-[\da-f]*\])? [^:]+:\d*( in \w+:)? log_prefix= (?:NOTICE|SECURITY|WARNING)%(__pid_re)s:?(?:\[C-[\da-f]*\])? [^:]+:\d*(?:(?: in)? \w+:)?
failregex = ^%(__prefix_line)s%(log_prefix)s Registration from '[^']*' failed for '<HOST>(:\d+)?' - (Wrong password|Username/auth name mismatch|No matching peer found|Not a local domain|Device does not match ACL|Peer is not supposed to register|ACL error \(permit/deny\)|Not a local domain)$ failregex = ^%(__prefix_line)s%(log_prefix)s Registration from '[^']*' failed for '<HOST>(:\d+)?' - (Wrong password|Username/auth name mismatch|No matching peer found|Not a local domain|Device does not match ACL|Peer is not supposed to register|ACL error \(permit/deny\)|Not a local domain)$
^%(__prefix_line)s%(log_prefix)s Call from '[^']*' \(<HOST>:\d+\) to extension '[^']*' rejected because extension not found in context ^%(__prefix_line)s%(log_prefix)s Call from '[^']*' \(<HOST>:\d+\) to extension '[^']*' rejected because extension not found in context

View File

@ -9,11 +9,11 @@ before = common.conf
_daemon = (auth|dovecot(-auth)?|auth-worker) _daemon = (auth|dovecot(-auth)?|auth-worker)
failregex = ^%(__prefix_line)s(%(__pam_auth)s(\(dovecot:auth\))?:)?\s+authentication failure; logname=\S* uid=\S* euid=\S* tty=dovecot ruser=\S* rhost=<HOST>(\s+user=\S*)?\s*$ failregex = ^%(__prefix_line)s(?:%(__pam_auth)s(?:\(dovecot:auth\))?:)?\s+authentication failure; logname=\S* uid=\S* euid=\S* tty=dovecot ruser=\S* rhost=<HOST>(?:\s+user=\S*)?\s*$
^%(__prefix_line)s(pop3|imap)-login: (Info: )?(Aborted login|Disconnected)(: Inactivity)? \(((auth failed, \d+ attempts)( in \d+ secs)?|tried to use (disabled|disallowed) \S+ auth)\):( user=<\S*>,)?( method=\S+,)? rip=<HOST>(, lip=(\d{1,3}\.){3}\d{1,3})?(, TLS( handshaking(: SSL_accept\(\) failed: error:[\dA-F]+:SSL routines:[TLS\d]+_GET_CLIENT_HELLO:unknown protocol)?)?(: Disconnected)?)?(, session=<\S+>)?\s*$ ^%(__prefix_line)s(?:pop3|imap)-login: (?:Info: )?(?:Aborted login|Disconnected)(?::(?: [^ \(]+)+)? \((?:auth failed, \d+ attempts( in \d+ secs)?|tried to use (disabled|disallowed) \S+ auth)\):( user=<[^>]+>,)?( method=\S+,)? rip=<HOST>(?:, lip=\S+)?(?:, TLS(?: handshaking(?:: SSL_accept\(\) failed: error:[\dA-F]+:SSL routines:[TLS\d]+_GET_CLIENT_HELLO:unknown protocol)?)?(: Disconnected)?)?(, session=<\S+>)?\s*$
^%(__prefix_line)s(Info|dovecot: auth\(default\)|auth-worker\(\d+\)): pam\(\S+,<HOST>\): pam_authenticate\(\) failed: (User not known to the underlying authentication module: \d+ Time\(s\)|Authentication failure \(password mismatch\?\))\s*$ ^%(__prefix_line)s(?:Info|dovecot: auth\(default\)|auth-worker\(\d+\)): pam\(\S+,<HOST>\): pam_authenticate\(\) failed: (User not known to the underlying authentication module: \d+ Time\(s\)|Authentication failure \(password mismatch\?\))\s*$
^%(__prefix_line)s(auth|auth-worker\(\d+\)): (pam|passwd-file)\(\S+,<HOST>\): unknown user\s*$ ^%(__prefix_line)s(?:auth|auth-worker\(\d+\)): (?:pam|passwd-file)\(\S+,<HOST>\): unknown user\s*$
^%(__prefix_line)s(auth|auth-worker\(\d+\)): Info: ldap\(\S*,<HOST>,\S*\): invalid credentials\s*$ ^%(__prefix_line)s(?:auth|auth-worker\(\d+\)): Info: ldap\(\S*,<HOST>,\S*\): invalid credentials\s*$
ignoreregex = ignoreregex =
@ -30,3 +30,4 @@ journalmatch = _SYSTEMD_UNIT=dovecot.service
# Author: Martin Waschbuesch # Author: Martin Waschbuesch
# Daniel Black (rewrote with begin and end anchors) # Daniel Black (rewrote with begin and end anchors)
# Martin O'Neal (added LDAP authentication failure regex) # Martin O'Neal (added LDAP authentication failure regex)
# Sergey G. Brester aka sebres (reviewed, optimized, IPv6-compatibility)

View File

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/env fail2ban-python
# Inspired by https://isc.sans.edu/forums/diary/When+Google+isnt+Google/15968/ # Inspired by https://isc.sans.edu/forums/diary/When+Google+isnt+Google/15968/
# #
# Written in Python to reuse built-in Python batteries and not depend on # Written in Python to reuse built-in Python batteries and not depend on

View File

@ -0,0 +1,49 @@
# Fail2Ban filter for unsuccesfull MongoDB authentication attempts
#
# Logfile /var/log/mongodb/mongodb.log
#
# add setting in /etc/mongodb.conf
# logpath=/var/log/mongodb/mongodb.log
#
# and use of the authentication
# auth = true
#
[Definition]
#failregex = ^\s+\[initandlisten\] connection accepted from <HOST>:\d+ \#(?P<__connid>\d+) \(1 connection now open\)<SKIPLINES>\s+\[conn(?P=__connid)\] Failed to authenticate\s+
failregex = ^\s+\[conn(?P<__connid>\d+)\] Failed to authenticate [^\n]+<SKIPLINES>\s+\[conn(?P=__connid)\] end connection <HOST>
ignoreregex =
[Init]
maxlines = 10
# DEV Notes:
#
# Regarding the multiline regex:
#
# There can be a nunber of non-related lines between the first and second part
# of this regex maxlines of 10 is quite generious.
#
# Note the capture __connid, includes the connection ID, used in second part of regex.
#
# The first regex is commented out (but will match also), because it is better to use
# the host from "end connection" line (uncommented above):
# - it has the same prefix, searching begins directly with failure message
# (so faster, because ignores success connections at all)
# - it is not so vulnerable in case of possible race condition
#
# Log example:
# 2016-10-20T09:54:27.108+0200 [initandlisten] connection accepted from 127.0.0.1:53276 #1 (1 connection now open)
# 2016-10-20T09:54:27.109+0200 [conn1] authenticate db: test { authenticate: 1, nonce: "xxx", user: "root", key: "xxx" }
# 2016-10-20T09:54:27.110+0200 [conn1] Failed to authenticate root@test with mechanism MONGODB-CR: AuthenticationFailed UserNotFound Could not find user root@test
# 2016-11-09T09:54:27.894+0100 [conn1] end connection 127.0.0.1:53276 (0 connections now open)
# 2016-11-09T11:55:58.890+0100 [initandlisten] connection accepted from 127.0.0.1:54266 #1510 (1 connection now open)
# 2016-11-09T11:55:58.892+0100 [conn1510] authenticate db: admin { authenticate: 1, nonce: "xxx", user: "root", key: "xxx" }
# 2016-11-09T11:55:58.892+0100 [conn1510] Failed to authenticate root@admin with mechanism MONGODB-CR: AuthenticationFailed key mismatch
# 2016-11-09T11:55:58.894+0100 [conn1510] end connection 127.0.0.1:54266 (0 connections now open)
#
# Authors: Alexander Finkhäuser
# Sergey G. Brester (sebres)

View File

@ -9,7 +9,7 @@ before = common.conf
_daemon = postfix(-\w+)?/(?:submission/|smtps/)?smtp[ds] _daemon = postfix(-\w+)?/(?:submission/|smtps/)?smtp[ds]
failregex = ^%(__prefix_line)swarning: [-._\w]+\[<HOST>\]: SASL ((?i)LOGIN|PLAIN|(?:CRAM|DIGEST)-MD5) authentication failed(: [ A-Za-z0-9+/:]*={0,2})?\s*$ failregex = ^%(__prefix_line)swarning: [-._\w]+\[<HOST>\]: SASL ((?i)LOGIN|PLAIN|(?:CRAM|DIGEST)-MD5) authentication failed(:[ A-Za-z0-9+/:]*={0,2})?\s*$
ignoreregex = authentication failed: Connection lost to authentication server$ ignoreregex = authentication failed: Connection lost to authentication server$

View File

@ -23,7 +23,7 @@ _daemon = (?:(sm-(mta|acceptingconnections)|sendmail))
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))$ 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)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}: 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\]$ ^%(__prefix_line)s\w{14}: (\S+ )?\[<HOST>\]: ((?i)expn|vrfy) \S+ \[rejected\]$
^(?P<__prefix>%(__prefix_line)s\w+: )<[^@]+@[^>]+>\.\.\. No such user here<SKIPLINES>(?P=__prefix)from=<[^@]+@[^>]+>, size=\d+, class=\d+, nrcpts=\d+, bodytype=\w+, proto=E?SMTP, daemon=MTA, relay=\S+ \[<HOST>\]$ ^(?P<__prefix>%(__prefix_line)s\w+: )<[^@]+@[^>]+>\.\.\. No such user here<SKIPLINES>(?P=__prefix)from=<[^@]+@[^>]+>, size=\d+, class=\d+, nrcpts=\d+, bodytype=\w+, proto=E?SMTP, daemon=MTA, relay=\S+ \[<HOST>\]$

View File

@ -20,9 +20,9 @@ _daemon = sshd
failregex = ^%(__prefix_line)s(?:error: PAM: )?[aA]uthentication (?:failure|error|failed) for .* from <HOST>( via \S+)?\s*$ failregex = ^%(__prefix_line)s(?:error: PAM: )?[aA]uthentication (?:failure|error|failed) for .* from <HOST>( via \S+)?\s*$
^%(__prefix_line)s(?:error: PAM: )?User not known to the underlying authentication module for .* from <HOST>\s*$ ^%(__prefix_line)s(?:error: PAM: )?User not known to the underlying authentication module for .* from <HOST>\s*$
^%(__prefix_line)sFailed \S+ for .*? from <HOST>(?: port \d*)?(?: ssh\d*)?(: (ruser .*|(\S+ ID \S+ \(serial \d+\) CA )?\S+ %(__md5hex)s(, client user ".*", client host ".*")?))?\s*$ ^%(__prefix_line)sFailed \S+ for (?P<cond_inv>invalid user )?(?P<user>(?P<cond_user>\S+)|(?(cond_inv)(?:(?! from ).)*?|[^:]+)) from <HOST>(?: port \d+)?(?: ssh\d*)?(?(cond_user):|(?:(?:(?! from ).)*)$)
^%(__prefix_line)sROOT LOGIN REFUSED.* FROM <HOST>\s*$ ^%(__prefix_line)sROOT LOGIN REFUSED.* FROM <HOST>\s*$
^%(__prefix_line)s[iI](?:llegal|nvalid) user .* from <HOST>\s*$ ^%(__prefix_line)s[iI](?:llegal|nvalid) user .*? from <HOST>(?: port \d+)?\s*$
^%(__prefix_line)sUser .+ from <HOST> not allowed because not listed in AllowUsers\s*$ ^%(__prefix_line)sUser .+ from <HOST> not allowed because not listed in AllowUsers\s*$
^%(__prefix_line)sUser .+ from <HOST> not allowed because listed in DenyUsers\s*$ ^%(__prefix_line)sUser .+ from <HOST> not allowed because listed in DenyUsers\s*$
^%(__prefix_line)sUser .+ from <HOST> not allowed because not in any group\s*$ ^%(__prefix_line)sUser .+ from <HOST> not allowed because not in any group\s*$

View File

@ -14,7 +14,7 @@ __pam_re=\(?%(__pam_auth)s(?:\(\S+\))?\)?:?
_daemon = vsftpd _daemon = vsftpd
failregex = ^%(__prefix_line)s%(__pam_re)s\s+authentication failure; logname=\S* uid=\S* euid=\S* tty=(ftp)? ruser=\S* rhost=<HOST>(?:\s+user=.*)?\s*$ failregex = ^%(__prefix_line)s%(__pam_re)s\s+authentication failure; logname=\S* uid=\S* euid=\S* tty=(ftp)? ruser=\S* rhost=<HOST>(?:\s+user=.*)?\s*$
^ \[pid \d+\] \[.+\] FAIL LOGIN: Client "<HOST>"\s*$ ^ \[pid \d+\] \[[^\]]+\] FAIL LOGIN: Client "<HOST>"(?:\s*$|,)
ignoreregex = ignoreregex =

View File

@ -731,6 +731,13 @@ logpath = %(mysql_log)s
backend = %(mysql_backend)s backend = %(mysql_backend)s
# Log wrong MongoDB auth (for details see filter 'filter.d/mongodb-auth.conf')
[mongodb-auth]
# change port when running with "--shardsvr" or "--configsvr" runtime operation
port = 27017
logpath = /var/log/mongodb/mongodb.log
# Jail for more extended banning of persistent abusers # Jail for more extended banning of persistent abusers
# !!! WARNINGS !!! # !!! WARNINGS !!!
# 1. Make sure that your loglevel specified in fail2ban.conf/.local # 1. Make sure that your loglevel specified in fail2ban.conf/.local
@ -810,8 +817,9 @@ maxretry = 1
[pass2allow-ftp] [pass2allow-ftp]
# this pass2allow example allows FTP traffic after successful HTTP authentication # this pass2allow example allows FTP traffic after successful HTTP authentication
port = ftp,ftp-data,ftps,ftps-data port = ftp,ftp-data,ftps,ftps-data
# knocking_url variable must be overridden to some secret value in filter.d/apache-pass.local # knocking_url variable must be overridden to some secret value in jail.local
filter = apache-pass knocking_url = /knocking/
filter = apache-pass[knocking_url="%(knocking_url)s"]
# access log of the website with HTTP auth # access log of the website with HTTP auth
logpath = %(apache_access_log)s logpath = %(apache_access_log)s
blocktype = RETURN blocktype = RETURN

View File

@ -36,3 +36,15 @@ mysql_log = /var/log/mysql/mysqld.log
roundcube_errors_log = /srv/www/roundcubemail/logs/errors roundcube_errors_log = /srv/www/roundcubemail/logs/errors
solidpop3d_log = %(syslog_mail)s solidpop3d_log = %(syslog_mail)s
# These services will log to the journal via syslog, so use the journal by
# default.
syslog_backend = systemd
sshd_backend = systemd
dropbear_backend = systemd
proftpd_backend = systemd
pureftpd_backend = systemd
wuftpd_backend = systemd
postfix_backend = systemd
dovecot_backend = systemd
mysql_backend = systemd

View File

@ -168,7 +168,7 @@ after = 1.conf
parser, i = self._getSharedSCPWI(resource) parser, i = self._getSharedSCPWI(resource)
if not i: if not i:
return [] return []
except UnicodeDecodeError, e: except UnicodeDecodeError as e:
logSys.error("Error decoding config file '%s': %s" % (resource, e)) logSys.error("Error decoding config file '%s': %s" % (resource, e))
return [] return []

View File

@ -221,7 +221,7 @@ class ConfigReaderUnshared(SafeConfigParserWithIncludes):
if not pOptions is None and option[1] in pOptions: if not pOptions is None and option[1] in pOptions:
continue continue
values[option[1]] = v values[option[1]] = v
except NoSectionError, e: except NoSectionError as e:
# No "Definition" section or wrong basedir # No "Definition" section or wrong basedir
logSys.error(e) logSys.error(e)
values[option[1]] = option[2] values[option[1]] = option[2]

View File

@ -329,7 +329,7 @@ class Fail2banRegex(object):
if ret is not None: if ret is not None:
found = True found = True
regex = self._ignoreregex[ret].inc() regex = self._ignoreregex[ret].inc()
except RegexException, e: except RegexException as e:
output( e ) output( e )
return False return False
return found return found
@ -346,7 +346,7 @@ class Fail2banRegex(object):
regex = self._failregex[match[0]] regex = self._failregex[match[0]]
regex.inc() regex.inc()
regex.appendIP(match) regex.appendIP(match)
except RegexException, e: except RegexException as e:
output( e ) output( e )
return False return False
except IndexError: except IndexError:
@ -510,7 +510,7 @@ class Fail2banRegex(object):
output( "Use log file : %s" % cmd_log ) output( "Use log file : %s" % cmd_log )
output( "Use encoding : %s" % self.encoding ) output( "Use encoding : %s" % self.encoding )
test_lines = self.file_lines_gen(hdlr) test_lines = self.file_lines_gen(hdlr)
except IOError, e: except IOError as e:
output( e ) output( e )
return False return False
elif cmd_log == "systemd-journal": # pragma: no cover elif cmd_log == "systemd-journal": # pragma: no cover

View File

@ -171,7 +171,7 @@ class JailReader(ConfigReader):
self.__actions.append(action) self.__actions.append(action)
else: else:
raise AttributeError("Unable to read action") raise AttributeError("Unable to read action")
except Exception, e: except Exception as e:
logSys.error("Error in action definition " + act) logSys.error("Error in action definition " + act)
logSys.debug("Caught exception: %s" % (e,)) logSys.debug("Caught exception: %s" % (e,))
return False return False
@ -192,7 +192,7 @@ class JailReader(ConfigReader):
stream = [] stream = []
for opt in self.__opts: for opt in self.__opts:
if opt == "logpath" and \ if opt == "logpath" and \
self.__opts.get('backend', None) != "systemd": not self.__opts.get('backend', None).startswith("systemd"):
found_files = 0 found_files = 0
for path in self.__opts[opt].split("\n"): for path in self.__opts[opt].split("\n"):
path = path.rsplit(" ", 1) path = path.rsplit(" ", 1)

View File

@ -42,7 +42,7 @@ if sys.version_info >= (3,):
try: try:
x = json.dumps(x, ensure_ascii=False).encode( x = json.dumps(x, ensure_ascii=False).encode(
locale.getpreferredencoding(), 'replace') locale.getpreferredencoding(), 'replace')
except Exception, e: # pragma: no cover except Exception as e: # pragma: no cover
logSys.error('json dumps failed: %s', e) logSys.error('json dumps failed: %s', e)
x = '{}' x = '{}'
return x return x
@ -51,7 +51,7 @@ if sys.version_info >= (3,):
try: try:
x = json.loads(x.decode( x = json.loads(x.decode(
locale.getpreferredencoding(), 'replace')) locale.getpreferredencoding(), 'replace'))
except Exception, e: # pragma: no cover except Exception as e: # pragma: no cover
logSys.error('json loads failed: %s', e) logSys.error('json loads failed: %s', e)
x = {} x = {}
return x return x
@ -70,7 +70,7 @@ else:
try: try:
x = json.dumps(_normalize(x), ensure_ascii=False).decode( x = json.dumps(_normalize(x), ensure_ascii=False).decode(
locale.getpreferredencoding(), 'replace') locale.getpreferredencoding(), 'replace')
except Exception, e: # pragma: no cover except Exception as e: # pragma: no cover
logSys.error('json dumps failed: %s', e) logSys.error('json dumps failed: %s', e)
x = '{}' x = '{}'
return x return x
@ -79,7 +79,7 @@ else:
try: try:
x = _normalize(json.loads(x.decode( x = _normalize(json.loads(x.decode(
locale.getpreferredencoding(), 'replace'))) locale.getpreferredencoding(), 'replace')))
except Exception, e: # pragma: no cover except Exception as e: # pragma: no cover
logSys.error('json loads failed: %s', e) logSys.error('json loads failed: %s', e)
x = {} x = {}
return x return x
@ -175,7 +175,7 @@ class Fail2BanDb(object):
logSys.info( logSys.info(
"Connected to fail2ban persistent database '%s'", filename) "Connected to fail2ban persistent database '%s'", filename)
except sqlite3.OperationalError, e: except sqlite3.OperationalError as e:
logSys.error( logSys.error(
"Error connecting to fail2ban persistent database '%s': %s", "Error connecting to fail2ban persistent database '%s': %s",
filename, e.args[0]) filename, e.args[0])
@ -293,8 +293,12 @@ class Fail2BanDb(object):
Jail to be added to the database. Jail to be added to the database.
""" """
cur.execute( cur.execute(
"INSERT OR REPLACE INTO jails(name, enabled) VALUES(?, 1)", "INSERT OR IGNORE INTO jails(name, enabled) VALUES(?, 1)",
(jail.name,)) (jail.name,))
if cur.rowcount <= 0:
cur.execute(
"UPDATE jails SET enabled = 1 WHERE name = ? AND enabled != 1",
(jail.name,))
@commitandrollback @commitandrollback
def delJail(self, cur, jail): def delJail(self, cur, jail):
@ -317,7 +321,7 @@ class Fail2BanDb(object):
cur.execute("UPDATE jails SET enabled=0") cur.execute("UPDATE jails SET enabled=0")
@commitandrollback @commitandrollback
def getJailNames(self, cur): def getJailNames(self, cur, enabled=None):
"""Get name of jails in database. """Get name of jails in database.
Currently only used for testing purposes. Currently only used for testing purposes.
@ -327,7 +331,11 @@ class Fail2BanDb(object):
set set
Set of jail names. Set of jail names.
""" """
cur.execute("SELECT name FROM jails") if enabled is None:
cur.execute("SELECT name FROM jails")
else:
cur.execute("SELECT name FROM jails WHERE enabled=%s" %
(int(enabled),))
return set(row[0] for row in cur.fetchmany()) return set(row[0] for row in cur.fetchmany())
@commitandrollback @commitandrollback

View File

@ -64,7 +64,7 @@ class DateTemplate(object):
def getRegex(self): def getRegex(self):
return self._regex return self._regex
def setRegex(self, regex, wordBegin=True): def setRegex(self, regex, wordBegin=True, wordEnd=True):
"""Sets regex to use for searching for date in log line. """Sets regex to use for searching for date in log line.
Parameters Parameters
@ -72,8 +72,12 @@ class DateTemplate(object):
regex : str regex : str
The regex the template will use for searching for a date. The regex the template will use for searching for a date.
wordBegin : bool wordBegin : bool
Defines whether the regex should be modified to search at Defines whether the regex should be modified to search at beginning of a
beginning of a word, by adding "\\b" to start of regex. word, by adding special boundary r'(?=^|\b|\W)' to start of regex.
Default True.
wordEnd : bool
Defines whether the regex should be modified to search at end of a word,
by adding special boundary r'(?=\b|\W|$)' to end of regex.
Default True. Default True.
Raises Raises
@ -82,8 +86,10 @@ class DateTemplate(object):
If regular expression fails to compile If regular expression fails to compile
""" """
regex = regex.strip() regex = regex.strip()
if (wordBegin and not re.search(r'^\^', regex)): if wordBegin and not re.search(r'^\^', regex):
regex = r'\b' + regex regex = r'(?=^|\b|\W)' + regex
if wordEnd and not re.search(r'\$$', regex):
regex += r'(?=\b|\W|$)'
self._regex = regex self._regex = regex
self._cRegex = re.compile(regex, re.UNICODE | re.IGNORECASE) self._cRegex = re.compile(regex, re.UNICODE | re.IGNORECASE)

View File

@ -24,6 +24,7 @@ __license__ = "GPL"
import codecs import codecs
import fcntl import fcntl
import locale import locale
import logging
import os import os
import re import re
import sys import sys
@ -82,6 +83,8 @@ class Filter(JailThread):
self.__lastDate = None self.__lastDate = None
## External command ## External command
self.__ignoreCommand = False self.__ignoreCommand = False
## Default or preferred encoding (to decode bytes from file or journal):
self.__encoding = locale.getpreferredencoding()
self.dateDetector = DateDetector() self.dateDetector = DateDetector()
self.dateDetector.addDefaultTemplate() self.dateDetector.addDefaultTemplate()
@ -105,7 +108,7 @@ class Filter(JailThread):
logSys.warning( logSys.warning(
"Mutliline regex set for jail '%s' " "Mutliline regex set for jail '%s' "
"but maxlines not greater than 1") "but maxlines not greater than 1")
except RegexException, e: except RegexException as e:
logSys.error(e) logSys.error(e)
raise e raise e
@ -138,7 +141,7 @@ class Filter(JailThread):
try: try:
regex = Regex(value) regex = Regex(value)
self.__ignoreRegex.append(regex) self.__ignoreRegex.append(regex)
except RegexException, e: except RegexException as e:
logSys.error(e) logSys.error(e)
raise e raise e
@ -279,6 +282,27 @@ class Filter(JailThread):
def getMaxLines(self): def getMaxLines(self):
return self.__lineBufferSize return self.__lineBufferSize
##
# Set the log file encoding
#
# @param encoding the encoding used with log files
def setLogEncoding(self, encoding):
if encoding.lower() == "auto":
encoding = locale.getpreferredencoding()
codecs.lookup(encoding) # Raise LookupError if invalid codec
self.__encoding = encoding
logSys.info("Set jail log file encoding to %s" % encoding)
return encoding
##
# Get the log file encoding
#
# @return log encoding value
def getLogEncoding(self):
return self.__encoding
## ##
# Main loop. # Main loop.
# #
@ -394,8 +418,31 @@ class Filter(JailThread):
return False return False
if sys.version_info >= (3,):
@staticmethod
def uni_decode(x, enc, errors='strict'):
try:
if isinstance(x, bytes):
return x.decode(enc, errors)
return x
except (UnicodeDecodeError, UnicodeEncodeError): # pragma: no cover - unsure if reachable
if errors != 'strict':
raise
return uni_decode(x, enc, 'replace')
else:
@staticmethod
def uni_decode(x, enc, errors='strict'):
try:
if isinstance(x, unicode):
return x.encode(enc, errors)
return x
except (UnicodeDecodeError, UnicodeEncodeError): # pragma: no cover - unsure if reachable
if errors != 'strict':
raise
return uni_decode(x, enc, 'replace')
def processLine(self, line, date=None, returnRawHost=False, def processLine(self, line, date=None, returnRawHost=False,
checkAllRegex=False): checkAllRegex=False, checkFindTime=False):
"""Split the time portion from log msg and return findFailures on them """Split the time portion from log msg and return findFailures on them
""" """
if date: if date:
@ -414,21 +461,17 @@ class Filter(JailThread):
tupleLine = (l, "", "") tupleLine = (l, "", "")
return "".join(tupleLine[::2]), self.findFailure( return "".join(tupleLine[::2]), self.findFailure(
tupleLine, date, returnRawHost, checkAllRegex) tupleLine, date, returnRawHost, checkAllRegex, checkFindTime)
def processLineAndAdd(self, line, date=None): def processLineAndAdd(self, line, date=None):
"""Processes the line for failures and populates failManager """Processes the line for failures and populates failManager
""" """
for element in self.processLine(line, date)[1]: for element in self.processLine(line, date, checkFindTime=True)[1]:
ip = element[1] ip = element[1]
unixTime = element[2] unixTime = element[2]
lines = element[3] lines = element[3]
logSys.debug("Processing line with time:%s and ip:%s" logSys.debug("Processing line with time:%s and ip:%s"
% (unixTime, ip)) % (unixTime, ip))
if unixTime < MyTime.time() - self.getFindTime():
logSys.debug("Ignore line since time %s < %s - %s"
% (unixTime, MyTime.time(), self.getFindTime()))
break
if self.inIgnoreIPList(ip, log_ignore=True): if self.inIgnoreIPList(ip, log_ignore=True):
continue continue
logSys.info("[%s] Found %s" % (self.jail.name, ip)) logSys.info("[%s] Found %s" % (self.jail.name, ip))
@ -457,7 +500,7 @@ class Filter(JailThread):
# @return a dict with IP and timestamp. # @return a dict with IP and timestamp.
def findFailure(self, tupleLine, date=None, returnRawHost=False, def findFailure(self, tupleLine, date=None, returnRawHost=False,
checkAllRegex=False): checkAllRegex=False, checkFindTime=False):
failList = list() failList = list()
# Checks if we must ignore this line. # Checks if we must ignore this line.
@ -489,6 +532,11 @@ class Filter(JailThread):
timeText = self.__lastTimeText or "".join(tupleLine[::2]) timeText = self.__lastTimeText or "".join(tupleLine[::2])
date = self.__lastDate date = self.__lastDate
if checkFindTime and date is not None and date < MyTime.time() - self.getFindTime():
logSys.log(5, "Ignore line since time %s < %s - %s",
date, MyTime.time(), self.getFindTime())
return failList
self.__lineBuffer = ( self.__lineBuffer = (
self.__lineBuffer + [tupleLine])[-self.__lineBufferSize:] self.__lineBuffer + [tupleLine])[-self.__lineBufferSize:]
logSys.log(5, "Looking for failregex match of %r" % self.__lineBuffer) logSys.log(5, "Looking for failregex match of %r" % self.__lineBuffer)
@ -536,7 +584,7 @@ class Filter(JailThread):
failRegex.getMatchedLines()]) failRegex.getMatchedLines()])
if not checkAllRegex: if not checkAllRegex:
break break
except RegexException, e: # pragma: no cover - unsure if reachable except RegexException as e: # pragma: no cover - unsure if reachable
logSys.error(e) logSys.error(e)
return failList return failList
@ -554,7 +602,6 @@ class FileFilter(Filter):
Filter.__init__(self, jail, **kwargs) Filter.__init__(self, jail, **kwargs)
## The log file path. ## The log file path.
self.__logs = dict() self.__logs = dict()
self.setLogEncoding("auto")
## ##
# Add a log file path # Add a log file path
@ -625,21 +672,9 @@ class FileFilter(Filter):
# @param encoding the encoding used with log files # @param encoding the encoding used with log files
def setLogEncoding(self, encoding): def setLogEncoding(self, encoding):
if encoding.lower() == "auto": encoding = super(FileFilter, self).setLogEncoding(encoding)
encoding = locale.getpreferredencoding()
codecs.lookup(encoding) # Raise LookupError if invalid codec
for log in self.__logs.itervalues(): for log in self.__logs.itervalues():
log.setEncoding(encoding) log.setEncoding(encoding)
self.__encoding = encoding
logSys.info("Set jail log file encoding to %s" % encoding)
##
# Get the log file encoding
#
# @return log encoding value
def getLogEncoding(self):
return self.__encoding
def getLog(self, path): def getLog(self, path):
return self.__logs.get(path, None) return self.__logs.get(path, None)
@ -660,15 +695,15 @@ class FileFilter(Filter):
try: try:
has_content = log.open() has_content = log.open()
# see http://python.org/dev/peps/pep-3151/ # see http://python.org/dev/peps/pep-3151/
except IOError, e: except IOError as e:
logSys.error("Unable to open %s" % filename) logSys.error("Unable to open %s" % filename)
logSys.exception(e) logSys.exception(e)
return False return False
except OSError, e: # pragma: no cover - requires race condition to tigger this except OSError as e: # pragma: no cover - requires race condition to tigger this
logSys.error("Error opening %s" % filename) logSys.error("Error opening %s" % filename)
logSys.exception(e) logSys.exception(e)
return False return False
except Exception, e: # pragma: no cover - Requires implemention error in FileContainer to generate except Exception as e: # pragma: no cover - Requires implemention error in FileContainer to generate
logSys.error("Internal errror in FileContainer open method - please report as a bug to https://github.com/fail2ban/fail2ban/issues") logSys.error("Internal errror in FileContainer open method - please report as a bug to https://github.com/fail2ban/fail2ban/issues")
logSys.exception(e) logSys.exception(e)
return False return False
@ -707,7 +742,12 @@ class FileFilter(Filter):
try: try:
import hashlib import hashlib
md5sum = hashlib.md5 try:
md5sum = hashlib.md5
# try to use it (several standards like FIPS forbid it):
md5sum(' ').hexdigest()
except: # pragma: no cover
md5sum = hashlib.sha1
except ImportError: # pragma: no cover except ImportError: # pragma: no cover
# hashlib was introduced in Python 2.5. For compatibility with those # hashlib was introduced in Python 2.5. For compatibility with those
# elderly Pythons, import from md5 # elderly Pythons, import from md5
@ -791,14 +831,19 @@ class FileContainer:
@staticmethod @staticmethod
def decode_line(filename, enc, line): def decode_line(filename, enc, line):
try: try:
line = line.decode(enc, 'strict') return line.decode(enc, 'strict')
except UnicodeDecodeError: except (UnicodeDecodeError, UnicodeEncodeError) as e:
logSys.warning( global _decode_line_warn
lev = logging.DEBUG
if _decode_line_warn.get(filename, 0) <= MyTime.time():
lev = logging.WARNING
_decode_line_warn[filename] = MyTime.time() + 24*60*60
logSys.log(lev,
"Error decoding line from '%s' with '%s'." "Error decoding line from '%s' with '%s'."
" Consider setting logencoding=utf-8 (or another appropriate" " Consider setting logencoding=utf-8 (or another appropriate"
" encoding) for this jail. Continuing" " encoding) for this jail. Continuing"
" to process line ignoring invalid characters: %r" % " to process line ignoring invalid characters: %r",
(filename, enc, line)) filename, enc, line)
# decode with replacing error chars: # decode with replacing error chars:
line = line.decode(enc, 'replace') line = line.decode(enc, 'replace')
return line return line
@ -819,6 +864,8 @@ class FileContainer:
## print "D: Closed %s with pos %d" % (handler, self.__pos) ## print "D: Closed %s with pos %d" % (handler, self.__pos)
## sys.stdout.flush() ## sys.stdout.flush()
_decode_line_warn = {}
## ##
# JournalFilter class. # JournalFilter class.
@ -858,11 +905,11 @@ class DNSUtils:
# retrieve ip (todo: use AF_INET6 for IPv6) # retrieve ip (todo: use AF_INET6 for IPv6)
try: try:
return set([i[4][0] for i in socket.getaddrinfo(dns, None, socket.AF_INET, 0, socket.IPPROTO_TCP)]) return set([i[4][0] for i in socket.getaddrinfo(dns, None, socket.AF_INET, 0, socket.IPPROTO_TCP)])
except socket.error, e: except socket.error as e:
logSys.warning("Unable to find a corresponding IP address for %s: %s" logSys.warning("Unable to find a corresponding IP address for %s: %s"
% (dns, e)) % (dns, e))
return list() return list()
except socket.error, e: except socket.error as e:
logSys.warning("Socket error raised trying to resolve hostname %s: %s" logSys.warning("Socket error raised trying to resolve hostname %s: %s"
% (dns, e)) % (dns, e))
return list() return list()
@ -871,7 +918,7 @@ class DNSUtils:
def ipToName(ip): def ipToName(ip):
try: try:
return socket.gethostbyaddr(ip)[0] return socket.gethostbyaddr(ip)[0]
except socket.error, e: except socket.error as e:
logSys.debug("Unable to find a name for the IP %s: %s" % (ip, e)) logSys.debug("Unable to find a name for the IP %s: %s" % (ip, e))
return None return None

View File

@ -138,7 +138,7 @@ class FilterPoll(FileFilter):
logSys.debug("%s has been modified", filename) logSys.debug("%s has been modified", filename)
self.__prevStats[filename] = stats self.__prevStats[filename] = stats
return True return True
except OSError, e: except OSError as e:
logSys.error("Unable to get stat on %s because of: %s" logSys.error("Unable to get stat on %s because of: %s"
% (filename, e)) % (filename, e))
self.__file404Cnt[filename] += 1 self.__file404Cnt[filename] += 1

View File

@ -44,7 +44,7 @@ if not hasattr(pyinotify, '__version__') \
try: try:
manager = pyinotify.WatchManager() manager = pyinotify.WatchManager()
del manager del manager
except Exception, e: except Exception as e:
raise ImportError("Pyinotify is probably not functional on this system: %s" raise ImportError("Pyinotify is probably not functional on this system: %s"
% str(e)) % str(e))

View File

@ -31,9 +31,9 @@ if LooseVersion(getattr(journal, '__version__', "0")) < '204':
raise ImportError("Fail2Ban requires systemd >= 204") raise ImportError("Fail2Ban requires systemd >= 204")
from .failmanager import FailManagerEmpty from .failmanager import FailManagerEmpty
from .filter import JournalFilter from .filter import JournalFilter, Filter
from .mytime import MyTime from .mytime import MyTime
from ..helpers import getLogger from ..helpers import getLogger, logging, splitwords
# Gets the instance of the logger. # Gets the instance of the logger.
logSys = getLogger(__name__) logSys = getLogger(__name__)
@ -54,14 +54,45 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
# @param jail the jail object # @param jail the jail object
def __init__(self, jail, **kwargs): def __init__(self, jail, **kwargs):
jrnlargs = FilterSystemd._getJournalArgs(kwargs)
JournalFilter.__init__(self, jail, **kwargs) JournalFilter.__init__(self, jail, **kwargs)
self.__modified = False self.__modified = 0
# Initialise systemd-journal connection # Initialise systemd-journal connection
self.__journal = journal.Reader(converters={'__CURSOR': lambda x: x}) self.__journal = journal.Reader(**jrnlargs)
self.__matches = [] self.__matches = []
self.setDatePattern(None) self.setDatePattern(None)
self.ticks = 0
logSys.debug("Created FilterSystemd") logSys.debug("Created FilterSystemd")
@staticmethod
def _getJournalArgs(kwargs):
args = {'converters':{'__CURSOR': lambda x: x}}
try:
args['path'] = kwargs.pop('journalpath')
except KeyError:
pass
try:
args['files'] = kwargs.pop('journalfiles')
except KeyError:
pass
else:
import glob
p = args['files']
if not isinstance(p, (list, set, tuple)):
p = splitwords(p)
files = []
for p in p:
files.extend(glob.glob(p))
args['files'] = list(set(files))
try:
args['flags'] = kwargs.pop('journalflags')
except KeyError:
pass
return args
## ##
# Add a journal match filters from list structure # Add a journal match filters from list structure
# #
@ -139,21 +170,9 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
def getJournalMatch(self): def getJournalMatch(self):
return self.__matches return self.__matches
## def uni_decode(self, x):
# Join group of log elements which may be a mix of bytes and strings v = Filter.uni_decode(x, self.getLogEncoding())
# return v
# @param elements list of strings and bytes
# @return elements joined as string
@staticmethod
def _joinStrAndBytes(elements):
strElements = []
for element in elements:
if isinstance(element, str):
strElements.append(element)
else:
strElements.append(str(element, errors='ignore'))
return " ".join(strElements)
## ##
# Format journal log entry into syslog style # Format journal log entry into syslog style
@ -161,52 +180,51 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
# @param entry systemd journal entry dict # @param entry systemd journal entry dict
# @return format log line # @return format log line
@classmethod def formatJournalEntry(self, logentry):
def formatJournalEntry(cls, logentry): # Be sure, all argument of line tuple should have the same type:
logelements = [""] uni_decode = self.uni_decode
if logentry.get('_HOSTNAME'): logelements = []
logelements.append(logentry['_HOSTNAME']) v = logentry.get('_HOSTNAME')
if logentry.get('SYSLOG_IDENTIFIER'): if v:
logelements.append(logentry['SYSLOG_IDENTIFIER']) logelements.append(uni_decode(v))
if logentry.get('SYSLOG_PID'): v = logentry.get('SYSLOG_IDENTIFIER')
logelements[-1] += ("[%i]" % logentry['SYSLOG_PID']) if not v:
elif logentry.get('_PID'): v = logentry.get('_COMM')
logelements[-1] += ("[%i]" % logentry['_PID']) if v:
logelements.append(uni_decode(v))
v = logentry.get('SYSLOG_PID')
if not v:
v = logentry.get('_PID')
if v:
logelements[-1] += ("[%i]" % v)
logelements[-1] += ":" logelements[-1] += ":"
elif logentry.get('_COMM'): if logelements[-1] == "kernel:":
logelements.append(logentry['_COMM']) if '_SOURCE_MONOTONIC_TIMESTAMP' in logentry:
if logentry.get('_PID'): monotonic = logentry.get('_SOURCE_MONOTONIC_TIMESTAMP')
logelements[-1] += ("[%i]" % logentry['_PID']) else:
logelements[-1] += ":" monotonic = logentry.get('__MONOTONIC_TIMESTAMP')[0]
if logelements[-1] == "kernel:": logelements.append("[%12.6f]" % monotonic.total_seconds())
if '_SOURCE_MONOTONIC_TIMESTAMP' in logentry: msg = logentry.get('MESSAGE','')
monotonic = logentry.get('_SOURCE_MONOTONIC_TIMESTAMP') if isinstance(msg, list):
else: logelements.append(" ".join(uni_decode(v) for v in msg))
monotonic = logentry.get('__MONOTONIC_TIMESTAMP')[0]
logelements.append("[%12.6f]" % monotonic.total_seconds())
if isinstance(logentry.get('MESSAGE',''), list):
logelements.append(" ".join(logentry['MESSAGE']))
else: else:
logelements.append(logentry.get('MESSAGE', '')) logelements.append(uni_decode(msg))
try: logline = " ".join(logelements)
logline = u" ".join(logelements)
except UnicodeDecodeError:
# Python 2, so treat as string
logline = " ".join([str(logline) for logline in logelements])
except TypeError:
# Python 3, one or more elements bytes
logSys.warning("Error decoding log elements from journal: %s" %
repr(logelements))
logline = cls._joinStrAndBytes(logelements)
date = logentry.get('_SOURCE_REALTIME_TIMESTAMP', date = logentry.get('_SOURCE_REALTIME_TIMESTAMP',
logentry.get('__REALTIME_TIMESTAMP')) logentry.get('__REALTIME_TIMESTAMP'))
logSys.debug("Read systemd journal entry: %r" % logSys.debug("Read systemd journal entry: %r" %
"".join([date.isoformat(), logline])) "".join([date.isoformat(), logline]))
return (('', date.isoformat(), logline), ## use the same type for 1st argument:
return ((logline[:0], date.isoformat(), logline),
time.mktime(date.timetuple()) + date.microsecond/1.0E6) time.mktime(date.timetuple()) + date.microsecond/1.0E6)
def seekToTime(self, date):
if not isinstance(date, datetime.datetime):
date = datetime.datetime.fromtimestamp(date)
self.__journal.seek_realtime(date)
## ##
# Main loop. # Main loop.
# #
@ -224,7 +242,7 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
# Seek to now - findtime in journal # Seek to now - findtime in journal
start_time = datetime.datetime.now() - \ start_time = datetime.datetime.now() - \
datetime.timedelta(seconds=int(self.getFindTime())) datetime.timedelta(seconds=int(self.getFindTime()))
self.__journal.seek_realtime(start_time) self.seekToTime(start_time)
# Move back one entry to ensure do not end up in dead space # Move back one entry to ensure do not end up in dead space
# if start time beyond end of journal # if start time beyond end of journal
try: try:
@ -233,29 +251,38 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
pass # Reading failure, so safe to ignore pass # Reading failure, so safe to ignore
while self.active: while self.active:
if not self.idle: # wait for records (or for timeout in sleeptime seconds):
while self.active:
try:
logentry = self.__journal.get_next()
except OSError:
logSys.warning(
"Error reading line from systemd journal")
continue
if logentry:
self.processLineAndAdd(
*self.formatJournalEntry(logentry))
self.__modified = True
else:
break
if self.__modified:
try:
while True:
ticket = self.failManager.toBan()
self.jail.putFailTicket(ticket)
except FailManagerEmpty:
self.failManager.cleanup(MyTime.time())
self.__modified = False
self.__journal.wait(self.sleeptime) self.__journal.wait(self.sleeptime)
if self.idle:
# because journal.wait will returns immediatelly if we have records in journal,
# just wait a little bit here for not idle, to prevent hi-load:
time.sleep(self.sleeptime)
continue
self.__modified = 0
while self.active:
logentry = None
try:
logentry = self.__journal.get_next()
except OSError as e:
logSys.error("Error reading line from systemd journal: %s",
e, exc_info=logSys.getEffectiveLevel() <= logging.DEBUG)
self.ticks += 1
if logentry:
self.processLineAndAdd(
*self.formatJournalEntry(logentry))
self.__modified += 1
if self.__modified >= 100: # todo: should be configurable
break
else:
break
if self.__modified:
try:
while True:
ticket = self.failManager.toBan()
self.jail.putFailTicket(ticket)
except FailManagerEmpty:
self.failManager.cleanup(MyTime.time())
logSys.debug((self.jail is not None and self.jail.name logSys.debug((self.jail is not None and self.jail.name
or "jailless") +" filter terminated") or "jailless") +" filter terminated")
return True return True

View File

@ -27,6 +27,7 @@ import logging
import Queue import Queue
from .actions import Actions from .actions import Actions
from ..client.jailreader import JailReader
from ..helpers import getLogger from ..helpers import getLogger
# Gets the instance of the logger. # Gets the instance of the logger.
@ -82,6 +83,7 @@ class Jail:
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, beArgs = JailReader.extractOptions(backend)
backend = backend.lower() # to assure consistent matching backend = backend.lower() # to assure consistent matching
backends = self._BACKENDS backends = self._BACKENDS
@ -98,7 +100,7 @@ class Jail:
for b in backends: for b in backends:
initmethod = getattr(self, '_init%s' % b.capitalize()) initmethod = getattr(self, '_init%s' % b.capitalize())
try: try:
initmethod() initmethod(**beArgs)
if backend != 'auto' and b != backend: if backend != 'auto' and b != backend:
logSys.warning("Could only initiated %r backend whenever " logSys.warning("Could only initiated %r backend whenever "
"%r was requested" % (b, backend)) "%r was requested" % (b, backend))
@ -106,7 +108,7 @@ class Jail:
logSys.info("Initiated %r backend" % b) logSys.info("Initiated %r backend" % b)
self.__actions = Actions(self) self.__actions = Actions(self)
return # we are done return # we are done
except ImportError, e: except ImportError as e:
# Log debug if auto, but error if specific # Log debug if auto, but error if specific
logSys.log( logSys.log(
logging.DEBUG if backend == "auto" else logging.ERROR, logging.DEBUG if backend == "auto" else logging.ERROR,
@ -117,28 +119,28 @@ class Jail:
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, **kwargs):
from filterpoll import FilterPoll from filterpoll import FilterPoll
logSys.info("Jail '%s' uses poller" % self.name) logSys.info("Jail '%s' uses poller %r" % (self.name, kwargs))
self.__filter = FilterPoll(self) self.__filter = FilterPoll(self, **kwargs)
def _initGamin(self): def _initGamin(self, **kwargs):
# Try to import gamin # Try to import gamin
from filtergamin import FilterGamin from filtergamin import FilterGamin
logSys.info("Jail '%s' uses Gamin" % self.name) logSys.info("Jail '%s' uses Gamin %r" % (self.name, kwargs))
self.__filter = FilterGamin(self) self.__filter = FilterGamin(self, **kwargs)
def _initPyinotify(self): def _initPyinotify(self, **kwargs):
# Try to import pyinotify # Try to import pyinotify
from filterpyinotify import FilterPyinotify from filterpyinotify import FilterPyinotify
logSys.info("Jail '%s' uses pyinotify" % self.name) logSys.info("Jail '%s' uses pyinotify %r" % (self.name, kwargs))
self.__filter = FilterPyinotify(self) self.__filter = FilterPyinotify(self, **kwargs)
def _initSystemd(self): # pragma: systemd no cover def _initSystemd(self, **kwargs): # pragma: systemd no cover
# Try to import systemd # Try to import systemd
from filtersystemd import FilterSystemd from filtersystemd import FilterSystemd
logSys.info("Jail '%s' uses systemd" % self.name) logSys.info("Jail '%s' uses systemd %r" % (self.name, kwargs))
self.__filter = FilterSystemd(self) self.__filter = FilterSystemd(self, **kwargs)
@property @property
def name(self): def name(self):

View File

@ -108,20 +108,20 @@ class Server:
pidFile = open(pidfile, 'w') pidFile = open(pidfile, 'w')
pidFile.write("%s\n" % os.getpid()) pidFile.write("%s\n" % os.getpid())
pidFile.close() pidFile.close()
except IOError, e: except IOError as e:
logSys.error("Unable to create PID file: %s" % e) logSys.error("Unable to create PID file: %s" % e)
# Start the communication # Start the communication
logSys.debug("Starting communication") logSys.debug("Starting communication")
try: try:
self.__asyncServer.start(sock, force) self.__asyncServer.start(sock, force)
except AsyncServerException, e: except AsyncServerException as e:
logSys.error("Could not start server: %s", e) logSys.error("Could not start server: %s", e)
# Removes the PID file. # Removes the PID file.
try: try:
logSys.debug("Remove PID file %s" % pidfile) logSys.debug("Remove PID file %s" % pidfile)
os.remove(pidfile) os.remove(pidfile)
except OSError, e: except OSError as e:
logSys.error("Unable to remove PID file: %s" % e) logSys.error("Unable to remove PID file: %s" % e)
logSys.info("Exiting Fail2ban") logSys.info("Exiting Fail2ban")
@ -237,13 +237,11 @@ class Server:
def setLogEncoding(self, name, encoding): def setLogEncoding(self, name, encoding):
filter_ = self.__jails[name].filter filter_ = self.__jails[name].filter
if isinstance(filter_, FileFilter): filter_.setLogEncoding(encoding)
filter_.setLogEncoding(encoding)
def getLogEncoding(self, name): def getLogEncoding(self, name):
filter_ = self.__jails[name].filter filter_ = self.__jails[name].filter
if isinstance(filter_, FileFilter): return filter_.getLogEncoding()
return filter_.getLogEncoding()
def setFindTime(self, name, value): def setFindTime(self, name, value):
self.__jails[name].filter.setFindTime(value) self.__jails[name].filter.setFindTime(value)
@ -543,7 +541,7 @@ class Server:
# the child gets a new PID, making it impossible for its PID to equal its # the child gets a new PID, making it impossible for its PID to equal its
# PGID. # PGID.
pid = os.fork() pid = os.fork()
except OSError, e: except OSError as e:
return((e.errno, e.strerror)) # ERROR (return a tuple) return((e.errno, e.strerror)) # ERROR (return a tuple)
if pid == 0: # The first child. if pid == 0: # The first child.
@ -564,7 +562,7 @@ class Server:
# fork guarantees that the child is no longer a session leader, thus # fork guarantees that the child is no longer a session leader, thus
# preventing the daemon from ever acquiring a controlling terminal. # preventing the daemon from ever acquiring a controlling terminal.
pid = os.fork() # Fork a second child. pid = os.fork() # Fork a second child.
except OSError, e: except OSError as e:
return((e.errno, e.strerror)) # ERROR (return a tuple) return((e.errno, e.strerror)) # ERROR (return a tuple)
if (pid == 0): # The second child. if (pid == 0): # The second child.

View File

@ -56,7 +56,7 @@ class Transmitter:
try: try:
ret = self.__commandHandler(command) ret = self.__commandHandler(command)
ack = 0, ret ack = 0, ret
except Exception, e: except Exception as e:
logSys.warning("Command %r has failed. Received %r" logSys.warning("Command %r has failed. Received %r"
% (command, e)) % (command, e))
ack = 1, e ack = 1, e

42
fail2ban/setup.py Normal file
View File

@ -0,0 +1,42 @@
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
# vi: set ft=python sts=4 ts=4 sw=4 noet :
# This file is part of Fail2Ban.
#
# Fail2Ban is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Fail2Ban is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Fail2Ban; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
__author__ = "Serg G. Brester"
__license__ = "GPL"
import os
import sys
def updatePyExec(bindir, executable=None):
"""Update fail2ban-python link to current python version (where f2b-modules located/installed)
"""
bindir = os.path.realpath(bindir)
if executable is None:
executable = sys.executable
pypath = os.path.join(bindir, 'fail2ban-python')
# if not exists or point to another version - update link:
isfile = os.path.isfile(pypath)
if not isfile or os.path.realpath(pypath) != os.path.realpath(executable):
if isfile:
os.unlink(pypath)
os.symlink(executable, pypath)
# extend current environment path (e.g. if fail2ban not yet installed):
if bindir not in os.environ["PATH"].split(os.pathsep):
os.environ["PATH"] = os.environ["PATH"] + os.pathsep + bindir;

View File

@ -49,7 +49,7 @@ if sys.version_info >= (2,7):
def testCategory(self): def testCategory(self):
categories = self.action.getCategories() categories = self.action.getCategories()
self.assertTrue("ssh" in categories) self.assertIn("ssh", categories)
self.assertTrue(len(categories) >= 10) self.assertTrue(len(categories) >= 10)
self.assertRaises( self.assertRaises(

View File

@ -101,21 +101,21 @@ class SMTPActionTest(unittest.TestCase):
self.assertEqual(self.smtpd.rcpttos, ["root"]) self.assertEqual(self.smtpd.rcpttos, ["root"])
subject = "Subject: [Fail2Ban] %s: banned %s" % ( subject = "Subject: [Fail2Ban] %s: banned %s" % (
self.jail.name, aInfo['ip']) self.jail.name, aInfo['ip'])
self.assertTrue(subject in self.smtpd.data.replace("\n", "")) self.assertIn(subject, self.smtpd.data.replace("\n", ""))
self.assertTrue( self.assertTrue(
"%i attempts" % aInfo['failures'] in self.smtpd.data) "%i attempts" % aInfo['failures'] in self.smtpd.data)
self.action.matches = "matches" self.action.matches = "matches"
self.action.ban(aInfo) self.action.ban(aInfo)
self.assertTrue(aInfo['matches'] in self.smtpd.data) self.assertIn(aInfo['matches'], self.smtpd.data)
self.action.matches = "ipjailmatches" self.action.matches = "ipjailmatches"
self.action.ban(aInfo) self.action.ban(aInfo)
self.assertTrue(aInfo['ipjailmatches'] in self.smtpd.data) self.assertIn(aInfo['ipjailmatches'], self.smtpd.data)
self.action.matches = "ipmatches" self.action.matches = "ipmatches"
self.action.ban(aInfo) self.action.ban(aInfo)
self.assertTrue(aInfo['ipmatches'] in self.smtpd.data) self.assertIn(aInfo['ipmatches'], self.smtpd.data)
def testOptions(self): def testOptions(self):
self.action.start() self.action.start()

View File

@ -65,12 +65,12 @@ class ExecuteActions(LogCaptureTestCase):
def testActionsManipulation(self): def testActionsManipulation(self):
self.__actions.add('test') self.__actions.add('test')
self.assertTrue(self.__actions['test']) self.assertTrue(self.__actions['test'])
self.assertTrue('test' in self.__actions) self.assertIn('test', self.__actions)
self.assertFalse('nonexistant action' in self.__actions) self.assertNotIn('nonexistant action', self.__actions)
self.__actions.add('test1') self.__actions.add('test1')
del self.__actions['test'] del self.__actions['test']
del self.__actions['test1'] del self.__actions['test1']
self.assertFalse('test' in self.__actions) self.assertNotIn('test', self.__actions)
self.assertEqual(len(self.__actions), 0) self.assertEqual(len(self.__actions), 0)
self.__actions.setBanTime(127) self.__actions.setBanTime(127)

View File

@ -36,7 +36,7 @@ from ..client.jailsreader import JailsReader
from ..client.actionreader import ActionReader from ..client.actionreader import ActionReader
from ..client.configurator import Configurator from ..client.configurator import Configurator
from ..version import version from ..version import version
from .utils import LogCaptureTestCase from .utils import LogCaptureTestCase, with_tmpdir
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files") TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
@ -94,9 +94,8 @@ option = %s
if not os.access(f, os.R_OK): if not os.access(f, os.R_OK):
self.assertFalse(self.c.read('d')) # should not be readable BUT present self.assertFalse(self.c.read('d')) # should not be readable BUT present
else: else:
# SkipTest introduced only in 2.7 thus can't yet use generally import platform
# raise unittest.SkipTest("Skipping on %s -- access rights are not enforced" % platform) raise unittest.SkipTest("Skipping on %s -- access rights are not enforced" % platform.platform())
pass
def testOptionalDotDDir(self): def testOptionalDotDDir(self):
self.assertFalse(self.c.read('c')) # nothing is there yet self.assertFalse(self.c.read('c')) # nothing is there yet
@ -281,8 +280,8 @@ class JailReaderTest(LogCaptureTestCase):
self.assertEqual(eval(act[2][5]).get('agent', '<wrong>'), useragent) self.assertEqual(eval(act[2][5]).get('agent', '<wrong>'), useragent)
self.assertEqual(act[3], ['set', 'blocklisttest', 'action', 'mynetwatchman', 'agent', useragent]) self.assertEqual(act[3], ['set', 'blocklisttest', 'action', 'mynetwatchman', 'agent', useragent])
def testGlob(self): @with_tmpdir
d = tempfile.mkdtemp(prefix="f2b-temp") def testGlob(self, d):
# Generate few files # Generate few files
# regular file # regular file
f1 = os.path.join(d, 'f1') f1 = os.path.join(d, 'f1')
@ -297,9 +296,6 @@ class JailReaderTest(LogCaptureTestCase):
self.assertEqual(JailReader._glob(f2), []) self.assertEqual(JailReader._glob(f2), [])
self.assertLogged('File %s is a dangling link, thus cannot be monitored' % f2) self.assertLogged('File %s is a dangling link, thus cannot be monitored' % f2)
self.assertEqual(JailReader._glob(os.path.join(d, 'nonexisting')), []) self.assertEqual(JailReader._glob(os.path.join(d, 'nonexisting')), [])
os.remove(f1)
os.remove(f2)
os.rmdir(d)
class FilterReaderTest(unittest.TestCase): class FilterReaderTest(unittest.TestCase):
@ -410,7 +406,7 @@ class FilterReaderTest(unittest.TestCase):
# from testcase01 # from testcase01
filterReader.get('Definition', 'failregex') filterReader.get('Definition', 'failregex')
filterReader.get('Definition', 'ignoreregex') filterReader.get('Definition', 'ignoreregex')
except Exception, e: # pragma: no cover - failed if reachable except Exception as e: # pragma: no cover - failed if reachable
self.fail('unexpected options after readexplicit: %s' % (e)) self.fail('unexpected options after readexplicit: %s' % (e))
@ -433,10 +429,10 @@ class JailsReaderTestCache(LogCaptureTestCase):
cnt += 1 cnt += 1
return cnt return cnt
def testTestJailConfCache(self): @with_tmpdir
def testTestJailConfCache(self, basedir):
saved_ll = configparserinc.logLevel saved_ll = configparserinc.logLevel
configparserinc.logLevel = logging.DEBUG configparserinc.logLevel = logging.DEBUG
basedir = tempfile.mkdtemp("fail2ban_conf")
try: try:
shutil.rmtree(basedir) shutil.rmtree(basedir)
shutil.copytree(CONFIG_DIR, basedir) shutil.copytree(CONFIG_DIR, basedir)
@ -468,7 +464,6 @@ class JailsReaderTestCache(LogCaptureTestCase):
cnt = self._getLoggedReadCount(r'action\.d/iptables-common\.conf') cnt = self._getLoggedReadCount(r'action\.d/iptables-common\.conf')
self.assertTrue(cnt == 1, "Unexpected count by reading of action files, cnt = %s" % cnt) self.assertTrue(cnt == 1, "Unexpected count by reading of action files, cnt = %s" % cnt)
finally: finally:
shutil.rmtree(basedir)
configparserinc.logLevel = saved_ll configparserinc.logLevel = saved_ll
@ -525,12 +520,12 @@ class JailsReaderTest(LogCaptureTestCase):
self.assertTrue(actionReader.read()) self.assertTrue(actionReader.read())
actionReader.getOptions({}) # populate _opts actionReader.getOptions({}) # populate _opts
if not actionName.endswith('-common'): if not actionName.endswith('-common'):
self.assertTrue('Definition' in actionReader.sections(), self.assertIn('Definition', actionReader.sections(),
msg="Action file %r is lacking [Definition] section" % actionConfig) msg="Action file %r is lacking [Definition] section" % actionConfig)
# all must have some actionban defined # all must have some actionban defined
self.assertTrue(actionReader._opts.get('actionban', '').strip(), self.assertTrue(actionReader._opts.get('actionban', '').strip(),
msg="Action file %r is lacking actionban" % actionConfig) msg="Action file %r is lacking actionban" % actionConfig)
self.assertTrue('Init' in actionReader.sections(), self.assertIn('Init', actionReader.sections(),
msg="Action file %r is lacking [Init] section" % actionConfig) msg="Action file %r is lacking [Init] section" % actionConfig)
def testReadStockJailConf(self): def testReadStockJailConf(self):
@ -582,7 +577,7 @@ class JailsReaderTest(LogCaptureTestCase):
self.assertTrue(len(actName)) self.assertTrue(len(actName))
self.assertTrue(isinstance(actOpt, dict)) self.assertTrue(isinstance(actOpt, dict))
if actName == 'iptables-multiport': if actName == 'iptables-multiport':
self.assertTrue('port' in actOpt) self.assertIn('port', actOpt)
actionReader = ActionReader( actionReader = ActionReader(
actName, jail, {}, basedir=CONFIG_DIR) actName, jail, {}, basedir=CONFIG_DIR)
@ -632,11 +627,13 @@ class JailsReaderTest(LogCaptureTestCase):
# and we know even some of them by heart # and we know even some of them by heart
for j in ['sshd', 'recidive']: for j in ['sshd', 'recidive']:
# by default we have 'auto' backend ATM # by default we have 'auto' backend ATM, but some distributions can overwrite it,
self.assertTrue(['add', j, 'auto'] in comm_commands) # (e.g. fedora default is 'systemd') therefore let check it without backend...
self.assertIn(['add', j],
(cmd[:2] for cmd in comm_commands if len(cmd) == 3 and cmd[0] == 'add'))
# and warn on useDNS # and warn on useDNS
self.assertTrue(['set', j, 'usedns', 'warn'] in comm_commands) self.assertIn(['set', j, 'usedns', 'warn'], comm_commands)
self.assertTrue(['start', j] in comm_commands) self.assertIn(['start', j], comm_commands)
# last commands should be the 'start' commands # last commands should be the 'start' commands
self.assertEqual(comm_commands[-1][0], 'start') self.assertEqual(comm_commands[-1][0], 'start')
@ -655,7 +652,7 @@ class JailsReaderTest(LogCaptureTestCase):
action_name = action.getName() action_name = action.getName()
if '<blocktype>' in str(commands): if '<blocktype>' in str(commands):
# Verify that it is among cInfo # Verify that it is among cInfo
self.assertTrue('blocktype' in action._initOpts) self.assertIn('blocktype', action._initOpts)
# Verify that we have a call to set it up # Verify that we have a call to set it up
blocktype_present = False blocktype_present = False
target_command = ['set', jail_name, 'action', action_name, 'blocktype'] target_command = ['set', jail_name, 'action', action_name, 'blocktype']
@ -716,8 +713,8 @@ class JailsReaderTest(LogCaptureTestCase):
self.assertEqual(configurator._Configurator__jails.getBaseDir(), '/tmp') self.assertEqual(configurator._Configurator__jails.getBaseDir(), '/tmp')
self.assertEqual(configurator.getBaseDir(), CONFIG_DIR) self.assertEqual(configurator.getBaseDir(), CONFIG_DIR)
def testMultipleSameAction(self): @with_tmpdir
basedir = tempfile.mkdtemp("fail2ban_conf") def testMultipleSameAction(self, basedir):
os.mkdir(os.path.join(basedir, "filter.d")) os.mkdir(os.path.join(basedir, "filter.d"))
os.mkdir(os.path.join(basedir, "action.d")) os.mkdir(os.path.join(basedir, "action.d"))
open(os.path.join(basedir, "action.d", "testaction1.conf"), 'w').close() open(os.path.join(basedir, "action.d", "testaction1.conf"), 'w').close()
@ -746,4 +743,33 @@ filter = testfilter1
# Python actions should not be passed `actname` # Python actions should not be passed `actname`
self.assertEqual(add_actions[-1][-1], "{}") self.assertEqual(add_actions[-1][-1], "{}")
shutil.rmtree(basedir) def testLogPathFileFilterBackend(self):
self.assertRaisesRegexp(ValueError, r"Have not found any log file for .* jail",
self._testLogPath, backend='polling')
def testLogPathSystemdBackend(self):
try: # pragma: systemd no cover
from ..server.filtersystemd import FilterSystemd
except Exception, e: # pragma: no cover
raise unittest.SkipTest("systemd python interface not available")
self._testLogPath(backend='systemd')
self._testLogPath(backend='systemd[journalflags=2]')
@with_tmpdir
def _testLogPath(self, basedir, backend):
jailfd = open(os.path.join(basedir, "jail.conf"), 'w')
jailfd.write("""
[testjail1]
enabled = true
backend = %s
logpath = %s/not/exist.log
/this/path/should/not/exist.log
action =
filter =
failregex = test <HOST>
""" % (backend, basedir))
jailfd.close()
jails = JailsReader(basedir=basedir)
self.assertTrue(jails.read())
self.assertTrue(jails.getOptions())
jails.convert()

View File

@ -48,12 +48,10 @@ class DatabaseTest(LogCaptureTestCase):
def setUp(self): def setUp(self):
"""Call before every test case.""" """Call before every test case."""
super(DatabaseTest, self).setUp() super(DatabaseTest, self).setUp()
if Fail2BanDb is None and sys.version_info >= (2,7): # pragma: no cover if Fail2BanDb is None: # pragma: no cover
raise unittest.SkipTest( raise unittest.SkipTest(
"Unable to import fail2ban database module as sqlite is not " "Unable to import fail2ban database module as sqlite is not "
"available.") "available.")
elif Fail2BanDb is None:
return
_, self.dbFilename = tempfile.mkstemp(".db", "fail2ban_") _, self.dbFilename = tempfile.mkstemp(".db", "fail2ban_")
self.db = Fail2BanDb(self.dbFilename) self.db = Fail2BanDb(self.dbFilename)
@ -123,7 +121,7 @@ class DatabaseTest(LogCaptureTestCase):
self.db.addLog(self.jail, self.fileContainer) self.db.addLog(self.jail, self.fileContainer)
self.assertTrue(filename in self.db.getLogPaths(self.jail)) self.assertIn(filename, self.db.getLogPaths(self.jail))
os.remove(filename) os.remove(filename)
def testUpdateLog(self): def testUpdateLog(self):
@ -318,6 +316,25 @@ class DatabaseTest(LogCaptureTestCase):
actions._Actions__checkBan() actions._Actions__checkBan()
self.assertLogged("ban ainfo %s, %s, %s, %s" % (True, True, True, True)) self.assertLogged("ban ainfo %s, %s, %s, %s" % (True, True, True, True))
def testDelAndAddJail(self):
self.testAddJail() # Add jail
# Delete jail (just disabled it):
self.db.delJail(self.jail)
jails = self.db.getJailNames()
self.assertIn(len(jails) == 1 and self.jail.name, jails)
jails = self.db.getJailNames(enabled=False)
self.assertIn(len(jails) == 1 and self.jail.name, jails)
jails = self.db.getJailNames(enabled=True)
self.assertTrue(len(jails) == 0)
# Add it again - should just enable it:
self.db.addJail(self.jail)
jails = self.db.getJailNames()
self.assertIn(len(jails) == 1 and self.jail.name, jails)
jails = self.db.getJailNames(enabled=True)
self.assertIn(len(jails) == 1 and self.jail.name, jails)
jails = self.db.getJailNames(enabled=False)
self.assertTrue(len(jails) == 0)
def testPurge(self): def testPurge(self):
if Fail2BanDb is None: # pragma: no cover if Fail2BanDb is None: # pragma: no cover
return return

View File

@ -39,7 +39,7 @@ except ImportError:
from ..client import fail2banregex from ..client import fail2banregex
from ..client.fail2banregex import Fail2banRegex, get_opt_parser, output from ..client.fail2banregex import Fail2banRegex, get_opt_parser, output
from .utils import LogCaptureTestCase, logSys from .utils import setUpMyTime, tearDownMyTime, LogCaptureTestCase, logSys
from .utils import CONFIG_DIR from .utils import CONFIG_DIR
@ -70,10 +70,12 @@ class Fail2banRegexTest(LogCaptureTestCase):
def setUp(self): def setUp(self):
"""Call before every test case.""" """Call before every test case."""
LogCaptureTestCase.setUp(self) LogCaptureTestCase.setUp(self)
setUpMyTime()
def tearDown(self): def tearDown(self):
"""Call after every test case.""" """Call after every test case."""
LogCaptureTestCase.tearDown(self) LogCaptureTestCase.tearDown(self)
tearDownMyTime()
def testWrongRE(self): def testWrongRE(self):
(opts, args, fail2banRegex) = _Fail2banRegex( (opts, args, fail2banRegex) = _Fail2banRegex(
@ -159,8 +161,8 @@ class Fail2banRegexTest(LogCaptureTestCase):
self.assertTrue(fail2banRegex.start(opts, args)) self.assertTrue(fail2banRegex.start(opts, args))
self.assertLogged('Lines: 13 lines, 0 ignored, 5 matched, 8 missed') self.assertLogged('Lines: 13 lines, 0 ignored, 5 matched, 8 missed')
self.assertLogged('141.3.81.106 Fri Aug 14 11:53:59 2015') self.assertLogged('141.3.81.106 Sun Aug 14 11:53:59 2005')
self.assertLogged('141.3.81.106 Fri Aug 14 11:54:59 2015') self.assertLogged('141.3.81.106 Sun Aug 14 11:54:59 2005')
def testWronChar(self): def testWronChar(self):
(opts, args, fail2banRegex) = _Fail2banRegex( (opts, args, fail2banRegex) = _Fail2banRegex(
@ -169,9 +171,8 @@ class Fail2banRegexTest(LogCaptureTestCase):
self.assertTrue(fail2banRegex.start(opts, args)) self.assertTrue(fail2banRegex.start(opts, args))
self.assertLogged('Lines: 4 lines, 0 ignored, 2 matched, 2 missed') self.assertLogged('Lines: 4 lines, 0 ignored, 2 matched, 2 missed')
self.assertLogged('Error decoding line'); self.assertLogged('Error decoding line')
self.assertLogged('Continuing to process line ignoring invalid characters:', '2015-01-14 20:00:58 user '); self.assertLogged('Continuing to process line ignoring invalid characters:')
self.assertLogged('Continuing to process line ignoring invalid characters:', '2015-01-14 20:00:59 user ');
self.assertLogged('Nov 8 00:16:12 main sshd[32548]: input_userauth_request: invalid user llinco') self.assertLogged('Nov 8 00:16:12 main sshd[32548]: input_userauth_request: invalid user llinco')
self.assertLogged('Nov 8 00:16:12 main sshd[32547]: pam_succeed_if(sshd:auth): error retrieving information about user llinco') self.assertLogged('Nov 8 00:16:12 main sshd[32547]: pam_succeed_if(sshd:auth): error retrieving information about user llinco')

View File

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/env fail2ban-python
import requests import requests
try: try:

View File

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/env fail2ban-python
import sys import sys
if sys.argv[1] == "10.0.0.1": if sys.argv[1] == "10.0.0.1":
exit(0) exit(0)

View File

@ -1,5 +1,5 @@
# failJSON: { "time": "2013-12-23T13:12:31", "match": true , "host": "173.255.225.101" } # failJSON: { "time": "2013-12-23T13:12:31", "match": true , "host": "173.255.225.101" }
[Mon Dec 23 13:12:31 2013] [error] [client 173.255.225.101] ModSecurity: [file "/etc/httpd/modsecurity.d/activated_rules/modsecurity_crs_21_protocol_anomalies.conf"] [line "47"] [id "960015"] [rev "1"] [msg "Request Missing an Accept Header"] [severity "NOTICE"] [ver "OWASP_CRS/2.2.8"] [maturity "9"] [accuracy "9"] [tag "OWASP_CRS/PROTOCOL_VIOLATION/MISSING_HEADER_ACCEPT"] [tag "WASCTC/WASC-21"][tag "OWASP_TOP_10/A7"] [tag "PCI/6.5.10"] Access denied with code 403 (phase 2). Operator EQ matched 0 at REQUEST_HEADERS. [hostname "www.mysite.net"] [uri "/"] [unique_id "Urf@f12qgHIAACrFOlgAAABA"] [Mon Dec 23 13:12:31 2013] [error] [client 173.255.225.101] ModSecurity: [file "/etc/httpd/modsecurity.d/activated_rules/modsecurity_crs_21_protocol_anomalies.conf"] [line "47"] [id "960015"] [rev "1"] [msg "Request Missing an Accept Header"] [severity "NOTICE"] [ver "OWASP_CRS/2.2.8"] [maturity "9"] [accuracy "9"] [tag "OWASP_CRS/PROTOCOL_VIOLATION/MISSING_HEADER_ACCEPT"] [tag "WASCTC/WASC-21"][tag "OWASP_TOP_10/A7"] [tag "PCI/6.5.10"] Access denied with code 403 (phase 2). Operator EQ matched 0 at REQUEST_HEADERS. [hostname "www.mysite.net"] [uri "/"] [unique_id "Urf@f12qgHIAACrFOlgAAABA"]
# failJSON: { "time": "2013-12-28T09:18:05", "match": true , "host": "32.65.254.69" } # failJSON: { "time": "2013-12-28T09:18:05", "match": true , "host": "32.65.254.69", "desc": "additional entry (and exact one space)" }
[Sat Dec 28 09:18:05 2013] [error] [client 32.65.254.69] ModSecurity: [file "/etc/httpd/modsecurity.d/10_asl_rules.conf"] [line "635"] [id "340069"] [rev "4"] [msg "Atomicorp.com UNSUPPORTED DELAYED Rules: Web vulnerability scanner"] [severity "CRITICAL"] Access denied with code 403 (phase 2). Pattern match "(?:nessus(?:_is_probing_you_|test)|^/w00tw00t\\\\.at\\\\.)" at REQUEST_URI. [hostname "192.81.249.191"] [uri "/w00tw00t.at.blackhats.romanian.anti-sec:)"] [unique_id "4Q6RdsBR@b4AAA65LRUAAAAA"] [Sat Dec 28 09:18:05 2013] [error] [client 32.65.254.69] ModSecurity: [file "/etc/httpd/modsecurity.d/10_asl_rules.conf"] [line "635"] [id "340069"] [rev "4"] [msg "Atomicorp.com UNSUPPORTED DELAYED Rules: Web vulnerability scanner"] [severity "CRITICAL"] Access denied with code 403 (phase 2). Pattern match "(?:nessus(?:_is_probing_you_|test)|^/w00tw00t\\\\.at\\\\.)" at REQUEST_URI. [hostname "192.81.249.191"] [uri "/w00tw00t.at.blackhats.romanian.anti-sec:)"] [unique_id "4Q6RdsBR@b4AAA65LRUAAAAA"]

View File

@ -22,4 +22,23 @@ Apr-27-13 02:25:10 [SSL-out] 217.194.197.97 max sender authentication errors (5)
Apr-27-13 02:25:10 [SSL-out] 217.194.197.97 max sender authentication errors (5) exceeded -- dropping connection - after reply: 535 5.7.8 Error: authentication failed: UGFzc3dvcmQ6; Apr-27-13 02:25:10 [SSL-out] 217.194.197.97 max sender authentication errors (5) exceeded -- dropping connection - after reply: 535 5.7.8 Error: authentication failed: UGFzc3dvcmQ6;
# failJSON: { "time": "2013-04-27T02:25:11", "match": true , "host": "217.194.197.97" } # failJSON: { "time": "2013-04-27T02:25:11", "match": true , "host": "217.194.197.97" }
Apr-27-13 02:25:11 [SSL-out] 217.194.197.97 max sender authentication errors (5) exceeded -- dropping connection - after reply: 535 5.7.8 Error: authentication failed: UGFzc3dvcmQ6; Apr-27-13 02:25:11 [SSL-out] 217.194.197.97 max sender authentication errors (5) exceeded -- dropping connection - after reply: 535 5.7.8 Error: authentication failed: UGFzc3dvcmQ6;
# failJSON: { "time": "2016-07-29T16:49:52", "match": true , "host": "0.0.0.0" }
Jul-29-16 16:49:52 m1-25391-06124 [Worker_1] [TLS-out] [RelayAttempt] 0.0.0.0 <user@example.com> to: user@example.org relay attempt blocked for: someone@example.org
# failJSON: { "time": "2016-07-30T17:07:25", "match": true , "host": "0.0.0.0" }
Jul-30-16 17:07:25 [Worker_1] [TLS-out] 0.0.0.0 [SMTP Error] 535 5.7.8 Error: authentication failed: UGFzc3dvcmQ6
# failJSON: { "time": "2016-07-30T17:11:05", "match": true , "host": "0.0.0.0" }
Jul-30-16 17:11:05 m1-13060-05386 [Worker_1] [TLS-out] 0.0.0.0 [SMTP Error] 535 5.7.8 Error: authentication failed: UGFzc3dvcmQ6
# failJSON: { "time": "2016-07-31T06:45:59", "match": true , "host": "0.0.0.0" }
Jul-31-16 06:45:59 [Worker_1] [TLS-in] [TLS-out] 0.0.0.0 [SMTP Error] 535 5.7.8 Error: authentication failed:
# failJSON: { "time": "2016-01-05T08:38:49", "match": true , "host": "0.0.0.0" }
Jan-05-16 08:38:49 m1-01129-09140 [Worker_1] [TLS-in] [TLS-out] [RelayAttempt] 0.0.0.0 <user@example.com> relay attempt blocked for (parsing): <user2@example>
# failJSON: { "time": "2016-06-12T16:43:37", "match": true , "host": "0.0.0.0" }
Jun-12-16 16:43:37 m1-64217-12013 [Worker_1] [TLS-in] [TLS-out] [RelayAttempt] 0.0.0.0 <user@example.com> to: user2@example.com relay attempt blocked for (parsing): <a.notheruser69@example.c>
# failJSON: { "time": "2016-01-22T22:25:51", "match": true , "host": "0.0.0.0" }
Jan-22-16 22:25:51 [Worker_1] [TLS-out] 0.0.0.0 [SMTP Error] 535 5.7.8 Error: authentication failed: Invalid authentication mechanism
# failJSON: { "time": "2016-03-19T13:42:20", "match": true , "host": "0.0.0.0" }
Mar-19-16 13:42:20 [Worker_1] [TLS-out] 0.0.0.0 [SMTP Error] 535 5.7.8 Error: authentication failed: Invalid base64 data in continued response
# failJSON: { "time": "2016-07-18T16:54:21", "match": true , "host": "0.0.0.0" }
Jul-18-16 16:54:21 [Worker_2] [TLS-out] 0.0.0.0 [SMTP Error] 535 5.7.8 Error: authentication failed: Connection lost to authentication server
# failJSON: { "time": "2016-07-18T17:14:23", "match": true , "host": "0.0.0.0" }
Jul-18-16 17:14:23 m1-76453-02949 [Worker_1] [TLS-out] 0.0.0.0 [SMTP Error] 535 5.7.8 Error: authentication failed: Connection lost to authentication server

View File

@ -43,6 +43,8 @@
# failJSON: { "time": "2004-11-04T18:30:40", "match": true , "host": "192.168.200.100" } # failJSON: { "time": "2004-11-04T18:30:40", "match": true , "host": "192.168.200.100" }
Nov 4 18:30:40 localhost asterisk[32229]: NOTICE[32257]: chan_sip.c:23417 in handle_request_register: Registration from '<sip:301@example.com>' failed for '192.168.200.100:36998' - Wrong password Nov 4 18:30:40 localhost asterisk[32229]: NOTICE[32257]: chan_sip.c:23417 in handle_request_register: Registration from '<sip:301@example.com>' failed for '192.168.200.100:36998' - Wrong password
# failJSON: { "time": "2016-08-19T11:11:26", "match": true , "host": "192.0.2.1", "desc": "Another log_prefix used (` in` should be optional)" }
[2016-08-19 11:11:26] NOTICE[12931]: chan_sip.c:28468 handle_request_register: Registration from 'sip:bob@192.0.2.1' failed for '192.0.2.1:42406' - Wrong password
# failed authentication attempt on INVITE using PJSIP # failed authentication attempt on INVITE using PJSIP
# failJSON: { "time": "2015-05-24T08:42:16", "match": true, "host": "10.250.251.252" } # failJSON: { "time": "2015-05-24T08:42:16", "match": true, "host": "10.250.251.252" }

View File

@ -73,3 +73,8 @@ Jul 02 13:49:32 hostname dovecot[442]: pop3-login: Disconnected (no auth attempt
# failJSON: { "time": "2005-03-23T06:10:52", "match": true , "host": "52.37.139.121" } # failJSON: { "time": "2005-03-23T06:10:52", "match": true , "host": "52.37.139.121" }
Mar 23 06:10:52 auth: Info: ldap(dog,52.37.139.121,): invalid credentials Mar 23 06:10:52 auth: Info: ldap(dog,52.37.139.121,): invalid credentials
# failJSON: { "time": "2005-07-26T11:11:21", "match": true , "host": "192.0.2.1" }
Jul 26 11:11:21 hostname dovecot: imap-login: Disconnected: Too many invalid commands (tried to use disallowed plaintext auth): user=<test>, rip=192.0.2.1, lip=192.168.1.1, session=<S5dIdTFCDKUWWMbU>
# failJSON: { "time": "2005-07-26T11:12:19", "match": true , "host": "192.0.2.2" }
Jul 26 11:12:19 hostname dovecot: imap-login: Disconnected: Too many invalid commands (auth failed, 1 attempts in 17 secs): user=<test>, method=PLAIN, rip=192.0.2.2, lip=192.168.1.1, TLS, session=<g3ZKeDECFqlWWMbU>

View File

@ -0,0 +1,30 @@
# failJSON: { "match": false }
2016-11-20T00:04:00.110+0100 [conn1] Failed to authenticate root@admin with mechanism MONGODB-CR: AuthenticationFailed UserNotFound Could not find user root@admin
# failJSON: { "time": "2016-11-20T00:04:00", "match": true , "host": "192.0.2.35" }
2016-11-20T00:04:00.111+0100 [conn1] end connection 192.0.2.35:53276 (0 connections now open)
# failJSON: { "match": false }
2016-11-20T00:24:00.110+0100 [conn5] Failed to authenticate root@admin with mechanism MONGODB-CR: AuthenticationFailed UserNotFound Could not find user root@admin
# failJSON: { "time": "2016-11-20T00:24:00", "match": true , "host": "192.0.2.171" }
2016-11-20T00:24:00.111+0100 [conn5] end connection 192.0.2.171:53276 (0 connections now open)
# failJSON: { "match": false }
2016-11-20T00:24:00.110+0100 [conn334] Failed to authenticate root@admin with mechanism MONGODB-CR: AuthenticationFailed key mismatch
# failJSON: { "time": "2016-11-20T00:24:00", "match": true , "host": "192.0.2.176" }
2016-11-20T00:24:00.111+0100 [conn334] end connection 192.0.2.176:53276 (0 connections now open)
# failJSON: { "match": false }
2016-11-20T00:24:00.110+0100 [conn56] Failed to authenticate root@admin with mechanism MONGODB-CR: AuthenticationFailed key mismatch
# failJSON: { "time": "2016-11-20T00:24:00", "match": true , "host": "192.0.2.1" }
2016-11-20T00:24:00.111+0100 [conn56] end connection 192.0.2.1:53276 (0 connections now open)
# failJSON: { "match": false }
2016-11-20T12:54:02.370+0100 [initandlisten] connection accepted from 127.0.0.1:58774 #2261 (1 connection now open)
# failJSON: { "match": false }
2016-11-20T12:54:02.370+0100 [conn2261] end connection 127.0.0.1:58774 (0 connections now open)
# failJSON: { "match": false }
2016-11-20T13:07:49.781+0100 [conn2271] authenticate db: admin { authenticate: 1, nonce: "xxx", user: "root", key: "xxx" }
# failJSON: { "time": "2016-11-20T13:07:49", "match": false , "host": "192.0.2.178" }
2016-11-20T13:07:49.834+0100 [conn2271] end connection 192.0.2.178:60268 (3 connections now open)

View File

@ -26,3 +26,7 @@ Jan 29 08:11:45 mail postfix-incoming/smtpd[10752]: warning: unknown[1.1.1.1]: S
# failJSON: { "time": "2005-04-12T02:24:11", "match": true , "host": "62.138.2.143" } # failJSON: { "time": "2005-04-12T02:24:11", "match": true , "host": "62.138.2.143" }
Apr 12 02:24:11 xxx postfix/smtps/smtpd[42]: warning: astra4139.startdedicated.de[62.138.2.143]: SASL LOGIN authentication failed: UGFzc3dvcmQ6 Apr 12 02:24:11 xxx postfix/smtps/smtpd[42]: warning: astra4139.startdedicated.de[62.138.2.143]: SASL LOGIN authentication failed: UGFzc3dvcmQ6
# failJSON: { "time": "2005-08-03T15:30:49", "match": true , "host": "98.191.84.74" }
Aug 3 15:30:49 ksusha postfix/smtpd[17041]: warning: mail.foldsandwalker.com[98.191.84.74]: SASL Plain authentication failed:

View File

@ -40,6 +40,8 @@ Feb 19 18:01:50 batman sm-mta[78152]: ruleset=check_relay, arg1=[196.213.73.146]
# failJSON: { "time": "2005-02-27T10:53:06", "match": true , "host": "209.15.212.253" } # 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 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-27T10:53:07", "match": true , "host": "1.2.3.4" }
Feb 27 10:53:07 strange sm-mta[18001]: u9A0GtpL018001: rejecting commands from example.com [1.2.3.4] due to pre-greeting traffic after 6 seconds
# failJSON: { "time": "2005-02-27T15:44:18", "match": true , "host": "41.204.78.137" } # 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] 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]

View File

@ -17,8 +17,10 @@ Jan 5 01:31:41 www sshd[1643]: ROOT LOGIN REFUSED FROM 1.2.3.4
Jan 5 01:31:41 www sshd[1643]: ROOT LOGIN REFUSED FROM ::ffff:1.2.3.4 Jan 5 01:31:41 www sshd[1643]: ROOT LOGIN REFUSED FROM ::ffff:1.2.3.4
#4 #4
# failJSON: { "time": "2005-07-20T14:42:11", "match": true , "host": "211.114.51.213" } # failJSON: { "time": "2005-07-20T14:42:11", "match": true , "host": "192.0.2.1", "desc": "Invalid user" }
Jul 20 14:42:11 localhost sshd[22708]: Invalid user ftp from 211.114.51.213 Jul 20 14:42:11 localhost sshd[22708]: Invalid user ftp from 192.0.2.1
# failJSON: { "time": "2005-07-20T14:42:12", "match": true , "host": "192.0.2.2", "desc": "Invalid user with port" }
Jul 20 14:42:12 localhost sshd[22708]: Invalid user ftp from 192.0.2.2 port 37220
#5 new filter introduced after looking at 44087D8C.9090407@bluewin.ch #5 new filter introduced after looking at 44087D8C.9090407@bluewin.ch
# yoh: added ':' after [sshd] since the case without is not really common any more # yoh: added ':' after [sshd] since the case without is not really common any more
@ -117,7 +119,13 @@ Sep 29 17:15:02 spaceman sshd[12946]: Failed password for user from 127.0.0.1 po
# failJSON: { "time": "2004-11-11T08:04:51", "match": true , "host": "127.0.0.1", "desc": "Injecting on username ssh 'from 10.10.1.1'@localhost" } # failJSON: { "time": "2004-11-11T08:04:51", "match": true , "host": "127.0.0.1", "desc": "Injecting on username ssh 'from 10.10.1.1'@localhost" }
Nov 11 08:04:51 redbamboo sshd[2737]: Failed password for invalid user from 10.10.1.1 from 127.0.0.1 port 58946 ssh2 Nov 11 08:04:51 redbamboo sshd[2737]: Failed password for invalid user from 10.10.1.1 from 127.0.0.1 port 58946 ssh2
# failJSON: { "time": "2004-11-11T08:04:52", "match": true , "host": "127.0.0.1", "desc": "More complex injecting on username ssh 'test from 10.10.1.2 port 55555 ssh2'@localhost" }
Nov 11 08:04:52 redbamboo sshd[2737]: Failed password for invalid user test from 10.10.1.2 port 55555 ssh2 from 127.0.0.1 port 58946 ssh2
# failJSON: { "time": "2004-11-11T08:04:52", "match": true , "host": "127.0.0.1", "desc": "More complex injecting on auth-info ssh test@localhost, auth-info: ' from 10.10.1.2 port 55555 ssh2'" }
Nov 11 08:04:52 redbamboo sshd[2737]: Failed password for invalid user test from 127.0.0.1 port 58946 ssh2: from 10.10.1.2 port 55555 ssh2
# failJSON: { "time": "2005-07-05T18:22:44", "match": true , "host": "127.0.0.1", "desc": "Failed publickey for ..." }
Jul 05 18:22:44 mercury sshd[4669]: Failed publickey for graysky from 127.0.0.1 port 37954 ssh2: RSA SHA256:v3dpapGleDaUKf$4V1vKyR9ZyUgjaJAmoCTcb2PLljI
# failJSON: { "match": false } # failJSON: { "match": false }
Nov 23 21:50:19 sshd[8148]: Disconnecting: Too many authentication failures for root [preauth] Nov 23 21:50:19 sshd[8148]: Disconnecting: Too many authentication failures for root [preauth]
@ -161,4 +169,3 @@ Apr 27 13:02:04 host sshd[29116]: Received disconnect from 1.2.3.4: 11: Normal S
# Match sshd auth errors on OpenSUSE systems # Match sshd auth errors on OpenSUSE systems
# failJSON: { "time": "2015-04-16T20:02:50", "match": true , "host": "222.186.21.217", "desc": "Authentication for user failed" } # failJSON: { "time": "2015-04-16T20:02:50", "match": true , "host": "222.186.21.217", "desc": "Authentication for user failed" }
2015-04-16T18:02:50.321974+00:00 host sshd[2716]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=222.186.21.217 user=root 2015-04-16T18:02:50.321974+00:00 host sshd[2716]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=222.186.21.217 user=root

View File

@ -12,3 +12,6 @@ Fri Jan 19 12:20:33 2007 [pid 27202] [anonymous] FAIL LOGIN: Client "64.106.46.9
# failJSON: { "time": "2004-10-23T21:15:42", "match": true , "host": "58.254.172.161" } # failJSON: { "time": "2004-10-23T21:15:42", "match": true , "host": "58.254.172.161" }
Oct 23 21:15:42 vps vsftpd: pam_unix(vsftpd:auth): authentication failure; logname= uid=0 euid=0 tty=ftp ruser=test rhost=58.254.172.161 Oct 23 21:15:42 vps vsftpd: pam_unix(vsftpd:auth): authentication failure; logname= uid=0 euid=0 tty=ftp ruser=test rhost=58.254.172.161
# failJSON: { "time": "2016-09-08T00:39:49", "match": true , "host": "192.0.2.1" }
Thu Sep 8 00:39:49 2016 [pid 15019] [guest] FAIL LOGIN: Client "::ffff:192.0.2.1", "User is not in the allow user list."

View File

@ -13,7 +13,7 @@ error: PAM: Authentication failure for kevin from 193.168.0.128
error: PAM: Authentication failure for kevin from 193.168.0.128 error: PAM: Authentication failure for kevin from 193.168.0.128
error: PAM: Authentication failure for kevin from 193.168.0.128 error: PAM: Authentication failure for kevin from 193.168.0.128
error: PAM: Authentication failure for kevin from 193.168.0.128 error: PAM: Authentication failure for kevin from 193.168.0.128
error: PAM: Authentication failure for kevin from 87.142.124.10 error: PAM: Authentication failure for göran from 87.142.124.10
error: PAM: Authentication failure for kevin from 87.142.124.10 error: PAM: Authentication failure for göran from 87.142.124.10
error: PAM: Authentication failure for kevin from 87.142.124.10 error: PAM: Authentication failure for göran from 87.142.124.10
error: PAM: Authentication failure for kevin from 87.142.124.10 error: PAM: Authentication failure for göran from 87.142.124.10

View File

@ -38,7 +38,7 @@ except ImportError:
from ..server.jail import Jail from ..server.jail import Jail
from ..server.filterpoll import FilterPoll from ..server.filterpoll import FilterPoll
from ..server.filter import Filter, FileFilter, DNSUtils from ..server.filter import Filter, FileFilter, FileContainer, locale, DNSUtils
from ..server.failmanager import FailManagerEmpty from ..server.failmanager import FailManagerEmpty
from ..server.mytime import MyTime from ..server.mytime import MyTime
from .utils import setUpMyTime, tearDownMyTime, mtimesleep, LogCaptureTestCase from .utils import setUpMyTime, tearDownMyTime, mtimesleep, LogCaptureTestCase
@ -166,6 +166,10 @@ def _copy_lines_between_files(in_, fout, n=None, skip=0, mode='a', terminal_line
return fout return fout
TEST_JOURNAL_FIELDS = {
"SYSLOG_IDENTIFIER": "fail2ban-testcases",
"PRIORITY": "7",
}
def _copy_lines_to_journal(in_, fields={},n=None, skip=0, terminal_line=""): # pragma: systemd no cover def _copy_lines_to_journal(in_, fields={},n=None, skip=0, terminal_line=""): # pragma: systemd no cover
"""Copy lines from one file to systemd journal """Copy lines from one file to systemd journal
@ -176,9 +180,7 @@ def _copy_lines_to_journal(in_, fields={},n=None, skip=0, terminal_line=""): # p
else: else:
fin = in_ fin = in_
# Required for filtering # Required for filtering
fields.update({"SYSLOG_IDENTIFIER": "fail2ban-testcases", fields.update(TEST_JOURNAL_FIELDS)
"PRIORITY": "7",
})
# Skip # Skip
for i in xrange(skip): for i in xrange(skip):
fin.readline() fin.readline()
@ -228,6 +230,19 @@ class BasicFilter(unittest.TestCase):
1) 1)
) )
def testWrongCharInTupleLine(self):
## line tuple has different types (ascii after ascii / unicode):
for a1 in ('', u'', b''):
for a2 in ('2016-09-05T20:18:56', u'2016-09-05T20:18:56', b'2016-09-05T20:18:56'):
for a3 in (
'Fail for "g\xc3\xb6ran" from 192.0.2.1',
u'Fail for "g\xc3\xb6ran" from 192.0.2.1',
b'Fail for "g\xc3\xb6ran" from 192.0.2.1'
):
# join should work if all arguments have the same type:
enc = locale.getpreferredencoding()
"".join([Filter.uni_decode(v, enc) for v in (a1, a2, a3)])
class IgnoreIP(LogCaptureTestCase): class IgnoreIP(LogCaptureTestCase):
@ -707,11 +722,16 @@ def get_monitor_failures_journal_testcase(Filter_): # pragma: systemd no cover
"""Call before every test case.""" """Call before every test case."""
self.test_file = os.path.join(TEST_FILES_DIR, "testcase-journal.log") self.test_file = os.path.join(TEST_FILES_DIR, "testcase-journal.log")
self.jail = DummyJail() self.jail = DummyJail()
self.filter = Filter_(self.jail) self.filter = None
# UUID used to ensure that only meeages generated # UUID used to ensure that only meeages generated
# as part of this test are picked up by the filter # as part of this test are picked up by the filter
self.test_uuid = str(uuid.uuid4()) self.test_uuid = str(uuid.uuid4())
self.name = "monitorjournalfailures-%s" % self.test_uuid self.name = "monitorjournalfailures-%s" % self.test_uuid
self.journal_fields = {
'TEST_FIELD': "1", 'TEST_UUID': self.test_uuid}
def _initFilter(self, **kwargs):
self.filter = Filter_(self.jail, **kwargs)
self.filter.addJournalMatch([ self.filter.addJournalMatch([
"SYSLOG_IDENTIFIER=fail2ban-testcases", "SYSLOG_IDENTIFIER=fail2ban-testcases",
"TEST_FIELD=1", "TEST_FIELD=1",
@ -720,16 +740,16 @@ def get_monitor_failures_journal_testcase(Filter_): # pragma: systemd no cover
"SYSLOG_IDENTIFIER=fail2ban-testcases", "SYSLOG_IDENTIFIER=fail2ban-testcases",
"TEST_FIELD=2", "TEST_FIELD=2",
"TEST_UUID=%s" % self.test_uuid]) "TEST_UUID=%s" % self.test_uuid])
self.journal_fields = {
'TEST_FIELD': "1", 'TEST_UUID': self.test_uuid}
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()
def tearDown(self): def tearDown(self):
self.filter.stop() if self.filter and self.filter.active:
self.filter.join() # wait for the thread to terminate self.filter.stop()
pass self.filter.join() # wait for the thread to terminate
pass
def testJournalFlagsArg(self):
self._initFilter(journalflags=2) # journal.RUNTIME_ONLY
def __str__(self): def __str__(self):
return "MonitorJournalFailures%s(%s)" \ return "MonitorJournalFailures%s(%s)" \
@ -761,6 +781,8 @@ def get_monitor_failures_journal_testcase(Filter_): # pragma: systemd no cover
self.assertEqual(attempts, test_attempts) self.assertEqual(attempts, test_attempts)
def test_grow_file(self): def test_grow_file(self):
self._initFilter()
self.filter.start()
self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan) self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan)
# Now let's feed it with entries from the file # Now let's feed it with entries from the file
@ -790,6 +812,8 @@ def get_monitor_failures_journal_testcase(Filter_): # pragma: systemd no cover
self.assert_correct_ban("193.168.0.128", 3) self.assert_correct_ban("193.168.0.128", 3)
def test_delJournalMatch(self): def test_delJournalMatch(self):
self._initFilter()
self.filter.start()
# Smoke test for removing of match # Smoke test for removing of match
# basic full test # basic full test
@ -819,6 +843,33 @@ def get_monitor_failures_journal_testcase(Filter_): # pragma: systemd no cover
# we should detect the failures # we should detect the failures
self.assertTrue(self.isFilled(6)) self.assertTrue(self.isFilled(6))
def test_WrongChar(self):
self._initFilter()
self.filter.start()
# Now let's feed it with entries from the file
_copy_lines_to_journal(
self.test_file, self.journal_fields, skip=15, n=4)
self.assertTrue(self.isFilled(10))
self.assert_correct_ban("87.142.124.10", 4)
# Add direct utf, unicode, blob:
for l in (
"error: PAM: Authentication failure for \xe4\xf6\xfc\xdf from 192.0.2.1",
u"error: PAM: Authentication failure for \xe4\xf6\xfc\xdf from 192.0.2.1",
b"error: PAM: Authentication failure for \xe4\xf6\xfc\xdf from 192.0.2.1".decode('utf-8', 'replace'),
"error: PAM: Authentication failure for \xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f from 192.0.2.2",
u"error: PAM: Authentication failure for \xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f from 192.0.2.2",
b"error: PAM: Authentication failure for \xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f from 192.0.2.2".decode('utf-8', 'replace')
):
fields = self.journal_fields
fields.update(TEST_JOURNAL_FIELDS)
journal.send(MESSAGE=l, **fields)
self.assertTrue(self.isFilled(10))
endtm = MyTime.time()+10
while len(self.jail) != 2 and MyTime.time() < endtm:
time.sleep(0.10)
self.assertEqual(sorted([self.jail.getFailTicket().getIP(), self.jail.getFailTicket().getIP()]),
["192.0.2.1", "192.0.2.2"])
return MonitorJournalFailures return MonitorJournalFailures

View File

@ -23,6 +23,7 @@ __license__ = "GPL"
import logging import logging
import os import os
import re
import sys import sys
import unittest import unittest
import tempfile import tempfile
@ -32,8 +33,11 @@ import datetime
from glob import glob from glob import glob
from StringIO import StringIO from StringIO import StringIO
from utils import LogCaptureTestCase, logSys as DefLogSys
from ..helpers import formatExceptionInfo, mbasename, TraceBack, FormatterWithTraceBack, getLogger from ..helpers import formatExceptionInfo, mbasename, TraceBack, FormatterWithTraceBack, getLogger
from ..helpers import splitwords from ..helpers import splitwords
from ..server.datedetector import DateDetector
from ..server.datetemplate import DatePatternRegex from ..server.datetemplate import DatePatternRegex
@ -67,6 +71,22 @@ class HelpersTest(unittest.TestCase):
self.assertEqual(splitwords(' 1\n 2, 3'), ['1', '2', '3']) self.assertEqual(splitwords(' 1\n 2, 3'), ['1', '2', '3'])
if sys.version_info >= (2,7):
def _sh_call(cmd):
import subprocess, locale
ret = subprocess.check_output(cmd, shell=True)
if sys.version_info >= (3,):
ret = ret.decode(locale.getpreferredencoding(), 'replace')
return str(ret).rstrip()
else:
def _sh_call(cmd):
import subprocess
ret = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).stdout.read()
return str(ret).rstrip()
def _getSysPythonVersion():
return _sh_call("fail2ban-python -c 'import sys; print(tuple(sys.version_info))'")
class SetupTest(unittest.TestCase): class SetupTest(unittest.TestCase):
def setUp(self): def setUp(self):
@ -76,6 +96,12 @@ class SetupTest(unittest.TestCase):
raise unittest.SkipTest( raise unittest.SkipTest(
"Seems to be running not out of source distribution" "Seems to be running not out of source distribution"
" -- cannot locate setup.py") " -- cannot locate setup.py")
# compare current version of python installed resp. active one:
sysVer = _getSysPythonVersion()
if sysVer != str(tuple(sys.version_info)):
raise unittest.SkipTest(
"Seems to be running with python distribution %s"
" -- install can be tested only with system distribution %s" % (str(tuple(sys.version_info)), sysVer))
def testSetupInstallRoot(self): def testSetupInstallRoot(self):
if not self.setup: if not self.setup:
@ -122,6 +148,14 @@ class SetupTest(unittest.TestCase):
'etc/fail2ban/jail.conf'): 'etc/fail2ban/jail.conf'):
self.assertTrue(os.path.exists(os.path.join(tmp, f)), self.assertTrue(os.path.exists(os.path.join(tmp, f)),
msg="Can't find %s" % f) msg="Can't find %s" % f)
# Because the install (test) path in virtual-env differs from some development-env,
# it is not a `tmp + '/usr/local/bin/'`, so search for it:
installedPath = _sh_call('find ' + tmp+ ' -name fail2ban-python').split('\n')
self.assertTrue(len(installedPath) > 0)
for installedPath in installedPath:
self.assertEqual(
os.path.realpath(installedPath), os.path.realpath(sys.executable))
finally: finally:
# clean up # clean up
shutil.rmtree(tmp) shutil.rmtree(tmp)
@ -130,7 +164,7 @@ class SetupTest(unittest.TestCase):
% (sys.executable, self.setup)) % (sys.executable, self.setup))
class TestsUtilsTest(unittest.TestCase): class TestsUtilsTest(LogCaptureTestCase):
def testmbasename(self): def testmbasename(self):
self.assertEqual(mbasename("sample.py"), 'sample') self.assertEqual(mbasename("sample.py"), 'sample')
@ -165,12 +199,88 @@ class TestsUtilsTest(unittest.TestCase):
if not ('fail2ban-testcases' in s): if not ('fail2ban-testcases' in s):
# we must be calling it from setup or nosetests but using at least # we must be calling it from setup or nosetests but using at least
# nose's core etc # nose's core etc
self.assertTrue('>' in s, msg="no '>' in %r" % s) self.assertIn('>', s)
elif not ('coverage' in s): elif not ('coverage' in s):
# There is only "fail2ban-testcases" in this case, no true traceback # There is only "fail2ban-testcases" in this case, no true traceback
self.assertFalse('>' in s, msg="'>' present in %r" % s) self.assertNotIn('>', s)
self.assertTrue(':' in s, msg="no ':' in %r" % s) self.assertIn(':', s)
def _testAssertionErrorRE(self, regexp, fun, *args, **kwargs):
self.assertRaisesRegexp(AssertionError, regexp, fun, *args, **kwargs)
def testExtendedAssertRaisesRE(self):
## test _testAssertionErrorRE several fail cases:
def _key_err(msg):
raise KeyError(msg)
self.assertRaises(KeyError,
self._testAssertionErrorRE, r"^failed$",
_key_err, 'failed')
self.assertRaises(AssertionError,
self._testAssertionErrorRE, r"^failed$",
self.fail, '__failed__')
self._testAssertionErrorRE(r'failed.* does not match .*__failed__',
lambda: self._testAssertionErrorRE(r"^failed$",
self.fail, '__failed__')
)
## no exception in callable:
self.assertRaises(AssertionError,
self._testAssertionErrorRE, r"", int, 1)
self._testAssertionErrorRE(r'0 AssertionError not raised X.* does not match .*AssertionError not raised',
lambda: self._testAssertionErrorRE(r"^0 AssertionError not raised X$",
lambda: self._testAssertionErrorRE(r"", int, 1))
)
def testExtendedAssertMethods(self):
## assertIn, assertNotIn positive case:
self.assertIn('a', ['a', 'b', 'c', 'd'])
self.assertIn('a', ('a', 'b', 'c', 'd',))
self.assertIn('a', 'cba')
self.assertIn('a', (c for c in 'cba' if c != 'b'))
self.assertNotIn('a', ['b', 'c', 'd'])
self.assertNotIn('a', ('b', 'c', 'd',))
self.assertNotIn('a', 'cbd')
self.assertNotIn('a', (c.upper() for c in 'cba' if c != 'b'))
## assertIn, assertNotIn negative case:
self._testAssertionErrorRE(r"'a' unexpectedly found in 'cba'",
self.assertNotIn, 'a', 'cba')
self._testAssertionErrorRE(r"1 unexpectedly found in \[0, 1, 2\]",
self.assertNotIn, 1, xrange(3))
self._testAssertionErrorRE(r"'A' unexpectedly found in \['C', 'A'\]",
self.assertNotIn, 'A', (c.upper() for c in 'cba' if c != 'b'))
self._testAssertionErrorRE(r"'a' was not found in 'xyz'",
self.assertIn, 'a', 'xyz')
self._testAssertionErrorRE(r"5 was not found in \[0, 1, 2\]",
self.assertIn, 5, xrange(3))
self._testAssertionErrorRE(r"'A' was not found in \['C', 'B'\]",
self.assertIn, 'A', (c.upper() for c in 'cba' if c != 'a'))
## assertLogged, assertNotLogged positive case:
logSys = DefLogSys
self.pruneLog()
logSys.debug('test "xyz"')
self.assertLogged('test "xyz"')
self.assertLogged('test', 'xyz', all=True)
self.assertNotLogged('test', 'zyx', all=False)
self.assertNotLogged('test_zyx', 'zyx', all=True)
self.assertLogged('test', 'zyx', all=False)
self.pruneLog()
logSys.debug('xxxx "xxx"')
self.assertNotLogged('test "xyz"')
self.assertNotLogged('test', 'xyz', all=False)
self.assertNotLogged('test', 'xyz', 'zyx', all=True)
## assertLogged, assertNotLogged negative case:
self.pruneLog()
logSys.debug('test "xyz"')
self._testAssertionErrorRE(r"All of the .* were found present in the log",
self.assertNotLogged, 'test "xyz"')
self._testAssertionErrorRE(r"was found in the log",
self.assertNotLogged, 'test', 'xyz', all=True)
self._testAssertionErrorRE(r"was not found in the log",
self.assertLogged, 'test', 'zyx', all=True)
self._testAssertionErrorRE(r"None among .* was found in the log",
self.assertLogged, 'test_zyx', 'zyx', all=False)
self._testAssertionErrorRE(r"All of the .* were found present in the log",
self.assertNotLogged, 'test', 'xyz', all=False)
def testFormatterWithTraceBack(self): def testFormatterWithTraceBack(self):
strout = StringIO() strout = StringIO()
@ -231,3 +341,47 @@ class CustomDateFormatsTest(unittest.TestCase):
self.assertEqual( self.assertEqual(
date, date,
datetime.datetime(2007, 1, 25, 16, 0)) datetime.datetime(2007, 1, 25, 16, 0))
def testAmbiguousDatePattern(self):
defDD = DateDetector()
defDD.addDefaultTemplate()
logSys = DefLogSys
for (matched, dp, line) in (
# positive case:
('Jan 23 21:59:59', None, 'Test failure Jan 23 21:59:59 for 192.0.2.1'),
# ambiguous "unbound" patterns (missed):
(False, None, 'Test failure TestJan 23 21:59:59.011 2015 for 192.0.2.1'),
(False, None, 'Test failure Jan 23 21:59:59123456789 for 192.0.2.1'),
# ambiguous "no optional year" patterns (matched):
('Aug 8 11:25:50', None, 'Aug 8 11:25:50 14430f2329b8 Authentication failed from 192.0.2.1'),
('Aug 8 11:25:50', None, '[Aug 8 11:25:50] 14430f2329b8 Authentication failed from 192.0.2.1'),
('Aug 8 11:25:50 2014', None, 'Aug 8 11:25:50 2014 14430f2329b8 Authentication failed from 192.0.2.1'),
# direct specified patterns:
('20:00:00 01.02.2003', r'%H:%M:%S %d.%m.%Y$', '192.0.2.1 at 20:00:00 01.02.2003'),
('[20:00:00 01.02.2003]', r'\[%H:%M:%S %d.%m.%Y\]', '192.0.2.1[20:00:00 01.02.2003]'),
('[20:00:00 01.02.2003]', r'\[%H:%M:%S %d.%m.%Y\]', '[20:00:00 01.02.2003]192.0.2.1'),
('[20:00:00 01.02.2003]', r'\[%H:%M:%S %d.%m.%Y\]$', '192.0.2.1[20:00:00 01.02.2003]'),
('[20:00:00 01.02.2003]', r'^\[%H:%M:%S %d.%m.%Y\]', '[20:00:00 01.02.2003]192.0.2.1'),
('[17/Jun/2011 17:00:45]', r'^\[%d/%b/%Y %H:%M:%S\]', '[17/Jun/2011 17:00:45] Attempt, IP address 192.0.2.1'),
('[17/Jun/2011 17:00:45]', r'\[%d/%b/%Y %H:%M:%S\]', 'Attempt [17/Jun/2011 17:00:45] IP address 192.0.2.1'),
('[17/Jun/2011 17:00:45]', r'\[%d/%b/%Y %H:%M:%S\]', 'Attempt IP address 192.0.2.1, date: [17/Jun/2011 17:00:45]'),
# direct specified patterns (begin/end, missed):
(False, r'%H:%M:%S %d.%m.%Y', '192.0.2.1x20:00:00 01.02.2003'),
(False, r'%H:%M:%S %d.%m.%Y', '20:00:00 01.02.2003x192.0.2.1'),
# direct specified patterns (begin/end, matched):
('20:00:00 01.02.2003', r'%H:%M:%S %d.%m.%Y', '192.0.2.1 20:00:00 01.02.2003'),
('20:00:00 01.02.2003', r'%H:%M:%S %d.%m.%Y', '20:00:00 01.02.2003 192.0.2.1'),
):
logSys.debug('== test: %r', (matched, dp, line))
if dp is None:
dd = defDD
else:
dp = DatePatternRegex(dp)
dd = DateDetector()
dd.appendTemplate(dp)
date = dd.getTime(line)
if matched:
self.assertTrue(date)
self.assertEqual(matched, date[1].group())
else:
self.assertEqual(date, None)

View File

@ -94,7 +94,7 @@ def testSampleRegexsFactory(name, basedir):
if jsonREMatch: if jsonREMatch:
try: try:
faildata = json.loads(jsonREMatch.group(1)) faildata = json.loads(jsonREMatch.group(1))
except ValueError, e: except ValueError as e:
raise ValueError("%s: %s:%i" % raise ValueError("%s: %s:%i" %
(e, logFile.filename(), logFile.filelineno())) (e, logFile.filename(), logFile.filelineno()))
line = next(logFile) line = next(logFile)

View File

@ -228,7 +228,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.assertTrue(self.jailName not in self.server._Server__jails) self.assertNotIn(self.jailName, self.server._Server__jails)
def testStartStopAllJail(self): def testStartStopAllJail(self):
self.server.addJail("TestJail2", "auto") self.server.addJail("TestJail2", "auto")
@ -242,8 +242,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.assertTrue(self.jailName not in self.server._Server__jails) self.assertNotIn(self.jailName, self.server._Server__jails)
self.assertTrue("TestJail2" not in self.server._Server__jails) self.assertNotIn("TestJail2", self.server._Server__jails)
def testJailIdle(self): def testJailIdle(self):
self.assertEqual( self.assertEqual(
@ -688,10 +688,7 @@ class Transmitter(TransmitterBase):
def testJournalMatch(self): def testJournalMatch(self):
if not filtersystemd: # pragma: no cover if not filtersystemd: # pragma: no cover
if sys.version_info >= (2, 7): raise unittest.SkipTest("systemd python interface not available")
raise unittest.SkipTest(
"systemd python interface not available")
return
jailName = "TestJail2" jailName = "TestJail2"
self.server.addJail(jailName, "systemd") self.server.addJail(jailName, "systemd")
values = [ values = [
@ -791,10 +788,8 @@ class TransmitterLogging(TransmitterBase):
self.setGetTest("logtarget", "STDERR") self.setGetTest("logtarget", "STDERR")
def testLogTargetSYSLOG(self): def testLogTargetSYSLOG(self):
if not os.path.exists("/dev/log") and sys.version_info >= (2, 7): if not os.path.exists("/dev/log"):
raise unittest.SkipTest("'/dev/log' not present") raise unittest.SkipTest("'/dev/log' not present")
elif not os.path.exists("/dev/log"):
return
self.assertTrue(self.server.getSyslogSocket(), "auto") self.assertTrue(self.server.getSyslogSocket(), "auto")
self.setGetTest("logtarget", "SYSLOG") self.setGetTest("logtarget", "SYSLOG")
self.assertTrue(self.server.getSyslogSocket(), "/dev/log") self.assertTrue(self.server.getSyslogSocket(), "/dev/log")

View File

@ -22,13 +22,17 @@ __author__ = "Yaroslav Halchenko"
__copyright__ = "Copyright (c) 2013 Yaroslav Halchenko" __copyright__ = "Copyright (c) 2013 Yaroslav Halchenko"
__license__ = "GPL" __license__ = "GPL"
import itertools
import logging import logging
import os import os
import re import re
import tempfile
import shutil
import sys import sys
import time import time
import unittest import unittest
from StringIO import StringIO from StringIO import StringIO
from functools import wraps
from ..server.mytime import MyTime from ..server.mytime import MyTime
from ..helpers import getLogger from ..helpers import getLogger
@ -45,6 +49,40 @@ if not CONFIG_DIR:
CONFIG_DIR = '/etc/fail2ban' CONFIG_DIR = '/etc/fail2ban'
def with_tmpdir(f):
"""Helper decorator to create a temporary directory
Directory gets removed after function returns, regardless
if exception was thrown of not
"""
@wraps(f)
def wrapper(self, *args, **kwargs):
tmp = tempfile.mkdtemp(prefix="f2b-temp")
try:
return f(self, tmp, *args, **kwargs)
finally:
# clean up
shutil.rmtree(tmp)
return wrapper
# backwards compatibility to python 2.6:
if not hasattr(unittest, 'SkipTest'): # pragma: no cover
class SkipTest(Exception):
pass
unittest.SkipTest = SkipTest
_org_AddError = unittest._TextTestResult.addError
def addError(self, test, err):
if err[0] is SkipTest:
if self.showAll:
self.stream.writeln(str(err[1]))
elif self.dots:
self.stream.write('s')
self.stream.flush()
return
_org_AddError(self, test, err)
unittest._TextTestResult.addError = addError
def mtimesleep(): def mtimesleep():
# no sleep now should be necessary since polling tracks now not only # no sleep now should be necessary since polling tracks now not only
# mtime but also ino and size # mtime but also ino and size
@ -183,13 +221,13 @@ def gatherTests(regexps=None, no_network=False):
try: try:
from ..server.filtergamin import FilterGamin from ..server.filtergamin import FilterGamin
filters.append(FilterGamin) filters.append(FilterGamin)
except Exception, e: # pragma: no cover except Exception as e: # pragma: no cover
logSys.warning("Skipping gamin backend testing. Got exception '%s'" % e) logSys.warning("Skipping gamin backend testing. Got exception '%s'" % e)
try: try:
from ..server.filterpyinotify import FilterPyinotify from ..server.filterpyinotify import FilterPyinotify
filters.append(FilterPyinotify) filters.append(FilterPyinotify)
except Exception, e: # pragma: no cover except Exception as e: # pragma: no cover
logSys.warning("I: Skipping pyinotify backend testing. Got exception '%s'" % e) logSys.warning("I: Skipping pyinotify backend testing. Got exception '%s'" % e)
for Filter_ in filters: for Filter_ in filters:
@ -198,7 +236,7 @@ def gatherTests(regexps=None, no_network=False):
try: # pragma: systemd no cover try: # pragma: systemd no cover
from ..server.filtersystemd import FilterSystemd from ..server.filtersystemd import FilterSystemd
tests.addTest(unittest.makeSuite(filtertestcase.get_monitor_failures_journal_testcase(FilterSystemd))) tests.addTest(unittest.makeSuite(filtertestcase.get_monitor_failures_journal_testcase(FilterSystemd)))
except Exception, e: # pragma: no cover except Exception as e: # pragma: no cover
logSys.warning("I: Skipping systemd backend testing. Got exception '%s'" % e) logSys.warning("I: Skipping systemd backend testing. Got exception '%s'" % e)
# Server test for logging elements which break logging used to support # Server test for logging elements which break logging used to support
@ -208,16 +246,45 @@ def gatherTests(regexps=None, no_network=False):
return tests return tests
# forwards compatibility of unittest.TestCase for some early python versions #
if not hasattr(unittest.TestCase, 'assertIn'): # Forwards compatibility of unittest.TestCase for some early python versions
def __assertIn(self, a, b, msg=None): #
if a not in b: # pragma: no cover
self.fail(msg or "%r was not found in %r" % (a, b)) if not hasattr(unittest.TestCase, 'assertRaisesRegexp'):
unittest.TestCase.assertIn = __assertIn def assertRaisesRegexp(self, exccls, regexp, fun, *args, **kwargs):
def __assertNotIn(self, a, b, msg=None): try:
if a in b: # pragma: no cover fun(*args, **kwargs)
self.fail(msg or "%r was found in %r" % (a, b)) except exccls as e:
unittest.TestCase.assertNotIn = __assertNotIn if re.search(regexp, str(e)) is None:
self.fail('\"%s\" does not match \"%s\"' % (regexp, e))
else:
self.fail('%s not raised' % getattr(exccls, '__name__'))
unittest.TestCase.assertRaisesRegexp = assertRaisesRegexp
# always custom following methods, because we use atm better version of both (support generators)
if True: ## if not hasattr(unittest.TestCase, 'assertIn'):
def assertIn(self, a, b, msg=None):
bb = b
wrap = False
if msg is None and hasattr(b, '__iter__') and not isinstance(b, basestring):
b, bb = itertools.tee(b)
wrap = True
if a not in b:
if wrap: bb = list(bb)
msg = msg or "%r was not found in %r" % (a, bb)
self.fail(msg)
unittest.TestCase.assertIn = assertIn
def assertNotIn(self, a, b, msg=None):
bb = b
wrap = False
if msg is None and hasattr(b, '__iter__') and not isinstance(b, basestring):
b, bb = itertools.tee(b)
wrap = True
if a in b:
if wrap: bb = list(bb)
msg = msg or "%r unexpectedly found in %r" % (a, bb)
self.fail(msg)
unittest.TestCase.assertNotIn = assertNotIn
class LogCaptureTestCase(unittest.TestCase): class LogCaptureTestCase(unittest.TestCase):
@ -241,6 +308,7 @@ class LogCaptureTestCase(unittest.TestCase):
def tearDown(self): def tearDown(self):
"""Call after every test case.""" """Call after every test case."""
# print "O: >>%s<<" % self._log.getvalue() # print "O: >>%s<<" % self._log.getvalue()
self.pruneLog()
logSys = getLogger("fail2ban") logSys = getLogger("fail2ban")
logSys.handlers = self._old_handlers logSys.handlers = self._old_handlers
logSys.level = self._old_level logSys.level = self._old_level
@ -248,7 +316,7 @@ class LogCaptureTestCase(unittest.TestCase):
def _is_logged(self, s): def _is_logged(self, s):
return s in self._log.getvalue() return s in self._log.getvalue()
def assertLogged(self, *s): def assertLogged(self, *s, **kwargs):
"""Assert that one of the strings was logged """Assert that one of the strings was logged
Preferable to assertTrue(self._is_logged(..))) Preferable to assertTrue(self._is_logged(..)))
@ -258,14 +326,23 @@ class LogCaptureTestCase(unittest.TestCase):
---------- ----------
s : string or list/set/tuple of strings s : string or list/set/tuple of strings
Test should succeed if string (or any of the listed) is present in the log Test should succeed if string (or any of the listed) is present in the log
all : boolean (default False) if True should fail if any of s not logged
""" """
logged = self._log.getvalue() logged = self._log.getvalue()
for s_ in s: if not kwargs.get('all', False):
if s_ in logged: # at least one entry should be found:
return for s_ in s:
raise AssertionError("None among %r was found in the log: %r" % (s, logged)) if s_ in logged:
return
if True: # pragma: no cover
self.fail("None among %r was found in the log: ===\n%s===" % (s, logged))
else:
# each entry should be found:
for s_ in s:
if s_ not in logged: # pragma: no cover
self.fail("%r was not found in the log: ===\n%s===" % (s_, logged))
def assertNotLogged(self, *s): def assertNotLogged(self, *s, **kwargs):
"""Assert that strings were not logged """Assert that strings were not logged
Parameters Parameters
@ -273,13 +350,22 @@ class LogCaptureTestCase(unittest.TestCase):
s : string or list/set/tuple of strings s : string or list/set/tuple of strings
Test should succeed if the string (or at least one of the listed) is not Test should succeed if the string (or at least one of the listed) is not
present in the log present in the log
all : boolean (default False) if True should fail if any of s logged
""" """
logged = self._log.getvalue() logged = self._log.getvalue()
for s_ in s: if not kwargs.get('all', False):
if s_ not in logged: for s_ in s:
return if s_ not in logged:
raise AssertionError("All of the %r were found present in the log: %r" % (s, logged)) return
if True: # pragma: no cover
self.fail("All of the %r were found present in the log: ===\n%s===" % (s, logged))
else:
for s_ in s:
if s_ in logged: # pragma: no cover
self.fail("%r was found in the log: ===\n%s===" % (s_, logged))
def pruneLog(self):
self._log.truncate(0)
def getLog(self): def getLog(self):
return self._log.getvalue() return self._log.getvalue()

View File

@ -24,4 +24,4 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko, Steven Hiscocks, Daniel Black"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2005-2016 Yaroslav Halchenko, 2013-2014 Steven Hiscocks, Daniel Black" __copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2005-2016 Yaroslav Halchenko, 2013-2014 Steven Hiscocks, Daniel Black"
__license__ = "GPL-v2+" __license__ = "GPL-v2+"
version = "0.9.5" version = "0.9.6"

View File

@ -1,7 +1,7 @@
check process fail2ban with pidfile /var/run/fail2ban/fail2ban.pid check process fail2ban with pidfile /var/run/fail2ban/fail2ban.pid
group services group services
start program = "/etc/init.d/fail2ban force-start" start program = "/etc/init.d/fail2ban force-start"
stop program = "/etc/init.d/fail2ban stop || :" stop program = "/etc/init.d/fail2ban stop"
if failed unixsocket /var/run/fail2ban/fail2ban.sock then restart if failed unixsocket /var/run/fail2ban/fail2ban.sock then restart
if 5 restarts within 5 cycles then timeout if 5 restarts within 5 cycles then timeout

View File

@ -1,12 +1,12 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4.
.TH FAIL2BAN-CLIENT "1" "July 2016" "fail2ban-client v0.9.5" "User Commands" .TH FAIL2BAN-CLIENT "1" "December 2016" "fail2ban-client v0.9.6" "User Commands"
.SH NAME .SH NAME
fail2ban-client \- configure and control the server fail2ban-client \- configure and control the server
.SH SYNOPSIS .SH SYNOPSIS
.B fail2ban-client .B fail2ban-client
[\fI\,OPTIONS\/\fR] \fI\,<COMMAND>\/\fR [\fI\,OPTIONS\/\fR] \fI\,<COMMAND>\/\fR
.SH DESCRIPTION .SH DESCRIPTION
Fail2Ban v0.9.5 reads log file that contains password failure report Fail2Ban v0.9.6 reads log file that contains password failure report
and bans the corresponding IP addresses using firewall rules. and bans the corresponding IP addresses using firewall rules.
.SH OPTIONS .SH OPTIONS
.TP .TP

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4.
.TH FAIL2BAN-REGEX "1" "July 2016" "fail2ban-regex 0.9.5" "User Commands" .TH FAIL2BAN-REGEX "1" "December 2016" "fail2ban-regex 0.9.6" "User Commands"
.SH NAME .SH NAME
fail2ban-regex \- test Fail2ban "failregex" option fail2ban-regex \- test Fail2ban "failregex" option
.SH SYNOPSIS .SH SYNOPSIS

View File

@ -1,12 +1,12 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4.
.TH FAIL2BAN-SERVER "1" "July 2016" "fail2ban-server v0.9.5" "User Commands" .TH FAIL2BAN-SERVER "1" "December 2016" "fail2ban-server v0.9.6" "User Commands"
.SH NAME .SH NAME
fail2ban-server \- start the server fail2ban-server \- start the server
.SH SYNOPSIS .SH SYNOPSIS
.B fail2ban-server .B fail2ban-server
[\fI\,OPTIONS\/\fR] [\fI\,OPTIONS\/\fR]
.SH DESCRIPTION .SH DESCRIPTION
Fail2Ban v0.9.5 reads log file that contains password failure report Fail2Ban v0.9.6 reads log file that contains password failure report
and bans the corresponding IP addresses using firewall rules. and bans the corresponding IP addresses using firewall rules.
.PP .PP
Only use this command for debugging purpose. Start the server with Only use this command for debugging purpose. Start the server with

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4.
.TH FAIL2BAN-TESTCASES "1" "July 2016" "fail2ban-testcases 0.9.5" "User Commands" .TH FAIL2BAN-TESTCASES "1" "December 2016" "fail2ban-testcases 0.9.6" "User Commands"
.SH NAME .SH NAME
fail2ban-testcases \- run Fail2Ban unit-tests fail2ban-testcases \- run Fail2Ban unit-tests
.SH SYNOPSIS .SH SYNOPSIS

View File

@ -19,9 +19,11 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
__author__ = "Cyril Jaquier, Steven Hiscocks, Yaroslav Halchenko" __author__ = "Cyril Jaquier, Steven Hiscocks, Yaroslav Halchenko"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2008-2013 Fail2Ban Contributors" __copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2008-2016 Fail2Ban Contributors"
__license__ = "GPL" __license__ = "GPL"
import platform
try: try:
import setuptools import setuptools
from setuptools import setup from setuptools import setup
@ -38,12 +40,40 @@ except ImportError:
# python 2.x # python 2.x
from distutils.command.build_py import build_py from distutils.command.build_py import build_py
from distutils.command.build_scripts import build_scripts from distutils.command.build_scripts import build_scripts
# all versions
from distutils.command.install_scripts import install_scripts
import os import os
from os.path import isfile, join, isdir, realpath from os.path import isfile, join, isdir, realpath
import sys import sys
import warnings import warnings
from glob import glob from glob import glob
from fail2ban.setup import updatePyExec
# Wrapper to install python binding (to current python version):
class install_scripts_f2b(install_scripts):
def get_outputs(self):
outputs = install_scripts.get_outputs(self)
fn = None
for fn in outputs:
if os.path.basename(fn) == 'fail2ban-server':
break
bindir = os.path.dirname(fn)
print('creating fail2ban-python binding -> %s' % (bindir,))
updatePyExec(bindir)
return outputs
# Update fail2ban-python env to current python version (where f2b-modules located/installed)
rootdir = os.path.realpath(os.path.dirname(
# __file__ seems to be overwritten sometimes on some python versions (e.g. bug of 2.6 by running under cProfile, etc.):
sys.argv[0] if os.path.basename(sys.argv[0]) == 'setup.py' else __file__
))
updatePyExec(os.path.join(rootdir, 'bin'))
if setuptools and "test" in sys.argv: if setuptools and "test" in sys.argv:
import logging import logging
logSys = logging.getLogger("fail2ban") logSys = logging.getLogger("fail2ban")
@ -85,6 +115,18 @@ if os.path.exists('/var/run'):
# realpath is used to possibly resolve /var/run -> /run symlink # realpath is used to possibly resolve /var/run -> /run symlink
data_files_extra += [(realpath('/var/run/fail2ban'), '')] data_files_extra += [(realpath('/var/run/fail2ban'), '')]
# Installing documentation files only under Linux or other GNU/ systems
# (e.g. GNU/kFreeBSD), since others might have protective mechanisms forbidding
# installation there (see e.g. #1233)
platform_system = platform.system().lower()
doc_files = ['README.md', 'DEVELOP', 'FILTERS', 'doc/run-rootless.txt']
if platform_system in ('solaris', 'sunos'):
doc_files.append('README.Solaris')
if platform_system in ('linux', 'solaris', 'sunos') or platform_system.startswith('gnu'):
data_files_extra.append(
('/usr/share/doc/fail2ban', doc_files)
)
# Get version number, avoiding importing fail2ban. # Get version number, avoiding importing fail2ban.
# This is due to tests not functioning for python3 as 2to3 takes place later # This is due to tests not functioning for python3 as 2to3 takes place later
exec(open(join("fail2ban", "version.py")).read()) exec(open(join("fail2ban", "version.py")).read())
@ -99,12 +141,16 @@ setup(
url = "http://www.fail2ban.org", url = "http://www.fail2ban.org",
license = "GPL", license = "GPL",
platforms = "Posix", platforms = "Posix",
cmdclass = {'build_py': build_py, 'build_scripts': build_scripts}, cmdclass = {
'build_py': build_py, 'build_scripts': build_scripts,
'install_scripts': install_scripts_f2b
},
scripts = [ scripts = [
'bin/fail2ban-client', 'bin/fail2ban-client',
'bin/fail2ban-server', 'bin/fail2ban-server',
'bin/fail2ban-regex', 'bin/fail2ban-regex',
'bin/fail2ban-testcases', 'bin/fail2ban-testcases',
# 'bin/fail2ban-python', -- link (binary), will be installed via install_scripts_f2b wrapper
], ],
packages = [ packages = [
'fail2ban', 'fail2ban',
@ -148,10 +194,6 @@ setup(
('/var/lib/fail2ban', ('/var/lib/fail2ban',
'' ''
), ),
('/usr/share/doc/fail2ban',
['README.md', 'README.Solaris', 'DEVELOP', 'FILTERS',
'doc/run-rootless.txt']
)
] + data_files_extra, ] + data_files_extra,
**setup_extra **setup_extra
) )