diff --git a/.gitignore b/.gitignore index c2e979e5..76a33e60 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ htmlcov .coverage *.orig *.rej +*.bak +__pycache__ diff --git a/.travis.yml b/.travis.yml index 398411bf..41eeca27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,14 +4,18 @@ language: python python: - "2.6" - "2.7" + - "3.2" + - "3.3" + - "pypy" before_install: - - sudo apt-get update -qq + - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then sudo apt-get update -qq; fi install: - pip install pyinotify - - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then sudo apt-get install -qq python-gamin; fi + - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then sudo apt-get install -qq python-gamin; cp /usr/share/pyshared/gamin.py /usr/lib/pyshared/python2.7/_gamin.so $VIRTUAL_ENV/lib/python2.7/site-packages/; fi - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then cd ..; pip install -q coveralls; cd -; fi script: - - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then export PYTHONPATH="$PYTHONPATH:/usr/share/pyshared:/usr/lib/pyshared/python2.7"; fi - - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then coverage run --rcfile=.travis_coveragerc fail2ban-testcases; else python ./fail2ban-testcases; fi + - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then coverage run --rcfile=.travis_coveragerc setup.py test; else python setup.py test; fi after_success: +# Coverage config file must be .coveragerc for coveralls + - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then cp -v .travis_coveragerc .coveragerc; fi - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then coveralls; fi diff --git a/.travis_coveragerc b/.travis_coveragerc index ac4a15d5..49fc3134 100644 --- a/.travis_coveragerc +++ b/.travis_coveragerc @@ -4,3 +4,4 @@ branch = True omit = /usr/* /home/travis/virtualenv/* + fail2ban/server/filtersystemd.py diff --git a/ChangeLog b/ChangeLog index df121e07..39c708d3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -4,9 +4,103 @@ |_| \__,_|_|_/___|_.__/\__,_|_||_| ================================================================================ -Fail2Ban (version 0.8.12.dev) 2014/01/22 +Fail2Ban (version 0.9.0) 2014/03/14 ================================================================================ +ver. 0.9.0 (2014/03/14 - beta +---------- + +Carries all fixes, features and enhancements from 0.8.13 (unreleased) with +major changes. + +The minimum supported python version is now 2.6. If you have python-2.4 or 2.5 +you can use the 0.8.12 version of fail2ban. + +Please take note of release notes: +https://github.com/fail2ban/fail2ban/releases/tag/0.9.0 + +Please test your configuration before relying on it. + +Nearly all development is thanks to Steven Hiscocks (THANKS!), merging, +testcases and timezone support from Daniel Black, and code-review and minor +additions from Yaroslav Halchenko. + +- Refactoring (IMPORTANT -- Please review your setup and configuration): + * [..bddbf1e] jail.conf was heavily refactored and now is similar + to how it looked on Debian systems: + - default action could be configured once for all jails + - jails definitions only provide customizations (port, logpath) + - no need to specify 'filter' if name matches jail name + * [..5aef036] Core functionality moved into fail2ban/ module. + Closes gh-26 + - tests included in module to aid testing and debugging + * Added fail2ban persistent database + - default location at /var/lib/fail2ban/fail2ban.sqlite3 + - allows active bans to be reinstated on restart + - log files read from last position after restart + * Added systemd journal backend + - Dependency on python-systemd + - New "journalmatch" option added to filter configs files + - New "systemd-journal" option added to fail2ban-regex + * Added python3 support + * Support %z (Timezone offset) and %f (sub-seconds) support for + datedetector. Enhanced existing date/time have been updated patterns to + support these. ISO8601 now defaults to localtime unless specified otherwise. + Some filters have been change as required to capture these elements in the + right timezone correctly. + * Log levels are now set by Syslog style strings e.g. DEBUG, ERROR. + - Log level INFO is now more verbose + * Optionally can read log files starting from "head" or "tail". + - See "logpath" option in jail.conf(5) man page. + * Can now set log encoding for files per jail. + - Default uses systemd locale. + +- New features: + * [..c7ae460] Multiline failregex. Close gh-54 + * [8af32ed] Guacamole filter and support for Apache Tomcat date + format + * [..b6059f4] 'timeout' option for actions Close gh-60 and Debian bug + #410077. Also it would now capture and include stdout and stderr + into logging messages in case of error or at DEBUG loglevel. + * Added action xarf-login-attack to report formatted attack messages + according to the XARF standard (v0.2). Close gh-105 + * Support PyPy + * Add filter for apache-botsearch + * Add filter for kerio. Thanks Tony Lawrence for blog of regexs and + providing samples. Close gh-120 + * Filter for stunnel + * Filter for Counter Strike 1.6. Thanks to onorua for logs. + Close gh-347 + * Filter for squirrelmail. Close gh-261 + * Filter for tine20. Close gh-583 + * Custom date formats (strptime) can now be set in filters and jail.conf + * Python based actions can now be created. + - SMTP action for sending emails on jail start, stop and ban. + * Added action to use badips.com reporting and blacklist + - Requires Python 2.7+ + +- Enhancements + * Fail2ban-regex - don't accumulate lines if not printing them. + add options to suppress output of missed/ignored lines. Close gh-644 + * Asterisk now supports syslog format + * Jail names increased to 26 characters and iptables prefix reduced + from fail2ban- to f2b- as suggested by buanzo in gh-462. + * Multiline filter for sendmail-spam. Close gh-418 + * Multiline regex for Disconnecting: Too many authentication failures for + root [preauth]\nConnection closed by 6X.XXX.XXX.XXX [preauth] + * Multiline regex for Disconnecting: Connection from 61.XX.XX.XX port + 51353\nToo many authentication failures for root [preauth]. Thanks + Helmut Grohne. Close gh-457 + * Replacing use of deprecated API (.warning, .assertEqual, etc) + * [..a648cc2] Filters can have options now too which are substituted into + failregex / ignoreregex + * [..e019ab7] Multiple instances of the same action are allowed in the + same jail -- use actname option to disambiguate. + * Add honeypot email address to exim-spam filter as argument + * Properties and methods of actions accessible from fail2ban-client + - Use of properties replaces command actions "cinfo" interface + + ver. 0.8.13 (2014/XX/XXX) - maintenance-only-from-now-on ----------- @@ -27,7 +121,7 @@ ver. 0.8.13 (2014/XX/XXX) - maintenance-only-from-now-on Thanks Noel Butler. ver. 0.8.12 (2014/01/22) - things-can-only-get-better ------------ +---------- - IMPORTANT incompatible changes: - Rename firewall-cmd-direct-new to firewallcmd-new to fit within jail name @@ -39,7 +133,7 @@ ver. 0.8.12 (2014/01/22) - things-can-only-get-better - allow for ",milliseconds" in the custom date format of proftpd.log - allow for ", referer ..." in apache-* filter for apache error logs. - allow for spaces at the beginning of kernel messages. Closes gh-448 - - recidive jail to block all protocols. Closes gh-440. Thanksg Ioan Indreias + - recidive jail to block all protocols. Closes gh-440. Thanks Ioan Indreias - smtps not a IANA standard and has been removed from Arch. Replaced with 465. Thanks Stefan. Closes gh-447 - add 'flushlogs' command to allow logrotation without clobbering logtarget @@ -52,7 +146,7 @@ ver. 0.8.12 (2014/01/22) - things-can-only-get-better - Asynchat changed to use push method which verifys whether all data was send. This ensures that all data is sent before closing the connection. - Removed unnecessary reference to as yet undeclared $jail_name when checking - a specific jail. + a specific jail in nagios script. - Filter dovecot reordered session and TLS items in regex with wider scope for session characters. Thanks Ivo Truxa. Closes gh-586 - A single bad failregex or command syntax in configuration files won't stop @@ -68,8 +162,11 @@ ver. 0.8.12 (2014/01/22) - things-can-only-get-better Thanks dani. Closes gh-503 - exim-spam filter to match spamassassin log entry for option SAdevnull. Thanks Ivo Truxa. Closes gh-533 + - filter.d/nsd.conf -- also amended Unix date template to match nsd format - Added to sshd filter expression for "Received disconnect from : 3: ...: Auth fail". Thanks Marcel Dopita. Closes gh-289 + - loglines now also report "[PID]" after the name portion + - Added filter.d/ejabberd-auth - Improved ACL-handling for Asterisk - loglines now also report "[PID]" after the name portion - Added improper command pipelining to postfix filter. @@ -79,10 +176,12 @@ ver. 0.8.12 (2014/01/22) - things-can-only-get-better - filter.d/solid-pop3d -- added thanks to Jacques Lav!gnotte on mailinglist. - Add filter for apache-modsecurity. - filter.d/nsd.conf -- also amended Unix date template to match nsd format - - Added filter.d/openwebmail filter thanks Ivo Truxa. Closes gh-543 - - Added filter.d/horde. + - Added openwebmail filter thanks Ivo Truxa. Closes gh-543 - Added filter for freeswitch. Thanks Jim and editors and authors of - http://wiki.freeswitch.org/wiki/Fail2ban. + http://wiki.freeswitch.org/wiki/Fail2ban + - Added groupoffice filter thanks to logs from Merijn Schering. + Closes gh-566 + - Added filter for horde - Added filter for squid. Thanks Roman Gelfand. - Added filter for ejabberd-auth. - Added filter.d/openwebmail filter thanks Ivo Truxa. Closes gh-543 @@ -95,7 +194,7 @@ ver. 0.8.12 (2014/01/22) - things-can-only-get-better ver. 0.8.11 (2013/11/13) - loves-unittests-and-tight-DoS-free-filter-regexes ------------ +---------- In light of CVE-2013-2178 that triggered our last release we have put a significant effort into tightening all of the regexs of our filters diff --git a/DEVELOP b/DEVELOP index 18d29ad4..e81bca7e 100644 --- a/DEVELOP +++ b/DEVELOP @@ -39,9 +39,9 @@ If you are developing filters see the FILTERS file for documentation. Code Testing ============ -Existing tests can be run by executing `fail2ban-testcases`. This has options -like --log-level that will probably be useful. `fail2ban-testcases --help` for -full options. +Existing tests can be run by executing `bin/fail2ban-testcases`. It has +options like --log-level that will probably be useful. Run +`bin/fail2ban-testcases --help` for the full list of options. Test cases should cover all usual cases, all exception cases and all inside / outside boundary conditions. @@ -54,7 +54,7 @@ Install the package python-coverage to visualise your test coverage. Run the following (note: on Debian-based systems, the script is called `python-coverage`): -coverage run fail2ban-testcases +coverage run bin/fail2ban-testcases coverage html Then look at htmlcov/index.html and see how much coverage your test cases @@ -268,159 +268,3 @@ action.py Takes care about executing start/check/ban/unban/stop commands - -Releasing -========= - -# Check distribution patches and see if they can be included - - * https://apps.fedoraproject.org/packages/fail2ban/sources - * http://sources.gentoo.org/cgi-bin/viewvc.cgi/gentoo-x86/net-analyzer/fail2ban/ - * http://svnweb.freebsd.org/ports/head/security/py-fail2ban/ - * https://build.opensuse.org/package/show?package=fail2ban&project=openSUSE%3AFactory - * http://sophie.zarb.org/sources/fail2ban (Mageia) - * https://trac.macports.org/browser/trunk/dports/security/fail2ban - -# Check distribution outstanding bugs - - * https://github.com/fail2ban/fail2ban/issues?sort=updated&state=open - * http://bugs.debian.org/cgi-bin/pkgreport.cgi?dist=unstable;package=fail2ban - * https://bugs.launchpad.net/ubuntu/+source/fail2ban - * http://bugs.sabayon.org/buglist.cgi?quicksearch=net-analyzer%2Ffail2ban - * https://bugs.archlinux.org/?project=5&cat%5B%5D=33&string=fail2ban - * https://bugs.gentoo.org/buglist.cgi?query_format=advanced&short_desc=fail2ban&bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=IN_PROGRESS&short_desc_type=allwords - * https://bugzilla.redhat.com/buglist.cgi?query_format=advanced&bug_status=NEW&bug_status=ASSIGNED&component=fail2ban&classification=Red%20Hat&classification=Fedora - * http://www.freebsd.org/cgi/query-pr-summary.cgi?text=fail2ban - * https://bugs.mageia.org/buglist.cgi?quicksearch=fail2ban - * https://build.opensuse.org/package/requests/openSUSE:Factory/fail2ban - -# Make sure the tests pass - - ./fail2ban-testcases-all - -# Ensure the version is correct - - in: - * ./common/version.py - * top of ChangeLog - * README.md - -# Ensure the MANIFEST is complete - -Run: - - python setup.py sdist - -Look for errors like: - 'testcases/files/logs/mysqld.log' not a regular file -- skipping - -Which indicates that testcases/files/logs/mysqld.log has been moved or is a directory - - tar -C /tmp -jxf dist/fail2ban-0.8.12.tar.bz2 - -# clean up current direcory - - diff -rul --exclude \*.pyc . /tmp/fail2ban-0.8.12/ - - # Only differences should be files that you don't want distributed. - -# Ensure the tests work from the tarball - - cd /tmp/fail2ban-0.8.12/ && ./fail2ban-testcases-all - -# Add/finalize the corresponding entry in the ChangeLog - - To generate a list of committers use e.g. - - git shortlog -sn 0.8.11.. | 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 entry of the ChangeLog has the right version and current date. - -# Update man pages - - (cd man ; ./generate-man ) - git commit -m 'DOC/ENH: update man pages for release' man/* - -# Prepare source and rpm binary distributions - - python setup.py sdist - python setup.py bdist_rpm - python setup.py upload - -# Provide a release sample to distributors - - * Arch Linux: - https://www.archlinux.org/packages/community/any/fail2ban/ - * Debian: Yaroslav Halchenko - http://packages.qa.debian.org/f/fail2ban.html - * FreeBSD: Christoph Theis theis@gmx.at>, Nick Hilliard - http://svnweb.freebsd.org/ports/head/security/py-fail2ban/Makefile?view=markup - http://www.freebsd.org/cgi/query-pr-summary.cgi?text=fail2ban - * Fedora: Axel Thimm - https://apps.fedoraproject.org/packages/fail2ban - http://pkgs.fedoraproject.org/cgit/fail2ban.git - https://admin.fedoraproject.org/pkgdb/acls/bugs/fail2ban - * Gentoo: netmon@gentoo.org - http://sources.gentoo.org/cgi-bin/viewvc.cgi/gentoo-x86/net-analyzer/fail2ban/metadata.xml?view=markup - https://bugs.gentoo.org/buglist.cgi?quicksearch=fail2ban - * openSUSE: Stephan Kulow - https://build.opensuse.org/package/show/openSUSE:Factory/fail2ban - * Mac Ports: @Malbrouck on github (gh-49) - https://trac.macports.org/browser/trunk/dports/security/fail2ban/Portfile - * Mageia: - https://bugs.mageia.org/buglist.cgi?quicksearch=fail2ban - An potentially to the fail2ban-users directory. - -# Wait for feedback from distributors - -# Prepare a release notice https://github.com/fail2ban/fail2ban/releases/new - - Upload the source/binaries from the dist directory and tag the release using the URL - -# Upload source/binaries to sourceforge http://sourceforge.net/projects/fail2ban/ - -# Run the following and update the wiki with output: - python -c 'import common.protocol; common.protocol.printWiki()' - - page: http://www.fail2ban.org/wiki/index.php/Commands - -* Update: - http://www.fail2ban.org/wiki/index.php?title=Template:Fail2ban_Versions&action=edit - - http://www.fail2ban.org/wiki/index.php?title=Template:Fail2ban_News&action=edit - move old bits to: - http://www.fail2ban.org/wiki/index.php?title=Template:Fail2ban_OldNews&action=edit - - http://www.fail2ban.org/wiki/index.php?title=Template:Fail2ban_Versions&action=edit - http://www.fail2ban.org/wiki/index.php/ChangeLog - http://www.fail2ban.org/wiki/index.php/Requirements (Check requirement) - http://www.fail2ban.org/wiki/index.php/Features - -* See if any filters are upgraded: - http://www.fail2ban.org/wiki/index.php/Special:AllPages - -# Email users and development list of release - -# notify distributors - -Post Release -============ - -Add the following to the top of the ChangeLog - -ver. 0.8.13 (2014/XX/XXX) - wanna-be-released ------------ - -- Fixes: - -- New Features: - -- Enhancements: - -Alter the git shortlog command in the previous section to refer to the just -released version. - -and adjust common/version.py to carry .dev suffix to signal -a version under development. diff --git a/MANIFEST b/MANIFEST index af3f564c..9987adaf 100644 --- a/MANIFEST +++ b/MANIFEST @@ -6,167 +6,202 @@ THANKS COPYING DEVELOP FILTERS -fail2ban-client -fail2ban-server -fail2ban-testcases -fail2ban-regex +fail2ban-2to3 fail2ban-testcases-all +fail2ban-testcases-all-python3 +bin/fail2ban-client +bin/fail2ban-server +bin/fail2ban-testcases +bin/fail2ban-regex +doc/run-rootless.txt +fail2ban/client/configreader.py +fail2ban/client/configparserinc.py +fail2ban/client/jailreader.py +fail2ban/client/fail2banreader.py +fail2ban/client/jailsreader.py +fail2ban/client/beautifier.py +fail2ban/client/filterreader.py +fail2ban/client/actionreader.py +fail2ban/client/__init__.py +fail2ban/client/configurator.py +fail2ban/client/csocket.py +fail2ban/server/asyncserver.py +fail2ban/server/database.py +fail2ban/server/filter.py +fail2ban/server/filterpyinotify.py +fail2ban/server/filtergamin.py +fail2ban/server/filterpoll.py +fail2ban/server/filtersystemd.py +fail2ban/server/iso8601.py +fail2ban/server/server.py +fail2ban/server/actions.py +fail2ban/server/faildata.py +fail2ban/server/failmanager.py +fail2ban/server/datedetector.py +fail2ban/server/jailthread.py +fail2ban/server/transmitter.py +fail2ban/server/action.py +fail2ban/server/ticket.py +fail2ban/server/jail.py +fail2ban/server/jails.py +fail2ban/server/__init__.py +fail2ban/server/banmanager.py +fail2ban/server/datetemplate.py +fail2ban/server/mytime.py +fail2ban/server/failregex.py +fail2ban/server/database.py +fail2ban/tests/banmanagertestcase.py +fail2ban/tests/failmanagertestcase.py +fail2ban/tests/clientreadertestcase.py +fail2ban/tests/filtertestcase.py +fail2ban/tests/__init__.py +fail2ban/tests/dummyjail.py +fail2ban/tests/samplestestcase.py +fail2ban/tests/datedetectortestcase.py +fail2ban/tests/actiontestcase.py +fail2ban/tests/servertestcase.py +fail2ban/tests/sockettestcase.py +fail2ban/tests/utils.py +fail2ban/tests/misctestcase.py +fail2ban/tests/databasetestcase.py +fail2ban/tests/config/jail.conf +fail2ban/tests/config/fail2ban.conf +fail2ban/tests/config/paths-common.conf +fail2ban/tests/config/paths-freebsd.conf +fail2ban/tests/config/paths-osx.conf +fail2ban/tests/config/paths-debian.conf +fail2ban/tests/config/filter.d/simple.conf +fail2ban/tests/config/action.d/brokenaction.conf +fail2ban/tests/files/config/apache-auth/digest/.htaccess +fail2ban/tests/files/config/apache-auth/digest/.htpasswd +fail2ban/tests/files/config/apache-auth/digest_time/.htaccess +fail2ban/tests/files/config/apache-auth/digest_time/.htpasswd +fail2ban/tests/files/config/apache-auth/basic/authz_owner/.htaccess +fail2ban/tests/files/config/apache-auth/basic/authz_owner/cant_get_me.html +fail2ban/tests/files/config/apache-auth/basic/authz_owner/.htpasswd +fail2ban/tests/files/config/apache-auth/basic/file/.htaccess +fail2ban/tests/files/config/apache-auth/basic/file/.htpasswd +fail2ban/tests/files/config/apache-auth/digest.py +fail2ban/tests/files/config/apache-auth/digest_wrongrelm/.htaccess +fail2ban/tests/files/config/apache-auth/digest_wrongrelm/.htpasswd +fail2ban/tests/files/config/apache-auth/digest_anon/.htaccess +fail2ban/tests/files/config/apache-auth/digest_anon/.htpasswd +fail2ban/tests/files/config/apache-auth/README +fail2ban/tests/files/config/apache-auth/noentry/.htaccess +fail2ban/tests/files/database_v1.db +fail2ban/tests/files/ignorecommand.py +fail2ban/tests/files/filter.d/substition.conf +fail2ban/tests/files/filter.d/testcase-common.conf +fail2ban/tests/files/filter.d/testcase01.conf +fail2ban/tests/files/testcase01.log +fail2ban/tests/files/testcase02.log +fail2ban/tests/files/testcase03.log +fail2ban/tests/files/testcase04.log +fail2ban/tests/files/testcase-usedns.log +fail2ban/tests/files/testcase-journal.log +fail2ban/tests/files/testcase-multiline.log +fail2ban/tests/files/logs/bsd/syslog-plain.txt +fail2ban/tests/files/logs/bsd/syslog-v.txt +fail2ban/tests/files/logs/bsd/syslog-vv.txt +fail2ban/tests/files/logs/3proxy +fail2ban/tests/files/logs/apache-auth +fail2ban/tests/files/logs/apache-badbots +fail2ban/tests/files/logs/apache-botscripts +fail2ban/tests/files/logs/apache-modsecurity +fail2ban/tests/files/logs/apache-nohome +fail2ban/tests/files/logs/apache-noscript +fail2ban/tests/files/logs/apache-overflows +fail2ban/tests/files/logs/assp +fail2ban/tests/files/logs/asterisk +fail2ban/tests/files/logs/counter-strike +fail2ban/tests/files/logs/courier-auth +fail2ban/tests/files/logs/courier-smtp +fail2ban/tests/files/logs/cyrus-imap +fail2ban/tests/files/logs/dovecot +fail2ban/tests/files/logs/dropbear +fail2ban/tests/files/logs/ejabberd-auth +fail2ban/tests/files/logs/exim +fail2ban/tests/files/logs/exim-spam +fail2ban/tests/files/logs/freeswitch +fail2ban/tests/files/logs/groupoffice +fail2ban/tests/files/logs/gssftpd +fail2ban/tests/files/logs/guacamole +fail2ban/tests/files/logs/kerio +fail2ban/tests/files/logs/lighttpd-auth +fail2ban/tests/files/logs/mysqld-auth +fail2ban/tests/files/logs/nsd +fail2ban/tests/files/logs/perdition +fail2ban/tests/files/logs/php-url-fopen +fail2ban/tests/files/logs/postfix-sasl +fail2ban/tests/files/logs/named-refused +fail2ban/tests/files/logs/nginx-http-auth +fail2ban/tests/files/logs/pam-generic +fail2ban/tests/files/logs/postfix +fail2ban/tests/files/logs/proftpd +fail2ban/tests/files/logs/pure-ftpd +fail2ban/tests/files/logs/qmail +fail2ban/tests/files/logs/recidive +fail2ban/tests/files/logs/roundcube-auth +fail2ban/tests/files/logs/selinux-ssh +fail2ban/tests/files/logs/sendmail-spam +fail2ban/tests/files/logs/sieve +fail2ban/tests/files/logs/squid +fail2ban/tests/files/logs/stunnel +fail2ban/tests/files/logs/suhosin +fail2ban/tests/files/logs/sogo-auth +fail2ban/tests/files/logs/solid-pop3d +fail2ban/tests/files/logs/sshd +fail2ban/tests/files/logs/sshd-ddos +fail2ban/tests/files/logs/vsftpd +fail2ban/tests/files/logs/webmin-auth +fail2ban/tests/files/logs/wuftpd +fail2ban/tests/files/logs/uwimap-auth +fail2ban/tests/files/logs/xinetd-fail +fail2ban/tests/config/jail.conf +fail2ban/tests/config/fail2ban.conf +fail2ban/tests/config/filter.d/simple.conf +fail2ban/tests/config/action.d/brokenaction.conf +setup.py +setup.cfg +fail2ban/__init__.py +fail2ban/exceptions.py +fail2ban/helpers.py +fail2ban/version.py +fail2ban/protocol.py setup.py setup.cfg kill-server -client/configreader.py -client/configparserinc.py -client/jailreader.py -client/fail2banreader.py -client/jailsreader.py -client/beautifier.py -client/filterreader.py -client/actionreader.py -client/__init__.py -client/configurator.py -client/csocket.py -server/asyncserver.py -server/filter.py -server/filterpyinotify.py -server/filtergamin.py -server/filterpoll.py -server/iso8601.py -server/server.py -server/actions.py -server/faildata.py -server/failmanager.py -server/datedetector.py -server/jailthread.py -server/transmitter.py -server/action.py -server/ticket.py -server/jail.py -server/jails.py -server/__init__.py -server/banmanager.py -server/datetemplate.py -server/mytime.py -server/failregex.py -testcases/actionstestcase.py -testcases/dummyjail.py -testcases/files/ignorecommand.py -testcases/files/testcase-usedns.log -testcases/files/logs/bsd/syslog-plain.txt -testcases/files/logs/bsd/syslog-v.txt -testcases/files/logs/bsd/syslog-vv.txt -testcases/files/logs/apache-overflows -testcases/files/logs/apache-modsecurity -testcases/files/logs/assp -testcases/files/logs/asterisk -testcases/files/logs/dovecot -testcases/files/logs/ejabberd-auth -testcases/files/logs/exim -testcases/files/logs/freeswitch -testcases/files/logs/groupoffice -testcases/files/logs/horde -testcases/files/logs/suhosin -testcases/files/logs/mysqld-auth -testcases/files/logs/named-refused -testcases/files/logs/nginx-http-auth -testcases/files/logs/nsd -testcases/files/logs/openwebmail -testcases/files/logs/pam-generic -testcases/files/logs/postfix -testcases/files/logs/proftpd -testcases/files/logs/pure-ftpd -testcases/files/logs/roundcube-auth -testcases/files/logs/postfix-sasl -testcases/files/logs/sogo-auth -testcases/files/logs/solid-pop3d -testcases/files/logs/squid -testcases/files/logs/sshd -testcases/files/logs/sshd-ddos -testcases/files/logs/vsftpd -testcases/files/logs/webmin-auth -testcases/files/logs/wuftpd -testcases/files/logs/3proxy -testcases/files/logs/apache-auth -testcases/files/logs/apache-badbots -testcases/files/logs/apache-nohome -testcases/files/logs/apache-noscript -testcases/files/logs/courierlogin -testcases/files/logs/couriersmtp -testcases/files/logs/cyrus-imap -testcases/files/logs/dropbear -testcases/files/logs/exim-spam -testcases/files/logs/gssftpd -testcases/files/logs/lighttpd-auth -testcases/files/logs/mysqld-auth -testcases/files/logs/perdition -testcases/files/logs/php-url-fopen -testcases/files/logs/qmail -testcases/files/logs/recidive -testcases/files/logs/sieve -testcases/files/logs/selinux-ssh -testcases/files/logs/sendmail-auth -testcases/files/logs/sendmail-reject -testcases/files/logs/suhosin -testcases/files/logs/uwimap-auth -testcases/files/logs/wuftpd -testcases/files/logs/xinetd-fail -testcases/files/config/apache-auth/digest/.htaccess -testcases/files/config/apache-auth/digest/.htpasswd -testcases/files/config/apache-auth/digest_time/.htaccess -testcases/files/config/apache-auth/digest_time/.htpasswd -testcases/files/config/apache-auth/basic/authz_owner/.htaccess -testcases/files/config/apache-auth/basic/authz_owner/cant_get_me.html -testcases/files/config/apache-auth/basic/authz_owner/.htpasswd -testcases/files/config/apache-auth/basic/file/.htaccess -testcases/files/config/apache-auth/basic/file/.htpasswd -testcases/files/config/apache-auth/digest.py -testcases/files/config/apache-auth/digest_wrongrelm/.htaccess -testcases/files/config/apache-auth/digest_wrongrelm/.htpasswd -testcases/files/config/apache-auth/digest_anon/.htaccess -testcases/files/config/apache-auth/digest_anon/.htpasswd -testcases/files/config/apache-auth/README -testcases/files/config/apache-auth/noentry/.htaccess -testcases/samplestestcase.py -testcases/banmanagertestcase.py -testcases/failmanagertestcase.py -testcases/clientreadertestcase.py -testcases/filtertestcase.py -testcases/__init__.py -testcases/datedetectortestcase.py -testcases/actiontestcase.py -testcases/servertestcase.py -testcases/sockettestcase.py -testcases/files/testcase01.log -testcases/files/testcase02.log -testcases/files/testcase03.log -testcases/files/testcase04.log -testcases/misctestcase.py -testcases/utils.py -common/__init__.py -common/exceptions.py -common/helpers.py -common/version.py -common/protocol.py config/jail.conf +config/fail2ban.conf config/filter.d/common.conf config/filter.d/apache-auth.conf config/filter.d/apache-badbots.conf +config/filter.d/apache-botsearch.conf config/filter.d/apache-modsecurity.conf config/filter.d/apache-nohome.conf config/filter.d/apache-noscript.conf config/filter.d/apache-overflows.conf config/filter.d/nginx-http-auth.conf -config/filter.d/courierlogin.conf -config/filter.d/couriersmtp.conf +config/filter.d/counter-strike.conf +config/filter.d/courier-auth.conf +config/filter.d/courier-smtp.conf config/filter.d/cyrus-imap.conf config/filter.d/ejabberd-auth.conf config/filter.d/exim.conf config/filter.d/freeswitch.conf config/filter.d/gssftpd.conf +config/filter.d/kerio.conf config/filter.d/horde.conf config/filter.d/suhosin.conf config/filter.d/named-refused.conf config/filter.d/nsd.conf config/filter.d/openwebmail.conf +config/filter.d/pam-generic.conf +config/filter.d/php-url-fopen.conf +config/filter.d/postfix-sasl.conf +config/filter.d/pam-generic.conf +config/filter.d/php-url-fopen.conf +config/filter.d/postfix-sasl.conf config/filter.d/postfix.conf config/filter.d/proftpd.conf config/filter.d/pure-ftpd.conf @@ -181,6 +216,7 @@ config/filter.d/solid-pop3d.conf config/filter.d/squid.conf config/filter.d/sshd.conf config/filter.d/sshd-ddos.conf +config/filter.d/stunnel.conf config/filter.d/vsftpd.conf config/filter.d/webmin-auth.conf config/filter.d/wuftpd.conf @@ -200,9 +236,15 @@ config/filter.d/3proxy.conf config/filter.d/apache-common.conf config/filter.d/exim-common.conf config/filter.d/exim-spam.conf +config/filter.d/freeswitch.conf config/filter.d/groupoffice.conf config/filter.d/perdition.conf config/filter.d/uwimap-auth.conf +config/filter.d/courier-auth.conf +config/filter.d/courier-smtp.conf +config/filter.d/ejabberd-auth.conf +config/filter.d/guacamole.conf +config/filter.d/sendmail-spam.conf config/action.d/apf.conf config/action.d/blocklist_de.conf config/action.d/osx-afctl.conf @@ -237,9 +279,11 @@ config/action.d/mynetwatchman.conf config/action.d/pf.conf config/action.d/sendmail.conf config/action.d/sendmail-buffered.conf +config/action.d/sendmail-whois-ipmatches.conf config/action.d/sendmail-whois.conf config/action.d/sendmail-whois-lines.conf config/action.d/shorewall.conf +config/action.d/xarf-login-attack.conf config/action.d/ufw.conf config/fail2ban.conf doc/run-rootless.txt @@ -270,7 +314,3 @@ files/fail2ban-tmpfiles.conf files/fail2ban.service files/ipmasq-ZZZzzz_fail2ban.rul files/gen_badbots -testcases/config/jail.conf -testcases/config/fail2ban.conf -testcases/config/filter.d/simple.conf -testcases/config/action.d/brokenaction.conf diff --git a/README.md b/README.md index b9b0cc60..308136a1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ / _|__ _(_) |_ ) |__ __ _ _ _ | _/ _` | | |/ /| '_ \/ _` | ' \ |_| \__,_|_|_/___|_.__/\__,_|_||_| - v0.8.12 2014/01/22 + v0.9.0 2014/03/14 ## Fail2Ban: ban hosts that cause multiple authentication errors @@ -11,6 +11,11 @@ password failures. It updates firewall rules to reject the IP address. These rules can be defined by the user. Fail2Ban can read multiple log files such as sshd or Apache web server ones. +Fail2Ban is able to reduce the rate of incorrect authentications attempts +however it cannot eliminate the risk that weak authentication presents. +Configure services to use only two factor or public/private authentication +mechanisms if you really want to protect services. + This README is a quick introduction to Fail2ban. More documentation, FAQ, HOWTOs are available in fail2ban(1) manpage and on the website http://www.fail2ban.org @@ -21,21 +26,22 @@ Installation: this case, you should use it instead.** Required: -- [Python >= 2.4](http://www.python.org) +- [Python2 >= 2.6 or Python >= 3.2](http://www.python.org) or [PyPy](http://pypy.org) Optional: - [pyinotify >= 0.8.3](https://github.com/seb-m/pyinotify) - Linux >= 2.6.13 - [gamin >= 0.0.21](http://www.gnome.org/~veillard/gamin) +- [systemd >= 204](http://www.freedesktop.org/wiki/Software/systemd) To install, just do: - tar xvfj fail2ban-0.8.12.tar.bz2 - cd fail2ban-0.8.12 + tar xvfj fail2ban-0.9.0.tar.bz2 + cd fail2ban-0.9.0 python setup.py install -This will install Fail2Ban into /usr/share/fail2ban. The executable scripts are -placed into /usr/bin, and configuration under /etc/fail2ban. +This will install Fail2Ban into the python library directory. The executable +scripts are placed into /usr/bin, and configuration under /etc/fail2ban. Fail2Ban should be correctly installed now. Just type: @@ -50,8 +56,7 @@ Configuration: You can configure Fail2Ban using the files in /etc/fail2ban. It is possible to configure the server using commands sent to it by fail2ban-client. The available commands are described in the fail2ban-client(1) manpage. Also see -fail2ban(1) manpage for further references and find even more documentation on -the website: http://www.fail2ban.org +fail2ban(1) and jail.conf(5) manpages for further references. Code status: ------------ diff --git a/THANKS b/THANKS index f7b77725..64eb4402 100644 --- a/THANKS +++ b/THANKS @@ -40,9 +40,12 @@ Frédéric Georgiy Mernov Guilhem Lettron Guillaume Delvit +Hank Leininger Hanno 'Rince' Wagner +Helmut Grohne Iain Lea Ivo Truxa +John Thoe Jacques Lav!gnotte Ioan Indreias Jonathan Kamens @@ -55,6 +58,7 @@ Justin Shore Kévin Drapel kjohnsonecl kojiro +Lars Kneschke Lee Clemens Manuel Arostegui Ramirez Marcel Dopita @@ -68,7 +72,9 @@ mEDI Merijn Schering Michael C. Haller Michael Hanselmann +Mika (mkl) Nick Munger +onorua Noel Butler Patrick Börjesson Raphaël Marichez @@ -84,8 +90,10 @@ silviogarbes Stefan Tatschner Stephen Gildea Steven Hiscocks +TESTOVIK Tom Pike Tomas Pihl +Tony Lawrence Tomasz Ciolek Tyler Vaclav Misek diff --git a/TODO b/TODO index 33263d3e..3c811a74 100644 --- a/TODO +++ b/TODO @@ -13,20 +13,6 @@ Legend: # partially done * done -- more detailed explaination in DEVELOP for new developers (eg. howto build this HEX numbers in ChangeLog) - -- Run tests though all filters/examples files - (see sshd example file) as unit - test - -* Removed relative imports - -* Cleanup fail2ban-client and fail2ban-server. Move code to server/ and client/ - -- Add timeout to external commands (signal alarm, watchdog thread, etc) - -- Uniformize filters and actions name. Use the software name (openssh, postfix, - proftp) and possible qualifier (e.g. auth) after a '-' - - Added tag for failregex. Add features using this information. Maybe add more tags @@ -37,23 +23,10 @@ Legend: - Auto-enable function (search for log files), check modification date to see if service is still in use -- Improve parsing of the action parameters in jailreader.py - - Better handling of the protocol in transmitter.py - Add gettext support (I18N) -- Multiline log reading - -- Improve execution of action. Why does subprocess.call deadlock with - multi-jails? - -# see Feature Request Tracking System at SourceForge.net - # improve documentation and website for user # better return values in function - -# refactoring in server.py, actions.py, filter.py - -* New backend: pyinotify diff --git a/fail2ban-client b/bin/fail2ban-client similarity index 96% rename from fail2ban-client rename to bin/fail2ban-client index b33a27ae..8737c49d 100755 --- a/fail2ban-client +++ b/bin/fail2ban-client @@ -25,19 +25,11 @@ __license__ = "GPL" import sys, string, os, pickle, re, logging, signal import getopt, time, shlex, socket -# Inserts our own modules path first in the list -# fix for bug #343821 -try: - from common.version import version -except ImportError, e: - sys.path.insert(1, "/usr/share/fail2ban") - from common.version import version - -# Now we can import the rest of modules -from common.protocol import printFormatted -from client.csocket import CSocket -from client.configurator import Configurator -from client.beautifier import Beautifier +from fail2ban.version import version +from fail2ban.protocol import printFormatted +from fail2ban.client.csocket import CSocket +from fail2ban.client.configurator import Configurator +from fail2ban.client.beautifier import Beautifier # Gets the instance of the logger. logSys = logging.getLogger("fail2ban.client") @@ -110,7 +102,7 @@ class Fail2banClient: def __sigTERMhandler(self, signum, frame): # Print a new line because we probably come from wait print - logSys.warn("Caught signal %d. Exiting" % signum) + logSys.warning("Caught signal %d. Exiting" % signum) sys.exit(-1) def __getCmdLineOptions(self, optList): @@ -333,7 +325,7 @@ class Fail2banClient: if verbose <= 0: logSys.setLevel(logging.ERROR) elif verbose == 1: - logSys.setLevel(logging.WARN) + logSys.setLevel(logging.WARNING) elif verbose == 2: logSys.setLevel(logging.INFO) else: diff --git a/fail2ban-regex b/bin/fail2ban-regex similarity index 66% rename from fail2ban-regex rename to bin/fail2ban-regex index 9055360d..270b70d7 100755 --- a/fail2ban-regex +++ b/bin/fail2ban-regex @@ -29,24 +29,23 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko" __copyright__ = "Copyright (c) 2004-2008 Cyril Jaquier, 2012-2013 Yaroslav Halchenko" __license__ = "GPL" -import getopt, sys, time, logging, os, urllib - -# Inserts our own modules path first in the list -# fix for bug #343821 -try: - from common.version import version -except ImportError, e: - sys.path.insert(1, "/usr/share/fail2ban") - from common.version import version - +import getopt, sys, time, logging, os, locale, shlex, urllib from optparse import OptionParser, Option -from client.configparserinc import SafeConfigParserWithIncludes from ConfigParser import NoOptionError, NoSectionError, MissingSectionHeaderError -from server.filter import Filter -from server.failregex import RegexException -from testcases.utils import FormatterWithTraceBack +try: + from systemd import journal + from fail2ban.server.filtersystemd import FilterSystemd +except ImportError: + journal = None + +from fail2ban.version import version +from fail2ban.client.filterreader import FilterReader +from fail2ban.server.filter import Filter +from fail2ban.server.failregex import RegexException + +from fail2ban.tests.utils import FormatterWithTraceBack # Gets the instance of the logger. logSys = logging.getLogger("fail2ban") @@ -72,6 +71,24 @@ def pprint_list(l, header=None): s = '' print s + "| " + "\n| ".join(l) + '\n`-' +def file_lines_gen(hdlr): + for line in hdlr: + try: + line = line.decode(fail2banRegex.encoding, 'strict') + except UnicodeDecodeError: + if sys.version_info >= (3,): # Python 3 must be decoded + line = line.decode(fail2banRegex.encoding, 'ignore') + yield line + +def journal_lines_gen(myjournal): + while True: + try: + entry = myjournal.get_next() + except OSError: + continue + if not entry: + break + yield FilterSystemd.formatJournalEntry(entry) def get_opt_parser(): # use module docstring for help output @@ -81,6 +98,7 @@ def get_opt_parser(): LOG: string a string representing a log line filename path to a log file (/var/log/auth.log) + "systemd-journal" search systemd journal (systemd-python required) REGEX: string a string representing a 'failregex' @@ -102,9 +120,18 @@ Report bugs to https://github.com/fail2ban/fail2ban/issues version="%prog " + version) p.add_options([ + Option("-d", "--datepattern", + help="set custom pattern used to match date/times"), + Option("-e", "--encoding", + help="File encoding. Default: system locale"), + Option("-L", "--maxlines", type=int, default=0, + help="maxlines for multi-line regex"), + Option("-m", "--journalmatch", + help="journalctl style matches overriding filter file. " + "\"systemd-journal\" only"), Option('-l', "--log-level", type="choice", dest="log_level", - choices=('heavydebug', 'debug', 'info', 'warning', 'error', 'fatal'), + choices=('heavydebug', 'debug', 'info', 'notice', 'warning', 'error', 'critical'), default=None, help="Log level for the Fail2Ban logger to use"), Option("-v", "--verbose", action='store_true', @@ -177,8 +204,6 @@ class LineStats(object): class Fail2banRegex(object): - CONFIG_DEFAULTS = {'configpath' : "/etc/fail2ban/"} - def __init__(self, opts): self._verbose = opts.verbose self._debuggex = opts.debuggex @@ -187,34 +212,80 @@ class Fail2banRegex(object): self._print_no_ignored = opts.print_no_ignored self._print_all_missed = opts.print_all_missed self._print_all_ignored = opts.print_all_ignored + self._maxlines_set = False # so we allow to override maxlines in cmdline + self._datepattern_set = False + self._journalmatch = None self._filter = Filter(None) self._ignoreregex = list() self._failregex = list() self._line_stats = LineStats() + if opts.maxlines: + self.setMaxLines(opts.maxlines) + if opts.journalmatch is not None: + self.setJournalMatch(opts.journalmatch.split()) + if opts.datepattern: + self.setDatePattern(opts.datepattern) + if opts.encoding: + self.encoding = opts.encoding + else: + self.encoding = locale.getpreferredencoding() + + + + def setDatePattern(self, pattern): + if not self._datepattern_set: + self._filter.setDatePattern(pattern) + self._datepattern_set = True + if pattern is not None: + print "Use datepattern : %s" % ( + self._filter.getDatePattern()[1], ) + + def setMaxLines(self, v): + if not self._maxlines_set: + self._filter.setMaxLines(int(v)) + self._maxlines_set = True + print "Use maxlines : %d" % self._filter.getMaxLines() + + def setJournalMatch(self, v): + if self._journalmatch is None: + self._journalmatch = v def readRegex(self, value, regextype): assert(regextype in ('fail', 'ignore')) regex = regextype + 'regex' if os.path.isfile(value): - reader = SafeConfigParserWithIncludes(defaults=self.CONFIG_DEFAULTS) - try: - reader.read(value) - print "Use %11s file : %s" % (regex, value) - # TODO: reuse functionality in client + print "Use %11s file : %s" % (regex, value) + reader = FilterReader(value, 'fail2ban-regex-jail', {}) + reader.setBaseDir(None) + + if reader.readexplicit(): + reader.getOptions(None) + readercommands = reader.convert() regex_values = [ - RegexStat(m) - for m in reader.get("Definition", regex).split('\n') - if m != ""] - except NoSectionError: - print "No [Definition] section in %s" % value - return False - except NoOptionError: - print "No %s option in %s" % (regex, value) - return False - except MissingSectionHeaderError: - print "No section headers in %s" % value + RegexStat(m[3]) + for m in filter( + lambda x: x[0] == 'set' and x[2] == "add%sregex" % regextype, + readercommands)] + # Read out and set possible value of maxlines + for command in readercommands: + if command[2] == "maxlines": + maxlines = int(command[3]) + try: + self.setMaxLines(maxlines) + except ValueError: + print "ERROR: Invalid value for maxlines (%(maxlines)r) " \ + "read from %(value)s" % locals() + return False + elif command[2] == 'addjournalmatch': + journalmatch = command[3] + self.setJournalMatch(shlex.split(journalmatch)) + elif command[2] == 'datepattern': + datepattern = command[3] + self.setDatePattern(datepattern) + else: + print "ERROR: failed to read %s" % value return False else: print "Use %11s line : %s" % (regex, shortstr(value)) @@ -230,7 +301,7 @@ class Fail2banRegex(object): def testIgnoreRegex(self, line): found = False try: - ret = self._filter.ignoreLine(line) + ret = self._filter.ignoreLine([(line, "", "")]) if ret is not None: found = True regex = self._ignoreregex[ret].inc() @@ -239,9 +310,11 @@ class Fail2banRegex(object): return False return found - def testRegex(self, line): + def testRegex(self, line, date=None): + orgLineBuffer = self._filter._Filter__lineBuffer + fullBuffer = len(orgLineBuffer) >= self._filter.getMaxLines() try: - line, ret = self._filter.processLine(line, checkAllRegex=True) + line, ret = self._filter.processLine(line, date, checkAllRegex=True) for match in ret: # Append True/False flag depending if line was matched by # more than one regex @@ -255,17 +328,34 @@ class Fail2banRegex(object): except IndexError: print "Sorry, but no found in regex" return False + for bufLine in orgLineBuffer[int(fullBuffer):]: + if bufLine not in self._filter._Filter__lineBuffer: + try: + self._line_stats.missed_lines.pop( + self._line_stats.missed_lines.index("".join(bufLine))) + self._line_stats.missed_lines_timeextracted.pop( + self._line_stats.missed_lines_timeextracted.index( + "".join(bufLine[::2]))) + except ValueError: + pass + else: + self._line_stats.matched += 1 return line, ret - def process(self, test_lines): for line_no, line in enumerate(test_lines): - if line.startswith('#') or not line.strip(): - # skip comment and empty lines - continue - is_ignored = fail2banRegex.testIgnoreRegex(line) - line_datetimestripped, ret = fail2banRegex.testRegex(line) + if isinstance(line, tuple): + line_datetimestripped, ret = fail2banRegex.testRegex( + line[0], line[1]) + line = "".join(line[0]) + else: + line = line.rstrip('\r\n') + if line.startswith('#') or not line: + # skip comment and empty lines + continue + line_datetimestripped, ret = fail2banRegex.testRegex(line) + is_ignored = fail2banRegex.testIgnoreRegex(line_datetimestripped) if is_ignored: self._line_stats.ignored += 1 @@ -284,7 +374,7 @@ class Fail2banRegex(object): self._line_stats.missed_lines_timeextracted.append(line_datetimestripped) self._line_stats.tested += 1 - if line_no % 10 == 0: + if line_no % 10 == 0 and self._filter.dateDetector is not None: self._filter.dateDetector.sortTemplate() @@ -339,7 +429,7 @@ class Fail2banRegex(object): " %s %s%s" % ( ip[1], timeString, - ip[3] and " (multiple regex matched)" or "")) + ip[-1] and " (multiple regex matched)" or "")) print "\n%s: %d total" % (title, total) pprint_list(out, " #) [# of hits] regular expression") @@ -350,12 +440,14 @@ class Fail2banRegex(object): _ = print_failregexes("Ignoreregex", self._ignoreregex) - print "\nDate template hits:" - out = [] - for template in self._filter.dateDetector.getTemplates(): - if self._verbose or template.getHits(): - out.append("[%d] %s" % (template.getHits(), template.getName())) - pprint_list(out, "[# of hits] date format") + if self._filter.dateDetector is not None: + print "\nDate template hits:" + out = [] + for template in self._filter.dateDetector.templates: + if self._verbose or template.hits: + out.append("[%d] %s" % ( + template.hits, template.name)) + pprint_list(out, "[# of hits] date format") print "\nLines: %s" % self._line_stats @@ -380,6 +472,11 @@ if __name__ == "__main__": parser.print_help() sys.exit(-1) + print + print "Running tests" + print "=============" + print + fail2banRegex = Fail2banRegex(opts) # We need 2 or 3 parameters @@ -394,9 +491,9 @@ if __name__ == "__main__": logSys.setLevel(getattr(logging, opts.log_level.upper())) else: # pragma: no cover # suppress the logging but it would leave unittests' progress dots - # ticking, unless like with '-l fatal' which would be silent + # ticking, unless like with '-l critical' which would be silent # unless error occurs - logSys.setLevel(getattr(logging, 'FATAL')) + logSys.setLevel(getattr(logging, 'CRITICAL')) # Add the default logging handler stdout = logging.StreamHandler(sys.stdout) @@ -410,33 +507,48 @@ if __name__ == "__main__": Formatter = logging.Formatter # Custom log format for the verbose tests runs - if opts.verbose > 1: # pragma: no cover + if opts.verbose: # pragma: no cover stdout.setFormatter(Formatter(' %(asctime)-15s %(thread)s' + fmt)) else: # pragma: no cover # just prefix with the space stdout.setFormatter(Formatter(fmt)) logSys.addHandler(stdout) - print - print "Running tests" - print "=============" - print - cmd_log, cmd_regex = args[:2] + fail2banRegex.readRegex(cmd_regex, 'fail') or sys.exit(-1) + if len(args) == 3: fail2banRegex.readRegex(args[2], 'ignore') or sys.exit(-1) - fail2banRegex.readRegex(cmd_regex, 'fail') or sys.exit(-1) - if os.path.isfile(cmd_log): try: - hdlr = open(cmd_log) + hdlr = open(cmd_log, 'rb') print "Use log file : %s" % cmd_log - test_lines = hdlr # Iterable + print "Use encoding : %s" % fail2banRegex.encoding + test_lines = file_lines_gen(hdlr) except IOError, e: print e sys.exit(-1) + elif cmd_log == "systemd-journal": + if not journal: + print "Error: systemd library not found. Exiting..." + sys.exit(-1) + myjournal = journal.Reader(converters={'__CURSOR': lambda x: x}) + journalmatch = fail2banRegex._journalmatch + fail2banRegex.setDatePattern(None) + if journalmatch: + try: + for element in journalmatch: + if element == "+": + myjournal.add_disjunction() + else: + myjournal.add_match(element) + except ValueError: + print "Error: Invalid journalmatch: %s" % shortstr(" ".join(journalmatch)) + sys.exit(-1) + print "Use journal match : %s" % " ".join(journalmatch) + test_lines = journal_lines_gen(myjournal) else: print "Use single line : %s" % shortstr(cmd_log) test_lines = [ cmd_log ] diff --git a/fail2ban-server b/bin/fail2ban-server similarity index 93% rename from fail2ban-server rename to bin/fail2ban-server index 404a1ced..aba19ab5 100755 --- a/fail2ban-server +++ b/bin/fail2ban-server @@ -24,15 +24,8 @@ __license__ = "GPL" import getopt, sys, logging, os -# Inserts our own modules path first in the list -# fix for bug #343821 -try: - from common.version import version -except ImportError, e: - sys.path.insert(1, "/usr/share/fail2ban") - from common.version import version - -from server.server import Server +from fail2ban.version import version +from fail2ban.server.server import Server # Gets the instance of the logger. logSys = logging.getLogger("fail2ban") @@ -104,10 +97,10 @@ class Fail2banServer: if opt[0] == "-x": self.__conf["force"] = True if opt[0] in ["-h", "--help"]: - self.dispUsage() + self.dispUsage() sys.exit(0) if opt[0] in ["-V", "--version"]: - self.dispVersion() + self.dispVersion() sys.exit(0) def start(self, argv): diff --git a/bin/fail2ban-testcases b/bin/fail2ban-testcases new file mode 100755 index 00000000..b3bddf1c --- /dev/null +++ b/bin/fail2ban-testcases @@ -0,0 +1,128 @@ +#!/usr/bin/python +# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*- +# vi: set ft=python sts=4 ts=4 sw=4 noet : +"""Script to run Fail2Ban tests battery +""" + +# This file is part of Fail2Ban. +# +# Fail2Ban is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Fail2Ban is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Fail2Ban; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +__author__ = "Cyril Jaquier" +__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2012- Yaroslav Halchenko" +__license__ = "GPL" + + +import unittest, logging, sys, time, os + +# Check if local fail2ban module exists, and use if it exists by +# modifying the path. This is such that tests can be used in dev +# environment. +if os.path.exists("fail2ban/__init__.py"): + sys.path.insert(0, ".") +from fail2ban.version import version + +from fail2ban.tests.utils import FormatterWithTraceBack, gatherTests +from fail2ban.server.mytime import MyTime + +from optparse import OptionParser, Option + +def get_opt_parser(): + # use module docstring for help output + p = OptionParser( + usage="%s [OPTIONS] [regexps]\n" % sys.argv[0] + __doc__, + version="%prog " + version) + + p.add_options([ + Option('-l', "--log-level", type="choice", + dest="log_level", + choices=('heavydebug', 'debug', 'info', 'notice', 'warning', 'error', 'critical'), + default=None, + help="Log level for the logger to use during running tests"), + Option('-n', "--no-network", action="store_true", + dest="no_network", + help="Do not run tests that require the network"), + Option("-t", "--log-traceback", action='store_true', + help="Enrich log-messages with compressed tracebacks"), + Option("--full-traceback", action='store_true', + help="Either to make the tracebacks full, not compressed (as by default)"), + + ]) + + return p + +parser = get_opt_parser() +(opts, regexps) = parser.parse_args() + +# +# Logging +# +logSys = logging.getLogger("fail2ban") + +# Numerical level of verbosity corresponding to a log "level" +verbosity = {'heavydebug': 4, + 'debug': 3, + 'info': 2, + 'notice': 2, + 'warning': 1, + 'error': 1, + 'critical': 0, + None: 1}[opts.log_level] + +if opts.log_level is not None: # pragma: no cover + # so we had explicit settings + logSys.setLevel(getattr(logging, opts.log_level.upper())) +else: # pragma: no cover + # suppress the logging but it would leave unittests' progress dots + # ticking, unless like with '-l critical' which would be silent + # unless error occurs + logSys.setLevel(getattr(logging, 'CRITICAL')) + +# Add the default logging handler +stdout = logging.StreamHandler(sys.stdout) + +fmt = ' %(message)s' + +if opts.log_traceback: + Formatter = FormatterWithTraceBack + fmt = (opts.full_traceback and ' %(tb)s' or ' %(tbc)s') + fmt +else: + Formatter = logging.Formatter + +# Custom log format for the verbose tests runs +if verbosity > 1: # pragma: no cover + stdout.setFormatter(Formatter(' %(asctime)-15s %(thread)s' + fmt)) +else: # pragma: no cover + # just prefix with the space + stdout.setFormatter(Formatter(fmt)) +logSys.addHandler(stdout) + +# +# Let know the version +# +if not opts.log_level or opts.log_level != 'critical': # pragma: no cover + print("Fail2ban %s test suite. Python %s. Please wait..." \ + % (version, str(sys.version).replace('\n', ''))) + +tests = gatherTests(regexps, opts.no_network) +# +# Run the tests +# +testRunner = unittest.TextTestRunner(verbosity=verbosity) + +tests_results = testRunner.run(tests) + +if not tests_results.wasSuccessful(): # pragma: no cover + sys.exit(1) diff --git a/client/actionreader.py b/client/actionreader.py deleted file mode 100644 index 8f60b55b..00000000 --- a/client/actionreader.py +++ /dev/null @@ -1,90 +0,0 @@ -# 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: Cyril Jaquier -# - -__author__ = "Cyril Jaquier" -__copyright__ = "Copyright (c) 2004 Cyril Jaquier" -__license__ = "GPL" - -import logging -from configreader import ConfigReader - -# Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.client.config") - -class ActionReader(ConfigReader): - - def __init__(self, action, name, **kwargs): - ConfigReader.__init__(self, **kwargs) - self.__file = action[0] - self.__cInfo = action[1] - self.__name = name - - def setFile(self, fileName): - self.__file = fileName - - def getFile(self): - return self.__file - - def setName(self, name): - self.__name = name - - def getName(self): - return self.__name - - def read(self): - return ConfigReader.read(self, "action.d/" + self.__file) - - def getOptions(self, pOpts): - opts = [["string", "actionstart", ""], - ["string", "actionstop", ""], - ["string", "actioncheck", ""], - ["string", "actionban", ""], - ["string", "actionunban", ""]] - self.__opts = ConfigReader.getOptions(self, "Definition", opts, pOpts) - - if self.has_section("Init"): - for opt in self.options("Init"): - if not self.__cInfo.has_key(opt): - self.__cInfo[opt] = self.get("Init", opt) - - def convert(self): - head = ["set", self.__name] - stream = list() - stream.append(head + ["addaction", self.__file]) - for opt in self.__opts: - if opt == "actionstart": - stream.append(head + ["actionstart", self.__file, self.__opts[opt]]) - elif opt == "actionstop": - stream.append(head + ["actionstop", self.__file, self.__opts[opt]]) - elif opt == "actioncheck": - stream.append(head + ["actioncheck", self.__file, self.__opts[opt]]) - elif opt == "actionban": - stream.append(head + ["actionban", self.__file, self.__opts[opt]]) - elif opt == "actionunban": - stream.append(head + ["actionunban", self.__file, self.__opts[opt]]) - # cInfo - if self.__cInfo: - for p in self.__cInfo: - stream.append(head + ["setcinfo", self.__file, p, self.__cInfo[p]]) - - return stream - diff --git a/client/filterreader.py b/client/filterreader.py deleted file mode 100644 index ee4fed66..00000000 --- a/client/filterreader.py +++ /dev/null @@ -1,79 +0,0 @@ -# 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: Cyril Jaquier -# - -__author__ = "Cyril Jaquier" -__copyright__ = "Copyright (c) 2004 Cyril Jaquier" -__license__ = "GPL" - -import os -import logging -from configreader import ConfigReader - -# Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.client.config") - -class FilterReader(ConfigReader): - - def __init__(self, fileName, name, **kwargs): - ConfigReader.__init__(self, **kwargs) - # Defer initialization to the set Methods - self.__file = self.__name = self.__opts = None - self.setFile(fileName) - self.setName(name) - - def setFile(self, fileName): - self.__file = fileName - self.__opts = None - - def getFile(self): - return self.__file - - def setName(self, name): - self.__name = name - - def getName(self): - return self.__name - - def read(self): - return ConfigReader.read(self, os.path.join("filter.d", self.__file)) - - def getOptions(self, pOpts): - opts = [["string", "ignoreregex", ""], - ["string", "failregex", ""], - ] - self.__opts = ConfigReader.getOptions(self, "Definition", opts, pOpts) - - def convert(self): - stream = list() - for opt in self.__opts: - if opt == "failregex": - for regex in self.__opts[opt].split('\n'): - # Do not send a command if the rule is empty. - if regex != '': - stream.append(["set", self.__name, "addfailregex", regex]) - elif opt == "ignoreregex": - for regex in self.__opts[opt].split('\n'): - # Do not send a command if the rule is empty. - if regex != '': - stream.append(["set", self.__name, "addignoreregex", regex]) - return stream - diff --git a/config/action.d/badips.py b/config/action.d/badips.py new file mode 100644 index 00000000..f9f587d8 --- /dev/null +++ b/config/action.d/badips.py @@ -0,0 +1,365 @@ +# 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. + +import sys +if sys.version_info < (2, 7): + raise ImportError("badips.py action requires Python >= 2.7") +import json +from functools import partial +import threading +import logging +if sys.version_info >= (3, ): + from urllib.request import Request, urlopen + from urllib.parse import urlencode + from urllib.error import HTTPError +else: + from urllib2 import Request, urlopen, HTTPError + from urllib import urlencode + +from fail2ban.server.actions import ActionBase +from fail2ban.version import version as f2bVersion + +class BadIPsAction(ActionBase): + """Fail2Ban action which resports bans to badips.com, and also + blacklist bad IPs listed on badips.com by using another action's + ban method. + + Parameters + ---------- + jail : Jail + The jail which the action belongs to. + name : str + Name assigned to the action. + category : str + Valid badips.com category for reporting failures. + score : int, optional + Minimum score for bad IPs. Default 3. + age : str, optional + Age of last report for bad IPs, per badips.com syntax. + Default "24h" (24 hours) + key : str, optional + Key issued by badips.com to report bans, for later retrieval + of personalised content. + banaction : str, optional + Name of banaction to use for blacklisting bad IPs. If `None`, + no blacklist of IPs will take place. + Default `None`. + bancategory : str, optional + Name of category to use for blacklisting, which can differ + from category used for reporting. e.g. may want to report + "postfix", but want to use whole "mail" category for blacklist. + Default `category`. + bankey : str, optional + Key issued by badips.com to blacklist IPs reported with the + associated key. + updateperiod : int, optional + Time in seconds between updating bad IPs blacklist. + Default 900 (15 minutes) + + Raises + ------ + ValueError + If invalid `category`, `score`, `banaction` or `updateperiod`. + """ + + _badips = "http://www.badips.com" + _Request = partial( + Request, headers={'User-Agent': "Fail2Ban %s" % f2bVersion}) + + def __init__(self, jail, name, category, score=3, age="24h", key=None, + banaction=None, bancategory=None, bankey=None, updateperiod=900): + super(BadIPsAction, self).__init__(jail, name) + + self.category = category + self.score = score + self.age = age + self.key = key + self.banaction = banaction + self.bancategory = bancategory or category + self.bankey = bankey + self.updateperiod = updateperiod + + self._bannedips = set() + # Used later for threading.Timer for updating badips + self._timer = None + + @classmethod + def getCategories(cls, incParents=False): + """Get badips.com categories. + + Returns + ------- + set + Set of categories. + + Raises + ------ + HTTPError + Any issues with badips.com request. + """ + try: + response = urlopen( + cls._Request("/".join([cls._badips, "get", "categories"]))) + except HTTPError as response: + messages = json.loads(response.read().decode('utf-8')) + self._logSys.error( + "Failed to fetch categories. badips.com response: '%s'", + messages['err']) + raise + else: + categories = json.loads(response.read().decode('utf-8'))['categories'] + categories_names = set( + value['Name'] for value in categories) + if incParents: + categories_names.update(set( + value['Parent'] for value in categories + if "Parent" in value)) + return categories_names + + @classmethod + def getList(cls, category, score, age, key=None): + """Get badips.com list of bad IPs. + + Parameters + ---------- + category : str + Valid badips.com category. + score : int + Minimum score for bad IPs. + age : str + Age of last report for bad IPs, per badips.com syntax. + key : str, optional + Key issued by badips.com to fetch IPs reported with the + associated key. + + Returns + ------- + set + Set of bad IPs. + + Raises + ------ + HTTPError + Any issues with badips.com request. + """ + try: + url = "?".join([ + "/".join([cls._badips, "get", "list", category, str(score)]), + urlencode({'age': age})]) + if key: + url = "&".join([url, urlencode({"key", key})]) + response = urlopen(cls._Request(url)) + except HTTPError as response: + messages = json.loads(response.read().decode('utf-8')) + self._logSys.error( + "Failed to fetch bad IP list. badips.com response: '%s'", + messages['err']) + raise + else: + return set(response.read().decode('utf-8').split()) + + @property + def category(self): + """badips.com category for reporting IPs. + """ + return self._category + + @category.setter + def category(self, category): + if category not in self.getCategories(): + self._logSys.error("Category name '%s' not valid. " + "see badips.com for list of valid categories", + category) + raise ValueError("Invalid category: %s" % category) + self._category = category + + @property + def bancategory(self): + """badips.com bancategory for fetching IPs. + """ + return self._bancategory + + @bancategory.setter + def bancategory(self, bancategory): + if bancategory not in self.getCategories(incParents=True): + self._logSys.error("Category name '%s' not valid. " + "see badips.com for list of valid categories", + bancategory) + raise ValueError("Invalid bancategory: %s" % bancategory) + self._bancategory = bancategory + + @property + def score(self): + """badips.com minimum score for fetching IPs. + """ + return self._score + + @score.setter + def score(self, score): + score = int(score) + if 0 <= score <= 5: + self._score = score + else: + raise ValueError("Score must be 0-5") + + @property + def banaction(self): + """Jail action to use for banning/unbanning. + """ + return self._banaction + + @banaction.setter + def banaction(self, banaction): + if banaction is not None and banaction not in self._jail.actions: + self._logSys.error("Action name '%s' not in jail '%s'", + banaction, self._jail.name) + raise ValueError("Invalid banaction") + self._banaction = banaction + + @property + def updateperiod(self): + """Period in seconds between banned bad IPs will be updated. + """ + return self._updateperiod + + @updateperiod.setter + def updateperiod(self, updateperiod): + updateperiod = int(updateperiod) + if updateperiod > 0: + self._updateperiod = updateperiod + else: + raise ValueError("Update period must be integer greater than 0") + + def _banIPs(self, ips): + for ip in ips: + try: + self._jail.actions[self.banaction].ban({ + 'ip': ip, + 'failures': 0, + 'matches': "", + 'ipmatches': "", + 'ipjailmatches': "", + }) + except Exception as e: + self._logSys.error( + "Error banning IP %s for jail '%s' with action '%s': %s", + ip, self._jail.name, self.banaction, e, + exc_info=self._logSys.getEffectiveLevel<=logging.DEBUG) + else: + self._bannedips.add(ip) + self._logSys.info( + "Banned IP %s for jail '%s' with action '%s'", + ip, self._jail.name, self.banaction) + + def _unbanIPs(self, ips): + for ip in ips: + try: + self._jail.actions[self.banaction].unban({ + 'ip': ip, + 'failures': 0, + 'matches': "", + 'ipmatches': "", + 'ipjailmatches': "", + }) + except Exception as e: + self._logSys.info( + "Error unbanning IP %s for jail '%s' with action '%s': %s", + ip, self._jail.name, self.banaction, e, + exc_info=self._logSys.getEffectiveLevel<=logging.DEBUG) + else: + self._logSys.info( + "Unbanned IP %s for jail '%s' with action '%s'", + ip, self._jail.name, self.banaction) + finally: + self._bannedips.remove(ip) + + def start(self): + """If `banaction` set, blacklists bad IPs. + """ + if self.banaction is not None: + self.update() + + def update(self): + """If `banaction` set, updates blacklisted IPs. + + Queries badips.com for list of bad IPs, removing IPs from the + blacklist if no longer present, and adds new bad IPs to the + blacklist. + """ + if self.banaction is not None: + if self._timer: + self._timer.cancel() + self._timer = None + + try: + ips = self.getList( + self.bancategory, self.score, self.age, self.bankey) + # Remove old IPs no longer listed + self._unbanIPs(self._bannedips - ips) + # Add new IPs which are now listed + self._banIPs(ips - self._bannedips) + + self._logSys.info( + "Updated IPs for jail '%s'. Update again in %i seconds", + self._jail.name, self.updateperiod) + finally: + self._timer = threading.Timer(self.updateperiod, self.update) + self._timer.start() + + def stop(self): + """If `banaction` set, clears blacklisted IPs. + """ + if self.banaction is not None: + if self._timer: + self._timer.cancel() + self._timer = None + self._unbanIPs(self._bannedips.copy()) + + def ban(self, aInfo): + """Reports banned IP to badips.com. + + Parameters + ---------- + aInfo : dict + Dictionary which includes information in relation to + the ban. + + Raises + ------ + HTTPError + Any issues with badips.com request. + """ + try: + url = "/".join([self._badips, "add", self.category, aInfo['ip']]) + if self.key: + url = "?".join([url, urlencode({"key", self.key})]) + response = urlopen(self._Request(url)) + except HTTPError as response: + messages = json.loads(response.read().decode('utf-8')) + self._logSys.error( + "Response from badips.com report: '%s'", + messages['err']) + raise + else: + messages = json.loads(response.read().decode('utf-8')) + self._logSys.info( + "Response from badips.com report: '%s'", + messages['suc']) + +Action = BadIPsAction diff --git a/config/action.d/firewallcmd-new.conf b/config/action.d/firewallcmd-new.conf index bae72ca2..62887967 100644 --- a/config/action.d/firewallcmd-new.conf +++ b/config/action.d/firewallcmd-new.conf @@ -8,19 +8,19 @@ before = iptables-blocktype.conf [Definition] -actionstart = firewall-cmd --direct --add-chain ipv4 filter fail2ban- - firewall-cmd --direct --add-rule ipv4 filter fail2ban- 1000 -j RETURN - firewall-cmd --direct --add-rule ipv4 filter 0 -m state --state NEW -p --dport -j fail2ban- +actionstart = firewall-cmd --direct --add-chain ipv4 filter f2b- + firewall-cmd --direct --add-rule ipv4 filter f2b- 1000 -j RETURN + firewall-cmd --direct --add-rule ipv4 filter 0 -m state --state NEW -p --dport -j f2b- -actionstop = firewall-cmd --direct --remove-rule ipv4 filter 0 -m state --state NEW -p --dport -j fail2ban- - firewall-cmd --direct --remove-rules ipv4 filter fail2ban- - firewall-cmd --direct --remove-chain ipv4 filter fail2ban- +actionstop = firewall-cmd --direct --remove-rule ipv4 filter 0 -m state --state NEW -p --dport -j f2b- + firewall-cmd --direct --remove-rules ipv4 filter f2b- + firewall-cmd --direct --remove-chain ipv4 filter f2b- -actioncheck = firewall-cmd --direct --get-chains ipv4 filter | grep -q '^fail2ban-$' +actioncheck = firewall-cmd --direct --get-chains ipv4 filter | grep -q 'f2b-$' -actionban = firewall-cmd --direct --add-rule ipv4 filter fail2ban- 0 -s -j +actionban = firewall-cmd --direct --add-rule ipv4 filter f2b- 0 -s -j -actionunban = firewall-cmd --direct --remove-rule ipv4 filter fail2ban- 0 -s -j +actionunban = firewall-cmd --direct --remove-rule ipv4 filter f2b- 0 -s -j [Init] diff --git a/config/action.d/iptables-allports.conf b/config/action.d/iptables-allports.conf index 91d40711..480badc7 100644 --- a/config/action.d/iptables-allports.conf +++ b/config/action.d/iptables-allports.conf @@ -17,23 +17,23 @@ before = iptables-blocktype.conf # Notes.: command executed once at the start of Fail2Ban. # Values: CMD # -actionstart = iptables -N fail2ban- - iptables -A fail2ban- -j RETURN - iptables -I -p -j fail2ban- +actionstart = iptables -N f2b- + iptables -A f2b- -j RETURN + iptables -I -p -j f2b- # Option: actionstop # Notes.: command executed once at the end of Fail2Ban # Values: CMD # -actionstop = iptables -D -p -j fail2ban- - iptables -F fail2ban- - iptables -X fail2ban- +actionstop = iptables -D -p -j f2b- + iptables -F f2b- + iptables -X f2b- # Option: actioncheck # Notes.: command executed once before each actionban command # Values: CMD # -actioncheck = iptables -n -L | grep -q 'fail2ban-[ \t]' +actioncheck = iptables -n -L | grep -q 'f2b-[ \t]' # Option: actionban # Notes.: command executed when banning an IP. Take care that the @@ -41,7 +41,7 @@ actioncheck = iptables -n -L | grep -q 'fail2ban-[ \t]' # Tags: See jail.conf(5) man page # Values: CMD # -actionban = iptables -I fail2ban- 1 -s -j +actionban = iptables -I f2b- 1 -s -j # Option: actionunban # Notes.: command executed when unbanning an IP. Take care that the @@ -49,7 +49,7 @@ actionban = iptables -I fail2ban- 1 -s -j # Tags: See jail.conf(5) man page # Values: CMD # -actionunban = iptables -D fail2ban- -s -j +actionunban = iptables -D f2b- -s -j [Init] diff --git a/config/action.d/iptables-ipset-proto4.conf b/config/action.d/iptables-ipset-proto4.conf index 9a445303..fc03c68c 100644 --- a/config/action.d/iptables-ipset-proto4.conf +++ b/config/action.d/iptables-ipset-proto4.conf @@ -27,16 +27,16 @@ before = iptables-blocktype.conf # Notes.: command executed once at the start of Fail2Ban. # Values: CMD # -actionstart = ipset --create fail2ban- iphash - iptables -I INPUT -p -m multiport --dports -m set --match-set fail2ban- src -j +actionstart = ipset --create f2b- iphash + iptables -I INPUT -p -m multiport --dports -m set --match-set f2b- src -j # Option: actionstop # Notes.: command executed once at the end of Fail2Ban # Values: CMD # -actionstop = iptables -D INPUT -p -m multiport --dports -m set --match-set fail2ban- src -j - ipset --flush fail2ban- - ipset --destroy fail2ban- +actionstop = iptables -D INPUT -p -m multiport --dports -m set --match-set f2b- src -j + ipset --flush f2b- + ipset --destroy f2b- # Option: actionban # Notes.: command executed when banning an IP. Take care that the @@ -44,7 +44,7 @@ actionstop = iptables -D INPUT -p -m multiport --dports -m set # Tags: See jail.conf(5) man page # Values: CMD # -actionban = ipset --test fail2ban- || ipset --add fail2ban- +actionban = ipset --test f2b- || ipset --add f2b- # Option: actionunban # Notes.: command executed when unbanning an IP. Take care that the @@ -52,7 +52,7 @@ actionban = ipset --test fail2ban- || ipset --add fail2ban- && ipset --del fail2ban- +actionunban = ipset --test f2b- && ipset --del f2b- [Init] diff --git a/config/action.d/iptables-ipset-proto6-allports.conf b/config/action.d/iptables-ipset-proto6-allports.conf index 933926e3..72fba9cd 100644 --- a/config/action.d/iptables-ipset-proto6-allports.conf +++ b/config/action.d/iptables-ipset-proto6-allports.conf @@ -24,16 +24,16 @@ before = iptables-blocktype.conf # Notes.: command executed once at the start of Fail2Ban. # Values: CMD # -actionstart = ipset create fail2ban- hash:ip timeout - iptables -I INPUT -m set --match-set fail2ban- src -j +actionstart = ipset create f2b- hash:ip timeout + iptables -I INPUT -m set --match-set f2b- src -j # Option: actionstop # Notes.: command executed once at the end of Fail2Ban # Values: CMD # -actionstop = iptables -D INPUT -m set --match-set fail2ban- src -j - ipset flush fail2ban- - ipset destroy fail2ban- +actionstop = iptables -D INPUT -m set --match-set f2b- src -j + ipset flush f2b- + ipset destroy f2b- # Option: actionban # Notes.: command executed when banning an IP. Take care that the @@ -41,7 +41,7 @@ actionstop = iptables -D INPUT -m set --match-set fail2ban- src -j timeout -exist +actionban = ipset add f2b- timeout -exist # Option: actionunban # Notes.: command executed when unbanning an IP. Take care that the @@ -49,7 +49,7 @@ actionban = ipset add fail2ban- timeout -exist # Tags: See jail.conf(5) man page # Values: CMD # -actionunban = ipset del fail2ban- -exist +actionunban = ipset del f2b- -exist [Init] diff --git a/config/action.d/iptables-ipset-proto6.conf b/config/action.d/iptables-ipset-proto6.conf index 4dfb1a62..5d848110 100644 --- a/config/action.d/iptables-ipset-proto6.conf +++ b/config/action.d/iptables-ipset-proto6.conf @@ -24,16 +24,16 @@ before = iptables-blocktype.conf # Notes.: command executed once at the start of Fail2Ban. # Values: CMD # -actionstart = ipset create fail2ban- hash:ip timeout - iptables -I INPUT -p -m multiport --dports -m set --match-set fail2ban- src -j +actionstart = ipset create f2b- hash:ip timeout + iptables -I INPUT -p -m multiport --dports -m set --match-set f2b- src -j # Option: actionstop # Notes.: command executed once at the end of Fail2Ban # Values: CMD # -actionstop = iptables -D INPUT -p -m multiport --dports -m set --match-set fail2ban- src -j - ipset flush fail2ban- - ipset destroy fail2ban- +actionstop = iptables -D INPUT -p -m multiport --dports -m set --match-set f2b- src -j + ipset flush f2b- + ipset destroy f2b- # Option: actionban # Notes.: command executed when banning an IP. Take care that the @@ -41,7 +41,7 @@ actionstop = iptables -D INPUT -p -m multiport --dports -m set # Tags: See jail.conf(5) man page # Values: CMD # -actionban = ipset add fail2ban- timeout -exist +actionban = ipset add f2b- timeout -exist # Option: actionunban # Notes.: command executed when unbanning an IP. Take care that the @@ -49,7 +49,7 @@ actionban = ipset add fail2ban- timeout -exist # Tags: See jail.conf(5) man page # Values: CMD # -actionunban = ipset del fail2ban- -exist +actionunban = ipset del f2b- -exist [Init] diff --git a/config/action.d/iptables-multiport-log.conf b/config/action.d/iptables-multiport-log.conf index 6084cb6c..5a611033 100644 --- a/config/action.d/iptables-multiport-log.conf +++ b/config/action.d/iptables-multiport-log.conf @@ -3,9 +3,9 @@ # Author: Guido Bozzetto # Modified: Cyril Jaquier # -# make "fail2ban-" chain to match drop IP -# make "fail2ban--log" chain to log and drop -# insert a jump to fail2ban- from -I if proto/port match +# make "f2b-" chain to match drop IP +# make "f2b--log" chain to log and drop +# insert a jump to f2b- from -I if proto/port match # # @@ -19,28 +19,28 @@ before = iptables-blocktype.conf # Notes.: command executed once at the start of Fail2Ban. # Values: CMD # -actionstart = iptables -N fail2ban- - iptables -A fail2ban- -j RETURN - iptables -I 1 -p -m multiport --dports -j fail2ban- - iptables -N fail2ban--log - iptables -I fail2ban--log -j LOG --log-prefix "$(expr fail2ban- : '\(.\{1,23\}\)'):DROP " --log-level warning -m limit --limit 6/m --limit-burst 2 - iptables -A fail2ban--log -j +actionstart = iptables -N f2b- + iptables -A f2b- -j RETURN + iptables -I 1 -p -m multiport --dports -j f2b- + iptables -N f2b--log + iptables -I f2b--log -j LOG --log-prefix "$(expr f2b- : '\(.\{1,23\}\)'):DROP " --log-level warning -m limit --limit 6/m --limit-burst 2 + iptables -A f2b--log -j # Option: actionstop # Notes.: command executed once at the end of Fail2Ban # Values: CMD # -actionstop = iptables -D -p -m multiport --dports -j fail2ban- - iptables -F fail2ban- - iptables -F fail2ban--log - iptables -X fail2ban- - iptables -X fail2ban--log +actionstop = iptables -D -p -m multiport --dports -j f2b- + iptables -F f2b- + iptables -F f2b--log + iptables -X f2b- + iptables -X f2b--log # Option: actioncheck # Notes.: command executed once before each actionban command # Values: CMD # -actioncheck = iptables -n -L fail2ban--log >/dev/null +actioncheck = iptables -n -L f2b--log >/dev/null # Option: actionban # Notes.: command executed when banning an IP. Take care that the @@ -48,7 +48,7 @@ actioncheck = iptables -n -L fail2ban--log >/dev/null # Tags: See jail.conf(5) man page # Values: CMD # -actionban = iptables -I fail2ban- 1 -s -j fail2ban--log +actionban = iptables -I f2b- 1 -s -j f2b--log # Option: actionunban # Notes.: command executed when unbanning an IP. Take care that the @@ -56,7 +56,7 @@ actionban = iptables -I fail2ban- 1 -s -j fail2ban--log # Tags: See jail.conf(5) man page # Values: CMD # -actionunban = iptables -D fail2ban- -s -j fail2ban--log +actionunban = iptables -D f2b- -s -j f2b--log [Init] diff --git a/config/action.d/iptables-multiport.conf b/config/action.d/iptables-multiport.conf index daa31148..ab3225bc 100644 --- a/config/action.d/iptables-multiport.conf +++ b/config/action.d/iptables-multiport.conf @@ -14,23 +14,23 @@ before = iptables-blocktype.conf # Notes.: command executed once at the start of Fail2Ban. # Values: CMD # -actionstart = iptables -N fail2ban- - iptables -A fail2ban- -j RETURN - iptables -I -p -m multiport --dports -j fail2ban- +actionstart = iptables -N f2b- + iptables -A f2b- -j RETURN + iptables -I -p -m multiport --dports -j f2b- # Option: actionstop # Notes.: command executed once at the end of Fail2Ban # Values: CMD # -actionstop = iptables -D -p -m multiport --dports -j fail2ban- - iptables -F fail2ban- - iptables -X fail2ban- +actionstop = iptables -D -p -m multiport --dports -j f2b- + iptables -F f2b- + iptables -X f2b- # Option: actioncheck # Notes.: command executed once before each actionban command # Values: CMD # -actioncheck = iptables -n -L | grep -q 'fail2ban-[ \t]' +actioncheck = iptables -n -L | grep -q 'f2b-[ \t]' # Option: actionban # Notes.: command executed when banning an IP. Take care that the @@ -38,7 +38,7 @@ actioncheck = iptables -n -L | grep -q 'fail2ban-[ \t]' # Tags: See jail.conf(5) man page # Values: CMD # -actionban = iptables -I fail2ban- 1 -s -j +actionban = iptables -I f2b- 1 -s -j # Option: actionunban # Notes.: command executed when unbanning an IP. Take care that the @@ -46,7 +46,7 @@ actionban = iptables -I fail2ban- 1 -s -j # Tags: See jail.conf(5) man page # Values: CMD # -actionunban = iptables -D fail2ban- -s -j +actionunban = iptables -D f2b- -s -j [Init] diff --git a/config/action.d/iptables-new.conf b/config/action.d/iptables-new.conf index f35f387c..9a4587b1 100644 --- a/config/action.d/iptables-new.conf +++ b/config/action.d/iptables-new.conf @@ -17,23 +17,23 @@ before = iptables-blocktype.conf # Notes.: command executed once at the start of Fail2Ban. # Values: CMD # -actionstart = iptables -N fail2ban- - iptables -A fail2ban- -j RETURN - iptables -I -m state --state NEW -p --dport -j fail2ban- +actionstart = iptables -N f2b- + iptables -A f2b- -j RETURN + iptables -I -m state --state NEW -p --dport -j f2b- # Option: actionstop # Notes.: command executed once at the end of Fail2Ban # Values: CMD # -actionstop = iptables -D -m state --state NEW -p --dport -j fail2ban- - iptables -F fail2ban- - iptables -X fail2ban- +actionstop = iptables -D -m state --state NEW -p --dport -j f2b- + iptables -F f2b- + iptables -X f2b- # Option: actioncheck # Notes.: command executed once before each actionban command # Values: CMD # -actioncheck = iptables -n -L | grep -q 'fail2ban-[ \t]' +actioncheck = iptables -n -L | grep -q 'f2b-[ \t]' # Option: actionban # Notes.: command executed when banning an IP. Take care that the @@ -41,7 +41,7 @@ actioncheck = iptables -n -L | grep -q 'fail2ban-[ \t]' # Tags: See jail.conf(5) man page # Values: CMD # -actionban = iptables -I fail2ban- 1 -s -j +actionban = iptables -I f2b- 1 -s -j # Option: actionunban # Notes.: command executed when unbanning an IP. Take care that the @@ -49,7 +49,7 @@ actionban = iptables -I fail2ban- 1 -s -j # Tags: See jail.conf(5) man page # Values: CMD # -actionunban = iptables -D fail2ban- -s -j +actionunban = iptables -D f2b- -s -j [Init] diff --git a/config/action.d/iptables-xt_recent-echo.conf b/config/action.d/iptables-xt_recent-echo.conf index bc2e8971..5d309b56 100644 --- a/config/action.d/iptables-xt_recent-echo.conf +++ b/config/action.d/iptables-xt_recent-echo.conf @@ -23,30 +23,30 @@ before = iptables-blocktype.conf # iptables-persistent package). # # Explanation of the rule below: -# Check if any packets coming from an IP on the fail2ban- +# Check if any packets coming from an IP on the f2b- # list have been seen in the last 3600 seconds. If yes, update the # timestamp for this IP and drop the packet. If not, let the packet # through. # -# Fail2ban inserts blacklisted hosts into the fail2ban- list +# Fail2ban inserts blacklisted hosts into the f2b- list # and removes them from the list after some time, according to its # own rules. The 3600 second timeout is independent and acts as a # safeguard in case the fail2ban process dies unexpectedly. The # shorter of the two timeouts actually matters. -actionstart = if [ `id -u` -eq 0 ];then iptables -I INPUT -m recent --update --seconds 3600 --name fail2ban- -j ;fi +actionstart = if [ `id -u` -eq 0 ];then iptables -I INPUT -m recent --update --seconds 3600 --name f2b- -j ;fi # Option: actionstop # Notes.: command executed once at the end of Fail2Ban # Values: CMD # -actionstop = echo / > /proc/net/xt_recent/fail2ban- - if [ `id -u` -eq 0 ];then iptables -D INPUT -m recent --update --seconds 3600 --name fail2ban- -j ;fi +actionstop = echo / > /proc/net/xt_recent/f2b- + if [ `id -u` -eq 0 ];then iptables -D INPUT -m recent --update --seconds 3600 --name f2b- -j ;fi # Option: actioncheck # Notes.: command executed once before each actionban command # Values: CMD # -actioncheck = test -e /proc/net/xt_recent/fail2ban- +actioncheck = test -e /proc/net/xt_recent/f2b- # Option: actionban # Notes.: command executed when banning an IP. Take care that the @@ -54,7 +54,7 @@ actioncheck = test -e /proc/net/xt_recent/fail2ban- # Tags: See jail.conf(5) man page # Values: CMD # -actionban = echo + > /proc/net/xt_recent/fail2ban- +actionban = echo + > /proc/net/xt_recent/f2b- # Option: actionunban # Notes.: command executed when unbanning an IP. Take care that the @@ -62,7 +62,7 @@ actionban = echo + > /proc/net/xt_recent/fail2ban- # Tags: See jail.conf(5) man page # Values: CMD # -actionunban = echo - > /proc/net/xt_recent/fail2ban- +actionunban = echo - > /proc/net/xt_recent/f2b- [Init] diff --git a/config/action.d/iptables.conf b/config/action.d/iptables.conf index 370e4731..5afe4bf1 100644 --- a/config/action.d/iptables.conf +++ b/config/action.d/iptables.conf @@ -14,23 +14,23 @@ before = iptables-blocktype.conf # Notes.: command executed once at the start of Fail2Ban. # Values: CMD # -actionstart = iptables -N fail2ban- - iptables -A fail2ban- -j RETURN - iptables -I -p --dport -j fail2ban- +actionstart = iptables -N f2b- + iptables -A f2b- -j RETURN + iptables -I -p --dport -j f2b- # Option: actionstop # Notes.: command executed once at the end of Fail2Ban # Values: CMD # -actionstop = iptables -D -p --dport -j fail2ban- - iptables -F fail2ban- - iptables -X fail2ban- +actionstop = iptables -D -p --dport -j f2b- + iptables -F f2b- + iptables -X f2b- # Option: actioncheck # Notes.: command executed once before each actionban command # Values: CMD # -actioncheck = iptables -n -L | grep -q 'fail2ban-[ \t]' +actioncheck = iptables -n -L | grep -q 'f2b-[ \t]' # Option: actionban # Notes.: command executed when banning an IP. Take care that the @@ -38,7 +38,7 @@ actioncheck = iptables -n -L | grep -q 'fail2ban-[ \t]' # Tags: See jail.conf(5) man page # Values: CMD # -actionban = iptables -I fail2ban- 1 -s -j +actionban = iptables -I f2b- 1 -s -j # Option: actionunban # Notes.: command executed when unbanning an IP. Take care that the @@ -46,7 +46,7 @@ actionban = iptables -I fail2ban- 1 -s -j # Tags: See jail.conf(5) man page # Values: CMD # -actionunban = iptables -D fail2ban- -s -j +actionunban = iptables -D f2b- -s -j [Init] diff --git a/config/action.d/sendmail-common.conf b/config/action.d/sendmail-common.conf index e2820470..26dcb4c8 100644 --- a/config/action.d/sendmail-common.conf +++ b/config/action.d/sendmail-common.conf @@ -8,6 +8,56 @@ after = sendmail-common.local +[Definition] + +# Option: actionstart +# Notes.: command executed once at the start of Fail2Ban. +# Values: CMD +# +actionstart = printf %%b "Subject: [Fail2Ban] : started on `uname -n` + Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` + From: <> + To: \n + Hi,\n + The jail has been started successfully.\n + Regards,\n + Fail2Ban" | /usr/sbin/sendmail -f + +# Option: actionstop +# Notes.: command executed once at the end of Fail2Ban +# Values: CMD +# +actionstop = printf %%b "Subject: [Fail2Ban] : stopped on `uname -n` + Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` + From: <> + To: \n + Hi,\n + The jail has been stopped.\n + Regards,\n + Fail2Ban" | /usr/sbin/sendmail -f + +# 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: See jail.conf(5) man page +# Values: CMD +# +actionban = + +# Option: actionunban +# Notes.: command executed when unbanning an IP. Take care that the +# command is executed with Fail2Ban user rights. +# Tags: See jail.conf(5) man page +# Values: CMD +# +actionunban = + [Init] # Recipient mail address diff --git a/config/action.d/sendmail-whois-ipjailmatches.conf b/config/action.d/sendmail-whois-ipjailmatches.conf new file mode 100644 index 00000000..45b1f312 --- /dev/null +++ b/config/action.d/sendmail-whois-ipjailmatches.conf @@ -0,0 +1,37 @@ +# Fail2Ban configuration file +# +# Author: Cyril Jaquier +# +# + +[INCLUDES] + +before = sendmail-common.conf + +[Definition] + +# Option: actionban +# Notes.: command executed when banning an IP. Take care that the +# command is executed with Fail2Ban user rights. +# Tags: See jail.conf(5) man page +# Values: CMD +# +actionban = printf %%b "Subject: [Fail2Ban] : banned from `uname -n` + Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` + From: <> + To: \n + Hi,\n + The IP has just been banned by Fail2Ban after + attempts against .\n\n + Here are more information about :\n + `/usr/bin/whois `\n\n + Matches for with failures IP:\n + \n\n + Regards,\n + Fail2Ban" | /usr/sbin/sendmail -f + +[Init] + +# Default name of the chain +# +name = default diff --git a/config/action.d/sendmail-whois-ipmatches.conf b/config/action.d/sendmail-whois-ipmatches.conf new file mode 100644 index 00000000..8193fb04 --- /dev/null +++ b/config/action.d/sendmail-whois-ipmatches.conf @@ -0,0 +1,37 @@ +# Fail2Ban configuration file +# +# Author: Cyril Jaquier +# +# + +[INCLUDES] + +before = sendmail-common.conf + +[Definition] + +# Option: actionban +# Notes.: command executed when banning an IP. Take care that the +# command is executed with Fail2Ban user rights. +# Tags: See jail.conf(5) man page +# Values: CMD +# +actionban = printf %%b "Subject: [Fail2Ban] : banned from `uname -n` + Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` + From: <> + To: \n + Hi,\n + The IP has just been banned by Fail2Ban after + attempts against .\n\n + Here are more information about :\n + `/usr/bin/whois `\n\n + Matches with failures IP:\n + \n\n + Regards,\n + Fail2Ban" | /usr/sbin/sendmail -f + +[Init] + +# Default name of the chain +# +name = default diff --git a/config/action.d/sendmail-whois-lines.conf b/config/action.d/sendmail-whois-lines.conf index e97868bd..270373e7 100644 --- a/config/action.d/sendmail-whois-lines.conf +++ b/config/action.d/sendmail-whois-lines.conf @@ -10,38 +10,6 @@ before = sendmail-common.conf [Definition] -# Option: actionstart -# Notes.: command executed once at the start of Fail2Ban. -# Values: CMD -# -actionstart = printf %%b "Subject: [Fail2Ban] : started on `uname -n` - Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` - From: <> - To: \n - Hi,\n - The jail has been started successfully.\n - Regards,\n - Fail2Ban" | /usr/sbin/sendmail -f - -# Option: actionstop -# Notes.: command executed once at the end of Fail2Ban -# Values: CMD -# -actionstop = printf %%b "Subject: [Fail2Ban] : stopped on `uname -n` - Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` - From: <> - To: \n - Hi,\n - The jail has been stopped.\n - Regards,\n - Fail2Ban" | /usr/sbin/sendmail -f - -# 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. @@ -62,14 +30,6 @@ actionban = printf %%b "Subject: [Fail2Ban] : banned from `uname -n` Regards,\n Fail2Ban" | /usr/sbin/sendmail -f -# Option: actionunban -# Notes.: command executed when unbanning an IP. Take care that the -# command is executed with Fail2Ban user rights. -# Tags: See jail.conf(5) man page -# Values: CMD -# -actionunban = - [Init] # Default name of the chain diff --git a/config/action.d/sendmail-whois-matches.conf b/config/action.d/sendmail-whois-matches.conf new file mode 100644 index 00000000..ed664766 --- /dev/null +++ b/config/action.d/sendmail-whois-matches.conf @@ -0,0 +1,37 @@ +# Fail2Ban configuration file +# +# Author: Cyril Jaquier +# +# + +[INCLUDES] + +before = sendmail-common.conf + +[Definition] + +# Option: actionban +# Notes.: command executed when banning an IP. Take care that the +# command is executed with Fail2Ban user rights. +# Tags: See jail.conf(5) man page +# Values: CMD +# +actionban = printf %%b "Subject: [Fail2Ban] : banned from `uname -n` + Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` + From: <> + To: \n + Hi,\n + The IP has just been banned by Fail2Ban after + attempts against .\n\n + Here are more information about :\n + `/usr/bin/whois `\n\n + Matches:\n + \n\n + Regards,\n + Fail2Ban" | /usr/sbin/sendmail -f + +[Init] + +# Default name of the chain +# +name = default diff --git a/config/action.d/sendmail-whois.conf b/config/action.d/sendmail-whois.conf index e428c44d..fc601277 100644 --- a/config/action.d/sendmail-whois.conf +++ b/config/action.d/sendmail-whois.conf @@ -10,38 +10,6 @@ before = sendmail-common.conf [Definition] -# Option: actionstart -# Notes.: command executed once at the start of Fail2Ban. -# Values: CMD -# -actionstart = printf %%b "Subject: [Fail2Ban] : started on `uname -n` - Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` - From: <> - To: \n - Hi,\n - The jail has been started successfully.\n - Regards,\n - Fail2Ban" | /usr/sbin/sendmail -f - -# Option: actionstop -# Notes.: command executed once at the end of Fail2Ban -# Values: CMD -# -actionstop = printf %%b "Subject: [Fail2Ban] : stopped on `uname -n` - Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` - From: <> - To: \n - Hi,\n - The jail has been stopped.\n - Regards,\n - Fail2Ban" | /usr/sbin/sendmail -f - -# 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. @@ -60,14 +28,6 @@ actionban = printf %%b "Subject: [Fail2Ban] : banned from `uname -n` Regards,\n Fail2Ban" | /usr/sbin/sendmail -f -# Option: actionunban -# Notes.: command executed when unbanning an IP. Take care that the -# command is executed with Fail2Ban user rights. -# Tags: See jail.conf(5) man page -# Values: CMD -# -actionunban = - [Init] # Default name of the chain diff --git a/config/action.d/sendmail.conf b/config/action.d/sendmail.conf index 70f38329..46050e11 100644 --- a/config/action.d/sendmail.conf +++ b/config/action.d/sendmail.conf @@ -10,38 +10,6 @@ before = sendmail-common.conf [Definition] -# Option: actionstart -# Notes.: command executed once at the start of Fail2Ban. -# Values: CMD -# -actionstart = printf %%b "Subject: [Fail2Ban] : started on `uname -n` - Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` - From: <> - To: \n - Hi,\n - The jail has been started successfully.\n - Regards,\n - Fail2Ban" | /usr/sbin/sendmail -f - -# Option: actionstop -# Notes.: command executed once at the end of Fail2Ban -# Values: CMD -# -actionstop = printf %%b "Subject: [Fail2Ban] : stopped on `uname -n` - Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` - From: <> - To: \n - Hi,\n - The jail has been stopped.\n - Regards,\n - Fail2Ban" | /usr/sbin/sendmail -f - -# 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. @@ -58,14 +26,6 @@ actionban = printf %%b "Subject: [Fail2Ban] : banned from `uname -n` Regards,\n Fail2Ban" | /usr/sbin/sendmail -f -# Option: actionunban -# Notes.: command executed when unbanning an IP. Take care that the -# command is executed with Fail2Ban user rights. -# Tags: See jail.conf(5) man page -# Values: CMD -# -actionunban = - [Init] # Default name of the chain diff --git a/config/action.d/smtp.py b/config/action.d/smtp.py new file mode 100644 index 00000000..933e27a0 --- /dev/null +++ b/config/action.d/smtp.py @@ -0,0 +1,225 @@ +# 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. + +import sys +import socket +import smtplib +from email.mime.text import MIMEText +from email.utils import formatdate, formataddr + +from fail2ban.server.actions import ActionBase, CallingMap + +messages = {} +messages['start'] = \ +"""Hi, + +The jail %(jailname)s has been started successfully. + +Regards, +Fail2Ban""" + +messages['stop'] = \ +"""Hi, + +The jail %(jailname)s has been stopped. + +Regards, +Fail2Ban""" + +messages['ban'] = {} +messages['ban']['head'] = \ +"""Hi, + +The IP %(ip)s has just been banned for %(bantime)s seconds +by Fail2Ban after %(failures)i attempts against %(jailname)s. +""" +messages['ban']['tail'] = \ +""" +Regards, +Fail2Ban""" +messages['ban']['matches'] = \ +""" +Matches for this ban: +%(matches)s +""" +messages['ban']['ipmatches'] = \ +""" +Matches for %(ip)s: +%(ipmatches)s +""" +messages['ban']['ipjailmatches'] = \ +""" +Matches for %(ip)s for jail %(jailname)s: +%(ipjailmatches)s +""" + +class SMTPAction(ActionBase): + """Fail2Ban action which sends emails to inform on jail starting, + stopping and bans. + """ + + def __init__( + self, jail, name, host="localhost", user=None, password=None, + sendername="Fail2Ban", sender="fail2ban", dest="root", matches=None): + """Initialise action. + + Parameters + ---------- + jail : Jail + The jail which the action belongs to. + name : str + Named assigned to the action. + host : str, optional + SMTP host, of host:port format. Default host "localhost" and + port "25" + user : str, optional + Username used for authentication with SMTP server. + password : str, optional + Password used for authentication with SMTP server. + sendername : str, optional + Name to use for from address in email. Default "Fail2Ban". + sender : str, optional + Email address to use for from address in email. + Default "fail2ban". + dest : str, optional + Email addresses of intended recipient(s) in comma space ", " + delimited format. Default "root". + matches : str, optional + Type of matches to be included from ban in email. Can be one + of "matches", "ipmatches" or "ipjailmatches". Default None + (see man jail.conf.5). + """ + + super(SMTPAction, self).__init__(jail, name) + + self.host = host + #TODO: self.ssl = ssl + + self.user = user + self.password =password + + self.fromname = sendername + self.fromaddr = sender + self.toaddr = dest + + self.matches = matches + + self.message_values = CallingMap( + jailname = self._jail.name, + hostname = socket.gethostname, + bantime = self._jail.actions.getBanTime, + ) + + def _sendMessage(self, subject, text): + """Sends message based on arguments and instance's properties. + + Parameters + ---------- + subject : str + Subject of the email. + text : str + Body of the email. + + Raises + ------ + SMTPConnectionError + Error on connecting to host. + SMTPAuthenticationError + Error authenticating with SMTP server. + SMTPException + See Python `smtplib` for full list of other possible + exceptions. + """ + msg = MIMEText(text) + msg['Subject'] = subject + msg['From'] = formataddr((self.fromname, self.fromaddr)) + msg['To'] = self.toaddr + msg['Date'] = formatdate() + + smtp = smtplib.SMTP() + try: + self._logSys.debug("Connected to SMTP '%s', response: %i: %s", + self.host, *smtp.connect(self.host)) + if self.user and self.password: + smtp.login(self.user, self.password) + failed_recipients = smtp.sendmail( + self.fromaddr, self.toaddr.split(", "), msg.as_string()) + except smtplib.SMTPConnectError: + self._logSys.error("Error connecting to host '%s'", self.host) + raise + except smtplib.SMTPAuthenticationError: + self._logSys.error( + "Failed to authenticate with host '%s' user '%s'", + self.host, self.user) + raise + except smtplib.SMTPException: + self._logSys.error( + "Error sending mail to host '%s' from '%s' to '%s'", + self.host, self.fromaddr, self.toaddr) + raise + else: + if failed_recipients: + self._logSys.warning( + "Email to '%s' failed to following recipients: %r", + self.toaddr, failed_recipients) + self._logSys.debug("Email '%s' successfully sent", subject) + finally: + try: + self._logSys.debug("Disconnected from '%s', response %i: %s", + self.host, *smtp.quit()) + except smtplib.SMTPServerDisconnected: + pass # Not connected + + def start(self): + """Sends email to recipients informing that the jail has started. + """ + self._sendMessage( + "[Fail2Ban] %(jailname)s: started on %(hostname)s" % + self.message_values, + messages['start'] % self.message_values) + + def stop(self): + """Sends email to recipients informing that the jail has stopped. + """ + self._sendMessage( + "[Fail2Ban] %(jailname)s: stopped on %(hostname)s" % + self.message_values, + messages['stop'] % self.message_values) + + def ban(self, aInfo): + """Sends email to recipients informing that ban has occurred. + + Parameters + ---------- + aInfo : dict + Dictionary which includes information in relation to + the ban. + """ + aInfo.update(self.message_values) + message = "".join([ + messages['ban']['head'], + messages['ban'].get(self.matches, ""), + messages['ban']['tail'] + ]) + self._sendMessage( + "[Fail2Ban] %(jailname)s: banned %(ip)s from %(hostname)s" % + aInfo, + message % aInfo) + +Action = SMTPAction diff --git a/config/action.d/xarf-login-attack.conf b/config/action.d/xarf-login-attack.conf new file mode 100644 index 00000000..32c611a1 --- /dev/null +++ b/config/action.d/xarf-login-attack.conf @@ -0,0 +1,124 @@ +# Fail2Ban action for sending xarf Login-Attack messages to IP owner +# +# IMPORTANT: +# +# Emailing a IP owner of abuse is a serious complain. Make sure that it is +# serious. Fail2ban developers and network owners recommend you only use this +# action for: +# * The recidive where the IP has been banned multiple times +# * Where maxretry has been set quite high, beyond the normal user typing +# password incorrectly. +# * For filters that have a low likelyhood of receiving human errors +# +# DEPENDANCIES: +# +# This requires the dig command from bind-utils +# +# This uses the https://abusix.com/contactdb.html to lookup abuse contacts. +# +# XARF is a specification for sending a formatted response +# for non-messaging based abuse including: +# +# Login-Attack, Malware-Attack, Fraud (Phishing, etc.), Info DNSBL +# +# For details see: +# https://github.com/abusix/xarf-specification +# http://www.x-arf.org/schemata.html +# +# Author: Daniel Black +# Based on complain written by Russell Odom +# +# + +[Definition] + +actionstart = + +actionstop = + +actioncheck = + +actionban = oifs=${IFS}; IFS=.;SEP_IP=( ); set -- ${SEP_IP}; ADDRESSES=$(dig +short -t txt -q $4.$3.$2.$1.abuse-contacts.abusix.org); IFS=${oifs} + IP= + FROM= + SERVICE= + FAILURES= + REPORTID=