Merge remote-tracking branch 'master' into 'ban-time-incr'

pull/716/head
sebres 2015-07-13 15:17:09 +02:00
commit 386da502ba
141 changed files with 2146 additions and 640 deletions

View File

@ -1,4 +1,11 @@
[run]
branch = True
omit = /usr*
source =
config
fail2ban
[report]
exclude_lines =
pragma: no cover
pragma: systemd no cover

View File

@ -1,24 +1,56 @@
# vim ft=yaml
# travis-ci.org definition for Fail2Ban build
# https://travis-ci.org/fail2ban/fail2ban/
language: python
python:
- "2.6"
- "2.7"
- "3.2"
- "3.3"
- "3.4"
- "pypy"
- 2.6
- 2.7
- pypy
- 3.2
- 3.3
- 3.4
- pypy3
before_install:
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then sudo apt-get update -qq; fi
- if [[ $TRAVIS_PYTHON_VERSION == 2* || $TRAVIS_PYTHON_VERSION == 'pypy' ]]; then export F2B_PY_2=true && echo "Set F2B_PY_2"; fi
- if [[ $TRAVIS_PYTHON_VERSION == 3* || $TRAVIS_PYTHON_VERSION == 'pypy3' ]]; then export F2B_PY_3=true && echo "Set F2B_PY_3"; fi
- travis_retry sudo apt-get update -qq
# Set this so sudo executes the correct python binary
# Anything not using sudo will already have the correct environment
- export VENV_BIN="$VIRTUAL_ENV/bin" && echo "VENV_BIN set to $VENV_BIN"
install:
- pip install pyinotify
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then sudo apt-get install -qq python-gamin; cp /usr/share/pyshared/gamin.py /usr/lib/pyshared/python2.7/_gamin.so $VIRTUAL_ENV/lib/python2.7/site-packages/; fi
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then cd ..; pip install -q coveralls; cd -; fi
# Install Python packages / dependencies
# coverage
- travis_retry pip install coverage
# coveralls
- travis_retry pip install coveralls
# dnspython or dnspython3
- if [[ "$F2B_PY_2" ]]; then travis_retry pip install dnspython; fi
- if [[ "$F2B_PY_3" ]]; then travis_retry pip install dnspython3; fi
# gamin - install manually (not in PyPI) - travis-ci system Python is 2.7
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then travis_retry 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
# pyinotify
- travis_retry pip install pyinotify
before_script:
# Manually execute 2to3 for now
- if [[ "$F2B_PY_3" ]]; then ./fail2ban-2to3; fi
script:
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then coverage run --rcfile=.travis_coveragerc setup.py test; else python setup.py test; fi
# test installation
- sudo python setup.py install
# Keep the legacy setup.py test approach of checking coverage for python2
- if [[ "$F2B_PY_2" ]]; then coverage run setup.py test; fi
# Coverage doesn't pick up setup.py test with python3, so run it directly
- if [[ "$F2B_PY_3" ]]; then coverage run bin/fail2ban-testcases; fi
# Use $VENV_BIN (not python) or else sudo will always run the system's python (2.7)
- sudo $VENV_BIN/pip install .
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
- coveralls
matrix:
fast_finish: true
# Might be worth looking into
#notifications:
# email: true
# irc:
# channels: "irc.freenode.org#fail2ban"
# template:
# - "%{repository}@%{branch}: %{message} (%{build_url})"
# on_success: change
# on_failure: change
# skip_join: true

View File

@ -1,7 +0,0 @@
[run]
branch = True
omit =
/usr/*
/home/travis/virtualenv/*
fail2ban/server/filtersystemd.py

View File

@ -15,3 +15,13 @@ join the [mailing list](https://lists.sourceforge.net/lists/listinfo/fail2ban-us
### You would like to contribute (new filters/actions/code/documentation)?
send a [pull request](https://github.com/fail2ban/fail2ban/pulls)
Pull requests guidelines
========================
- If there is an issue on github to be closed by the pull request, include
```Closes #ISSUE``` (where ISSUE is issue's number)
- Add a brief summary of the change to the ChangeLog file into a corresponding
section out of Fixes, New Features or Enhancements (improvements to existing
features)

112
ChangeLog
View File

@ -3,21 +3,67 @@
| _/ _` | | |/ /| '_ \/ _` | ' \
|_| \__,_|_|_/___|_.__/\__,_|_||_|
================================================================================
Fail2Ban (version 0.9.2.dev) 2014/xx/xx
================================================================================
Fail2Ban: Changelog
===================
ver. 0.9.2 (2014/xx/xx) - increment ban time
----------
ver. 0.9.3 (2015/XX/XXX) - increment ban time
-----------
- IMPORTANT incompatible changes:
* filter.d/roundcube-auth.conf
- Changed logpath to 'errors' log (was 'userlogins')
- Fixes:
* purge database will be executed now (within observer).
* database functionality extended with bad ips.
* restoring currently banned ip after service restart fixed
(now < timeofban + bantime), ignore old log failures (already banned)
* reload in interactive mode appends all the jails twice (gh-825)
* reload server/jail failed if database used (but was not changed) and
some jail active (gh-1072)
* filter.d/dovecot.conf - also match unknown user in passwd-file.
Thanks Anton Shestakov
* Fix fail2ban-regex not parsing journalmatch correctly from filter config
* filter.d/asterisk.conf - fix security log support for Asterisk 12+
* filter.d/roundcube-auth.conf
- Updated regex to work with 'errors' log (1.0.5 and 1.1.1)
- Added regex to work with 'userlogins' log
* action.d/sendmail*.conf - use LC_ALL (superseeding LC_TIME) to override
locale on systems with customized LC_ALL
* performance fix: minimizes connection overhead, close socket only at
communication end (gh-1099)
* unbanip always deletes ip from database (independent of bantime, also if
currently not banned or persistent)
- New Features:
* increment ban time (+ observer) functionality introduced.
Thanks Serg G. Brester (sebres)
* New filters:
- froxlor-auth Thanks Joern Muehlencord
- Enhancements:
* action.d/cloudflare.conf - improved documentation on how to allow
multiple CF accounts, and jail.conf got new compound action
definition action_cf_mwl to submit cloudflare report.
* Check access to socket for more detailed logging on error (gh-595)
* fail2ban-testcases man page
* filter.d/apache-badbots.conf, filter.d/nginx-botsearch.conf - add
HEAD method verb
* Revamp of Travis and coverage automated testing
ver. 0.9.2 (2015/04/29) - better-quick-now-than-later
----------
- Fixes:
* Fix ufw action commands
* infinite busy loop on _escapedTags match in substituteRecursiveTags gh-907.
Thanks TonyThompson
* port[s] typo in jail.conf/nginx-http-auth gh-913. Thanks Frederik Wagner
(fnerdwq)
* $ typo in jail.conf. Thanks Skibbi. Debian bug #767255
* grep'ing for IP in *mail-whois-lines.conf should now match also
at the begginning and EOL. Thanks Dean Lee
at the beginning and EOL. Thanks Dean Lee
* jail.conf
- php-url-fopen: separate logpath entries by newline
* failregex declared direct in jail was joined to single line (specifying of
@ -26,23 +72,67 @@ ver. 0.9.2 (2014/xx/xx) - increment ban time
details. Thanks bes.internal
* filter.d/postfix-sasl.conf - failregex is now case insensitive
* filters.d/postfix.conf - add 'Client host rejected error message' failregex
* fail2ban/__init__.py - add strptime thread safety hack-around
* recidive uses iptables-allports banaction by default now.
Avoids problems with iptables versions not understanding 'all' for
protocols and ports
* filter.d/dovecot.conf
- match pam_authenticate line from EL7
- match unknown user line from EL7
* Use use_poll=True for Python 2.7 and >=3.4 to overcome "Bad file
descriptor" msgs issue (gh-161)
* filter.d/postfix-sasl.conf - tweak failregex and add ignoreregex to ignore
system authentication issues
* fail2ban-regex reads filter file(s) completely, incl. '.local' file etc.
(gh-954)
* firewallcmd-* actions: split output into separate lines for grepping (gh-908)
* Guard unicode encode/decode issues while storing records in the database.
Fixes "binding parameter error (unsupported type)" (gh-973), thanks to kot
for reporting
* filter.d/sshd added regex for matching openSUSE ssh authentication failure
* filter.d/asterisk.conf:
- Dropped "Sending fake auth rejection" failregex since it incorrectly
targets the asterisk server itself
- match "hacking attempt detected" logs
- New Features:
* increment ban time (+ observer) functionality introduced.
Thanks Serg G. Brester (sebres)
* New interpolation feature for config readers - `%(known/parameter)s`.
- New filters:
- postfix-rbl Thanks Lee Clemens
- apache-fakegooglebot.conf Thanks Lee Clemens
- nginx-botsearch Thanks Frantisek Sumsal
- drupal-auth Thanks Lee Clemens
- New recursive embedded substitution feature added:
- `<<PREF>HOST>` becomes `<IPV4HOST>` for PREF=`IPV4`;
- `<<PREF>HOST>` becomes `1.2.3.4` for PREF=`IPV4` and IPV4HOST=`1.2.3.4`;
- New interpolation feature for config readers - `%(known/parameter)s`.
(means last known option with name `parameter`). This interpolation makes
possible to extend a stock filter or jail regexp in .local file
(opposite to simply set failregex/ignoreregex that overwrites it),
see gh-867.
- Monit config for fail2ban in /files/monit
- Monit config for fail2ban in files/monit/
- New actions:
- action.d/firewallcmd-multiport and action.d/firewallcmd-allports Thanks Donald Yandt
- action.d/sendmail-geoip-lines.conf
- action.d/nsupdate to update DNSBL. Thanks Andrew St. Jean
- New status argument for fail2ban-client -- flavor:
fail2ban-client status <jail> [flavor]
- empty or "basic" works as-is
- "cymru" additionally prints (ASN, Country RIR) per banned IP
(requires dnspython or dnspython3)
- Flush log at USR1 signal
- Enhancements:
* Enable multiport for firewallcmd-new action. Closes gh-834
* files/debian-initd migrated from the debian branch and should be
suitable for manual installations now (thanks Juan Karlo de Guzman)
* Define empty ignoreregex in filters which didn't have it to avoid
warnings (gh-934)
* action.d/{sendmail-*,xarf-login-attack}.conf - report local
timezone not UTC time/zone. Closes gh-911
* Conditionally log Ignore IP with reason (dns, ip, command). Closes gh-916
* Absorbed DNSUtils.cidr into addr2bin in filter.py, added unittests
* Added syslogsocket configuration to fail2ban.conf
* Note in the jail.conf for the recidive jail to increase dbpurgeage (gh-964)
ver. 0.9.1 (2014/10/29) - better, faster, stronger
----------

View File

@ -56,9 +56,12 @@ following (note: on Debian-based systems, the script is called
`python-coverage`)::
coverage run bin/fail2ban-testcases
coverage report
Optionally:
coverage html
Then look at htmlcov/index.html and see how much coverage your test cases
And then browse htmlcov/index.html and see how much coverage your test cases
exert over the code base. Full coverage is a good thing however it may not be
complete. Try to ensure tests cover as many independent paths through the
code.
@ -88,7 +91,7 @@ Testing can now be done inside a vagrant VM. Vagrantfile provided in
source code repository established two VMs:
- VM "secure" which can be used for testing fail2ban code.
- VM "attacker" which hcan be used to perform attack against our "secure" VM.
- VM "attacker" which can be used to perform attack against our "secure" VM.
Both VMs are sharing the 192.168.200/24 network. If you are using this network
take a look into the Vagrantfile and change the IP.

348
MANIFEST
View File

@ -9,14 +9,155 @@ RELEASE
THANKS
TODO
Vagrantfile
bin/fail2ban-client
bin/fail2ban-regex
bin/fail2ban-server
bin/fail2ban-testcases
config/action.d/apf.conf
config/action.d/badips.conf
config/action.d/badips.py
config/action.d/blocklist_de.conf
config/action.d/bsd-ipfw.conf
config/action.d/cloudflare.conf
config/action.d/complain.conf
config/action.d/dshield.conf
config/action.d/dummy.conf
config/action.d/firewallcmd-allports.conf
config/action.d/firewallcmd-ipset.conf
config/action.d/firewallcmd-multiport.conf
config/action.d/firewallcmd-new.conf
config/action.d/hostsdeny.conf
config/action.d/ipfilter.conf
config/action.d/ipfw.conf
config/action.d/iptables-allports.conf
config/action.d/iptables-common.conf
config/action.d/iptables-ipset-proto4.conf
config/action.d/iptables-ipset-proto6-allports.conf
config/action.d/iptables-ipset-proto6.conf
config/action.d/iptables-multiport-log.conf
config/action.d/iptables-multiport.conf
config/action.d/iptables-new.conf
config/action.d/iptables-xt_recent-echo.conf
config/action.d/iptables.conf
config/action.d/mail-buffered.conf
config/action.d/mail-whois-lines.conf
config/action.d/mail-whois.conf
config/action.d/mail.conf
config/action.d/mynetwatchman.conf
config/action.d/nsupdate.conf
config/action.d/nsupdate.conf
config/action.d/osx-afctl.conf
config/action.d/osx-ipfw.conf
config/action.d/pf.conf
config/action.d/route.conf
config/action.d/sendmail-buffered.conf
config/action.d/sendmail-common.conf
config/action.d/sendmail-geoip-lines.conf
config/action.d/sendmail-whois-ipjailmatches.conf
config/action.d/sendmail-whois-ipmatches.conf
config/action.d/sendmail-whois-lines.conf
config/action.d/sendmail-whois-matches.conf
config/action.d/sendmail-whois.conf
config/action.d/sendmail.conf
config/action.d/shorewall.conf
config/action.d/smtp.py
config/action.d/symbiosis-blacklist-allports.conf
config/action.d/ufw.conf
config/action.d/xarf-login-attack.conf
config/fail2ban.conf
config/filter.d/3proxy.conf
config/filter.d/apache-auth.conf
config/filter.d/apache-badbots.conf
config/filter.d/apache-botsearch.conf
config/filter.d/apache-common.conf
config/filter.d/apache-fakegooglebot.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/apache-shellshock.conf
config/filter.d/assp.conf
config/filter.d/asterisk.conf
config/filter.d/botsearch-common.conf
config/filter.d/common.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/directadmin.conf
config/filter.d/dovecot.conf
config/filter.d/dropbear.conf
config/filter.d/ejabberd-auth.conf
config/filter.d/exim-common.conf
config/filter.d/exim-spam.conf
config/filter.d/exim.conf
config/filter.d/freeswitch.conf
config/filter.d/groupoffice.conf
config/filter.d/gssftpd.conf
config/filter.d/guacamole.conf
config/filter.d/horde.conf
config/filter.d/ignorecommands
config/filter.d/ignorecommands/apache-fakegooglebot
config/filter.d/kerio.conf
config/filter.d/lighttpd-auth.conf
config/filter.d/monit.conf
config/filter.d/mysqld-auth.conf
config/filter.d/nagios.conf
config/filter.d/named-refused.conf
config/filter.d/nginx-botsearch.conf
config/filter.d/nginx-http-auth.conf
config/filter.d/nsd.conf
config/filter.d/openwebmail.conf
config/filter.d/oracleims.conf
config/filter.d/pam-generic.conf
config/filter.d/pam-generic.conf
config/filter.d/pam-generic.conf
config/filter.d/perdition.conf
config/filter.d/php-url-fopen.conf
config/filter.d/php-url-fopen.conf
config/filter.d/php-url-fopen.conf
config/filter.d/portsentry.conf
config/filter.d/postfix-rbl.conf
config/filter.d/postfix-sasl.conf
config/filter.d/postfix-sasl.conf
config/filter.d/postfix-sasl.conf
config/filter.d/postfix.conf
config/filter.d/proftpd.conf
config/filter.d/pure-ftpd.conf
config/filter.d/qmail.conf
config/filter.d/recidive.conf
config/filter.d/roundcube-auth.conf
config/filter.d/selinux-common.conf
config/filter.d/selinux-ssh.conf
config/filter.d/sendmail-auth.conf
config/filter.d/sendmail-reject.conf
config/filter.d/sendmail-spam.conf
config/filter.d/sieve.conf
config/filter.d/sogo-auth.conf
config/filter.d/solid-pop3d.conf
config/filter.d/squid.conf
config/filter.d/squirrelmail.conf
config/filter.d/sshd-ddos.conf
config/filter.d/sshd.conf
config/filter.d/stunnel.conf
config/filter.d/suhosin.conf
config/filter.d/tine20.conf
config/filter.d/uwimap-auth.conf
config/filter.d/vsftpd.conf
config/filter.d/webmin-auth.conf
config/filter.d/wuftpd.conf
config/filter.d/xinetd-fail.conf
config/jail.conf
config/paths-common.conf
config/paths-debian.conf
config/paths-fedora.conf
config/paths-freebsd.conf
config/paths-osx.conf
doc/run-rootless.txt
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/__init__.py
fail2ban/client/__init__.py
fail2ban/client/actionreader.py
fail2ban/client/beautifier.py
@ -28,6 +169,9 @@ fail2ban/client/fail2banreader.py
fail2ban/client/filterreader.py
fail2ban/client/jailreader.py
fail2ban/client/jailsreader.py
fail2ban/exceptions.py
fail2ban/helpers.py
fail2ban/protocol.py
fail2ban/server/__init__.py
fail2ban/server/action.py
fail2ban/server/actions.py
@ -64,6 +208,8 @@ fail2ban/tests/clientreadertestcase.py
fail2ban/tests/config/action.d/brokenaction.conf
fail2ban/tests/config/fail2ban.conf
fail2ban/tests/config/filter.d/simple.conf
fail2ban/tests/config/filter.d/test.conf
fail2ban/tests/config/filter.d/test.local
fail2ban/tests/config/jail.conf
fail2ban/tests/config/paths-common.conf
fail2ban/tests/config/paths-debian.conf
@ -74,6 +220,7 @@ fail2ban/tests/datedetectortestcase.py
fail2ban/tests/dummyjail.py
fail2ban/tests/failmanagertestcase.py
fail2ban/tests/files/action.d/action.py
fail2ban/tests/files/action.d/action_checkainfo.py
fail2ban/tests/files/action.d/action_errors.py
fail2ban/tests/files/action.d/action_modifyainfo.py
fail2ban/tests/files/action.d/action_noAction.py
@ -104,6 +251,7 @@ fail2ban/tests/files/logs/apache-auth
fail2ban/tests/files/logs/apache-badbots
fail2ban/tests/files/logs/apache-botscripts
fail2ban/tests/files/logs/apache-botsearch
fail2ban/tests/files/logs/apache-fakegooglebot
fail2ban/tests/files/logs/apache-modsecurity
fail2ban/tests/files/logs/apache-nohome
fail2ban/tests/files/logs/apache-noscript
@ -135,6 +283,7 @@ fail2ban/tests/files/logs/monit
fail2ban/tests/files/logs/mysqld-auth
fail2ban/tests/files/logs/nagios
fail2ban/tests/files/logs/named-refused
fail2ban/tests/files/logs/nginx-botsearch
fail2ban/tests/files/logs/nginx-http-auth
fail2ban/tests/files/logs/nsd
fail2ban/tests/files/logs/openwebmail
@ -144,6 +293,7 @@ fail2ban/tests/files/logs/perdition
fail2ban/tests/files/logs/php-url-fopen
fail2ban/tests/files/logs/portsentry
fail2ban/tests/files/logs/postfix
fail2ban/tests/files/logs/postfix-rbl
fail2ban/tests/files/logs/postfix-sasl
fail2ban/tests/files/logs/proftpd
fail2ban/tests/files/logs/pure-ftpd
@ -182,170 +332,38 @@ fail2ban/tests/samplestestcase.py
fail2ban/tests/servertestcase.py
fail2ban/tests/sockettestcase.py
fail2ban/tests/utils.py
setup.py
setup.cfg
fail2ban/__init__.py
fail2ban/exceptions.py
fail2ban/helpers.py
fail2ban/version.py
fail2ban/protocol.py
kill-server
config/action.d/apf.conf
config/action.d/badips.conf
config/action.d/badips.py
config/action.d/blocklist_de.conf
config/action.d/bsd-ipfw.conf
config/action.d/cloudflare.conf
config/action.d/complain.conf
config/action.d/dshield.conf
config/action.d/dummy.conf
config/action.d/firewallcmd-ipset.conf
config/action.d/firewallcmd-new.conf
config/action.d/hostsdeny.conf
config/action.d/ipfilter.conf
config/action.d/ipfw.conf
config/action.d/iptables-allports.conf
config/action.d/iptables-common.conf
config/action.d/iptables-ipset-proto4.conf
config/action.d/iptables-ipset-proto6-allports.conf
config/action.d/iptables-ipset-proto6.conf
config/action.d/iptables-multiport-log.conf
config/action.d/iptables-multiport.conf
config/action.d/iptables-new.conf
config/action.d/iptables-xt_recent-echo.conf
config/action.d/iptables.conf
config/action.d/mail-buffered.conf
config/action.d/mail-whois-lines.conf
config/action.d/mail-whois.conf
config/action.d/mail.conf
config/action.d/mynetwatchman.conf
config/action.d/osx-afctl.conf
config/action.d/osx-ipfw.conf
config/action.d/pf.conf
config/action.d/route.conf
config/action.d/sendmail-buffered.conf
config/action.d/sendmail-common.conf
config/action.d/sendmail-whois-ipjailmatches.conf
config/action.d/sendmail-whois-ipmatches.conf
config/action.d/sendmail-whois-lines.conf
config/action.d/sendmail-whois-matches.conf
config/action.d/sendmail-whois.conf
config/action.d/sendmail.conf
config/action.d/shorewall.conf
config/action.d/smtp.py
config/action.d/symbiosis-blacklist-allports.conf
config/action.d/ufw.conf
config/action.d/xarf-login-attack.conf
config/fail2ban.conf
config/filter.d/3proxy.conf
config/filter.d/apache-auth.conf
config/filter.d/apache-badbots.conf
config/filter.d/apache-botsearch.conf
config/filter.d/apache-common.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/apache-shellshock.conf
config/filter.d/assp.conf
config/filter.d/asterisk.conf
config/filter.d/common.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/directadmin.conf
config/filter.d/dovecot.conf
config/filter.d/dropbear.conf
config/filter.d/ejabberd-auth.conf
config/filter.d/exim-common.conf
config/filter.d/exim-spam.conf
config/filter.d/exim.conf
config/filter.d/freeswitch.conf
config/filter.d/groupoffice.conf
config/filter.d/gssftpd.conf
config/filter.d/guacamole.conf
config/filter.d/horde.conf
config/filter.d/kerio.conf
config/filter.d/lighttpd-auth.conf
config/filter.d/monit.conf
config/filter.d/mysqld-auth.conf
config/filter.d/nagios.conf
config/filter.d/named-refused.conf
config/filter.d/nginx-http-auth.conf
config/filter.d/nsd.conf
config/filter.d/openwebmail.conf
config/filter.d/oracleims.conf
config/filter.d/pam-generic.conf
config/filter.d/pam-generic.conf
config/filter.d/pam-generic.conf
config/filter.d/perdition.conf
config/filter.d/php-url-fopen.conf
config/filter.d/php-url-fopen.conf
config/filter.d/php-url-fopen.conf
config/filter.d/portsentry.conf
config/filter.d/postfix-sasl.conf
config/filter.d/postfix-sasl.conf
config/filter.d/postfix-sasl.conf
config/filter.d/postfix.conf
config/filter.d/proftpd.conf
config/filter.d/pure-ftpd.conf
config/filter.d/qmail.conf
config/filter.d/recidive.conf
config/filter.d/roundcube-auth.conf
config/filter.d/selinux-common.conf
config/filter.d/selinux-ssh.conf
config/filter.d/sendmail-auth.conf
config/filter.d/sendmail-reject.conf
config/filter.d/sendmail-spam.conf
config/filter.d/sieve.conf
config/filter.d/sogo-auth.conf
config/filter.d/solid-pop3d.conf
config/filter.d/squid.conf
config/filter.d/squirrelmail.conf
config/filter.d/sshd-ddos.conf
config/filter.d/sshd.conf
config/filter.d/stunnel.conf
config/filter.d/suhosin.conf
config/filter.d/tine20.conf
config/filter.d/uwimap-auth.conf
config/filter.d/vsftpd.conf
config/filter.d/webmin-auth.conf
config/filter.d/wuftpd.conf
config/filter.d/xinetd-fail.conf
config/jail.conf
config/paths-common.conf
config/paths-debian.conf
config/paths-fedora.conf
config/paths-freebsd.conf
config/paths-osx.conf
man/fail2ban-client.1
man/fail2ban.1
man/jail.conf.5
man/fail2ban-client.h2m
man/fail2ban-server.1
man/fail2ban-server.h2m
man/fail2ban-regex.1
man/fail2ban-regex.h2m
man/generate-man
files/bash-completion
files/cacti/README
files/cacti/cacti_host_template_fail2ban.xml
files/cacti/fail2ban_stats.sh
files/debian-initd
files/gentoo-initd
files/fail2ban-logrotate
files/fail2ban-tmpfiles.conf
files/fail2ban.service
files/fail2ban.upstart
files/gen_badbots
files/gentoo-confd
files/redhat-initd
files/gentoo-initd
files/ipmasq-ZZZzzz_fail2ban.rul
files/logwatch/fail2ban
files/macosx-initd
files/monit/fail2ban
files/nagios/README
files/nagios/check_fail2ban
files/redhat-initd
files/solaris-fail2ban.xml
files/solaris-svc-fail2ban
files/suse-initd
files/fail2ban-logrotate
files/fail2ban.upstart
files/logwatch/fail2ban
files/cacti/fail2ban_stats.sh
files/cacti/cacti_host_template_fail2ban.xml
files/cacti/README
files/nagios/check_fail2ban
files/nagios/README
files/bash-completion
files/fail2ban-tmpfiles.conf
files/fail2ban.service
files/ipmasq-ZZZzzz_fail2ban.rul
files/gen_badbots
kill-server
man/fail2ban-client.1
man/fail2ban-client.h2m
man/fail2ban-regex.1
man/fail2ban-regex.h2m
man/fail2ban-server.1
man/fail2ban-server.h2m
man/fail2ban.1
man/generate-man
man/jail.conf.5
setup.cfg
setup.py

View File

@ -2,3 +2,4 @@ include ChangeLog COPYING DEVELOP FILTERS README.* THANKS TODO CONTRIBUTING* Vag
graft doc
graft files
recursive-include config *.conf *.py
recursive-include config/filter.d/ignorecommands *

View File

@ -2,7 +2,7 @@
/ _|__ _(_) |_ ) |__ __ _ _ _
| _/ _` | | |/ /| '_ \/ _` | ' \
|_| \__,_|_|_/___|_.__/\__,_|_||_|
v0.9.1.dev 2014/??/??
v0.9.2.dev0 2015/xx/xx
## Fail2Ban: ban hosts that cause multiple authentication errors
@ -33,11 +33,12 @@ Optional:
- Linux >= 2.6.13
- [gamin >= 0.0.21](http://www.gnome.org/~veillard/gamin)
- [systemd >= 204](http://www.freedesktop.org/wiki/Software/systemd)
- [dnspython](http://www.dnspython.org/)
To install, just do:
tar xvfj fail2ban-0.9.1.tar.bz2
cd fail2ban-0.9.1
tar xvfj fail2ban-0.9.2.tar.bz2
cd fail2ban-0.9.2
python setup.py install
This will install Fail2Ban into the python library directory. The executable

12
RELEASE
View File

@ -61,24 +61,24 @@ Preparation
* Which indicates that testcases/files/logs/mysqld.log has been moved or is a directory::
tar -C /tmp -jxf dist/fail2ban-0.9.2.tar.bz2
tar -C /tmp -jxf dist/fail2ban-0.9.3.tar.bz2
* clean up current direcory::
diff -rul --exclude \*.pyc . /tmp/fail2ban-0.9.2/
diff -rul --exclude \*.pyc . /tmp/fail2ban-0.9.3/
* Only differences should be files that you don't want distributed.
* Ensure the tests work from the tarball::
cd /tmp/fail2ban-0.9.2/ && export PYTHONPATH=`pwd` && bin/fail2ban-testcases
cd /tmp/fail2ban-0.9.3/ && bin/fail2ban-testcases
* Add/finalize the corresponding entry in the ChangeLog
* To generate a list of committers use e.g.::
git shortlog -sn 0.9.2.. | sed -e 's,^[ 0-9\t]*,,g' | tr '\n' '\|' | sed -e 's:|:, :g'
git shortlog -sn 0.9.3.. | 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.
@ -101,7 +101,7 @@ Preparation
* Tag the release by using a signed (and annotated) tag. Cut/paste
release ChangeLog entry as tag annotation::
git tag -s 0.9.2
git tag -s 0.9.3
Pre Release
===========
@ -197,5 +197,5 @@ Add the following to the top of the ChangeLog::
Alter the git shortlog command in the previous section to refer to the just
released version.
and adjust fail2ban/version.py to carry .dev suffix to signal
and adjust fail2ban/version.py to carry .dev0 suffix to signal
a version under development.

4
THANKS
View File

@ -6,6 +6,7 @@ the project. If you have been left off, please let us know
(preferably send a pull request on github with the "fix") and you will
be added
Aaron Brice
Adam Tkac
Adrien Clerc
ache
@ -13,6 +14,7 @@ ag4ve (Shawn)
Alasdair D. Campbell
Amir Caspi
Amy
Andrew St. Jean
Andrey G. Grozin
Andy Fragen
Arturo 'Buanzo' Busleiman
@ -39,6 +41,7 @@ Enrico Labedzki
Eugene Hopkinson (SlowRiot)
ftoppi
François Boulogne
Frantisek Sumsal
Frédéric
Georgiy Mernov
Guilhem Lettron
@ -83,6 +86,7 @@ Michael Hanselmann
Mika (mkl)
Nick Munger
onorua
Orion Poplawski
Paul Marrapese
Paul Traina
Noel Butler

View File

@ -22,8 +22,17 @@ __author__ = "Cyril Jaquier"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import sys, string, os, pickle, re, logging, signal
import getopt, time, shlex, socket
import getopt
import logging
import os
import pickle
import re
import shlex
import signal
import socket
import string
import sys
import time
from fail2ban.version import version
from fail2ban.protocol import printFormatted
@ -144,32 +153,58 @@ class Fail2banClient:
return self.__processCmd([["ping"]], False)
def __processCmd(self, cmd, showRet = True):
beautifier = Beautifier()
streamRet = True
for c in cmd:
beautifier.setInputCmd(c)
try:
client = CSocket(self.__conf["socket"])
ret = client.send(c)
if ret[0] == 0:
logSys.debug("OK : " + `ret[1]`)
client = None
try:
beautifier = Beautifier()
streamRet = True
for c in cmd:
beautifier.setInputCmd(c)
try:
if not client:
client = CSocket(self.__conf["socket"])
ret = client.send(c)
if ret[0] == 0:
logSys.debug("OK : " + `ret[1]`)
if showRet:
print beautifier.beautify(ret[1])
else:
logSys.error("NOK: " + `ret[1].args`)
if showRet:
print beautifier.beautifyError(ret[1])
streamRet = False
except socket.error:
if showRet:
print beautifier.beautify(ret[1])
else:
logSys.error("NOK: " + `ret[1].args`)
self.__logSocketError()
return False
except Exception, e:
if showRet:
print beautifier.beautifyError(ret[1])
streamRet = False
except socket.error:
if showRet:
logSys.error("Unable to contact server. Is it running?")
return False
except Exception, e:
if showRet:
logSys.error(e)
return False
logSys.error(e)
return False
finally:
if client:
client.close()
return streamRet
def __logSocketError(self):
try:
if os.access(self.__conf["socket"], os.F_OK):
# This doesn't check if path is a socket,
# but socket.error should be raised
if os.access(self.__conf["socket"], os.W_OK):
# Permissions look good, but socket.error was raised
logSys.error("Unable to contact server. Is it running?")
else:
logSys.error("Permission denied to socket: %s,"
" (you must be root)", self.__conf["socket"])
else:
logSys.error("Failed to access socket path: %s."
" Is fail2ban running?",
self.__conf["socket"])
except Exception as e:
logSys.error("Exception while checking socket access: %s",
self.__conf["socket"])
logSys.error(e)
##
# Process a command line.
#

View File

@ -29,7 +29,15 @@ __author__ = "Fail2Ban Developers"
__copyright__ = "Copyright (c) 2004-2008 Cyril Jaquier, 2012-2014 Yaroslav Halchenko"
__license__ = "GPL"
import getopt, sys, time, logging, os, locale, shlex, time, urllib
import getopt
import locale
import logging
import os
import shlex
import sys
import time
import time
import urllib
from optparse import OptionParser, Option
from ConfigParser import NoOptionError, NoSectionError, MissingSectionHeaderError
@ -220,6 +228,7 @@ class Fail2banRegex(object):
self._datepattern_set = False
self._journalmatch = None
self.share_config=dict()
self._filter = Filter(None)
self._ignoreregex = list()
self._failregex = list()
@ -260,38 +269,47 @@ class Fail2banRegex(object):
def readRegex(self, value, regextype):
assert(regextype in ('fail', 'ignore'))
regex = regextype + 'regex'
if os.path.isfile(value):
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[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)
if os.path.isfile(value) or os.path.isfile(value + '.conf'):
if os.path.basename(os.path.dirname(value)) == 'filter.d':
## within filter.d folder - use standard loading algorithm to load filter completely (with .local etc.):
basedir = os.path.dirname(os.path.dirname(value))
value = os.path.splitext(os.path.basename(value))[0]
print "Use %11s filter file : %s, basedir: %s" % (regex, value, basedir)
reader = FilterReader(value, 'fail2ban-regex-jail', {}, share_config=self.share_config, basedir=basedir)
if not reader.read():
print "ERROR: failed to load filter %s" % value
return False
else:
print "ERROR: failed to read %s" % value
return False
## foreign file - readexplicit this file and includes if possible:
print "Use %11s file : %s" % (regex, value)
reader = FilterReader(value, 'fail2ban-regex-jail', {}, share_config=self.share_config)
reader.setBaseDir(None)
if not reader.readexplicit():
print "ERROR: failed to read %s" % value
return False
reader.getOptions(None)
readercommands = reader.convert()
regex_values = [
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(journalmatch)
elif command[2] == 'datepattern':
datepattern = command[3]
self.setDatePattern(datepattern)
else:
print "Use %11s line : %s" % (regex, shortstr(value))
regex_values = [RegexStat(value)]

View File

@ -22,7 +22,9 @@ __author__ = "Cyril Jaquier"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import getopt, sys, os
import getopt
import os
import sys
from fail2ban.version import version
from fail2ban.server.server import Server

View File

@ -25,7 +25,10 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2012- Yaroslav Halchenko"
__license__ = "GPL"
import logging
import unittest, sys, time, os
import os
import sys
import time
import unittest
# 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

View File

@ -35,6 +35,7 @@ else:
from fail2ban.server.actions import ActionBase
from fail2ban.version import version as f2bVersion
class BadIPsAction(ActionBase):
"""Fail2Ban action which reports bans to badips.com, and also
blacklist bad IPs listed on badips.com by using another action's
@ -111,6 +112,8 @@ class BadIPsAction(ActionBase):
------
HTTPError
Any issues with badips.com request.
ValueError
If badips.com response didn't contain necessary information
"""
try:
response = urlopen(
@ -122,7 +125,13 @@ class BadIPsAction(ActionBase):
messages['err'])
raise
else:
categories = json.loads(response.read().decode('utf-8'))['categories']
response_json = json.loads(response.read().decode('utf-8'))
if not 'categories' in response_json:
err = "badips.com response lacked categories specification. Response was: %s" \
% (response_json,)
self._logSys.error(err)
raise ValueError(err)
categories = response_json['categories']
categories_names = set(
value['Name'] for value in categories)
if incParents:

View File

@ -38,7 +38,7 @@ actioncheck =
# Values: CMD
#
# requires an ipfw rule like "deny ip from table(1) to me"
actionban = ipfw table <table> add <ip>
actionban = e=`ipfw table <table> add <ip> 2>&1`; x=$?; [ $x -eq 0 -o "$e" = 'ipfw: setsockopt(IP_FW_TABLE_XADD): File exists' ] || { echo "$e" 1>&2; exit $x; }
# Option: actionunban
@ -47,7 +47,7 @@ actionban = ipfw table <table> add <ip>
# Tags: See jail.conf(5) man page
# Values: CMD
#
actionunban = ipfw table <table> delete <ip>
actionunban = e=`ipfw table <table> delete <ip> 2>&1`; x=$?; [ $x -eq 0 -o "$e" = 'ipfw: setsockopt(IP_FW_TABLE_XDEL): No such process' ] || { echo "$e" 1>&2; exit $x; }
[Init]
# Option: table

View File

@ -1,10 +1,14 @@
#
# Author: Mike Rushton
#
# Referenced from from http://www.normyee.net/blog/2012/02/02/adding-cloudflare-support-to-fail2ban by NORM YEE
# IMPORTANT
#
# To get your Cloudflare API key: https://www.cloudflare.com/my-account
# Please set jail.local's permission to 640 because it contains your CF API key.
#
# This action depends on curl.
# Referenced from http://www.normyee.net/blog/2012/02/02/adding-cloudflare-support-to-fail2ban by NORM YEE
#
# To get your CloudFlare API Key: https://www.cloudflare.com/a/account/my-account
[Definition]
@ -34,7 +38,8 @@ actioncheck =
# <time> unix timestamp of the ban time
# Values: CMD
#
actionban = curl https://www.cloudflare.com/api_json.html -d 'a=ban' -d 'tkn=<cftoken>' -d 'email=<cfuser>' -d 'key=<ip>'
actionban = curl -s -o /dev/null https://www.cloudflare.com/api_json.html -d 'a=ban' -d 'tkn=<cftoken>' -d 'email=<cfuser>' -d 'key=<ip>'
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights.
@ -43,13 +48,19 @@ actionban = curl https://www.cloudflare.com/api_json.html -d 'a=ban' -d 'tkn=<cf
# <time> unix timestamp of the ban time
# Values: CMD
#
actionunban = curl https://www.cloudflare.com/api_json.html -d 'a=nul' -d 'tkn=<cftoken>' -d 'email=<cfuser>' -d 'key=<ip>'
actionunban = curl -s -o /dev/null https://www.cloudflare.com/api_json.html -d 'a=nul' -d 'tkn=<cftoken>' -d 'email=<cfuser>' -d 'key=<ip>'
[Init]
# Default Cloudflare API token
cftoken =
# If you like to use this action with mailing whois lines, you could use the composite action
# action_cf_mwl predefined in jail.conf, just define in your jail:
#
# action = %(action_cf_mwl)s
# # Your CF account e-mail
# cfemail =
# # Your CF API Key
# cfapikey =
# Default Cloudflare username
cfuser =
cftoken =
cfuser =

View File

@ -8,6 +8,8 @@
before = iptables-blocktype.conf
[Definition]
actionstart = firewall-cmd --direct --add-chain ipv4 filter f2b-<name>
firewall-cmd --direct --add-rule ipv4 filter f2b-<name> 1000 -j RETURN
firewall-cmd --direct --add-rule ipv4 filter <chain> 0 -j f2b-<name>
@ -17,10 +19,9 @@ actionstop = firewall-cmd --direct --remove-rule ipv4 filter <chain> 0 -j f2b-<n
firewall-cmd --direct --remove-chain ipv4 filter f2b-<name>
# Note: uses regular expression whitespaces '\s' & end of line '$'
# Example actioncheck: firewall-cmd --direct --get-chains ipv4 filter | grep -q '\sf2b-recidive$'
# Example actioncheck: firewall-cmd --direct --get-chains ipv4 filter | sed -e 's, ,\n,g' | grep -q '^f2b-recidive$'
actioncheck = firewall-cmd --direct --get-chains ipv4 filter | grep -q '\sf2b-<name>$'
actioncheck = firewall-cmd --direct --get-chains ipv4 filter | sed -e 's, ,\n,g' | grep -q '^f2b-<name>$'
actionban = firewall-cmd --direct --add-rule ipv4 filter f2b-<name> 0 -s <ip> -j <blocktype>

View File

@ -17,10 +17,9 @@ actionstop = firewall-cmd --direct --remove-rule ipv4 filter <chain> 0 -m state
firewall-cmd --direct --remove-rules ipv4 filter f2b-<name>
firewall-cmd --direct --remove-chain ipv4 filter f2b-<name>
# Note: uses regular expression whitespaces '\s' & end of line '$'
# Example actioncheck: firewall-cmd --direct --get-chains ipv4 filter | grep -q '\sf2b-apache-modsecurity$'
# Example actioncheck: firewall-cmd --direct --get-chains ipv4 filter | sed -e 's, ,\n,g' | grep -q '^f2b-apache-modsecurity$'
actioncheck = firewall-cmd --direct --get-chains ipv4 filter | grep -q '\sf2b-<name>$'
actioncheck = firewall-cmd --direct --get-chains ipv4 filter | sed -e 's, ,\n,g' | grep -q '^f2b-<name>$'
actionban = firewall-cmd --direct --add-rule ipv4 filter f2b-<name> 0 -s <ip> -j <blocktype>
@ -59,6 +58,6 @@ protocol = tcp
# $ sudo firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 0 -m state --state NEW -p tcp -m multiport --dports 80,443 -j f2b-apache-modsecurity
# success
# actioncheck:
# $ firewall-cmd --direct --get-chains ipv4 filter f2b-apache-modsecurity | grep -q '\sf2b-apache-modsecurity$'
# $ firewall-cmd --direct --get-chains ipv4 filter f2b-apache-modsecurity | sed -e 's, ,\n,g' | grep -q '^f2b-apache-modsecurity$'
# f2b-apache-modsecurity

View File

@ -0,0 +1,114 @@
# Fail2Ban configuration file
#
# Author: Andrew St. Jean
#
# Use nsupdate to perform dynamic DNS updates on a BIND zone file.
# One may want to do this to update a local RBL with banned IP addresses.
#
# Options
#
# domain DNS domain that will appear in nsupdate add and delete
# commands.
#
# ttl The time to live (TTL) in seconds of the TXT resource
# record.
#
# rdata Data portion of the TXT resource record.
#
# nsupdatecmd Full path to the nsupdate command.
#
# keyfile Full path to TSIG key file used for authentication between
# nsupdate and BIND.
#
# Create an nsupdate.local to set at least the <domain> and <keyfile>
# options as they don't have default values.
#
# The ban and unban commands assume nsupdate will authenticate to the BIND
# server using a TSIG key. The full path to the key file must be specified
# in the <keyfile> parameter. Use this command to generate your TSIG key.
#
# dnssec-keygen -a HMAC-MD5 -b 256 -n HOST <key_name>
#
# Replace <key_name> with some meaningful name.
#
# This command will generate two files. Specify the .private file in the
# <keyfile> option. Note that the .key file must also be present in the same
# directory for nsupdate to use the key.
#
# Don't forget to add the key and appropriate allow-update or update-policy
# option to your named.conf file.
#
[Definition]
# Option: actionstart
# Notes.: command executed once at the start of Fail2Ban.
# Values: CMD
#
actionstart =
# Option: actionstop
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD
#
actionstop =
# Option: actioncheck
# Notes.: command executed once before each actionban command
# Values: CMD
#
actioncheck =
# Option: actionban
# Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: See jail.conf(5) man page
# Values: CMD
#
actionban = echo <ip> | awk -F. '{print "prereq nxrrset "$4"."$3"."$2"."$1".<domain> TXT"; print "update add "$4"."$3"."$2"."$1".<domain> <ttl> IN TXT \"<rdata>\""; print "send"}' | <nsupdatecmd> -k <keyfile>
# 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 = echo <ip> | awk -F. '{print "update delete "$4"."$3"."$2"."$1".<domain>"; print "send"}' | <nsupdatecmd> -k <keyfile>
[Init]
# Option: domain
# Notes.: DNS domain that nsupdate will update.
# Values: STRING
#
domain =
# Option: ttl
# Notes.: time to live (TTL) in seconds of TXT resource record
# added by nsupdate.
# Values: NUM
#
ttl = 60
# Option: rdata
# Notes.: data portion of the TXT resource record added by nsupdate.
# Values: STRING
#
rdata = Your IP has been banned
# Option: nsupdatecmd
# Notes.: specifies the full path to the nsupdate program that dynamically
# updates BIND zone files.
# Values: CMD
#
nsupdatecmd = /usr/bin/nsupdate
# Option: keyfile
# Notes.: specifies the full path to the file containing the
# TSIG key for communicating with BIND.
# Values: STRING
#
keyfile =

View File

@ -15,7 +15,7 @@ after = sendmail-common.local
# Values: CMD
#
actionstart = printf %%b "Subject: [Fail2Ban] <name>: started on `uname -n`
Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"`
Date: `LC_ALL=C date +"%%a, %%d %%h %%Y %%T %%z"`
From: <sendername> <<sender>>
To: <dest>\n
Hi,\n
@ -28,7 +28,7 @@ actionstart = printf %%b "Subject: [Fail2Ban] <name>: started on `uname -n`
# Values: CMD
#
actionstop = printf %%b "Subject: [Fail2Ban] <name>: stopped on `uname -n`
Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"`
Date: `LC_ALL=C date +"%%a, %%d %%h %%Y %%T %%z"`
From: <sendername> <<sender>>
To: <dest>\n
Hi,\n
@ -40,7 +40,7 @@ actionstop = printf %%b "Subject: [Fail2Ban] <name>: stopped on `uname -n`
# Notes.: command executed once before each actionban command
# Values: CMD
#
actioncheck =
actioncheck =
# Option: actionban
# Notes.: command executed when banning an IP. Take care that the
@ -48,7 +48,7 @@ actioncheck =
# Tags: See jail.conf(5) man page
# Values: CMD
#
actionban =
actionban =
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
@ -56,7 +56,7 @@ actionban =
# Tags: See jail.conf(5) man page
# Values: CMD
#
actionunban =
actionunban =
[Init]

View File

@ -0,0 +1,49 @@
# Fail2Ban configuration file
#
# Author: Viktor Szépe
#
#
[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.
# You need to install geoiplookup and the GeoLite or GeoIP databases.
# (geoip-bin and geoip-database in Debian)
# The host command comes from bind9-host package.
# Tags: See jail.conf(5) man page
# Values: CMD
#
actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n`
Date: `LC_ALL=C date +"%%a, %%d %%h %%Y %%T %%z"`
From: <sendername> <<sender>>
To: <dest>\n
Hi,\n
The IP <ip> has just been banned by Fail2Ban after
<failures> attempts against <name>.\n\n
Here is more information about <ip>:\n
http://bgp.he.net/ip/<ip>
http://www.projecthoneypot.org/ip_<ip>
http://whois.domaintools.com/<ip>\n\n
Country:`geoiplookup -f /usr/share/GeoIP/GeoIP.dat "<ip>" | cut -d':' -f2-`
AS:`geoiplookup -f /usr/share/GeoIP/GeoIPASNum.dat "<ip>" | cut -d':' -f2-`
hostname: `host -t A <ip> 2>&1`\n\n
Lines containing IP:<ip> in <logpath>\n
`grep -E '(^|[^0-9])<ip>([^0-9]|$)' <logpath>`\n\n
Regards,\n
Fail2Ban" | /usr/sbin/sendmail -f <sender> <dest>
[Init]
# Default name of the chain
#
name = default
# Path to the log files which contain relevant lines for the abuser IP
#
logpath = /dev/null

View File

@ -17,13 +17,13 @@ before = sendmail-common.conf
# Values: CMD
#
actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n`
Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"`
Date: `LC_ALL=C date +"%%a, %%d %%h %%Y %%T %%z"`
From: <sendername> <<sender>>
To: <dest>\n
Hi,\n
The IP <ip> has just been banned by Fail2Ban after
<failures> attempts against <name>.\n\n
Here are more information about <ip>:\n
Here is more information about <ip>:\n
`/usr/bin/whois <ip>`\n\n
Matches for <name> with <ipjailfailures> failures IP:<ip>\n
<ipjailmatches>\n\n

View File

@ -17,13 +17,13 @@ before = sendmail-common.conf
# Values: CMD
#
actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n`
Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"`
Date: `LC_ALL=C date +"%%a, %%d %%h %%Y %%T %%z"`
From: <sendername> <<sender>>
To: <dest>\n
Hi,\n
The IP <ip> has just been banned by Fail2Ban after
<failures> attempts against <name>.\n\n
Here are more information about <ip>:\n
Here is more information about <ip>:\n
`/usr/bin/whois <ip>`\n\n
Matches with <ipfailures> failures IP:<ip>\n
<ipmatches>\n\n

View File

@ -17,7 +17,7 @@ before = sendmail-common.conf
# Values: CMD
#
actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n`
Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"`
Date: `LC_ALL=C date +"%%a, %%d %%h %%Y %%T %%z"`
From: <sendername> <<sender>>
To: <dest>\n
Hi,\n

View File

@ -17,13 +17,13 @@ before = sendmail-common.conf
# Values: CMD
#
actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n`
Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"`
Date: `LC_ALL=C date +"%%a, %%d %%h %%Y %%T %%z"`
From: <sendername> <<sender>>
To: <dest>\n
Hi,\n
The IP <ip> has just been banned by Fail2Ban after
<failures> attempts against <name>.\n\n
Here are more information about <ip>:\n
Here is more information about <ip>:\n
`/usr/bin/whois <ip>`\n\n
Matches:\n
<matches>\n\n

View File

@ -17,7 +17,7 @@ before = sendmail-common.conf
# Values: CMD
#
actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n`
Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"`
Date: `LC_ALL=C date +"%%a, %%d %%h %%Y %%T %%z"`
From: <sendername> <<sender>>
To: <dest>\n
Hi,\n

View File

@ -17,7 +17,7 @@ before = sendmail-common.conf
# Values: CMD
#
actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n`
Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"`
Date: `LC_ALL=C date +"%%a, %%d %%h %%Y %%T %%z"`
From: <sendername> <<sender>>
To: <dest>\n
Hi,\n

View File

@ -68,6 +68,7 @@ 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.

View File

@ -13,9 +13,11 @@ actionstop =
actioncheck =
actionban = [ -n "<application>" ] && app="app <application>" ; ufw insert <insertpos> <blocktype> from <ip> to <destination> $app
actionban = [ -n "<application>" ] && app="app <application>"
ufw insert <insertpos> <blocktype> from <ip> to <destination> $app
actionunban = [ -n "<application>" ] && app="app <application>" ; ufw delete <blocktype> from <ip> to <destination> $app
actionunban = [ -n "<application>" ] && app="app <application>"
ufw delete <blocktype> from <ip> to <destination> $app
[Init]
# Option: insertpos

View File

@ -1,7 +1,7 @@
# Fail2Ban action for sending xarf Login-Attack messages to IP owner
#
# IMPORTANT:
#
# 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:
@ -46,7 +46,7 @@ actionban = oifs=${IFS}; IFS=.;SEP_IP=( <ip> ); set -- ${SEP_IP}; ADDRESSES=$(di
REPORTID=<time>@`uname -n`
TLP=<tlp>
PORT=<port>
DATE=`LC_TIME=C date -u --date=@<time> +"%%a, %%d %%h %%Y %%T +0000"`
DATE=`LC_ALL=C date --date=@<time> +"%%a, %%d %%h %%Y %%T %%z"`
if [ ! -z "$ADDRESSES" ]; then
(printf -- %%b "<header>\n<message>\n<report>\n";
date '+Note: Local timezone is %%z (%%Z)';
@ -70,7 +70,7 @@ footer = \n\n--Abuse-bfbb0f920793ac03cb8634bde14d8a1e--
report = --Abuse-bfbb0f920793ac03cb8634bde14d8a1e\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nContent-Type: text/plain; charset=utf-8; name=\"report.txt\";\n\n---\nReported-From: $FROM\nCategory: abuse\nReport-ID: $REPORTID\nReport-Type: login-attack\nService: $SERVICE\nVersion: 0.2\nUser-Agent: Fail2ban v0.9\nDate: $DATE\nSource-Type: ip-address\nSource: $IP\nPort: $PORT\nSchema-URL: http://www.x-arf.org/schema/abuse_login-attack_0.1.2.json\nAttachment: text/plain\nOccurances: $FAILURES\nTLP: $TLP\n\n\n--Abuse-bfbb0f920793ac03cb8634bde14d8a1e\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nContent-Type: text/plain; charset=utf8; name=\"logfile.log\";
# Option: Message
# Notes: This can be modified by the users
# Notes: This can be modified by the users
message = Dear Sir/Madam,\n\nWe have detected abuse from the IP address $IP, which according to abusix.com is on your network. We would appreciate if you would investigate and take action as appropriate.\n\nLog lines are given below, but please ask if you require any further information.\n\n(If you are not the correct person to contact about this please accept our apologies - your e-mail address was extracted from the whois record by an automated process.)\n\n This mail was generated by Fail2Ban in a X-ARF format! You can find more information about x-arf at http://www.x-arf.org/specification.html.\n\nThe recipient address of this report was provided by the Abuse Contact DB by abusix.com. abusix.com does not maintain the content of the database. All information which we pass out, derives from the RIR databases and is processed for ease of use. If you want to change or report non working abuse contacts please contact the appropriate RIR. If you have any further question, contact abusix.com directly via email (info@abusix.com). Information about the Abuse Contact Database can be found here: https://abusix.com/global-reporting/abuse-contact-db\nabusix.com is neither responsible nor liable for the content or accuracy of this message.\n
# Option: loglines
@ -97,7 +97,7 @@ mailargs = -f <sender>
# Option: tlp
# Notes.: Traffic light protocol defining the sharing of this information.
# http://www.trusted-introducer.org/ISTLPv11.pdf
# green is share to those involved in network security but it is not
# green is share to those involved in network security but it is not
# to be released to the public.
tlp = green

View File

@ -34,6 +34,12 @@ loglevel = INFO
#
logtarget = /var/log/fail2ban.log
# Option: syslogsocket
# Notes: Set the syslog socket file. Only used when logtarget is SYSLOG
# auto uses platform.system() to determine predefined paths
# Values: [ auto | FILE ] Default: auto
syslogsocket = auto
# Option: socket
# Notes.: Set the socket file. This is used to communicate with the daemon. Do
# not remove this file when Fail2ban runs. It will not be possible to

View File

@ -10,7 +10,7 @@
badbotscustom = EmailCollector|WebEMailExtrac|TrackBack/1\.02|sogou music spider
badbots = Atomic_Email_Hunter/4\.0|atSpider/1\.0|autoemailspider|bwh3_user_agent|China Local Browse 2\.6|ContactBot/0\.2|ContentSmartz|DataCha0s/2\.0|DBrowse 1\.4b|DBrowse 1\.4d|Demo Bot DOT 16b|Demo Bot Z 16b|DSurf15a 01|DSurf15a 71|DSurf15a 81|DSurf15a VA|EBrowse 1\.4b|Educate Search VxB|EmailSiphon|EmailSpider|EmailWolf 1\.00|ESurf15a 15|ExtractorPro|Franklin Locator 1\.8|FSurf15a 01|Full Web Bot 0416B|Full Web Bot 0516B|Full Web Bot 2816B|Guestbook Auto Submitter|Industry Program 1\.0\.x|ISC Systems iRc Search 2\.1|IUPUI Research Bot v 1\.9a|LARBIN-EXPERIMENTAL \(efp@gmx\.net\)|LetsCrawl\.com/1\.0 +http\://letscrawl\.com/|Lincoln State Web Browser|LMQueueBot/0\.2|LWP\:\:Simple/5\.803|Mac Finder 1\.0\.xx|MFC Foundation Class Library 4\.0|Microsoft URL Control - 6\.00\.8xxx|Missauga Locate 1\.0\.0|Missigua Locator 1\.9|Missouri College Browse|Mizzu Labs 2\.2|Mo College 1\.9|MVAClient|Mozilla/2\.0 \(compatible; NEWT ActiveX; Win32\)|Mozilla/3\.0 \(compatible; Indy Library\)|Mozilla/3\.0 \(compatible; scan4mail \(advanced version\) http\://www\.peterspages\.net/?scan4mail\)|Mozilla/4\.0 \(compatible; Advanced Email Extractor v2\.xx\)|Mozilla/4\.0 \(compatible; Iplexx Spider/1\.0 http\://www\.iplexx\.at\)|Mozilla/4\.0 \(compatible; MSIE 5\.0; Windows NT; DigExt; DTS Agent|Mozilla/4\.0 efp@gmx\.net|Mozilla/5\.0 \(Version\: xxxx Type\:xx\)|NameOfAgent \(CMS Spider\)|NASA Search 1\.0|Nsauditor/1\.x|PBrowse 1\.4b|PEval 1\.4b|Poirot|Port Huron Labs|Production Bot 0116B|Production Bot 2016B|Production Bot DOT 3016B|Program Shareware 1\.0\.2|PSurf15a 11|PSurf15a 51|PSurf15a VA|psycheclone|RSurf15a 41|RSurf15a 51|RSurf15a 81|searchbot admin@google\.com|ShablastBot 1\.0|snap\.com beta crawler v0|Snapbot/1\.0|Snapbot/1\.0 \(Snap Shots&#44; +http\://www\.snap\.com\)|sogou develop spider|Sogou Orion spider/3\.0\(+http\://www\.sogou\.com/docs/help/webmasters\.htm#07\)|sogou spider|Sogou web spider/3\.0\(+http\://www\.sogou\.com/docs/help/webmasters\.htm#07\)|sohu agent|SSurf15a 11 |TSurf15a 11|Under the Rainbow 2\.2|User-Agent\: Mozilla/4\.0 \(compatible; MSIE 6\.0; Windows NT 5\.1\)|VadixBot|WebVulnCrawl\.unknown/1\.0 libwww-perl/5\.803|Wells Search II|WEP Search 00
failregex = ^<HOST> -.*"(GET|POST).*HTTP.*"(?:%(badbots)s|%(badbotscustom)s)"$
failregex = ^<HOST> -.*"(GET|POST|HEAD).*HTTP.*"(?:%(badbots)s|%(badbotscustom)s)"$
ignoreregex =

View File

@ -17,7 +17,9 @@
[INCLUDES]
# overwrite with apache-common.local if _apache_error_client is incorrect.
# Load regexes for filtering from botsearch-common.conf
before = apache-common.conf
botsearch-common.conf
[Definition]
@ -31,18 +33,8 @@ ignoreregex =
# Webroot represents the webroot on which all other files are based
webroot = /var/www/
# Block is the actual non-found directories to block
block = (<webmail>|<phpmyadmin>|<wordpress>)[^,]*
# These are just convient definitions that assist the blocking of stuff that
# isn't installed
webmail = roundcube|(ext)?mail|horde|(v-?)?webmail
phpmyadmin = (typo3/|xampp/|admin/|)(pma|(php)?[Mm]y[Aa]dmin)
wordpress = wp-(login|signup)\.php
# DEV Notes:
#
# Author: Daniel Black
# Author: Daniel Black

View File

@ -0,0 +1,14 @@
# Fail2Ban filter for fake Googlebot User Agents
[Definition]
failregex = ^<HOST> .*Googlebot.*$
ignoreregex =
# DEV Notes:
#
# Author: Lee Clemens
# Thanks: Johannes B. Ullrich, Ph.D.
# Reference: https://isc.sans.edu/forums/diary/When+Google+isnt+Google/15968/

View File

@ -13,6 +13,8 @@ _daemon = asterisk
__pid_re = (?:\[\d+\])
iso8601 = \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+[+-]\d{4}
# All Asterisk log messages begin like this:
log_prefix= (?:NOTICE|SECURITY)%(__pid_re)s:?(?:\[C-[\da-f]*\])? \S+:\d*( in \w+:)?
@ -22,8 +24,8 @@ failregex = ^(%(__prefix_line)s|\[\]\s*)%(log_prefix)s Registration from '[^']*'
^(%(__prefix_line)s|\[\]\s*)%(log_prefix)s No registration for peer '[^']*' \(from <HOST>\)$
^(%(__prefix_line)s|\[\]\s*)%(log_prefix)s Host <HOST> failed MD5 authentication for '[^']*' \([^)]+\)$
^(%(__prefix_line)s|\[\]\s*)%(log_prefix)s Failed to authenticate (user|device) [^@]+@<HOST>\S*$
^(%(__prefix_line)s|\[\]\s*)%(log_prefix)s (?:handle_request_subscribe: )?Sending fake auth rejection for (device|user) \d*<sip:[^@]+@<HOST>>;tag=\w+\S*$
^(%(__prefix_line)s|\[\]\s*)%(log_prefix)s SecurityEvent="(FailedACL|InvalidAccountID|ChallengeResponseFailed|InvalidPassword)",EventTV="[\d-]+",Severity="[\w]+",Service="[\w]+",EventVersion="\d+",AccountID="\d*",SessionID="0x[\da-f]+",LocalAddress="IPV[46]/(UD|TC)P/[\da-fA-F:.]+/\d+",RemoteAddress="IPV[46]/(UD|TC)P/<HOST>/\d+"(,Challenge="\w+",ReceivedChallenge="\w+")?(,ReceivedHash="[\da-f]+")?(,ACLName="\w+")?$
^(%(__prefix_line)s|\[\]\s*)%(log_prefix)s hacking attempt detected '<HOST>'$
^(%(__prefix_line)s|\[\]\s*)%(log_prefix)s SecurityEvent="(FailedACL|InvalidAccountID|ChallengeResponseFailed|InvalidPassword)",EventTV="([\d-]+|%(iso8601)s)",Severity="[\w]+",Service="[\w]+",EventVersion="\d+",AccountID="(\d*|<unknown>)",SessionID=".+",LocalAddress="IPV[46]/(UDP|TCP|WS)/[\da-fA-F:.]+/\d+",RemoteAddress="IPV[46]/(UDP|TCP|WS)/<HOST>/\d+"(,Challenge="[\w/]+")?(,ReceivedChallenge="\w+")?(,Response="\w+",ExpectedResponse="\w*")?(,ReceivedHash="[\da-f]+")?(,ACLName="\w+")?$
^(%(__prefix_line)s|\[\]\s*WARNING%(__pid_re)s:?(?:\[C-[\da-f]*\])? )Ext\. s: "Rejecting unknown SIP connection from <HOST>"$
ignoreregex =

View File

@ -0,0 +1,19 @@
# Generic configuration file for -botsearch filters
[Init]
# Block is the actual non-found directories to block
block = \/?(<webmail>|<phpmyadmin>|<wordpress>|cgi-bin|mysqladmin)[^,]*
# These are just convient definitions that assist the blocking of stuff that
# isn't installed
webmail = roundcube|(ext)?mail|horde|(v-?)?webmail
phpmyadmin = (typo3/|xampp/|admin/|)(pma|(php)?[Mm]y[Aa]dmin)
wordpress = wp-(login|signup)\.php
# DEV Notes:
# Taken from apache-botsearch filter
#
# Author: Frantisek Sumsal

View File

@ -53,4 +53,8 @@ __bsd_syslog_verbose = (<[^.]+\.[^.]+>)
# This can be optional (for instance if we match named native log files)
__prefix_line = \s*%(__bsd_syslog_verbose)s?\s*(?:%(__hostname)s )?(?:%(__kernel_prefix)s )?(?:@vserver_\S+ )?%(__daemon_combs_re)s?\s%(__daemon_extra_re)s?\s*
# PAM authentication mechanism check for failures, e.g.: pam_unix, pam_sss,
# pam_ldap
__pam_auth = pam_unix
# Author: Yaroslav Halchenko

View File

@ -6,6 +6,7 @@
failregex = ^: Bad Rcon: "rcon \d+ "\S+" sv_contact ".*?"" from "<HOST>:\d+"$
ignoreregex =
[Init]

View File

@ -9,9 +9,10 @@ before = common.conf
_daemon = (auth|dovecot(-auth)?|auth-worker)
failregex = ^%(__prefix_line)s(pam_unix(\(dovecot:auth\))?:)?\s+authentication failure; logname=\S* uid=\S* euid=\S* tty=dovecot ruser=\S* rhost=<HOST>(\s+user=\S*)?\s*$
failregex = ^%(__prefix_line)s(%(__pam_auth)s(\(dovecot:auth\))?:)?\s+authentication failure; logname=\S* uid=\S* euid=\S* tty=dovecot ruser=\S* rhost=<HOST>(\s+user=\S*)?\s*$
^%(__prefix_line)s(pop3|imap)-login: (Info: )?(Aborted login|Disconnected)(: Inactivity)? \(((auth failed, \d+ attempts)( in \d+ secs)?|tried to use (disabled|disallowed) \S+ auth)\):( user=<\S*>,)?( method=\S+,)? rip=<HOST>(, lip=(\d{1,3}\.){3}\d{1,3})?(, TLS( handshaking(: SSL_accept\(\) failed: error:[\dA-F]+:SSL routines:[TLS\d]+_GET_CLIENT_HELLO:unknown protocol)?)?(: Disconnected)?)?(, session=<\S+>)?\s*$
^%(__prefix_line)s(Info|dovecot: auth\(default\)): pam\(\S+,<HOST>\): pam_authenticate\(\) failed: (User not known to the underlying authentication module: \d+ Time\(s\)|Authentication failure \(password mismatch\?\))\s*$
^%(__prefix_line)s(Info|dovecot: auth\(default\)|auth-worker\(\d+\)): pam\(\S+,<HOST>\): pam_authenticate\(\) failed: (User not known to the underlying authentication module: \d+ Time\(s\)|Authentication failure \(password mismatch\?\))\s*$
^%(__prefix_line)s(auth|auth-worker\(\d+\)): (pam|passwd-file)\(\S+,<HOST>\): unknown user\s*$
ignoreregex =

View File

@ -0,0 +1,26 @@
# Fail2Ban filter to block repeated failed login attempts to Drupal site(s)
#
#
# Drupal must be setup to use Syslog, which defaults to the following format:
#
# !base_url|!timestamp|!type|!ip|!request_uri|!referer|!uid|!link|!message
#
#
[INCLUDES]
before = common.conf
[Definition]
failregex = ^%(__prefix_line)s(https?:\/\/)([\da-z\.-]+)\.([a-z\.]{2,6})(\/[\w\.-]+)*\|\d{10}\|user\|<HOST>\|.+\|.+\|\d\|.*\|Login attempt failed for .+\.$
ignoreregex =
# DEV Notes:
#
# https://www.drupal.org/documentation/modules/syslog
#
# Author: Lee Clemens

View File

@ -0,0 +1,37 @@
# Fail2Ban configuration file to block repeated failed login attempts to Frolor installation(s)
#
# Froxlor needs to log to Syslog User (e.g. /var/log/user.log) with one of the following messages
# <syslog prefix> Froxlor: [Login Action <HOST>] Unknown user '<USER>' tried to login.
# <syslog prefix> Froxlor: [Login Action <HOST>] User '<USER>' tried to login with wrong password.
#
# Author: Joern Muehlencord
#
[INCLUDES]
# Read common prefixes. If any customizations available -- read them from
# common.local
before = common.conf
[Definition]
_daemon = Froxlor
# Option: failregex
# Notes.: regex to match the password failures messages in the logfile. The
# host must be matched by a group named "host". The tag "<HOST>" can
# be used for standard IP/hostname matching and is only an alias for
# (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
# Values: TEXT
#
failregex = ^%(__prefix_line)s\[Login Action <HOST>\] Unknown user \S* tried to login.$
^%(__prefix_line)s\[Login Action <HOST>\] User \S* tried to login with wrong password.$
# Option: ignoreregex
# Notes.: regex to ignore. If this regex matches, the line is ignored.
# Values: TEXT
#
ignoreregex =

View File

@ -8,7 +8,7 @@
failregex = ^\[\]LOGIN FAILED for user: "\S+" from IP: <HOST>$
ignoreregex =
# Author: Daniel Black

View File

@ -0,0 +1,32 @@
#!/usr/bin/python
# Inspired by https://isc.sans.edu/forums/diary/When+Google+isnt+Google/15968/
#
# Written in Python to reuse built-in Python batteries and not depend on
# presence of host and cut commands
#
import sys
def process_args(argv):
if len(argv) != 2:
sys.stderr.write("Please provide a single IP as an argument. Got: %s\n"
% (argv[1:]))
sys.exit(2)
ip = argv[1]
from fail2ban.server.filter import DNSUtils
if not DNSUtils.isValidIP(ip):
sys.stderr.write("Argument must be a single valid IP. Got: %s\n"
% ip)
sys.exit(3)
return ip
def is_googlebot(ip):
import re
from fail2ban.server.filter import DNSUtils
host = DNSUtils.ipToName(ip)
sys.exit(0 if (host and re.match('crawl-.*\.googlebot\.com', host)) else 1)
if __name__ == '__main__':
is_googlebot(process_args(sys.argv))

View File

@ -6,6 +6,9 @@ failregex = ^ SMTP Spam attack detected from <HOST>,
^ IP address <HOST> found in DNS blacklist \S+, mail from \S+ to \S+$
^ Relay attempt from IP address <HOST>
^ Attempt to deliver to unknown recipient \S+, from \S+, IP address <HOST>$
ignoreregex =
[Init]
datepattern = ^\[%%d/%%b/%%Y %%H:%%M:%%S\]

View File

@ -7,3 +7,4 @@
failregex = ^\[[A-Z]+\s+\]\s*error\s*:\s*Warning:\s+Client '<HOST>' supplied unknown user '\w+' accessing monit httpd$
^\[[A-Z]+\s+\]\s*error\s*:\s*Warning:\s+Client '<HOST>' supplied wrong password for user '\w+' accessing monit httpd$
ignoreregex =

View File

@ -0,0 +1,20 @@
# Fail2Ban filter to match web requests for selected URLs that don't exist
#
[INCLUDES]
# Load regexes for filtering
before = botsearch-common.conf
[Definition]
failregex = ^<HOST> \- \S+ \[\] \"(GET|POST|HEAD) \/<block> \S+\" 404 .+$
^ \[error\] \d+#\d+: \*\d+ (\S+ )?\"\S+\" (failed|is not found) \(2\: No such file or directory\), client\: <HOST>\, server\: \S*\, request: \"(GET|POST|HEAD) \/<block> \S+\"\, .*?$
ignoreregex =
# DEV Notes:
# Based on apache-botsearch filter
#
# Author: Frantisek Sumsal

View File

@ -24,3 +24,5 @@ _daemon = nsd
failregex = ^\[\]%(__prefix_line)sinfo: ratelimit block .* query <HOST> TYPE255$
^\[\]%(__prefix_line)sinfo: .* <HOST> refused, no acl matches\.$
ignoreregex =

View File

@ -13,7 +13,7 @@ before = common.conf
# Default: catch all failed logins
_ttys_re=\S*
__pam_re=\(?pam_unix(?:\(\S+\))?\)?:?
__pam_re=\(?%(__pam_auth)s(?:\(\S+\))?\)?:?
_daemon = \S+
failregex = ^%(__prefix_line)s%(__pam_re)s\s+authentication failure; logname=\S* uid=\S* euid=\S* tty=%(_ttys_re)s ruser=\S* rhost=<HOST>(?:\s+user=.*)?\s*$

View File

@ -6,5 +6,7 @@
failregex = \/<HOST> Port\: [0-9]+ (TCP|UDP) Blocked$
ignoreregex =
# Author: Pacop <pacoparu@gmail.com>

View File

@ -0,0 +1,19 @@
# Fail2Ban filter for Postfix's RBL based Blocked hosts
#
#
[INCLUDES]
# Read common prefixes. If any customizations available -- read them from
# common.local
before = common.conf
[Definition]
_daemon = postfix/smtpd
failregex = ^%(__prefix_line)sNOQUEUE: reject: RCPT from \S+\[<HOST>\]: 454 4\.7\.1 Service unavailable; Client host \[\S+\] blocked using .* from=<\S*> to=<\S+> proto=ESMTP helo=<\S*>$
ignoreregex =
# Author: Lee Clemens

View File

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

View File

@ -2,6 +2,9 @@
#
# Set "UseReverseDNS off" in proftpd.conf to avoid the need for DNS.
# See: http://www.proftpd.org/docs/howto/DNS.html
# When the default locale for your system is not en_US.UTF-8
# on Debian-based systems be sure to add this to /etc/default/proftpd
# export LC_TIME="en_US.UTF-8"
[INCLUDES]

View File

@ -1,6 +1,10 @@
# Fail2Ban configuration file for roundcube web server
#
# By default failed logins are printed to 'errors'. The first regex matches those
# The second regex matches those printed to 'userlogins'
# The userlogins log file can be enabled by setting $config['log_logins'] = true; in config.inc.php
#
# The logpath in your jail can be updated to userlogins if you wish
#
[INCLUDES]
@ -9,7 +13,8 @@ before = common.conf
[Definition]
failregex = ^\s*(\[\])?(%(__hostname)s roundcube: IMAP Error)?: (FAILED login|Login failed) for .*? from <HOST>(\. .* in .*?/rcube_imap\.php on line \d+ \(\S+ \S+\))?$
failregex = ^\s*(\[\])?(%(__hostname)s\s*(roundcube:)?\s*(<[\w]+>)? IMAP Error)?: (FAILED login|Login failed) for .*? from <HOST>(\. .* in .*?/rcube_imap\.php on line \d+ \(\S+ \S+\))?$
^\[\]:\s*(<[\w]+>)? Failed login for [\w\-\.\+]+(@[\w\-\.\+]+\.[a-zA-Z]{2,6})? from <HOST> in session \w+( \(error: \d\))?$
ignoreregex =
# DEV Notes:
@ -26,4 +31,4 @@ ignoreregex =
# arbitrary user input and IMAP response doesn't inject the wrong IP for
# fail2ban
#
# Author: Teodor Micu & Yaroslav Halchenko & terence namusonge & Daniel Black
# Author: Teodor Micu & Yaroslav Halchenko & terence namusonge & Daniel Black & Lee Clemens

View File

@ -7,7 +7,7 @@
failregex = ^\s+\d\s<HOST>\s+[A-Z_]+_DENIED/403 .*$
^\s+\d\s<HOST>\s+NONE/405 .*$
ignoreregex =
# Author: Daniel Black

View File

@ -3,6 +3,7 @@
failregex = ^ \[LOGIN_ERROR\].*from <HOST>: Unknown user or password incorrect\.$
ignoreregex =
[Init]

View File

@ -33,6 +33,7 @@ failregex = ^%(__prefix_line)s(?:error: PAM: )?[aA]uthentication (?:failure|erro
^(?P<__prefix>%(__prefix_line)s)User .+ not allowed because account is locked<SKIPLINES>(?P=__prefix)(?:error: )?Received disconnect from <HOST>: 11: .+ \[preauth\]$
^(?P<__prefix>%(__prefix_line)s)Disconnecting: Too many authentication failures for .+? \[preauth\]<SKIPLINES>(?P=__prefix)(?:error: )?Connection closed by <HOST> \[preauth\]$
^(?P<__prefix>%(__prefix_line)s)Connection from <HOST> port \d+(?: on \S+ port \d+)?<SKIPLINES>(?P=__prefix)Disconnecting: Too many authentication failures for .+? \[preauth\]$
^%(__prefix_line)spam_unix\(sshd:auth\):\s+authentication failure;\s*logname=\S*\s*uid=\d*\s*euid=\d*\s*tty=\S*\s*ruser=\S*\s*rhost=<HOST>\s.*$
ignoreregex =

View File

@ -4,6 +4,8 @@
failregex = ^ LOG\d\[\d+:\d+\]:\ SSL_accept from <HOST>:\d+ : (?P<CODE>[\dA-F]+): error:(?P=CODE):SSL routines:SSL3_GET_CLIENT_CERTIFICATE:peer did not return a certificate$
ignoreregex =
# DEV NOTES:
#
# Author: Daniel Black

View File

@ -10,7 +10,7 @@ before = common.conf
[Definition]
__pam_re=\(?pam_unix(?:\(\S+\))?\)?:?
__pam_re=\(?%(__pam_auth)s(?:\(\S+\))?\)?:?
_daemon = vsftpd
failregex = ^%(__prefix_line)s%(__pam_re)s\s+authentication failure; logname=\S* uid=\S* euid=\S* tty=(ftp)? ruser=\S* rhost=<HOST>(?:\s+user=.*)?\s*$

View File

@ -11,7 +11,7 @@ before = common.conf
[Definition]
_daemon = wu-ftpd
__pam_re=\(?pam_unix(?:\(wu-ftpd:auth\))?\)?:?
__pam_re=\(?%(__pam_auth)s(?:\(wu-ftpd:auth\))?\)?:?
failregex = ^%(__prefix_line)sfailed login from \S+ \[<HOST>\]\s*$
^%(__prefix_line)s%(__pam_re)s\s+authentication failure; logname=\S* uid=\S* euid=\S* tty=(ftp)? ruser=\S* rhost=<HOST>(?:\s+user=.*)?\s*$

View File

@ -117,6 +117,11 @@ maxretry = 5
# See "journalmatch" in the jails associated filter config
# auto: will try to use the following backends, in order:
# pyinotify, gamin, polling.
#
# Note: if systemd backend is choses as the default but you enable a jail
# for which logs are present only in its own log files, specify some other
# backend for that jail (e.g. polling) and provide empty value for
# journalmatch. See https://github.com/fail2ban/fail2ban/issues/959#issuecomment-74901200
backend = auto
# "usedns" specifies if jails should trust hostnames in logs,
@ -207,6 +212,10 @@ action_mwl = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(por
action_xarf = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
xarf-login-attack[service=%(__name__)s, sender="%(sender)s", logpath=%(logpath)s, port="%(port)s"]
# ban IP on CloudFlare & send an e-mail with whois report and relevant log lines
# to the destemail.
action_cf_mwl = cloudflare[cfuser="%(cfemail)s", cftoken="%(cfapikey)s"]
%(mta)s-whois-lines[name=%(__name__)s, dest="%(destemail)s", logpath=%(logpath)s, chain="%(chain)s"]
# Report block via blocklist.de fail2ban reporting service API
#
@ -315,6 +324,14 @@ logpath = %(apache_error_log)s
maxretry = 2
[apache-fakegooglebot]
port = http,https
logpath = %(apache_access_log)s
maxretry = 1
ignorecommand = %(ignorecommands_dir)s/apache-fakegooglebot <ip>
[apache-modsecurity]
port = http,https
@ -329,9 +346,14 @@ maxretry = 1
[nginx-http-auth]
ports = http,https
port = http,https
logpath = %(nginx_error_log)s
[nginx-botsearch]
port = http,https
logpath = %(nginx_error_log)s
maxretry = 2
# Ban attackers that try to use PHP's URL-fopen() functionality
# through GET/POST variables. - Experimental, with more than a year
@ -364,7 +386,7 @@ logpath = %(lighttpd_error_log)s
[roundcube-auth]
port = http,https
logpath = /var/log/roundcube/userlogins
logpath = logpath = %(roundcube_errors_log)s
[openwebmail]
@ -405,6 +427,11 @@ maxretry = 5
#
#
[drupal-auth]
port = http,https
logpath = %(syslog_daemon)s
[guacamole]
port = http,https
@ -423,6 +450,12 @@ port = 10000
logpath = %(syslog_authpriv)s
[froxlor-auth]
port = http,https
logpath = %(syslog_authpriv)s
#
# HTTP Proxy servers
#
@ -439,6 +472,7 @@ logpath = /var/log/squid/access.log
port = 3128
logpath = /var/log/3proxy.log
#
# FTP servers
#
@ -503,6 +537,13 @@ port = smtp,465,submission
logpath = %(postfix_log)s
[postfix-rbl]
port = smtp,465,submission
logpath = %(syslog_mail)s
maxretry = 1
[sendmail-auth]
port = submission,465,smtp
@ -687,15 +728,16 @@ maxretry = 5
# Jail for more extended banning of persistent abusers
# !!! WARNING !!!
# Make sure that your loglevel specified in fail2ban.conf/.local
# is not at DEBUG level -- which might then cause fail2ban to fall into
# an infinite loop constantly feeding itself with non-informative lines
# !!! WARNINGS !!!
# 1. Make sure that your loglevel specified in fail2ban.conf/.local
# is not at DEBUG level -- which might then cause fail2ban to fall into
# an infinite loop constantly feeding itself with non-informative lines
# 2. Increase dbpurgeage defined in fail2ban.conf to e.g. 648000 (7.5 days)
# to maintain entries for failed logins for sufficient amount of time
[recidive]
logpath = /var/log/fail2ban.log
port = all
protocol = all
banaction = iptables-allports
bantime = 1w
findtime = 1d
maxretry = 5

View File

@ -61,3 +61,8 @@ dovecot_log = %(syslog_mail_warn)s
solidpop3d_log = %(syslog_local0)s
mysql_log = %(syslog_daemon)s
roundcube_errors_log = /var/log/roundcube/errors
# Directory with ignorecommand scripts
ignorecommands_dir = /etc/fail2ban/filter.d/ignorecommands

View File

@ -35,6 +35,3 @@ exim_main_log = /var/log/exim4/mainlog
# was in debian squeezy but not in wheezy
# /etc/proftpd/proftpd.conf (SystemLog)
proftpd_log = /var/log/proftpd/proftpd.log

View File

@ -35,3 +35,5 @@ apache_access_log = /var/log/httpd/*access_log
exim_main_log = /var/log/exim/main.log
mysql_log = /var/lib/mysql/mysqld.log
roundcube_errors_log = /var/log/roundcubemail/errors

View File

@ -37,6 +37,7 @@ Below derived from:
logging.NOTICE = logging.INFO + 5
logging.addLevelName(logging.NOTICE, 'NOTICE')
# define a new logger function for notice
# this is exactly like existing info, critical, debug...etc
def _Logger_notice(self, msg, *args, **kwargs):
@ -53,6 +54,7 @@ def _Logger_notice(self, msg, *args, **kwargs):
logging.Logger.notice = _Logger_notice
# define a new root level notice function
# this is exactly like existing info, critical, debug...etc
def _root_notice(msg, *args, **kwargs):
@ -68,3 +70,7 @@ logging.notice = _root_notice
# add NOTICE to the priority map of all the levels
logging.handlers.SysLogHandler.priority_map['NOTICE'] = 'notice'
from time import strptime
# strptime thread safety hack-around - http://bugs.python.org/issue7980
strptime("2012", "%Y")

View File

@ -32,6 +32,7 @@ from ..helpers import getLogger
# Gets the instance of the logger.
logSys = getLogger(__name__)
class ActionReader(DefinitionInitConfigReader):
_configOpts = [

View File

@ -27,6 +27,7 @@ from ..helpers import getLogger
# Gets the instance of the logger.
logSys = getLogger(__name__)
##
# Beautify the output of the client.
#
@ -34,18 +35,19 @@ logSys = getLogger(__name__)
# converted into user readable messages.
class Beautifier:
def __init__(self, cmd = None):
self.__inputCmd = cmd
def setInputCmd(self, cmd):
self.__inputCmd = cmd
def getInputCmd(self):
return self.__inputCmd
def beautify(self, response):
logSys.debug("Beautify " + `response` + " with " + `self.__inputCmd`)
logSys.debug(
"Beautify " + repr(response) + " with " + repr(self.__inputCmd))
inC = self.__inputCmd
msg = response
try:
@ -85,6 +87,9 @@ class Beautifier:
val = " ".join(res1[1]) if isinstance(res1[1], list) else res1[1]
msg.append("%s %s:\t%s" % (prefix1, res1[0], val))
msg = "\n".join(msg)
elif inC[1] == "syslogsocket":
msg = "Current syslog socket is:\n"
msg = msg + "`- " + response
elif inC[1] == "logtarget":
msg = "Current logging target is:\n"
msg = msg + "`- " + response
@ -99,7 +104,7 @@ class Beautifier:
elif response == 4:
msg = msg + "DEBUG"
else:
msg = msg + `response`
msg = msg + repr(response)
elif inC[1] == "dbfile":
if response is None:
msg = "Database currently disabled"
@ -180,13 +185,14 @@ class Beautifier:
msg += ", ".join(response)
except Exception:
logSys.warning("Beautifier error. Please report the error")
logSys.error("Beautify " + `response` + " with " + `self.__inputCmd` +
" failed")
msg = msg + `response`
logSys.error("Beautify " + repr(response) + " with "
+ repr(self.__inputCmd) + " failed")
msg = msg + repr(response)
return msg
def beautifyError(self, response):
logSys.debug("Beautify (error) " + `response` + " with " + `self.__inputCmd`)
logSys.debug("Beautify (error) " + repr(response) + " with "
+ repr(self.__inputCmd))
msg = response
if isinstance(response, UnknownJailException):
msg = "Sorry but the jail '" + response.args[0] + "' does not exist"

View File

@ -24,7 +24,8 @@ __author__ = 'Yaroslav Halhenko'
__copyright__ = 'Copyright (c) 2007 Yaroslav Halchenko'
__license__ = 'GPL'
import os, sys
import os
import sys
from ..helpers import getLogger
if sys.version_info >= (3,2): # pragma: no cover
@ -66,6 +67,7 @@ logLevel = 7
__all__ = ['SafeConfigParserWithIncludes']
class SafeConfigParserWithIncludes(SafeConfigParser):
"""
Class adds functionality to SafeConfigParser to handle included

View File

@ -24,7 +24,8 @@ __author__ = "Cyril Jaquier"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import glob, os
import glob
import os
from ConfigParser import NoOptionError, NoSectionError
from .configparserinc import SafeConfigParserWithIncludes, logLevel
@ -33,6 +34,7 @@ from ..helpers import getLogger
# Gets the instance of the logger.
logSys = getLogger(__name__)
class ConfigReader():
"""Generic config reader class.
@ -135,6 +137,7 @@ class ConfigReader():
return self._cfg.getOptions(*args, **kwargs)
return {}
class ConfigReaderUnshared(SafeConfigParserWithIncludes):
"""Unshared config reader (previously ConfigReader).
@ -186,7 +189,7 @@ class ConfigReaderUnshared(SafeConfigParserWithIncludes):
if config_files_read:
return True
logSys.error("Found no accessible config files for %r under %s",
( filename, self.getBaseDir() ))
filename, self.getBaseDir())
return False
else:
logSys.error("Found no accessible config files for %r " % filename
@ -232,10 +235,11 @@ class ConfigReaderUnshared(SafeConfigParserWithIncludes):
logSys.log(logLevel, "Non essential option '%s' not defined in '%s'.", option[1], sec)
except ValueError:
logSys.warning("Wrong value for '" + option[1] + "' in '" + sec +
"'. Using default one: '" + `option[2]` + "'")
"'. Using default one: '" + repr(option[2]) + "'")
values[option[1]] = option[2]
return values
class DefinitionInitConfigReader(ConfigReader):
"""Config reader for files with options grouped in [Definition] and
[Init] sections.
@ -281,7 +285,7 @@ class DefinitionInitConfigReader(ConfigReader):
if self.has_section("Init"):
for opt in self.options("Init"):
if not self._initOpts.has_key(opt):
if not opt in self._initOpts:
self._initOpts[opt] = self.get("Init", opt)
def convert(self):

View File

@ -31,6 +31,7 @@ from ..helpers import getLogger
# Gets the instance of the logger.
logSys = getLogger(__name__)
class Configurator:
def __init__(self, force_enable=False, share_config=None):

View File

@ -26,43 +26,42 @@ __license__ = "GPL"
#from cPickle import dumps, loads, HIGHEST_PROTOCOL
from pickle import dumps, loads, HIGHEST_PROTOCOL
import socket, sys
if sys.version_info >= (3,):
# b"" causes SyntaxError in python <= 2.5, so below implements equivalent
EMPTY_BYTES = bytes("", encoding="ascii")
else:
# python 2.x, string type is equivalent to bytes.
EMPTY_BYTES = ""
from ..protocol import CSPROTO
import socket
import sys
class CSocket:
if sys.version_info >= (3,):
END_STRING = bytes("<F2B_END_COMMAND>", encoding='ascii')
else:
END_STRING = "<F2B_END_COMMAND>"
def __init__(self, sock = "/var/run/fail2ban/fail2ban.sock"):
def __init__(self, sock="/var/run/fail2ban/fail2ban.sock"):
# Create an INET, STREAMing socket
#self.csock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.__csock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
#self.csock.connect(("localhost", 2222))
self.__csock.connect(sock)
def __del__(self):
self.close(False)
def send(self, msg):
# Convert every list member to string
obj = dumps([str(m) for m in msg], HIGHEST_PROTOCOL)
self.__csock.send(obj + CSocket.END_STRING)
ret = self.receive(self.__csock)
self.__csock.send(obj + CSPROTO.END)
return self.receive(self.__csock)
def close(self, sendEnd=True):
if not self.__csock:
return
if sendEnd:
self.__csock.sendall(CSPROTO.CLOSE + CSPROTO.END)
self.__csock.close()
return ret
self.__csock = None
@staticmethod
def receive(sock):
msg = EMPTY_BYTES
while msg.rfind(CSocket.END_STRING) == -1:
msg = CSPROTO.EMPTY
while msg.rfind(CSPROTO.END) == -1:
chunk = sock.recv(6)
if chunk == '':
raise RuntimeError, "socket connection broken"
raise RuntimeError("socket connection broken")
msg = msg + chunk
return loads(msg)

View File

@ -30,6 +30,7 @@ from ..helpers import getLogger
# Gets the instance of the logger.
logSys = getLogger(__name__)
class Fail2banReader(ConfigReader):
def __init__(self, **kwargs):
@ -46,12 +47,13 @@ class Fail2banReader(ConfigReader):
def getOptions(self):
opts = [["string", "loglevel", "INFO" ],
["string", "logtarget", "STDERR"],
["string", "syslogsocket", "auto"],
["string", "dbfile", "/var/lib/fail2ban/fail2ban.sqlite3"],
["string", "dbpurgeage", "1d"]]
self.__opts = ConfigReader.getOptions(self, "Definition", opts)
def convert(self):
order = {"loglevel":0, "logtarget":1, "dbfile":2, "dbpurgeage":3}
order = {"loglevel":0, "logtarget":1, "syslogsocket":2, "dbfile":50, "dbpurgeage":51}
stream = list()
for opt in self.__opts:
if opt in order:

View File

@ -24,7 +24,8 @@ __author__ = "Cyril Jaquier"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import os, shlex
import os
import shlex
from .configreader import DefinitionInitConfigReader
from ..server.action import CommandAction
@ -33,6 +34,7 @@ from ..helpers import getLogger
# Gets the instance of the logger.
logSys = getLogger(__name__)
class FilterReader(DefinitionInitConfigReader):
_configOpts = [
@ -50,17 +52,17 @@ class FilterReader(DefinitionInitConfigReader):
def getCombined(self):
combinedopts = dict(list(self._opts.items()) + list(self._initOpts.items()))
if not len(combinedopts):
return {};
return {}
opts = CommandAction.substituteRecursiveTags(combinedopts)
if not opts:
raise ValueError('recursive tag definitions unable to be resolved')
return opts;
return opts
def convert(self):
stream = list()
opts = self.getCombined()
if not len(opts):
return stream;
return stream
for opt, value in opts.iteritems():
if opt == "failregex":
for regex in value.split('\n'):
@ -71,7 +73,7 @@ class FilterReader(DefinitionInitConfigReader):
for regex in value.split('\n'):
# Do not send a command if the rule is empty.
if regex != '':
stream.append(["set", self._jailName, "addignoreregex", regex])
stream.append(["set", self._jailName, "addignoreregex", regex])
if self._initOpts:
if 'maxlines' in self._initOpts:
# We warn when multiline regex is used without maxlines > 1

View File

@ -24,8 +24,10 @@ __author__ = "Cyril Jaquier"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import re, glob, os.path
import glob
import json
import os.path
import re
from .configreader import ConfigReaderUnshared, ConfigReader
from .filterreader import FilterReader
@ -35,6 +37,7 @@ from ..helpers import getLogger
# Gets the instance of the logger.
logSys = getLogger(__name__)
class JailReader(ConfigReader):
optionCRE = re.compile("^((?:\w|-|_|\.)+)(?:\[(.*)\])?$")

View File

@ -31,6 +31,7 @@ from ..helpers import getLogger
# Gets the instance of the logger.
logSys = getLogger(__name__)
class JailsReader(ConfigReader):
def __init__(self, force_enable=False, **kwargs):
@ -50,6 +51,7 @@ class JailsReader(ConfigReader):
return self.__jails
def read(self):
self.__jails = list()
return ConfigReader.read(self, "jail")
def getOptions(self, section=None):

View File

@ -23,12 +23,14 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2012 Yaroslav Halchenko"
__license__ = "GPL"
#
# Jails
#
class DuplicateJailException(Exception):
pass
class UnknownJailException(KeyError):
pass

View File

@ -26,11 +26,13 @@ import traceback
import re
import logging
def formatExceptionInfo():
""" Consistently format exception information """
cla, exc = sys.exc_info()[:2]
return (cla.__name__, str(exc))
#
# Following "traceback" functions are adopted from PyMVPA distributed
# under MIT/Expat and copyright by PyMVPA developers (i.e. me and
@ -49,6 +51,7 @@ def mbasename(s):
base = os.path.basename(os.path.dirname(s)) + '.' + base
return base
class TraceBack(object):
"""Customized traceback to be included in debug messages
"""
@ -94,6 +97,7 @@ class TraceBack(object):
return sftb
class FormatterWithTraceBack(logging.Formatter):
"""Custom formatter which expands %(tb) and %(tbc) with tracebacks
@ -108,6 +112,7 @@ class FormatterWithTraceBack(logging.Formatter):
record.tbc = record.tb = self._tb()
return logging.Formatter.format(self, record)
def getLogger(name):
"""Get logging.Logger instance with Fail2Ban logger name convention
"""
@ -115,6 +120,7 @@ def getLogger(name):
name = "fail2ban.%s" % name.rpartition(".")[-1]
return logging.getLogger(name)
def excepthook(exctype, value, traceback):
"""Except hook used to log unhandled exceptions to Fail2Ban log
"""

View File

@ -29,6 +29,16 @@ import textwrap
##
# Describes the protocol used to communicate with the server.
class dotdict(dict):
def __getattr__(self, name):
return self[name]
CSPROTO = dotdict({
"EMPTY": b"",
"END": b"<F2B_END_COMMAND>",
"CLOSE": b"<F2B_CLOSE_COMMAND>"
})
protocol = [
['', "BASIC", ""],
["start", "starts the server and the jails"],
@ -44,6 +54,8 @@ protocol = [
["get loglevel", "gets the logging level"],
["set logtarget <TARGET>", "sets logging target to <TARGET>. Can be STDOUT, STDERR, SYSLOG or a file"],
["get logtarget", "gets logging target"],
["set syslogsocket auto|<SOCKET>", "sets the syslog socket path to auto or <SOCKET>. Only used if logtarget is SYSLOG"],
["get syslogsocket", "gets syslog socket path"],
["flushlogs", "flushes the logtarget if a file and reopens it. For log rotation."],
['', "DATABASE", ""],
["set dbfile <FILE>", "set the location of fail2ban persistent datastore. Set to \"None\" to disable"],
@ -54,7 +66,7 @@ protocol = [
["add <JAIL> <BACKEND>", "creates <JAIL> using <BACKEND>"],
["start <JAIL>", "starts the jail <JAIL>"],
["stop <JAIL>", "stops the jail <JAIL>. The jail is removed"],
["status <JAIL>", "gets the current status of <JAIL>"],
["status <JAIL> [FLAVOR]", "gets the current status of <JAIL>, with optional flavor or extended info"],
['', "JAIL CONFIGURATION", ""],
["set <JAIL> idle on|off", "sets the idle state of <JAIL>"],
["set <JAIL> addignoreip <IP>", "adds <IP> to the ignore list of <JAIL>"],
@ -117,6 +129,7 @@ protocol = [
["get <JAIL> action <ACT> <PROPERTY>", "gets the value of <PROPERTY> for the action <ACT> for <JAIL>"],
]
##
# Prints the protocol in a "man" format. This is used for the
# "-h" output of fail2ban-client.
@ -141,6 +154,7 @@ def printFormatted():
line = ' ' * (INDENT + MARGIN) + n.strip()
print line
##
# Prints the protocol in a "mediawiki" format.
@ -157,6 +171,7 @@ def printWiki():
print "| <span style=\"white-space:nowrap;\"><tt>" + m[0] + "</tt></span> || || " + m[1]
print "|}"
def __printWikiHeader(section, desc):
print
print "=== " + section + " ==="

View File

@ -21,8 +21,14 @@ __author__ = "Cyril Jaquier and Fail2Ban Contributors"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2012 Yaroslav Halchenko"
__license__ = "GPL"
import logging, os, subprocess, time, signal, tempfile
import threading, re
import logging
import os
import re
import signal
import subprocess
import tempfile
import threading
import time
from abc import ABCMeta
from collections import MutableMapping
@ -49,6 +55,7 @@ _RETCODE_HINTS = {
signame = dict((num, name)
for name, num in signal.__dict__.iteritems() if name.startswith("SIG"))
class CallingMap(MutableMapping):
"""A Mapping type which returns the result of callable values.
@ -94,6 +101,7 @@ class CallingMap(MutableMapping):
def copy(self):
return self.__class__(self.data.copy())
class ActionBase(object):
"""An abstract base class for actions in Fail2Ban.
@ -176,6 +184,7 @@ class ActionBase(object):
"""
pass
class CommandAction(ActionBase):
"""A action which executes OS shell commands.
@ -362,6 +371,7 @@ class CommandAction(ActionBase):
@classmethod
def substituteRecursiveTags(cls, tags):
"""Sort out tag definitions within other tags.
Since v.0.9.2 supports embedded interpolation (see test cases for examples).
so: becomes:
a = 3 a = 3
@ -378,38 +388,46 @@ class CommandAction(ActionBase):
Dictionary of tags(keys) and their values, with tags
within the values recursively replaced.
"""
t = re.compile(r'<([^ >]+)>')
for tag in tags.iterkeys():
if tag in cls._escapedTags:
# Escaped so won't match
continue
value = str(tags[tag])
m = t.search(value)
done = []
#logSys.log(5, 'TAG: %s, value: %s' % (tag, value))
while m:
found_tag = m.group(1)
#logSys.log(5, 'found: %s' % found_tag)
if found_tag == tag or found_tag in done:
# recursive definitions are bad
#logSys.log(5, 'recursion fail tag: %s value: %s' % (tag, value) )
return False
elif found_tag in cls._escapedTags:
t = re.compile(r'<([^ <>]+)>')
# repeat substitution while embedded-recursive (repFlag is True)
while True:
repFlag = False
# substitute each value:
for tag in tags.iterkeys():
if tag in cls._escapedTags:
# Escaped so won't match
continue
else:
if tags.has_key(found_tag):
value = value.replace('<%s>' % found_tag , tags[found_tag])
#logSys.log(5, 'value now: %s' % value)
done.append(found_tag)
m = t.search(value, m.start())
else:
# Missing tags are ok so we just continue on searching.
# cInfo can contain aInfo elements like <HOST> and valid shell
value = str(tags[tag])
# search and replace all tags within value, that can be interpolated using other tags:
m = t.search(value)
done = []
#logSys.log(5, 'TAG: %s, value: %s' % (tag, value))
while m:
found_tag = m.group(1)
#logSys.log(5, 'found: %s' % found_tag)
if found_tag == tag or found_tag in done:
# recursive definitions are bad
#logSys.log(5, 'recursion fail tag: %s value: %s' % (tag, value) )
return False
if found_tag in cls._escapedTags or not found_tag in tags:
# Escaped or missing tags - just continue on searching after end of match
# Missing tags are ok - cInfo can contain aInfo elements like <HOST> and valid shell
# constructs like <STDIN>.
m = t.search(value, m.start() + 1)
#logSys.log(5, 'TAG: %s, newvalue: %s' % (tag, value))
tags[tag] = value
m = t.search(value, m.end())
continue
value = value.replace('<%s>' % found_tag , tags[found_tag])
#logSys.log(5, 'value now: %s' % value)
done.append(found_tag)
m = t.search(value, m.start())
#logSys.log(5, 'TAG: %s, newvalue: %s' % (tag, value))
# was substituted?
if tags[tag] != value:
# check still contains any tag - should be repeated (possible embedded-recursive substitution):
if t.search(value):
repFlag = True
tags[tag] = value
if not repFlag:
break
return tags
@staticmethod
@ -507,10 +525,10 @@ class CommandAction(ActionBase):
realCmd = self.replaceTag(cmd, aInfo)
else:
realCmd = cmd
# Replace static fields
realCmd = self.replaceTag(realCmd, self._properties)
return self.executeCmd(realCmd, self.timeout)
@staticmethod
@ -540,7 +558,7 @@ class CommandAction(ActionBase):
if not realCmd:
logSys.debug("Nothing to do")
return True
_cmd_lock.acquire()
try: # Try wrapped within another try needed for python version < 2.5
stdout = tempfile.TemporaryFile(suffix=".stdout", prefix="fai2ban_")
@ -595,4 +613,4 @@ class CommandAction(ActionBase):
% (retcode, msg % locals()))
return False
raise RuntimeError("Command execution failed: %s" % realCmd)

View File

@ -24,9 +24,10 @@ __author__ = "Cyril Jaquier"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import time, logging
import logging
import os
import sys
import time
if sys.version_info >= (3, 3):
import importlib.machinery
else:
@ -47,6 +48,7 @@ from ..helpers import getLogger
# Gets the instance of the logger.
logSys = getLogger(__name__)
class Actions(JailThread, Mapping):
"""Handles jail actions.
@ -297,7 +299,7 @@ class Actions(JailThread, Mapping):
True if an IP address get banned.
"""
ticket = self._jail.getFailTicket()
if ticket != False:
if ticket:
aInfo = CallingMap()
bTicket = BanManager.createBanTicket(ticket)
if ticket.getBanTime() is not None:
@ -391,11 +393,21 @@ class Actions(JailThread, Mapping):
self._jail.name, name, aInfo, e,
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
@property
def status(self):
"""Status of active bans, and total ban counts.
def status(self, flavor="basic"):
"""Status of current and total ban counts and current banned IP list.
"""
ret = [("Currently banned", self.__banManager.size()),
# TODO: Allow this list to be printed as 'status' output
supported_flavors = ["basic", "cymru"]
if flavor is None or flavor not in supported_flavors:
logSys.warning("Unsupported extended jail status flavor %r. Supported: %s" % (flavor, supported_flavors))
# Always print this information (basic)
ret = [("Currently banned", self.__banManager.size()),
("Total banned", self.__banManager.getBanTotal()),
("Banned IP list", self.__banManager.getBanList())]
if flavor == "cymru":
cymru_info = self.__banManager.getBanListExtendedCymruInfo()
ret += \
[("Banned ASN list", self.__banManager.geBanListExtendedASN(cymru_info)),
("Banned Country list", self.__banManager.geBanListExtendedCountry(cymru_info)),
("Banned RIR list", self.__banManager.geBanListExtendedRIR(cymru_info))]
return ret

View File

@ -25,20 +25,20 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
from pickle import dumps, loads, HIGHEST_PROTOCOL
import asyncore, asynchat, socket, os, sys, traceback, fcntl
import asynchat
import asyncore
import fcntl
import os
import socket
import sys
import traceback
from ..protocol import CSPROTO
from ..helpers import getLogger,formatExceptionInfo
# Gets the instance of the logger.
logSys = getLogger(__name__)
if sys.version_info >= (3,):
# b"" causes SyntaxError in python <= 2.5, so below implements equivalent
EMPTY_BYTES = bytes("", encoding="ascii")
else:
# python 2.x, string type is equivalent to bytes.
EMPTY_BYTES = ""
##
# Request handler class.
#
@ -47,17 +47,12 @@ else:
class RequestHandler(asynchat.async_chat):
if sys.version_info >= (3,):
END_STRING = bytes("<F2B_END_COMMAND>", encoding="ascii")
else:
END_STRING = "<F2B_END_COMMAND>"
def __init__(self, conn, transmitter):
asynchat.async_chat.__init__(self, conn)
self.__transmitter = transmitter
self.__buffer = []
# Sets the terminator.
self.set_terminator(RequestHandler.END_STRING)
self.set_terminator(CSPROTO.END)
def collect_incoming_data(self, data):
#logSys.debug("Received raw data: " + str(data))
@ -69,16 +64,23 @@ class RequestHandler(asynchat.async_chat):
# This method is called once we have a complete request.
def found_terminator(self):
# Pop whole buffer
message = self.__buffer
self.__buffer = []
# Joins the buffer items.
message = loads(EMPTY_BYTES.join(self.__buffer))
message = CSPROTO.EMPTY.join(message)
# Closes the channel if close was received
if message == CSPROTO.CLOSE:
self.close_when_done()
return
# Deserialize
message = loads(message)
# Gives the message to the transmitter.
message = self.__transmitter.proceed(message)
# Serializes the response.
message = dumps(message, HIGHEST_PROTOCOL)
# Sends the response to the client.
self.push(message + RequestHandler.END_STRING)
# Closes the channel.
self.close_when_done()
self.push(message + CSPROTO.END)
def handle_error(self):
e1, e2 = formatExceptionInfo()
@ -86,6 +88,7 @@ class RequestHandler(asynchat.async_chat):
logSys.error(traceback.format_exc().splitlines())
self.close()
##
# Asynchronous server class.
#
@ -149,8 +152,12 @@ class AsyncServer(asyncore.dispatcher):
self.__init = True
# TODO Add try..catch
# There's a bug report for Python 2.6/3.0 that use_poll=True yields some 2.5 incompatibilities:
logSys.debug("Detected Python 2.6 or greater. asyncore.loop() not using poll")
asyncore.loop(use_poll=False) # fixes the "Unexpected communication problem" issue on Python 2.6 and 3.0
if (sys.version_info >= (2, 7) and sys.version_info < (2, 8)) \
or (sys.version_info >= (3, 4)): # if python 2.7 ...
logSys.debug("Detected Python 2.7. asyncore.loop() using poll")
asyncore.loop(use_poll=True) # workaround for the "Bad file descriptor" issue on Python 2.7, gh-161
else:
asyncore.loop(use_poll=False) # fixes the "Unexpected communication problem" issue on Python 2.6 and 3.0
##
# Stops the communication server.
@ -177,6 +184,7 @@ class AsyncServer(asyncore.dispatcher):
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
fcntl.fcntl(fd, fcntl.F_SETFD, flags|fcntl.FD_CLOEXEC)
##
# AsyncServerException is used to wrap communication exceptions.

View File

@ -33,6 +33,7 @@ from ..helpers import getLogger
# Gets the instance of the logger.
logSys = getLogger(__name__)
##
# Banning Manager.
#
@ -118,6 +119,124 @@ class BanManager:
finally:
self.__lock.release()
##
# Returns normalized value
#
# @return value or "unknown" if value is None or empty string
@staticmethod
def handleBlankResult(value):
if value is None or len(value) == 0:
return "unknown"
else:
return value
##
# Returns Cymru DNS query information
#
# @return {"asn": [], "country": [], "rir": []} dict for self.__banList IPs
def getBanListExtendedCymruInfo(self):
return_dict = {"asn": [], "country": [], "rir": []}
try:
import dns.exception
import dns.resolver
except ImportError:
logSys.error("dnspython package is required but could not be imported")
return_dict["asn"].append("error")
return_dict["country"].append("error")
return_dict["rir"].append("error")
return return_dict
self.__lock.acquire()
try:
for banData in self.__banList:
ip = banData.getIP()
# Reference: http://www.team-cymru.org/Services/ip-to-asn.html#dns
# TODO: IPv6 compatibility
reversed_ip = ".".join(reversed(ip.split(".")))
question = "%s.origin.asn.cymru.com" % reversed_ip
try:
answers = dns.resolver.query(question, "TXT")
for rdata in answers:
asn, net, country, rir, changed =\
[answer.strip("'\" ") for answer in rdata.to_text().split("|")]
asn = self.handleBlankResult(asn)
country = self.handleBlankResult(country)
rir = self.handleBlankResult(rir)
return_dict["asn"].append(self.handleBlankResult(asn))
return_dict["country"].append(self.handleBlankResult(country))
return_dict["rir"].append(self.handleBlankResult(rir))
except dns.resolver.NXDOMAIN:
return_dict["asn"].append("nxdomain")
return_dict["country"].append("nxdomain")
return_dict["rir"].append("nxdomain")
except dns.exception.DNSException as dnse:
logSys.error("Unhandled DNSException querying Cymru for %s TXT" % question)
logSys.exception(dnse)
except Exception as e:
logSys.error("Unhandled Exception querying Cymru for %s TXT" % question)
logSys.exception(e)
except Exception as e:
logSys.error("Failure looking up extended Cymru info")
logSys.exception(e)
finally:
self.__lock.release()
return return_dict
##
# Returns list of Banned ASNs from Cymru info
#
# Use getBanListExtendedCymruInfo() to provide cymru_info
#
# @return list of Banned ASNs
def geBanListExtendedASN(self, cymru_info):
self.__lock.acquire()
try:
return [asn for asn in cymru_info["asn"]]
except Exception as e:
logSys.error("Failed to lookup ASN")
logSys.exception(e)
return []
finally:
self.__lock.release()
##
# Returns list of Banned Countries from Cymru info
#
# Use getBanListExtendedCymruInfo() to provide cymru_info
#
# @return list of Banned Countries
def geBanListExtendedCountry(self, cymru_info):
self.__lock.acquire()
try:
return [country for country in cymru_info["country"]]
except Exception as e:
logSys.error("Failed to lookup Country")
logSys.exception(e)
return []
finally:
self.__lock.release()
##
# Returns list of Banned RIRs from Cymru info
#
# Use getBanListExtendedCymruInfo() to provide cymru_info
#
# @return list of Banned RIRs
def geBanListExtendedRIR(self, cymru_info):
self.__lock.acquire()
try:
return [rir for rir in cymru_info["rir"]]
except Exception as e:
logSys.error("Failed to lookup RIR")
logSys.exception(e)
return []
finally:
self.__lock.release()
##
# Create a ban ticket.
#
@ -170,20 +289,19 @@ class BanManager:
return True
finally:
self.__lock.release()
##
# Get the size of the ban list.
#
# @return the size
def size(self):
try:
self.__lock.acquire()
return len(self.__banList)
finally:
self.__lock.release()
##
# Check if a ticket is in the list.
#

View File

@ -21,11 +21,12 @@ __author__ = "Steven Hiscocks"
__copyright__ = "Copyright (c) 2013 Steven Hiscocks"
__license__ = "GPL"
import sys
import shutil, time
import sqlite3
import json
import locale
import shutil
import sqlite3
import sys
import time
from functools import wraps
from threading import RLock
@ -37,17 +38,55 @@ from ..helpers import getLogger
logSys = getLogger(__name__)
if sys.version_info >= (3,):
sqlite3.register_adapter(
dict,
lambda x: json.dumps(x, ensure_ascii=False).encode(
locale.getpreferredencoding(), 'replace'))
sqlite3.register_converter(
"JSON",
lambda x: json.loads(x.decode(
locale.getpreferredencoding(), 'replace')))
def _json_dumps_safe(x):
try:
x = json.dumps(x, ensure_ascii=False).encode(
locale.getpreferredencoding(), 'replace')
except Exception, e: # pragma: no cover
logSys.error('json dumps failed: %s', e)
x = '{}'
return x
def _json_loads_safe(x):
try:
x = json.loads(x.decode(
locale.getpreferredencoding(), 'replace'))
except Exception, e: # pragma: no cover
logSys.error('json loads failed: %s', e)
x = {}
return x
else:
sqlite3.register_adapter(dict, json.dumps)
sqlite3.register_converter("JSON", json.loads)
def _normalize(x):
if isinstance(x, dict):
return dict((_normalize(k), _normalize(v)) for k, v in x.iteritems())
elif isinstance(x, list):
return [_normalize(element) for element in x]
elif isinstance(x, unicode):
return x.encode(locale.getpreferredencoding())
else:
return x
def _json_dumps_safe(x):
try:
x = json.dumps(_normalize(x), ensure_ascii=False).decode(
locale.getpreferredencoding(), 'replace')
except Exception, e: # pragma: no cover
logSys.error('json dumps failed: %s', e)
x = '{}'
return x
def _json_loads_safe(x):
try:
x = _normalize(json.loads(x.decode(
locale.getpreferredencoding(), 'replace')))
except Exception, e: # pragma: no cover
logSys.error('json loads failed: %s', e)
x = {}
return x
sqlite3.register_adapter(dict, _json_dumps_safe)
sqlite3.register_converter("JSON", _json_loads_safe)
def commitandrollback(f):
@wraps(f)
@ -57,6 +96,7 @@ def commitandrollback(f):
return f(self, self._db.cursor(), *args, **kwargs)
return wrapper
class Fail2BanDb(object):
"""Fail2Ban database for storing persistent data.
@ -136,6 +176,7 @@ class Fail2BanDb(object):
"CREATE INDEX bips_timeofban ON bips(timeofban);" \
"CREATE INDEX bips_ip ON bips(ip);" \
def __init__(self, filename, purgeAge=24*60*60, outDatedFactor=3):
try:
self._lock = RLock()
@ -410,7 +451,7 @@ class Fail2BanDb(object):
"INSERT INTO bans(jail, ip, timeofban, bantime, bancount, data) VALUES(?, ?, ?, ?, ?, ?)",
(jail.name, ticket.getIP(), int(round(ticket.getTime())), ticket.getBanTime(jail.actions.getBanTime()), ticket.getBanCount(),
{"matches": ticket.getMatches(),
"failures": ticket.getAttempt()}))
"failures": ticket.getAttempt()}))
cur.execute(
"INSERT OR REPLACE INTO bips(ip, jail, timeofban, bantime, bancount, data) VALUES(?, ?, ?, ?, ?, ?)",
(ticket.getIP(), jail.name, int(round(ticket.getTime())), ticket.getBanTime(jail.actions.getBanTime()), ticket.getBanCount(),
@ -425,8 +466,8 @@ class Fail2BanDb(object):
----------
jail : Jail
Jail in which the ban has occurred.
ticket : BanTicket
Ticket of the ban to be removed.
ip : str
IP to be removed.
"""
queryArgs = (jail.name, ip);
cur.execute(
@ -476,8 +517,8 @@ class Fail2BanDb(object):
tickets = []
for ip, timeofban, data in self._getBans(**kwargs):
#TODO: Implement data parts once arbitrary match keys completed
tickets.append(FailTicket(ip, timeofban, data['matches']))
tickets[-1].setAttempt(data['failures'])
tickets.append(FailTicket(ip, timeofban, data.get('matches')))
tickets[-1].setAttempt(data.get('failures', 1))
return tickets
def getBansMerged(self, ip=None, jail=None, bantime=None):
@ -529,8 +570,8 @@ class Fail2BanDb(object):
prev_banip = banip
matches = []
failures = 0
matches.extend(data['matches'])
failures += data['failures']
matches.extend(data.get('matches', []))
failures += data.get('failures', 1)
prev_timeofban = timeofban
ticket = FailTicket(banip, prev_timeofban, matches)
ticket.setAttempt(failures)

View File

@ -31,6 +31,7 @@ logSys = getLogger(__name__)
logLevel = 6
class DateDetector(object):
"""Manages one or more date templates to find a date within a log line.

View File

@ -155,6 +155,7 @@ class DateEpoch(DateTemplate):
return (float(dateMatch.group()), dateMatch)
return None
class DatePatternRegex(DateTemplate):
"""Date template, with regex/pattern
@ -238,6 +239,7 @@ class DatePatternRegex(DateTemplate):
if value is not None)
return reGroupDictStrptime(groupdict), dateMatch
class DateTai64n(DateTemplate):
"""A date template which matches TAI64N formate timestamps.

View File

@ -29,6 +29,7 @@ from ..helpers import getLogger
# Gets the instance of the logger.
logSys = getLogger(__name__)
class FailData:
def __init__(self):

View File

@ -34,6 +34,7 @@ from ..helpers import getLogger
# Gets the instance of the logger.
logSys = getLogger(__name__)
class FailManager:
def __init__(self):
@ -91,7 +92,7 @@ class FailManager:
ip = ticket.getIP()
unixTime = ticket.getTime()
matches = ticket.getMatches()
if self.__failList.has_key(ip):
if ip in self.__failList:
fData = self.__failList[ip]
if fData.getLastReset() < unixTime - self.__maxTime:
fData.setLastReset(unixTime)
@ -139,7 +140,7 @@ class FailManager:
self.__lock.release()
def __delFailure(self, ip):
if self.__failList.has_key(ip):
if ip in self.__failList:
del self.__failList[ip]
def toBan(self, ip=None):
@ -157,5 +158,6 @@ class FailManager:
finally:
self.__lock.release()
class FailManagerEmpty(Exception):
pass

View File

@ -21,7 +21,10 @@ __author__ = "Cyril Jaquier"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import re, sre_constants, sys
import re
import sre_constants
import sys
##
# Regular expression class.
@ -55,6 +58,7 @@ class Regex:
except sre_constants.error:
raise RegexException("Unable to compile regular expression '%s'" %
regex)
def __str__(self):
return "%s(%r)" % (self.__class__.__name__, self._regex)
##
@ -91,7 +95,6 @@ class Regex:
except ValueError:
self._matchLineEnd = len(self._matchCache.string)
lineCount1 = self._matchCache.string.count(
"\n", 0, self._matchLineStart)
lineCount2 = self._matchCache.string.count(
@ -182,6 +185,7 @@ class Regex:
else:
return ["".join(line) for line in self._matchedTupleLines]
##
# Exception dedicated to the class Regex.

View File

@ -21,7 +21,14 @@ __author__ = "Cyril Jaquier and Fail2Ban Contributors"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2013 Yaroslav Halchenko"
__license__ = "GPL"
import re, os, fcntl, sys, locale, codecs, datetime, logging
import codecs
import datetime
import fcntl
import locale
import logging
import os
import re
import sys
from .failmanager import FailManagerEmpty, FailManager
from .observer import Observers
@ -44,6 +51,7 @@ logSys = getLogger(__name__)
# that matches a given regular expression. This class is instantiated by
# a Jail object.
class Filter(JailThread):
##
@ -82,7 +90,6 @@ class Filter(JailThread):
self.dateDetector.addDefaultTemplate()
logSys.debug("Created %s" % self)
def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, self.jail)
@ -105,7 +112,6 @@ class Filter(JailThread):
logSys.error(e)
raise e
def delFailRegex(self, index):
try:
del self.__failRegex[index]
@ -339,6 +345,10 @@ class Filter(JailThread):
logSys.debug("Remove " + ip + " from ignore list")
self.__ignoreIpList.remove(ip)
def logIgnoreIp(self, ip, log_ignore, ignore_source="unknown source"):
if log_ignore:
logSys.info("[%s] Ignore %s by %s" % (self.jail.name, ip, ignore_source))
def getIgnoreIP(self):
return self.__ignoreIpList
@ -350,7 +360,7 @@ class Filter(JailThread):
# @param ip IP address
# @return True if IP address is in ignore list
def inIgnoreIPList(self, ip):
def inIgnoreIPList(self, ip, log_ignore=False):
for i in self.__ignoreIpList:
# An empty string is always false
if i == "":
@ -364,26 +374,29 @@ class Filter(JailThread):
"(?<=b)1+", bin(DNSUtils.addr2bin(s[1]))).group())
s[1] = long(s[1])
try:
a = DNSUtils.cidr(s[0], s[1])
b = DNSUtils.cidr(ip, s[1])
a = DNSUtils.addr2bin(s[0], cidr=s[1])
b = DNSUtils.addr2bin(ip, cidr=s[1])
except Exception:
# Check if IP in DNS
ips = DNSUtils.dnsToIp(i)
if ip in ips:
self.logIgnoreIp(ip, log_ignore, ignore_source="dns")
return True
else:
continue
if a == b:
self.logIgnoreIp(ip, log_ignore, ignore_source="ip")
return True
if self.__ignoreCommand:
command = CommandAction.replaceTag(self.__ignoreCommand, { 'ip': ip } )
logSys.debug('ignore command: ' + command)
return CommandAction.executeCmd(command)
ret_ignore = CommandAction.executeCmd(command)
self.logIgnoreIp(ip, log_ignore and ret_ignore, ignore_source="command")
return ret_ignore
return False
def processLine(self, line, date=None, returnRawHost=False,
checkAllRegex=False):
"""Split the time portion from log msg and return findFailures on them
@ -421,8 +434,7 @@ class Filter(JailThread):
logSys.debug("Ignore line since time %s < %s - %s"
% (unixTime, MyTime.time(), self.getFindTime()))
break
if self.inIgnoreIPList(ip):
logSys.info("[%s] Ignore %s" % (self.jail.name, ip))
if self.inIgnoreIPList(ip, log_ignore=True):
continue
logSys.info(
"[%s] Found %s - %s", self.jail.name, ip, datetime.datetime.fromtimestamp(unixTime).strftime("%Y-%m-%d %H:%M:%S")
@ -537,8 +549,7 @@ class Filter(JailThread):
logSys.error(e)
return failList
@property
def status(self):
def status(self, flavor="basic"):
"""Status of failures detected by filter.
"""
ret = [("Currently failed", self.failManager.size()),
@ -578,7 +589,6 @@ class FileFilter(Filter):
# to be overridden by backends
pass
##
# Delete a log path
#
@ -774,11 +784,10 @@ class FileFilter(Filter):
logSys.debug("Position %s from %s, found time %s (%s) within %s seeks", lastpos, fs, unixTime,
(datetime.datetime.fromtimestamp(unixTime).strftime("%Y-%m-%d %H:%M:%S") if unixTime is not None else ''), cntr)
@property
def status(self):
def status(self, flavor="basic"):
"""Status of Filter plus files being monitored.
"""
ret = super(FileFilter, self).status
ret = super(FileFilter, self).status(flavor=flavor)
path = [m.getFileName() for m in self.getLogPath()]
ret.append(("File list", path))
return ret
@ -799,6 +808,7 @@ except ImportError: # pragma: no cover
import md5
md5sum = md5.new
class FileContainer:
def __init__(self, filename, encoding, tail = False):
@ -893,11 +903,13 @@ class FileContainer:
line = line.decode(self.getEncoding(), 'strict')
except UnicodeDecodeError:
logSys.warning(
"Error decoding line from '%s' with '%s'. Continuing "
"Error decoding line from '%s' with '%s'."
" Consider setting logencoding=utf-8 (or another appropriate"
" encoding) for this jail. Continuing"
" to process line ignoring invalid characters: %r" %
(self.getFileName(), self.getEncoding(), line))
if sys.version_info >= (3,): # In python3, must be decoded
line = line.decode(self.getEncoding(), 'ignore')
# decode with replacing error chars:
line = line.decode(self.getEncoding(), 'replace')
return line
def close(self):
@ -933,7 +945,9 @@ class JournalFilter(Filter): # pragma: systemd no cover
# This class contains only static methods used to handle DNS and IP
# addresses.
import socket, struct
import socket
import struct
class DNSUtils:
@ -951,6 +965,14 @@ class DNSUtils:
% (dns, e))
return list()
@staticmethod
def ipToName(ip):
try:
return socket.gethostbyaddr(ip)[0]
except socket.error, e:
logSys.debug("Unable to find a name for the IP %s: %s" % (ip, e))
return None
@staticmethod
def searchIP(text):
""" Search if an IP address if directly available and return
@ -997,22 +1019,18 @@ class DNSUtils:
return ipList
@staticmethod
def cidr(i, n):
""" Convert an IP address string with a CIDR mask into a 32-bit
integer.
def addr2bin(ipstring, cidr=None):
""" Convert a string IPv4 address into binary form.
If cidr is supplied, return the network address for the given block
"""
# 32-bit IPv4 address mask
MASK = 0xFFFFFFFFL
return ~(MASK >> n) & MASK & DNSUtils.addr2bin(i)
if cidr is None:
return struct.unpack("!L", socket.inet_aton(ipstring))[0]
else:
MASK = 0xFFFFFFFFL
return ~(MASK >> cidr) & MASK & DNSUtils.addr2bin(ipstring)
@staticmethod
def addr2bin(string):
""" Convert a string IPv4 address into an unsigned integer.
def bin2addr(ipbin):
""" Convert a binary IPv4 address into string n.n.n.n form.
"""
return struct.unpack("!L", socket.inet_aton(string))[0]
@staticmethod
def bin2addr(addr):
""" Convert a numeric IPv4 address into string n.n.n.n form.
"""
return socket.inet_ntoa(struct.pack("!L", addr))
return socket.inet_ntoa(struct.pack("!L", ipbin))

View File

@ -23,7 +23,8 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2012 Yaroslav Halchenko"
__license__ = "GPL"
import time, fcntl
import fcntl
import time
import gamin
@ -35,6 +36,7 @@ from ..helpers import getLogger
# Gets the instance of the logger.
logSys = getLogger(__name__)
##
# Log reader class.
#
@ -60,16 +62,14 @@ class FilterGamin(FileFilter):
fcntl.fcntl(fd, fcntl.F_SETFD, flags|fcntl.FD_CLOEXEC)
logSys.debug("Created FilterGamin")
def callback(self, path, event):
logSys.debug("Got event: " + `event` + " for " + path)
logSys.debug("Got event: " + repr(event) + " for " + path)
if event in (gamin.GAMCreated, gamin.GAMChanged, gamin.GAMExists):
logSys.debug("File changed: " + path)
self.__modified = True
self._process_file(path)
def _process_file(self, path):
"""Process a given file
@ -121,7 +121,6 @@ class FilterGamin(FileFilter):
logSys.debug(self.jail.name + ": filter terminated")
return True
def stop(self):
super(FilterGamin, self).stop()
self.__cleanup()

View File

@ -24,7 +24,8 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier; 2012 Yaroslav Halchenko"
__license__ = "GPL"
import time, os
import os
import time
from .failmanager import FailManagerEmpty
from .filter import FileFilter
@ -34,6 +35,7 @@ from ..helpers import getLogger
# Gets the instance of the logger.
logSys = getLogger(__name__)
##
# Log reader class.
#

View File

@ -51,6 +51,7 @@ except Exception, e:
# Gets the instance of the logger.
logSys = getLogger(__name__)
##
# Log reader class.
#
@ -73,7 +74,6 @@ class FilterPyinotify(FileFilter):
self.__watches = dict()
logSys.debug("Created FilterPyinotify")
def callback(self, event, origin=''):
logSys.debug("%sCallback for Event: %s", origin, event)
path = event.pathname
@ -95,7 +95,6 @@ class FilterPyinotify(FileFilter):
self._process_file(path)
def _process_file(self, path):
"""Process a given file
@ -112,7 +111,6 @@ class FilterPyinotify(FileFilter):
self.dateDetector.sortTemplate()
self.__modified = False
def _addFileWatcher(self, path):
wd = self.__monitor.add_watch(path, pyinotify.IN_MODIFY)
self.__watches.update(wd)
@ -144,7 +142,6 @@ class FilterPyinotify(FileFilter):
self._addFileWatcher(path)
self._process_file(path)
##
# Delete a log path
#
@ -163,7 +160,6 @@ class FilterPyinotify(FileFilter):
self.__monitor.rm_watch(wdInt)
logSys.debug("Removed monitor for the parent directory %s", path_dir)
##
# Main loop.
#

View File

@ -22,7 +22,8 @@ __author__ = "Steven Hiscocks"
__copyright__ = "Copyright (c) 2013 Steven Hiscocks"
__license__ = "GPL"
import datetime, time
import datetime
import time
from distutils.version import LooseVersion
from systemd import journal
@ -37,6 +38,7 @@ from ..helpers import getLogger
# Gets the instance of the logger.
logSys = getLogger(__name__)
##
# Journal reader class.
#
@ -60,7 +62,6 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
self.setDatePattern(None)
logSys.debug("Created FilterSystemd")
##
# Add a journal match filters from list structure
#
@ -259,9 +260,8 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
or "jailless") +" filter terminated")
return True
@property
def status(self):
ret = super(FilterSystemd, self).status
def status(self, flavor="basic"):
ret = super(FilterSystemd, self).status(flavor=flavor)
ret.append(("Journal matches",
[" + ".join(" ".join(match) for match in self.__matches)]))
return ret

View File

@ -23,7 +23,10 @@ __author__ = "Cyril Jaquier, Lee Clemens, Yaroslav Halchenko"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2012 Lee Clemens, 2012 Yaroslav Halchenko"
__license__ = "GPL"
import Queue, logging, math, random
import logging
import math
import random
import Queue
from .actions import Actions
from ..helpers import getLogger
@ -32,6 +35,7 @@ from .mytime import MyTime
# Gets the instance of the logger.
logSys = getLogger(__name__)
class Jail:
"""Fail2Ban jail, which manages a filter and associated actions.
@ -120,7 +124,6 @@ class Jail:
raise RuntimeError(
"Failed to initialize any backend for Jail %r" % self.name)
def _initPolling(self):
from filterpoll import FilterPoll
logSys.info("Jail '%s' uses poller" % self.name)
@ -179,13 +182,12 @@ class Jail:
self.filter.idle = value
self.actions.idle = value
@property
def status(self):
def status(self, flavor="basic"):
"""The status of the jail.
"""
return [
("Filter", self.filter.status),
("Actions", self.actions.status),
("Filter", self.filter.status(flavor=flavor)),
("Actions", self.actions.status(flavor=flavor)),
]
def putFailTicket(self, ticket):
@ -269,7 +271,7 @@ class Jail:
forbantime = self.actions.getBanTime()
for ticket in self.database.getCurrentBans(jail=self, forbantime=forbantime):
#logSys.debug('restored ticket: %s', ticket)
if not self.filter.inIgnoreIPList(ticket.getIP()):
if not self.filter.inIgnoreIPList(ticket.getIP(), log_ignore=True):
# mark ticked was restored from database - does not put it again into db:
ticket.setRestored(True)
self.putFailTicket(ticket)

View File

@ -26,10 +26,11 @@ __license__ = "GPL"
import sys
from threading import Thread
from abc import abstractproperty, abstractmethod
from abc import abstractmethod
from ..helpers import excepthook
class JailThread(Thread):
"""Abstract class for threading elements in Fail2Ban.
@ -59,6 +60,7 @@ class JailThread(Thread):
# excepthook workaround for threads, derived from:
# http://bugs.python.org/issue1230540#msg91244
run = self.run
def run_with_except_hook(*args, **kwargs):
try:
run(*args, **kwargs)
@ -66,8 +68,8 @@ class JailThread(Thread):
excepthook(*sys.exc_info())
self.run = run_with_except_hook
@abstractproperty
def status(self): # pragma: no cover - abstract
@abstractmethod
def status(self, flavor="basic"): # pragma: no cover - abstract
"""Abstract - Should provide status information.
"""
pass

View File

@ -21,7 +21,10 @@ __author__ = "Cyril Jaquier"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import time, datetime, re
import datetime
import re
import time
##
# MyTime class.

View File

@ -25,7 +25,12 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
from threading import Lock, RLock
import logging, logging.handlers, sys, os, signal
import logging
import logging.handlers
import os
import signal
import stat
import sys
from .observer import Observers, ObserverThread
from .jails import Jails
@ -44,6 +49,7 @@ except ImportError:
# Dont print error here, as database may not even be used
Fail2BanDb = None
class Server:
def __init__(self, daemon = False):
@ -56,20 +62,32 @@ class Server:
self.__asyncServer = AsyncServer(self.__transm)
self.__logLevel = None
self.__logTarget = None
self.__syslogSocket = None
self.__autoSyslogSocketPaths = {
'Darwin': '/var/run/syslog',
'FreeBSD': '/var/run/log',
'Linux': '/dev/log',
}
# Set logging level
self.setLogLevel("INFO")
self.setLogTarget("STDOUT")
self.setSyslogSocket("auto")
def __sigTERMhandler(self, signum, frame):
logSys.debug("Caught signal %d. Exiting" % signum)
self.quit()
def __sigUSR1handler(self, signum, fname):
logSys.debug("Caught signal %d. Flushing logs" % signum)
self.flushLogs()
def start(self, sock, pidfile, force = False):
logSys.info("Starting Fail2ban v%s", version.version)
# Install signal handlers
signal.signal(signal.SIGTERM, self.__sigTERMhandler)
signal.signal(signal.SIGINT, self.__sigTERMhandler)
signal.signal(signal.SIGUSR1, self.__sigUSR1handler)
# Ensure unhandled exceptions are logged
sys.excepthook = excepthook
@ -131,7 +149,6 @@ class Server:
finally:
self.__loggingLock.release()
def addJail(self, name, backend):
# Create an observer if not yet created and start it:
if Observers.Main is None:
@ -337,9 +354,9 @@ class Server:
finally:
self.__lock.release()
def statusJail(self, name):
return self.__jails[name].status
def statusJail(self, name, flavor="basic"):
return self.__jails[name].status(flavor=flavor)
# Logging
##
@ -377,7 +394,7 @@ class Server:
return self.__logLevel
finally:
self.__loggingLock.release()
##
# Sets the logging target.
#
@ -393,7 +410,21 @@ class Server:
# Syslog daemons already add date to the message.
formatter = logging.Formatter("%(name)s[%(process)d]: %(levelname)s %(message)s")
facility = logging.handlers.SysLogHandler.LOG_DAEMON
hdlr = logging.handlers.SysLogHandler("/dev/log", facility=facility)
if self.__syslogSocket == "auto":
import platform
self.__syslogSocket = self.__autoSyslogSocketPaths.get(
platform.system())
if self.__syslogSocket is not None\
and os.path.exists(self.__syslogSocket)\
and stat.S_ISSOCK(os.stat(
self.__syslogSocket).st_mode):
hdlr = logging.handlers.SysLogHandler(
self.__syslogSocket, facility=facility)
else:
logSys.error(
"Syslog socket file: %s does not exists"
" or is not a socket" % self.__syslogSocket)
return False
elif target == "STDOUT":
hdlr = logging.StreamHandler(sys.stdout)
elif target == "STDERR":
@ -430,20 +461,44 @@ class Server:
# Does not display this message at startup.
if not self.__logTarget is None:
logSys.info("Start Fail2ban v%s", version.version)
logSys.info("Changed logging target to %s", target)
logSys.info(
"Changed logging target to %s for Fail2ban v%s"
% ((target
if target != "SYSLOG"
else "%s (%s)"
% (target, self.__syslogSocket)),
version.version))
# Sets the logging target.
self.__logTarget = target
return True
finally:
self.__loggingLock.release()
##
# Sets the syslog socket.
#
# syslogsocket is the full path to the syslog socket
# @param syslogsocket the syslog socket path
def setSyslogSocket(self, syslogsocket):
self.__syslogSocket = syslogsocket
# Conditionally reload, logtarget depends on socket path when SYSLOG
return self.__logTarget != "SYSLOG"\
or self.setLogTarget(self.__logTarget)
def getLogTarget(self):
try:
self.__loggingLock.acquire()
return self.__logTarget
finally:
self.__loggingLock.release()
def getSyslogSocket(self):
try:
self.__loggingLock.acquire()
return self.__syslogSocket
finally:
self.__loggingLock.release()
def flushLogs(self):
if self.__logTarget not in ['STDERR', 'STDOUT', 'SYSLOG']:
for handler in getLogger("fail2ban").handlers:
@ -461,24 +516,27 @@ class Server:
return "flushed"
def setDatabase(self, filename):
if len(self.__jails) == 0:
if filename.lower() == "none":
self.__db = None
else:
if Fail2BanDb is not None:
self.__db = Fail2BanDb(filename)
self.__db.delAllJails()
else:
logSys.error(
"Unable to import fail2ban database module as sqlite "
"is not available.")
else:
# if not changed - nothing to do
if self.__db and self.__db.filename == filename:
return
if not self.__db and filename.lower() == 'none':
return
if len(self.__jails) != 0:
raise RuntimeError(
"Cannot change database when there are jails present")
if filename.lower() == "none":
self.__db = None
else:
if Fail2BanDb is not None:
self.__db = Fail2BanDb(filename)
self.__db.delAllJails()
else:
logSys.error(
"Unable to import fail2ban database module as sqlite "
"is not available.")
def getDatabase(self):
return self.__db
def __createDaemon(self): # pragma: no cover
""" Detach a process from the controlling terminal and run it in the

View File

@ -28,6 +28,7 @@ locale_time = LocaleTime()
timeRE = TimeRE()
timeRE['z'] = r"(?P<z>Z|[+-]\d{2}(?::?[0-5]\d)?)"
def reGroupDictStrptime(found_dict):
"""Return time from dictionary of strptime fields

View File

@ -30,6 +30,7 @@ from .mytime import MyTime
# Gets the instance of the logger.
logSys = getLogger(__name__)
class Ticket:
def __init__(self, ip, time=None, matches=None):
@ -59,7 +60,7 @@ class Ticket:
def __eq__(self, other):
try:
return self.__ip == other.__ip and \
round(self.__time,2) == round(other.__time,2) and \
round(self.__time, 2) == round(other.__time, 2) and \
self.__attempt == other.__attempt and \
self.__matches == other.__matches
except AttributeError:

Some files were not shown because too many files have changed in this diff Show More