Long delayed and possibly incomplete 0.9.2 release:

ver. 0.9.2 (2015/04/26) - better-quick-now-than-later
 ----------
 
 - Fixes:
    * 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 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
      multiple expressions was not possible).
    * filters.d/exim.conf - cover different settings of exim logs
      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:
    - New filters:
      - postfix-rbl  Thanks Lee Clemens
      - apache-fakegooglebot.conf  Thanks Lee Clemens
      - nginx-botsearch  Thanks Frantisek Sumsal
    - 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/
    - 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)
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1
 
 iEYEABECAAYFAlU9lg8ACgkQjRFFY3XAJMgFTgCfeDp7M0Xh1J9sbnehVL5fnMT3
 xOoAnA0qN8bR/zGXf1ofDPsZuPEo90k6
 =Iyl6
 -----END PGP SIGNATURE-----

Merge tag '0.9.2' into debian

Long delayed and possibly incomplete 0.9.2 release:

ver. 0.9.2 (2015/04/26) - better-quick-now-than-later
----------

- Fixes:
   * 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 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
     multiple expressions was not possible).
   * filters.d/exim.conf - cover different settings of exim logs
     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:
   - New filters:
     - postfix-rbl  Thanks Lee Clemens
     - apache-fakegooglebot.conf  Thanks Lee Clemens
     - nginx-botsearch  Thanks Frantisek Sumsal
   - 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/
   - 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)

* tag '0.9.2': (140 commits)
  DOC: Slight tune up to RELEASE doc -- no need for PYTHONPATH to run tests
  MANIFEST: updated for some new files, sorted all entries, removed some duplicates
  Initial changes for the release -- simplified ChangeLog header etc
  added \s after host
  replaced .* before rhost with regex matching all the previous fields
  Fixed typo in filter description authentification instead of authentication
  Fixed the UTC -> CEST difference...
  Added changes to ChangeLog & updated sample test cases
  updated filter.d/sshd.conf
  Do not run smtp tests if no_network set
  BF: if install pypy -- come back to original directory
  BF(OSX): apparently exceptions could not be compared for identity, use repr
  very long time resolving IP for address "abcdef" on some PDC, under NAT etc. - replaced via "abcdef.abcdef" to prevent searching in local domains;
  fix test for invalid IP (use TEST-NET-1 according to RFC 5737): since fef031b3cd failed, because on some platforms like vm:debian 10.0.0.0 returns 'localhost' (intern network).
  Match hacking attempt IP instead of asterisk server IP (closes #1000)
  BF: fixing up version comparison for pypy.  Issue appeared in 2.5.0
  ENH: minor formatting, no functional changes
  BF: do not expect setting logtarget to SYSLOG to work on non-Linuxes
  Added a comment about systemd backend for jails with logs outside of journal (Closes #959)
  DOC: make a warning for recidive jail to increase dbpurgeage (Closes #964)
  ...
pull/1858/head
Yaroslav Halchenko 2015-04-26 21:51:19 -04:00
commit a1dbfdb478
92 changed files with 1500 additions and 397 deletions

1
.gitignore vendored
View File

@ -9,3 +9,4 @@ htmlcov
*.bak *.bak
__pycache__ __pycache__
.vagrant/ .vagrant/
.idea/

View File

@ -8,12 +8,17 @@ python:
- "3.3" - "3.3"
- "3.4" - "3.4"
- "pypy" - "pypy"
- "pypy3"
before_install: before_install:
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then sudo apt-get update -qq; fi - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then travis_retry sudo apt-get update -qq; fi
install: install:
- pip install pyinotify - travis_retry 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* || $TRAVIS_PYTHON_VERSION == 'pypy' ]]; then travis_retry pip install dnspython; fi
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then cd ..; pip install -q coveralls; cd -; fi - if [[ $TRAVIS_PYTHON_VERSION == 3* || $TRAVIS_PYTHON_VERSION == 'pypy3' ]]; then travis_retry pip install dnspython3; fi
- 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
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then cd ..; travis_retry pip install -q coveralls; cd -; fi
# overcome buggy pypy
- if [[ $TRAVIS_PYTHON_VERSION == pypy ]] ; then dpkg --compare-versions $(pypy --version 2>&1 | awk '/PyPy/{print $2;}') ge 2.5.1 || { d=$PWD; cd /tmp; wget http://buildbot.pypy.org/nightly/trunk/pypy-c-jit-latest-linux64.tar.bz2; tar -xjvf pypy*bz2; cd pypy-*/bin/; export PATH=$PWD:$PATH; cd $d; } ; fi
script: script:
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then coverage run --rcfile=.travis_coveragerc setup.py test; else python setup.py test; fi - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then coverage run --rcfile=.travis_coveragerc setup.py test; else python setup.py test; fi
# test installation # test installation

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)? ### You would like to contribute (new filters/actions/code/documentation)?
send a [pull request](https://github.com/fail2ban/fail2ban/pulls) 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)

View File

@ -3,17 +3,20 @@
| _/ _` | | |/ /| '_ \/ _` | ' \ | _/ _` | | |/ /| '_ \/ _` | ' \
|_| \__,_|_|_/___|_.__/\__,_|_||_| |_| \__,_|_|_/___|_.__/\__,_|_||_|
================================================================================ Fail2Ban: Changelog
Fail2Ban (version 0.9.1.dev) 2014/10/29 ===================
================================================================================
ver. 0.9.2 (2014/XX/XXX) - wanna-be-released ver. 0.9.2 (2015/04/26) - better-quick-now-than-later
----------- ----------
- Fixes: - Fixes:
* 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 * $ typo in jail.conf. Thanks Skibbi. Debian bug #767255
* grep'ing for IP in *mail-whois-lines.conf should now match also * 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 * jail.conf
- php-url-fopen: separate logpath entries by newline - php-url-fopen: separate logpath entries by newline
* failregex declared direct in jail was joined to single line (specifying of * failregex declared direct in jail was joined to single line (specifying of
@ -21,19 +24,67 @@ ver. 0.9.2 (2014/XX/XXX) - wanna-be-released
* filters.d/exim.conf - cover different settings of exim logs * filters.d/exim.conf - cover different settings of exim logs
details. Thanks bes.internal details. Thanks bes.internal
* filter.d/postfix-sasl.conf - failregex is now case insensitive * 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: - New Features:
- New filters:
- postfix-rbl Thanks Lee Clemens
- apache-fakegooglebot.conf Thanks Lee Clemens
- nginx-botsearch Thanks Frantisek Sumsal
- 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`. - New interpolation feature for config readers - `%(known/parameter)s`.
(means last known option with name `parameter`). This interpolation makes (means last known option with name `parameter`). This interpolation makes
possible to extend a stock filter or jail regexp in .local file possible to extend a stock filter or jail regexp in .local file
(opposite to simply set failregex/ignoreregex that overwrites it), (opposite to simply set failregex/ignoreregex that overwrites it),
see gh-867. 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: - Enhancements:
* Enable multiport for firewallcmd-new action. Closes gh-834 * Enable multiport for firewallcmd-new action. Closes gh-834
* files/debian-initd migrated from the debian branch and should be * files/debian-initd migrated from the debian branch and should be
suitable for manual installations now (thanks Juan Karlo de Guzman) 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 ver. 0.9.1 (2014/10/29) - better, faster, stronger

348
MANIFEST
View File

@ -9,14 +9,155 @@ RELEASE
THANKS THANKS
TODO TODO
Vagrantfile 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-2to3
fail2ban-testcases-all fail2ban-testcases-all
fail2ban-testcases-all-python3 fail2ban-testcases-all-python3
bin/fail2ban-client fail2ban/__init__.py
bin/fail2ban-server
bin/fail2ban-testcases
bin/fail2ban-regex
doc/run-rootless.txt
fail2ban/client/__init__.py fail2ban/client/__init__.py
fail2ban/client/actionreader.py fail2ban/client/actionreader.py
fail2ban/client/beautifier.py fail2ban/client/beautifier.py
@ -28,6 +169,9 @@ fail2ban/client/fail2banreader.py
fail2ban/client/filterreader.py fail2ban/client/filterreader.py
fail2ban/client/jailreader.py fail2ban/client/jailreader.py
fail2ban/client/jailsreader.py fail2ban/client/jailsreader.py
fail2ban/exceptions.py
fail2ban/helpers.py
fail2ban/protocol.py
fail2ban/server/__init__.py fail2ban/server/__init__.py
fail2ban/server/action.py fail2ban/server/action.py
fail2ban/server/actions.py fail2ban/server/actions.py
@ -64,6 +208,8 @@ fail2ban/tests/clientreadertestcase.py
fail2ban/tests/config/action.d/brokenaction.conf fail2ban/tests/config/action.d/brokenaction.conf
fail2ban/tests/config/fail2ban.conf fail2ban/tests/config/fail2ban.conf
fail2ban/tests/config/filter.d/simple.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/jail.conf
fail2ban/tests/config/paths-common.conf fail2ban/tests/config/paths-common.conf
fail2ban/tests/config/paths-debian.conf fail2ban/tests/config/paths-debian.conf
@ -74,6 +220,7 @@ fail2ban/tests/datedetectortestcase.py
fail2ban/tests/dummyjail.py fail2ban/tests/dummyjail.py
fail2ban/tests/failmanagertestcase.py fail2ban/tests/failmanagertestcase.py
fail2ban/tests/files/action.d/action.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_errors.py
fail2ban/tests/files/action.d/action_modifyainfo.py fail2ban/tests/files/action.d/action_modifyainfo.py
fail2ban/tests/files/action.d/action_noAction.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-badbots
fail2ban/tests/files/logs/apache-botscripts fail2ban/tests/files/logs/apache-botscripts
fail2ban/tests/files/logs/apache-botsearch fail2ban/tests/files/logs/apache-botsearch
fail2ban/tests/files/logs/apache-fakegooglebot
fail2ban/tests/files/logs/apache-modsecurity fail2ban/tests/files/logs/apache-modsecurity
fail2ban/tests/files/logs/apache-nohome fail2ban/tests/files/logs/apache-nohome
fail2ban/tests/files/logs/apache-noscript fail2ban/tests/files/logs/apache-noscript
@ -135,6 +283,7 @@ fail2ban/tests/files/logs/monit
fail2ban/tests/files/logs/mysqld-auth fail2ban/tests/files/logs/mysqld-auth
fail2ban/tests/files/logs/nagios fail2ban/tests/files/logs/nagios
fail2ban/tests/files/logs/named-refused fail2ban/tests/files/logs/named-refused
fail2ban/tests/files/logs/nginx-botsearch
fail2ban/tests/files/logs/nginx-http-auth fail2ban/tests/files/logs/nginx-http-auth
fail2ban/tests/files/logs/nsd fail2ban/tests/files/logs/nsd
fail2ban/tests/files/logs/openwebmail 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/php-url-fopen
fail2ban/tests/files/logs/portsentry fail2ban/tests/files/logs/portsentry
fail2ban/tests/files/logs/postfix fail2ban/tests/files/logs/postfix
fail2ban/tests/files/logs/postfix-rbl
fail2ban/tests/files/logs/postfix-sasl fail2ban/tests/files/logs/postfix-sasl
fail2ban/tests/files/logs/proftpd fail2ban/tests/files/logs/proftpd
fail2ban/tests/files/logs/pure-ftpd fail2ban/tests/files/logs/pure-ftpd
@ -182,170 +332,38 @@ fail2ban/tests/samplestestcase.py
fail2ban/tests/servertestcase.py fail2ban/tests/servertestcase.py
fail2ban/tests/sockettestcase.py fail2ban/tests/sockettestcase.py
fail2ban/tests/utils.py fail2ban/tests/utils.py
setup.py
setup.cfg
fail2ban/__init__.py
fail2ban/exceptions.py
fail2ban/helpers.py
fail2ban/version.py fail2ban/version.py
fail2ban/protocol.py files/bash-completion
kill-server files/cacti/README
config/action.d/apf.conf files/cacti/cacti_host_template_fail2ban.xml
config/action.d/badips.conf files/cacti/fail2ban_stats.sh
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/debian-initd 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/gentoo-confd
files/redhat-initd files/gentoo-initd
files/ipmasq-ZZZzzz_fail2ban.rul
files/logwatch/fail2ban
files/macosx-initd files/macosx-initd
files/monit/fail2ban
files/nagios/README
files/nagios/check_fail2ban
files/redhat-initd
files/solaris-fail2ban.xml files/solaris-fail2ban.xml
files/solaris-svc-fail2ban files/solaris-svc-fail2ban
files/suse-initd files/suse-initd
files/fail2ban-logrotate kill-server
files/fail2ban.upstart man/fail2ban-client.1
files/logwatch/fail2ban man/fail2ban-client.h2m
files/cacti/fail2ban_stats.sh man/fail2ban-regex.1
files/cacti/cacti_host_template_fail2ban.xml man/fail2ban-regex.h2m
files/cacti/README man/fail2ban-server.1
files/nagios/check_fail2ban man/fail2ban-server.h2m
files/nagios/README man/fail2ban.1
files/bash-completion man/generate-man
files/fail2ban-tmpfiles.conf man/jail.conf.5
files/fail2ban.service setup.cfg
files/ipmasq-ZZZzzz_fail2ban.rul setup.py
files/gen_badbots

View File

@ -2,3 +2,4 @@ include ChangeLog COPYING DEVELOP FILTERS README.* THANKS TODO CONTRIBUTING* Vag
graft doc graft doc
graft files graft files
recursive-include config *.conf *.py 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 2015/04/26
## Fail2Ban: ban hosts that cause multiple authentication errors ## Fail2Ban: ban hosts that cause multiple authentication errors
@ -33,11 +33,12 @@ Optional:
- Linux >= 2.6.13 - Linux >= 2.6.13
- [gamin >= 0.0.21](http://www.gnome.org/~veillard/gamin) - [gamin >= 0.0.21](http://www.gnome.org/~veillard/gamin)
- [systemd >= 204](http://www.freedesktop.org/wiki/Software/systemd) - [systemd >= 204](http://www.freedesktop.org/wiki/Software/systemd)
- [dnspython](http://www.dnspython.org/)
To install, just do: To install, just do:
tar xvfj fail2ban-0.9.1.tar.bz2 tar xvfj fail2ban-0.9.2.tar.bz2
cd fail2ban-0.9.1 cd fail2ban-0.9.2
python setup.py install python setup.py install
This will install Fail2Ban into the python library directory. The executable This will install Fail2Ban into the python library directory. The executable

View File

@ -72,7 +72,7 @@ Preparation
* Ensure the tests work from the tarball:: * Ensure the tests work from the tarball::
cd /tmp/fail2ban-0.9.2/ && export PYTHONPATH=`pwd` && bin/fail2ban-testcases cd /tmp/fail2ban-0.9.2/ && bin/fail2ban-testcases
* Add/finalize the corresponding entry in the ChangeLog * Add/finalize the corresponding entry in the ChangeLog

4
THANKS
View File

@ -13,6 +13,7 @@ ag4ve (Shawn)
Alasdair D. Campbell Alasdair D. Campbell
Amir Caspi Amir Caspi
Amy Amy
Andrew St. Jean
Andrey G. Grozin Andrey G. Grozin
Andy Fragen Andy Fragen
Arturo 'Buanzo' Busleiman Arturo 'Buanzo' Busleiman
@ -33,11 +34,13 @@ Daniel B.
Daniel Black Daniel Black
David Nutter David Nutter
Derek Atkins Derek Atkins
Donald Yandt
Eric Gerbier Eric Gerbier
Enrico Labedzki Enrico Labedzki
Eugene Hopkinson (SlowRiot) Eugene Hopkinson (SlowRiot)
ftoppi ftoppi
François Boulogne François Boulogne
Frantisek Sumsal
Frédéric Frédéric
Georgiy Mernov Georgiy Mernov
Guilhem Lettron Guilhem Lettron
@ -82,6 +85,7 @@ Michael Hanselmann
Mika (mkl) Mika (mkl)
Nick Munger Nick Munger
onorua onorua
Orion Poplawski
Paul Marrapese Paul Marrapese
Paul Traina Paul Traina
Noel Butler Noel Butler

View File

@ -220,6 +220,7 @@ class Fail2banRegex(object):
self._datepattern_set = False self._datepattern_set = False
self._journalmatch = None self._journalmatch = None
self.share_config=dict()
self._filter = Filter(None) self._filter = Filter(None)
self._ignoreregex = list() self._ignoreregex = list()
self._failregex = list() self._failregex = list()
@ -260,38 +261,47 @@ class Fail2banRegex(object):
def readRegex(self, value, regextype): def readRegex(self, value, regextype):
assert(regextype in ('fail', 'ignore')) assert(regextype in ('fail', 'ignore'))
regex = regextype + 'regex' regex = regextype + 'regex'
if os.path.isfile(value): if os.path.isfile(value) or os.path.isfile(value + '.conf'):
print "Use %11s file : %s" % (regex, value) if os.path.basename(os.path.dirname(value)) == 'filter.d':
reader = FilterReader(value, 'fail2ban-regex-jail', {}) ## within filter.d folder - use standard loading algorithm to load filter completely (with .local etc.):
reader.setBaseDir(None) basedir = os.path.dirname(os.path.dirname(value))
value = os.path.splitext(os.path.basename(value))[0]
if reader.readexplicit(): print "Use %11s filter file : %s, basedir: %s" % (regex, value, basedir)
reader.getOptions(None) reader = FilterReader(value, 'fail2ban-regex-jail', {}, share_config=self.share_config, basedir=basedir)
readercommands = reader.convert() if not reader.read():
regex_values = [ print "ERROR: failed to load filter %s" % value
RegexStat(m[3]) return False
for m in filter(
lambda x: x[0] == 'set' and x[2] == "add%sregex" % regextype,
readercommands)]
# Read out and set possible value of maxlines
for command in readercommands:
if command[2] == "maxlines":
maxlines = int(command[3])
try:
self.setMaxLines(maxlines)
except ValueError:
print "ERROR: Invalid value for maxlines (%(maxlines)r) " \
"read from %(value)s" % locals()
return False
elif command[2] == 'addjournalmatch':
journalmatch = command[3]
self.setJournalMatch(shlex.split(journalmatch))
elif command[2] == 'datepattern':
datepattern = command[3]
self.setDatePattern(datepattern)
else: else:
print "ERROR: failed to read %s" % value ## foreign file - readexplicit this file and includes if possible:
return False 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(shlex.split(journalmatch))
elif command[2] == 'datepattern':
datepattern = command[3]
self.setDatePattern(datepattern)
else: else:
print "Use %11s line : %s" % (regex, shortstr(value)) print "Use %11s line : %s" % (regex, shortstr(value))
regex_values = [RegexStat(value)] regex_values = [RegexStat(value)]

View File

@ -111,6 +111,8 @@ class BadIPsAction(ActionBase):
------ ------
HTTPError HTTPError
Any issues with badips.com request. Any issues with badips.com request.
ValueError
If badips.com response didn't contain necessary information
""" """
try: try:
response = urlopen( response = urlopen(
@ -122,7 +124,13 @@ class BadIPsAction(ActionBase):
messages['err']) messages['err'])
raise raise
else: 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( categories_names = set(
value['Name'] for value in categories) value['Name'] for value in categories)
if incParents: if incParents:

View File

@ -38,7 +38,7 @@ actioncheck =
# Values: CMD # Values: CMD
# #
# requires an ipfw rule like "deny ip from table(1) to me" # 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 # Option: actionunban
@ -47,7 +47,7 @@ actionban = ipfw table <table> add <ip>
# Tags: See jail.conf(5) man page # Tags: See jail.conf(5) man page
# Values: CMD # 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] [Init]
# Option: table # Option: table

View File

@ -0,0 +1,53 @@
# Fail2Ban configuration file
#
# Author: Donald Yandt
# Because of the --remove-rules in stop this action requires firewalld-0.3.8+
[INCLUDES]
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>
actionstop = firewall-cmd --direct --remove-rule ipv4 filter <chain> 0 -j f2b-<name>
firewall-cmd --direct --remove-rules ipv4 filter f2b-<name>
firewall-cmd --direct --remove-chain ipv4 filter f2b-<name>
# 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 | sed -e 's, ,\n,g' | grep -q '^f2b-<name>$'
actionban = firewall-cmd --direct --add-rule ipv4 filter f2b-<name> 0 -s <ip> -j <blocktype>
actionunban = firewall-cmd --direct --remove-rule ipv4 filter f2b-<name> 0 -s <ip> -j <blocktype>
[Init]
# Default name of the chain
#
name = default
chain = INPUT_direct
# DEV NOTES:
#
# Author: Donald Yandt
# Uses "FirewallD" instead of the "iptables daemon".
#
#
# Output:
# actionstart:
# $ firewall-cmd --direct --add-chain ipv4 filter f2b-recidive
# success
# $ firewall-cmd --direct --add-rule ipv4 filter f2b-recidive 1000 -j RETURN
# success
# $ sudo firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 0 -j f2b-recidive
# success

View File

@ -0,0 +1,63 @@
# Fail2Ban configuration file
#
# Author: Donald Yandt
# Because of the --remove-rules in stop this action requires firewalld-0.3.8+
[INCLUDES]
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 -m state --state NEW -p <protocol> -m multiport --dports <port> -j f2b-<name>
actionstop = firewall-cmd --direct --remove-rule ipv4 filter <chain> 0 -m state --state NEW -p <protocol> -m multiport --dports <port> -j f2b-<name>
firewall-cmd --direct --remove-rules ipv4 filter f2b-<name>
firewall-cmd --direct --remove-chain ipv4 filter f2b-<name>
# 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 | sed -e 's, ,\n,g' | grep -q '^f2b-<name>$'
actionban = firewall-cmd --direct --add-rule ipv4 filter f2b-<name> 0 -s <ip> -j <blocktype>
actionunban = firewall-cmd --direct --remove-rule ipv4 filter f2b-<name> 0 -s <ip> -j <blocktype>
[Init]
# Default name of the chain
name = default
chain = INPUT_direct
# Could also use port numbers separated by a comma.
port = 1:65535
# Option: protocol
# Values: [ tcp | udp | icmp | all ]
protocol = tcp
# DEV NOTES:
#
# Author: Donald Yandt
# Uses "FirewallD" instead of the "iptables daemon".
#
#
# Output:
# actionstart:
# $ firewall-cmd --direct --add-chain ipv4 filter f2b-apache-modsecurity
# success
# $ firewall-cmd --direct --add-rule ipv4 filter f2b-apache-modsecurity 1000 -j RETURN
# success
# $ 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 | 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 # Values: CMD
# #
actionstart = printf %%b "Subject: [Fail2Ban] <name>: started on `uname -n` actionstart = printf %%b "Subject: [Fail2Ban] <name>: started on `uname -n`
Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` Date: `LC_TIME=C date +"%%a, %%d %%h %%Y %%T %%z"`
From: <sendername> <<sender>> From: <sendername> <<sender>>
To: <dest>\n To: <dest>\n
Hi,\n Hi,\n
@ -28,7 +28,7 @@ actionstart = printf %%b "Subject: [Fail2Ban] <name>: started on `uname -n`
# Values: CMD # Values: CMD
# #
actionstop = printf %%b "Subject: [Fail2Ban] <name>: stopped on `uname -n` actionstop = printf %%b "Subject: [Fail2Ban] <name>: stopped on `uname -n`
Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` Date: `LC_TIME=C date +"%%a, %%d %%h %%Y %%T %%z"`
From: <sendername> <<sender>> From: <sendername> <<sender>>
To: <dest>\n To: <dest>\n
Hi,\n Hi,\n

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_TIME=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 # Values: CMD
# #
actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n` 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_TIME=C date +"%%a, %%d %%h %%Y %%T %%z"`
From: <sendername> <<sender>> From: <sendername> <<sender>>
To: <dest>\n To: <dest>\n
Hi,\n Hi,\n
The IP <ip> has just been banned by Fail2Ban after The IP <ip> has just been banned by Fail2Ban after
<failures> attempts against <name>.\n\n <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 `/usr/bin/whois <ip>`\n\n
Matches for <name> with <ipjailfailures> failures IP:<ip>\n Matches for <name> with <ipjailfailures> failures IP:<ip>\n
<ipjailmatches>\n\n <ipjailmatches>\n\n

View File

@ -17,13 +17,13 @@ before = sendmail-common.conf
# Values: CMD # Values: CMD
# #
actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n` 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_TIME=C date +"%%a, %%d %%h %%Y %%T %%z"`
From: <sendername> <<sender>> From: <sendername> <<sender>>
To: <dest>\n To: <dest>\n
Hi,\n Hi,\n
The IP <ip> has just been banned by Fail2Ban after The IP <ip> has just been banned by Fail2Ban after
<failures> attempts against <name>.\n\n <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 `/usr/bin/whois <ip>`\n\n
Matches with <ipfailures> failures IP:<ip>\n Matches with <ipfailures> failures IP:<ip>\n
<ipmatches>\n\n <ipmatches>\n\n

View File

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

View File

@ -17,13 +17,13 @@ before = sendmail-common.conf
# Values: CMD # Values: CMD
# #
actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n` 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_TIME=C date +"%%a, %%d %%h %%Y %%T %%z"`
From: <sendername> <<sender>> From: <sendername> <<sender>>
To: <dest>\n To: <dest>\n
Hi,\n Hi,\n
The IP <ip> has just been banned by Fail2Ban after The IP <ip> has just been banned by Fail2Ban after
<failures> attempts against <name>.\n\n <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 `/usr/bin/whois <ip>`\n\n
Matches:\n Matches:\n
<matches>\n\n <matches>\n\n

View File

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

View File

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

View File

@ -46,7 +46,7 @@ actionban = oifs=${IFS}; IFS=.;SEP_IP=( <ip> ); set -- ${SEP_IP}; ADDRESSES=$(di
REPORTID=<time>@`uname -n` REPORTID=<time>@`uname -n`
TLP=<tlp> TLP=<tlp>
PORT=<port> PORT=<port>
DATE=`LC_TIME=C date -u --date=@<time> +"%%a, %%d %%h %%Y %%T +0000"` DATE=`LC_TIME=C date --date=@<time> +"%%a, %%d %%h %%Y %%T %%z"`
if [ ! -z "$ADDRESSES" ]; then if [ ! -z "$ADDRESSES" ]; then
(printf -- %%b "<header>\n<message>\n<report>\n"; (printf -- %%b "<header>\n<message>\n<report>\n";
date '+Note: Local timezone is %%z (%%Z)'; date '+Note: Local timezone is %%z (%%Z)';

View File

@ -34,6 +34,12 @@ loglevel = INFO
# #
logtarget = /var/log/fail2ban.log 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 # Option: socket
# Notes.: Set the socket file. This is used to communicate with the daemon. Do # 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 # not remove this file when Fail2ban runs. It will not be possible to

View File

@ -17,7 +17,9 @@
[INCLUDES] [INCLUDES]
# overwrite with apache-common.local if _apache_error_client is incorrect. # overwrite with apache-common.local if _apache_error_client is incorrect.
# Load regexes for filtering from botsearch-common.conf
before = apache-common.conf before = apache-common.conf
botsearch-common.conf
[Definition] [Definition]
@ -31,18 +33,8 @@ ignoreregex =
# Webroot represents the webroot on which all other files are based # Webroot represents the webroot on which all other files are based
webroot = /var/www/ 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: # 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

@ -22,7 +22,7 @@ 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 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 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 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 hacking attempt detected '<HOST>'$
^(%(__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 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*WARNING%(__pid_re)s:?(?:\[C-[\da-f]*\])? )Ext\. s: "Rejecting unknown SIP connection from <HOST>"$ ^(%(__prefix_line)s|\[\]\s*WARNING%(__pid_re)s:?(?:\[C-[\da-f]*\])? )Ext\. s: "Rejecting unknown SIP connection from <HOST>"$

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) # 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* __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 # Author: Yaroslav Halchenko

View File

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

View File

@ -9,9 +9,10 @@ before = common.conf
_daemon = (auth|dovecot(-auth)?|auth-worker) _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(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)sauth-worker\(\d+\): pam\(\S+,<HOST>\): unknown user\s*$
ignoreregex = ignoreregex =

View File

@ -8,7 +8,7 @@
failregex = ^\[\]LOGIN FAILED for user: "\S+" from IP: <HOST>$ failregex = ^\[\]LOGIN FAILED for user: "\S+" from IP: <HOST>$
ignoreregex =
# Author: Daniel Black # 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+$ ^ IP address <HOST> found in DNS blacklist \S+, mail from \S+ to \S+$
^ Relay attempt from IP address <HOST> ^ Relay attempt from IP address <HOST>
^ Attempt to deliver to unknown recipient \S+, from \S+, IP address <HOST>$ ^ Attempt to deliver to unknown recipient \S+, from \S+, IP address <HOST>$
ignoreregex =
[Init] [Init]
datepattern = ^\[%%d/%%b/%%Y %%H:%%M:%%S\] 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$ 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$ ^\[[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) \/<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) \/<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$ failregex = ^\[\]%(__prefix_line)sinfo: ratelimit block .* query <HOST> TYPE255$
^\[\]%(__prefix_line)sinfo: .* <HOST> refused, no acl matches\.$ ^\[\]%(__prefix_line)sinfo: .* <HOST> refused, no acl matches\.$
ignoreregex =

View File

@ -13,7 +13,7 @@ before = common.conf
# Default: catch all failed logins # Default: catch all failed logins
_ttys_re=\S* _ttys_re=\S*
__pam_re=\(?pam_unix(?:\(\S+\))?\)?:? __pam_re=\(?%(__pam_auth)s(?:\(\S+\))?\)?:?
_daemon = \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*$ 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$ failregex = \/<HOST> Port\: [0-9]+ (TCP|UDP) Blocked$
ignoreregex =
# Author: Pacop <pacoparu@gmail.com> # 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) _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] [Init]

View File

@ -13,6 +13,7 @@ before = common.conf
_daemon = postfix/(submission/)?smtp(d|s) _daemon = postfix/(submission/)?smtp(d|s)
failregex = ^%(__prefix_line)sNOQUEUE: reject: RCPT from \S+\[<HOST>\]: 554 5\.7\.1 .*$ failregex = ^%(__prefix_line)sNOQUEUE: reject: RCPT from \S+\[<HOST>\]: 554 5\.7\.1 .*$
^%(__prefix_line)sNOQUEUE: reject: RCPT from \S+\[<HOST>\]: 450 4\.7\.1 Client host rejected: cannot find your hostname, (\[\S*\]); from=<\S*> to=<\S+> proto=ESMTP helo=<\S*>$
^%(__prefix_line)sNOQUEUE: reject: RCPT from \S+\[<HOST>\]: 450 4\.7\.1 : Helo command rejected: Host not found; from=<> to=<> proto=ESMTP helo= *$ ^%(__prefix_line)sNOQUEUE: reject: RCPT from \S+\[<HOST>\]: 450 4\.7\.1 : Helo command rejected: Host not found; from=<> to=<> proto=ESMTP helo= *$
^%(__prefix_line)sNOQUEUE: reject: VRFY from \S+\[<HOST>\]: 550 5\.1\.1 .*$ ^%(__prefix_line)sNOQUEUE: reject: VRFY from \S+\[<HOST>\]: 550 5\.1\.1 .*$
^%(__prefix_line)simproper command pipelining after \S+ from [^[]*\[<HOST>\]:?$ ^%(__prefix_line)simproper command pipelining after \S+ from [^[]*\[<HOST>\]:?$

View File

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

View File

@ -3,6 +3,7 @@
failregex = ^ \[LOGIN_ERROR\].*from <HOST>: Unknown user or password incorrect\.$ failregex = ^ \[LOGIN_ERROR\].*from <HOST>: Unknown user or password incorrect\.$
ignoreregex =
[Init] [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)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)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\]$ ^(?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 = 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$ 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: # DEV NOTES:
# #
# Author: Daniel Black # Author: Daniel Black

View File

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

View File

@ -11,7 +11,7 @@ before = common.conf
[Definition] [Definition]
_daemon = wu-ftpd _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*$ 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*$ ^%(__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

@ -79,6 +79,11 @@ maxretry = 5
# See "journalmatch" in the jails associated filter config # See "journalmatch" in the jails associated filter config
# auto: will try to use the following backends, in order: # auto: will try to use the following backends, in order:
# pyinotify, gamin, polling. # 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 backend = auto
# "usedns" specifies if jails should trust hostnames in logs, # "usedns" specifies if jails should trust hostnames in logs,
@ -277,6 +282,14 @@ logpath = %(apache_error_log)s
maxretry = 2 maxretry = 2
[apache-fakegooglebot]
port = http,https
logpath = %(apache_access_log)s
maxretry = 1
ignorecommand = %(ignorecommands_dir)s/apache-fakegooglebot <ip>
[apache-modsecurity] [apache-modsecurity]
port = http,https port = http,https
@ -291,9 +304,14 @@ maxretry = 1
[nginx-http-auth] [nginx-http-auth]
ports = http,https port = http,https
logpath = %(nginx_error_log)s 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 # Ban attackers that try to use PHP's URL-fopen() functionality
# through GET/POST variables. - Experimental, with more than a year # through GET/POST variables. - Experimental, with more than a year
@ -465,6 +483,13 @@ port = smtp,465,submission
logpath = %(postfix_log)s logpath = %(postfix_log)s
[postfix-rbl]
port = smtp,465,submission
logpath = %(syslog_mail)s
maxretry = 1
[sendmail-auth] [sendmail-auth]
port = submission,465,smtp port = submission,465,smtp
@ -649,15 +674,16 @@ maxretry = 5
# Jail for more extended banning of persistent abusers # Jail for more extended banning of persistent abusers
# !!! WARNING !!! # !!! WARNINGS !!!
# Make sure that your loglevel specified in fail2ban.conf/.local # 1. Make sure that your loglevel specified in fail2ban.conf/.local
# is not at DEBUG level -- which might then cause fail2ban to fall into # is not at DEBUG level -- which might then cause fail2ban to fall into
# an infinite loop constantly feeding itself with non-informative lines # 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] [recidive]
logpath = /var/log/fail2ban.log logpath = /var/log/fail2ban.log
port = all banaction = iptables-allports
protocol = all
bantime = 604800 ; 1 week bantime = 604800 ; 1 week
findtime = 86400 ; 1 day findtime = 86400 ; 1 day
maxretry = 5 maxretry = 5

View File

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

View File

@ -68,3 +68,7 @@ logging.notice = _root_notice
# add NOTICE to the priority map of all the levels # add NOTICE to the priority map of all the levels
logging.handlers.SysLogHandler.priority_map['NOTICE'] = 'notice' 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

@ -85,6 +85,9 @@ class Beautifier:
val = " ".join(res1[1]) if isinstance(res1[1], list) else res1[1] val = " ".join(res1[1]) if isinstance(res1[1], list) else res1[1]
msg.append("%s %s:\t%s" % (prefix1, res1[0], val)) msg.append("%s %s:\t%s" % (prefix1, res1[0], val))
msg = "\n".join(msg) msg = "\n".join(msg)
elif inC[1] == "syslogsocket":
msg = "Current syslog socket is:\n"
msg = msg + "`- " + response
elif inC[1] == "logtarget": elif inC[1] == "logtarget":
msg = "Current logging target is:\n" msg = "Current logging target is:\n"
msg = msg + "`- " + response msg = msg + "`- " + response

View File

@ -46,6 +46,7 @@ class Fail2banReader(ConfigReader):
def getOptions(self): def getOptions(self):
opts = [["string", "loglevel", "INFO" ], opts = [["string", "loglevel", "INFO" ],
["string", "logtarget", "STDERR"], ["string", "logtarget", "STDERR"],
["string", "syslogsocket", "auto"],
["string", "dbfile", "/var/lib/fail2ban/fail2ban.sqlite3"], ["string", "dbfile", "/var/lib/fail2ban/fail2ban.sqlite3"],
["int", "dbpurgeage", 86400]] ["int", "dbpurgeage", 86400]]
self.__opts = ConfigReader.getOptions(self, "Definition", opts) self.__opts = ConfigReader.getOptions(self, "Definition", opts)
@ -57,6 +58,8 @@ class Fail2banReader(ConfigReader):
stream.append(["set", "loglevel", self.__opts[opt]]) stream.append(["set", "loglevel", self.__opts[opt]])
elif opt == "logtarget": elif opt == "logtarget":
stream.append(["set", "logtarget", self.__opts[opt]]) stream.append(["set", "logtarget", self.__opts[opt]])
elif opt == "syslogsocket":
stream.append(["set", "syslogsocket", self.__opts[opt]])
elif opt == "dbfile": elif opt == "dbfile":
stream.append(["set", "dbfile", self.__opts[opt]]) stream.append(["set", "dbfile", self.__opts[opt]])
elif opt == "dbpurgeage": elif opt == "dbpurgeage":

View File

@ -50,17 +50,17 @@ class FilterReader(DefinitionInitConfigReader):
def getCombined(self): def getCombined(self):
combinedopts = dict(list(self._opts.items()) + list(self._initOpts.items())) combinedopts = dict(list(self._opts.items()) + list(self._initOpts.items()))
if not len(combinedopts): if not len(combinedopts):
return {}; return {}
opts = CommandAction.substituteRecursiveTags(combinedopts) opts = CommandAction.substituteRecursiveTags(combinedopts)
if not opts: if not opts:
raise ValueError('recursive tag definitions unable to be resolved') raise ValueError('recursive tag definitions unable to be resolved')
return opts; return opts
def convert(self): def convert(self):
stream = list() stream = list()
opts = self.getCombined() opts = self.getCombined()
if not len(opts): if not len(opts):
return stream; return stream
for opt, value in opts.iteritems(): for opt, value in opts.iteritems():
if opt == "failregex": if opt == "failregex":
for regex in value.split('\n'): for regex in value.split('\n'):
@ -71,7 +71,7 @@ class FilterReader(DefinitionInitConfigReader):
for regex in value.split('\n'): for regex in value.split('\n'):
# Do not send a command if the rule is empty. # Do not send a command if the rule is empty.
if regex != '': if regex != '':
stream.append(["set", self._jailName, "addignoreregex", regex]) stream.append(["set", self._jailName, "addignoreregex", regex])
if self._initOpts: if self._initOpts:
if 'maxlines' in self._initOpts: if 'maxlines' in self._initOpts:
# We warn when multiline regex is used without maxlines > 1 # We warn when multiline regex is used without maxlines > 1

View File

@ -44,6 +44,8 @@ protocol = [
["get loglevel", "gets the logging level"], ["get loglevel", "gets the logging level"],
["set logtarget <TARGET>", "sets logging target to <TARGET>. Can be STDOUT, STDERR, SYSLOG or a file"], ["set logtarget <TARGET>", "sets logging target to <TARGET>. Can be STDOUT, STDERR, SYSLOG or a file"],
["get logtarget", "gets logging target"], ["get logtarget", "gets logging target"],
["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."], ["flushlogs", "flushes the logtarget if a file and reopens it. For log rotation."],
['', "DATABASE", ""], ['', "DATABASE", ""],
["set dbfile <FILE>", "set the location of fail2ban persistent datastore. Set to \"None\" to disable"], ["set dbfile <FILE>", "set the location of fail2ban persistent datastore. Set to \"None\" to disable"],
@ -54,7 +56,7 @@ protocol = [
["add <JAIL> <BACKEND>", "creates <JAIL> using <BACKEND>"], ["add <JAIL> <BACKEND>", "creates <JAIL> using <BACKEND>"],
["start <JAIL>", "starts the jail <JAIL>"], ["start <JAIL>", "starts the jail <JAIL>"],
["stop <JAIL>", "stops the jail <JAIL>. The jail is removed"], ["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", ""], ['', "JAIL CONFIGURATION", ""],
["set <JAIL> idle on|off", "sets the idle state of <JAIL>"], ["set <JAIL> idle on|off", "sets the idle state of <JAIL>"],
["set <JAIL> addignoreip <IP>", "adds <IP> to the ignore list of <JAIL>"], ["set <JAIL> addignoreip <IP>", "adds <IP> to the ignore list of <JAIL>"],

View File

@ -362,6 +362,7 @@ class CommandAction(ActionBase):
@classmethod @classmethod
def substituteRecursiveTags(cls, tags): def substituteRecursiveTags(cls, tags):
"""Sort out tag definitions within other tags. """Sort out tag definitions within other tags.
Since v.0.9.2 supports embedded interpolation (see test cases for examples).
so: becomes: so: becomes:
a = 3 a = 3 a = 3 a = 3
@ -378,38 +379,46 @@ class CommandAction(ActionBase):
Dictionary of tags(keys) and their values, with tags Dictionary of tags(keys) and their values, with tags
within the values recursively replaced. within the values recursively replaced.
""" """
t = re.compile(r'<([^ >]+)>') t = re.compile(r'<([^ <>]+)>')
for tag in tags.iterkeys(): # repeat substitution while embedded-recursive (repFlag is True)
if tag in cls._escapedTags: while True:
# Escaped so won't match repFlag = False
continue # substitute each value:
value = str(tags[tag]) for tag in tags.iterkeys():
m = t.search(value) if tag in cls._escapedTags:
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:
# Escaped so won't match # Escaped so won't match
continue continue
else: value = str(tags[tag])
if tags.has_key(found_tag): # search and replace all tags within value, that can be interpolated using other tags:
value = value.replace('<%s>' % found_tag , tags[found_tag]) m = t.search(value)
#logSys.log(5, 'value now: %s' % value) done = []
done.append(found_tag) #logSys.log(5, 'TAG: %s, value: %s' % (tag, value))
m = t.search(value, m.start()) while m:
else: found_tag = m.group(1)
# Missing tags are ok so we just continue on searching. #logSys.log(5, 'found: %s' % found_tag)
# cInfo can contain aInfo elements like <HOST> and valid shell 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 tags.has_key(found_tag):
# 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>. # constructs like <STDIN>.
m = t.search(value, m.start() + 1) m = t.search(value, m.end())
#logSys.log(5, 'TAG: %s, newvalue: %s' % (tag, value)) continue
tags[tag] = value 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 return tags
@staticmethod @staticmethod

View File

@ -370,11 +370,21 @@ class Actions(JailThread, Mapping):
self._jail.name, name, aInfo, e, self._jail.name, name, aInfo, e,
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG) exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
@property def status(self, flavor="basic"):
def status(self): """Status of current and total ban counts and current banned IP list.
"""Status of active bans, and total ban counts.
""" """
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()), ("Total banned", self.__banManager.getBanTotal()),
("Banned IP list", self.__banManager.getBanList())] ("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 return ret

View File

@ -149,8 +149,12 @@ class AsyncServer(asyncore.dispatcher):
self.__init = True self.__init = True
# TODO Add try..catch # TODO Add try..catch
# There's a bug report for Python 2.6/3.0 that use_poll=True yields some 2.5 incompatibilities: # 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") if (sys.version_info >= (2, 7) and sys.version_info < (2, 8)) \
asyncore.loop(use_poll=False) # fixes the "Unexpected communication problem" issue on Python 2.6 and 3.0 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. # Stops the communication server.

View File

@ -118,6 +118,124 @@ class BanManager:
finally: finally:
self.__lock.release() 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. # Create a ban ticket.
# #

View File

@ -37,17 +37,51 @@ from ..helpers import getLogger
logSys = getLogger(__name__) logSys = getLogger(__name__)
if sys.version_info >= (3,): if sys.version_info >= (3,):
sqlite3.register_adapter( def _json_dumps_safe(x):
dict, try:
lambda x: json.dumps(x, ensure_ascii=False).encode( x = json.dumps(x, ensure_ascii=False).encode(
locale.getpreferredencoding(), 'replace')) locale.getpreferredencoding(), 'replace')
sqlite3.register_converter( except Exception, e: # pragma: no cover
"JSON", logSys.error('json dumps failed: %s', e)
lambda x: json.loads(x.decode( x = '{}'
locale.getpreferredencoding(), 'replace'))) 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: else:
sqlite3.register_adapter(dict, json.dumps) def _normalize(x):
sqlite3.register_converter("JSON", json.loads) 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): def commitandrollback(f):
@wraps(f) @wraps(f)
@ -374,7 +408,7 @@ class Fail2BanDb(object):
"INSERT INTO bans(jail, ip, timeofban, data) VALUES(?, ?, ?, ?)", "INSERT INTO bans(jail, ip, timeofban, data) VALUES(?, ?, ?, ?)",
(jail.name, ticket.getIP(), int(round(ticket.getTime())), (jail.name, ticket.getIP(), int(round(ticket.getTime())),
{"matches": ticket.getMatches(), {"matches": ticket.getMatches(),
"failures": ticket.getAttempt()})) "failures": ticket.getAttempt()}))
@commitandrollback @commitandrollback
def delBan(self, cur, jail, ticket): def delBan(self, cur, jail, ticket):
@ -431,8 +465,8 @@ class Fail2BanDb(object):
tickets = [] tickets = []
for ip, timeofban, data in self._getBans(**kwargs): for ip, timeofban, data in self._getBans(**kwargs):
#TODO: Implement data parts once arbitrary match keys completed #TODO: Implement data parts once arbitrary match keys completed
tickets.append(FailTicket(ip, timeofban, data['matches'])) tickets.append(FailTicket(ip, timeofban, data.get('matches')))
tickets[-1].setAttempt(data['failures']) tickets[-1].setAttempt(data.get('failures', 1))
return tickets return tickets
def getBansMerged(self, ip=None, jail=None, bantime=None): def getBansMerged(self, ip=None, jail=None, bantime=None):
@ -484,8 +518,8 @@ class Fail2BanDb(object):
prev_banip = banip prev_banip = banip
matches = [] matches = []
failures = 0 failures = 0
matches.extend(data['matches']) matches.extend(data.get('matches', []))
failures += data['failures'] failures += data.get('failures', 1)
prev_timeofban = timeofban prev_timeofban = timeofban
ticket = FailTicket(banip, prev_timeofban, matches) ticket = FailTicket(banip, prev_timeofban, matches)
ticket.setAttempt(failures) ticket.setAttempt(failures)

View File

@ -338,6 +338,10 @@ class Filter(JailThread):
logSys.debug("Remove " + ip + " from ignore list") logSys.debug("Remove " + ip + " from ignore list")
self.__ignoreIpList.remove(ip) 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): def getIgnoreIP(self):
return self.__ignoreIpList return self.__ignoreIpList
@ -349,7 +353,7 @@ class Filter(JailThread):
# @param ip IP address # @param ip IP address
# @return True if IP address is in ignore list # @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: for i in self.__ignoreIpList:
# An empty string is always false # An empty string is always false
if i == "": if i == "":
@ -363,22 +367,26 @@ class Filter(JailThread):
"(?<=b)1+", bin(DNSUtils.addr2bin(s[1]))).group()) "(?<=b)1+", bin(DNSUtils.addr2bin(s[1]))).group())
s[1] = long(s[1]) s[1] = long(s[1])
try: try:
a = DNSUtils.cidr(s[0], s[1]) a = DNSUtils.addr2bin(s[0], cidr=s[1])
b = DNSUtils.cidr(ip, s[1]) b = DNSUtils.addr2bin(ip, cidr=s[1])
except Exception: except Exception:
# Check if IP in DNS # Check if IP in DNS
ips = DNSUtils.dnsToIp(i) ips = DNSUtils.dnsToIp(i)
if ip in ips: if ip in ips:
self.logIgnoreIp(ip, log_ignore, ignore_source="dns")
return True return True
else: else:
continue continue
if a == b: if a == b:
self.logIgnoreIp(ip, log_ignore, ignore_source="ip")
return True return True
if self.__ignoreCommand: if self.__ignoreCommand:
command = CommandAction.replaceTag(self.__ignoreCommand, { 'ip': ip } ) command = CommandAction.replaceTag(self.__ignoreCommand, { 'ip': ip } )
logSys.debug('ignore command: ' + command) 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 return False
@ -418,8 +426,7 @@ class Filter(JailThread):
logSys.debug("Ignore line since time %s < %s - %s" logSys.debug("Ignore line since time %s < %s - %s"
% (unixTime, MyTime.time(), self.getFindTime())) % (unixTime, MyTime.time(), self.getFindTime()))
break break
if self.inIgnoreIPList(ip): if self.inIgnoreIPList(ip, log_ignore=True):
logSys.info("[%s] Ignore %s" % (self.jail.name, ip))
continue continue
logSys.info("[%s] Found %s" % (self.jail.name, ip)) logSys.info("[%s] Found %s" % (self.jail.name, ip))
## print "D: Adding a ticket for %s" % ((ip, unixTime, [line]),) ## print "D: Adding a ticket for %s" % ((ip, unixTime, [line]),)
@ -529,8 +536,7 @@ class Filter(JailThread):
logSys.error(e) logSys.error(e)
return failList return failList
@property def status(self, flavor="basic"):
def status(self):
"""Status of failures detected by filter. """Status of failures detected by filter.
""" """
ret = [("Currently failed", self.failManager.size()), ret = [("Currently failed", self.failManager.size()),
@ -686,11 +692,10 @@ class FileFilter(Filter):
db.updateLog(self.jail, container) db.updateLog(self.jail, container)
return True return True
@property def status(self, flavor="basic"):
def status(self):
"""Status of Filter plus files being monitored. """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()] path = [m.getFileName() for m in self.getLogPath()]
ret.append(("File list", path)) ret.append(("File list", path))
return ret return ret
@ -792,11 +797,13 @@ class FileContainer:
line = line.decode(self.getEncoding(), 'strict') line = line.decode(self.getEncoding(), 'strict')
except UnicodeDecodeError: except UnicodeDecodeError:
logSys.warning( 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" % " to process line ignoring invalid characters: %r" %
(self.getFileName(), self.getEncoding(), line)) (self.getFileName(), self.getEncoding(), line))
if sys.version_info >= (3,): # In python3, must be decoded # decode with replacing error chars:
line = line.decode(self.getEncoding(), 'ignore') line = line.decode(self.getEncoding(), 'replace')
return line return line
def close(self): def close(self):
@ -854,6 +861,14 @@ class DNSUtils:
% (dns, e)) % (dns, e))
return list() 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 @staticmethod
def searchIP(text): def searchIP(text):
""" Search if an IP address if directly available and return """ Search if an IP address if directly available and return
@ -900,22 +915,18 @@ class DNSUtils:
return ipList return ipList
@staticmethod @staticmethod
def cidr(i, n): def addr2bin(ipstring, cidr=None):
""" Convert an IP address string with a CIDR mask into a 32-bit """ Convert a string IPv4 address into binary form.
integer. If cidr is supplied, return the network address for the given block
""" """
# 32-bit IPv4 address mask if cidr is None:
MASK = 0xFFFFFFFFL return struct.unpack("!L", socket.inet_aton(ipstring))[0]
return ~(MASK >> n) & MASK & DNSUtils.addr2bin(i) else:
MASK = 0xFFFFFFFFL
return ~(MASK >> cidr) & MASK & DNSUtils.addr2bin(ipstring)
@staticmethod @staticmethod
def addr2bin(string): def bin2addr(ipbin):
""" Convert a string IPv4 address into an unsigned integer. """ Convert a binary IPv4 address into string n.n.n.n form.
""" """
return struct.unpack("!L", socket.inet_aton(string))[0] return socket.inet_ntoa(struct.pack("!L", ipbin))
@staticmethod
def bin2addr(addr):
""" Convert a numeric IPv4 address into string n.n.n.n form.
"""
return socket.inet_ntoa(struct.pack("!L", addr))

View File

@ -259,9 +259,8 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
or "jailless") +" filter terminated") or "jailless") +" filter terminated")
return True return True
@property def status(self, flavor="basic"):
def status(self): ret = super(FilterSystemd, self).status(flavor=flavor)
ret = super(FilterSystemd, self).status
ret.append(("Journal matches", ret.append(("Journal matches",
[" + ".join(" ".join(match) for match in self.__matches)])) [" + ".join(" ".join(match) for match in self.__matches)]))
return ret return ret

View File

@ -174,13 +174,12 @@ class Jail:
self.filter.idle = value self.filter.idle = value
self.actions.idle = value self.actions.idle = value
@property def status(self, flavor="basic"):
def status(self):
"""The status of the jail. """The status of the jail.
""" """
return [ return [
("Filter", self.filter.status), ("Filter", self.filter.status(flavor=flavor)),
("Actions", self.actions.status), ("Actions", self.actions.status(flavor=flavor)),
] ]
def putFailTicket(self, ticket): def putFailTicket(self, ticket):
@ -214,7 +213,7 @@ class Jail:
if self.database is not None: if self.database is not None:
for ticket in self.database.getBansMerged( for ticket in self.database.getBansMerged(
jail=self, bantime=self.actions.getBanTime()): jail=self, bantime=self.actions.getBanTime()):
if not self.filter.inIgnoreIPList(ticket.getIP()): if not self.filter.inIgnoreIPList(ticket.getIP(), log_ignore=True):
self.__queue.put(ticket) self.__queue.put(ticket)
logSys.info("Jail '%s' started" % self.name) logSys.info("Jail '%s' started" % self.name)

View File

@ -26,7 +26,7 @@ __license__ = "GPL"
import sys import sys
from threading import Thread from threading import Thread
from abc import abstractproperty, abstractmethod from abc import abstractmethod
from ..helpers import excepthook from ..helpers import excepthook
@ -66,8 +66,8 @@ class JailThread(Thread):
excepthook(*sys.exc_info()) excepthook(*sys.exc_info())
self.run = run_with_except_hook self.run = run_with_except_hook
@abstractproperty @abstractmethod
def status(self): # pragma: no cover - abstract def status(self, flavor="basic"): # pragma: no cover - abstract
"""Abstract - Should provide status information. """Abstract - Should provide status information.
""" """
pass pass

View File

@ -25,7 +25,7 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
from threading import Lock, RLock from threading import Lock, RLock
import logging, logging.handlers, sys, os, signal import logging, logging.handlers, sys, os, signal, stat
from .jails import Jails from .jails import Jails
from .filter import FileFilter, JournalFilter from .filter import FileFilter, JournalFilter
@ -55,20 +55,33 @@ class Server:
self.__asyncServer = AsyncServer(self.__transm) self.__asyncServer = AsyncServer(self.__transm)
self.__logLevel = None self.__logLevel = None
self.__logTarget = None self.__logTarget = None
self.__syslogSocket = None
self.__autoSyslogSocketPaths = {
'Darwin': '/var/run/syslog',
'FreeBSD': '/var/run/log',
'Linux': '/dev/log',
}
# Set logging level # Set logging level
self.setLogLevel("INFO") self.setLogLevel("INFO")
self.setLogTarget("STDOUT") self.setLogTarget("STDOUT")
self.setSyslogSocket("auto")
def __sigTERMhandler(self, signum, frame): def __sigTERMhandler(self, signum, frame):
logSys.debug("Caught signal %d. Exiting" % signum) logSys.debug("Caught signal %d. Exiting" % signum)
self.quit() self.quit()
def __sigUSR1handler(self, signum, fname):
logSys.debug("Caught signal %d. Flushing logs" % signum)
self.flushLogs()
def start(self, sock, pidfile, force = False): def start(self, sock, pidfile, force = False):
logSys.info("Starting Fail2ban v" + version.version) logSys.info("Starting Fail2ban v" + version.version)
# Install signal handlers # Install signal handlers
signal.signal(signal.SIGTERM, self.__sigTERMhandler) signal.signal(signal.SIGTERM, self.__sigTERMhandler)
signal.signal(signal.SIGINT, self.__sigTERMhandler) signal.signal(signal.SIGINT, self.__sigTERMhandler)
signal.signal(signal.SIGUSR1, self.__sigUSR1handler)
# Ensure unhandled exceptions are logged # Ensure unhandled exceptions are logged
sys.excepthook = excepthook sys.excepthook = excepthook
@ -320,9 +333,9 @@ class Server:
finally: finally:
self.__lock.release() self.__lock.release()
def statusJail(self, name): def statusJail(self, name, flavor="basic"):
return self.__jails[name].status return self.__jails[name].status(flavor=flavor)
# Logging # Logging
## ##
@ -360,7 +373,7 @@ class Server:
return self.__logLevel return self.__logLevel
finally: finally:
self.__loggingLock.release() self.__loggingLock.release()
## ##
# Sets the logging target. # Sets the logging target.
# #
@ -376,7 +389,21 @@ class Server:
# Syslog daemons already add date to the message. # Syslog daemons already add date to the message.
formatter = logging.Formatter("%(name)s[%(process)d]: %(levelname)s %(message)s") formatter = logging.Formatter("%(name)s[%(process)d]: %(levelname)s %(message)s")
facility = logging.handlers.SysLogHandler.LOG_DAEMON 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": elif target == "STDOUT":
hdlr = logging.StreamHandler(sys.stdout) hdlr = logging.StreamHandler(sys.stdout)
elif target == "STDERR": elif target == "STDERR":
@ -412,21 +439,44 @@ class Server:
logger.addHandler(hdlr) logger.addHandler(hdlr)
# Does not display this message at startup. # Does not display this message at startup.
if not self.__logTarget is None: if not self.__logTarget is None:
logSys.info("Changed logging target to %s for Fail2ban v%s" % logSys.info(
(target, version.version)) "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. # Sets the logging target.
self.__logTarget = target self.__logTarget = target
return True return True
finally: finally:
self.__loggingLock.release() 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): def getLogTarget(self):
try: try:
self.__loggingLock.acquire() self.__loggingLock.acquire()
return self.__logTarget return self.__logTarget
finally: finally:
self.__loggingLock.release() self.__loggingLock.release()
def getSyslogSocket(self):
try:
self.__loggingLock.acquire()
return self.__syslogSocket
finally:
self.__loggingLock.release()
def flushLogs(self): def flushLogs(self):
if self.__logTarget not in ['STDERR', 'STDOUT', 'SYSLOG']: if self.__logTarget not in ['STDERR', 'STDOUT', 'SYSLOG']:
for handler in getLogger("fail2ban").handlers: for handler in getLogger("fail2ban").handlers:

View File

@ -55,7 +55,7 @@ class Ticket:
def __eq__(self, other): def __eq__(self, other):
try: try:
return self.__ip == other.__ip and \ 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.__attempt == other.__attempt and \
self.__matches == other.__matches self.__matches == other.__matches
except AttributeError: except AttributeError:

View File

@ -121,6 +121,12 @@ class Transmitter:
return self.__server.getLogTarget() return self.__server.getLogTarget()
else: else:
raise Exception("Failed to change log target") raise Exception("Failed to change log target")
elif name == "syslogsocket":
value = command[1]
if self.__server.setSyslogSocket(value):
return self.__server.getSyslogSocket()
else:
raise Exception("Failed to change syslog socket")
#Database #Database
elif name == "dbfile": elif name == "dbfile":
self.__server.setDatabase(command[1]) self.__server.setDatabase(command[1])
@ -264,6 +270,8 @@ class Transmitter:
return self.__server.getLogLevel() return self.__server.getLogLevel()
elif name == "logtarget": elif name == "logtarget":
return self.__server.getLogTarget() return self.__server.getLogTarget()
elif name == "syslogsocket":
return self.__server.getSyslogSocket()
#Database #Database
elif name == "dbfile": elif name == "dbfile":
db = self.__server.getDatabase() db = self.__server.getDatabase()
@ -333,5 +341,8 @@ class Transmitter:
elif len(command) == 1: elif len(command) == 1:
name = command[0] name = command[0]
return self.__server.statusJail(name) return self.__server.statusJail(name)
elif len(command) == 2:
name = command[0]
flavor = command[1]
return self.__server.statusJail(name, flavor=flavor)
raise Exception("Invalid command (no status)") raise Exception("Invalid command (no status)")

View File

@ -86,7 +86,7 @@ class ExecuteActions(LogCaptureTestCase):
self.__actions.stop() self.__actions.stop()
self.__actions.join() self.__actions.join()
self.assertEqual(self.__actions.status,[("Currently banned", 0 ), self.assertEqual(self.__actions.status(),[("Currently banned", 0 ),
("Total banned", 0 ), ("Banned IP list", [] )]) ("Total banned", 0 ), ("Banned IP list", [] )])

View File

@ -59,6 +59,8 @@ class CommandActionTest(LogCaptureTestCase):
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C>'}), {'A': '<C>'}) self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C>'}), {'A': '<C>'})
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C> <D> <X>','X':'fun'}), {'A': '<C> <D> fun', 'X':'fun'}) self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C> <D> <X>','X':'fun'}), {'A': '<C> <D> fun', 'X':'fun'})
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C> <B>', 'B': 'cool'}), {'A': '<C> cool', 'B': 'cool'}) self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C> <B>', 'B': 'cool'}), {'A': '<C> cool', 'B': 'cool'})
# Escaped tags should be ignored
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<matches> <B>', 'B': 'cool'}), {'A': '<matches> cool', 'B': 'cool'})
# Multiple stuff on same line is ok # Multiple stuff on same line is ok
self.assertEqual(CommandAction.substituteRecursiveTags({'failregex': 'to=<honeypot> fromip=<IP> evilperson=<honeypot>', 'honeypot': 'pokie', 'ignoreregex': ''}), self.assertEqual(CommandAction.substituteRecursiveTags({'failregex': 'to=<honeypot> fromip=<IP> evilperson=<honeypot>', 'honeypot': 'pokie', 'ignoreregex': ''}),
{ 'failregex': "to=pokie fromip=<IP> evilperson=pokie", { 'failregex': "to=pokie fromip=<IP> evilperson=pokie",
@ -71,6 +73,14 @@ class CommandActionTest(LogCaptureTestCase):
'ABC': '123 192.0.2.0', 'ABC': '123 192.0.2.0',
'xyz': '890 123 192.0.2.0', 'xyz': '890 123 192.0.2.0',
}) })
# obscure embedded case
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<<PREF>HOST>', 'PREF': 'IPV4'}),
{'A': '<IPV4HOST>', 'PREF': 'IPV4'})
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<<PREF>HOST>', 'PREF': 'IPV4', 'IPV4HOST': '1.2.3.4'}),
{'A': '1.2.3.4', 'PREF': 'IPV4', 'IPV4HOST': '1.2.3.4'})
# more embedded within a string and two interpolations
self.assertEqual(CommandAction.substituteRecursiveTags({'A': 'A <IP<PREF>HOST> B IP<PREF> C', 'PREF': 'V4', 'IPV4HOST': '1.2.3.4'}),
{'A': 'A 1.2.3.4 B IPV4 C', 'PREF': 'V4', 'IPV4HOST': '1.2.3.4'})
def testReplaceTag(self): def testReplaceTag(self):
aInfo = { aInfo = {

View File

@ -30,7 +30,6 @@ from ..server.banmanager import BanManager
from ..server.ticket import BanTicket from ..server.ticket import BanTicket
class AddFailure(unittest.TestCase): class AddFailure(unittest.TestCase):
def setUp(self): def setUp(self):
"""Call before every test case.""" """Call before every test case."""
self.__ticket = BanTicket('193.168.0.128', 1167605999.0) self.__ticket = BanTicket('193.168.0.128', 1167605999.0)
@ -39,19 +38,77 @@ class AddFailure(unittest.TestCase):
def tearDown(self): def tearDown(self):
"""Call after every test case.""" """Call after every test case."""
pass
def testAdd(self): def testAdd(self):
self.assertEqual(self.__banManager.size(), 1) self.assertEqual(self.__banManager.size(), 1)
def testAddDuplicate(self): def testAddDuplicate(self):
self.assertFalse(self.__banManager.addBanTicket(self.__ticket)) self.assertFalse(self.__banManager.addBanTicket(self.__ticket))
self.assertEqual(self.__banManager.size(), 1) self.assertEqual(self.__banManager.size(), 1)
def testInListOK(self): def testInListOK(self):
ticket = BanTicket('193.168.0.128', 1167605999.0) ticket = BanTicket('193.168.0.128', 1167605999.0)
self.assertTrue(self.__banManager._inBanList(ticket)) self.assertTrue(self.__banManager._inBanList(ticket))
def testInListNOK(self): def testInListNOK(self):
ticket = BanTicket('111.111.1.111', 1167605999.0) ticket = BanTicket('111.111.1.111', 1167605999.0)
self.assertFalse(self.__banManager._inBanList(ticket)) self.assertFalse(self.__banManager._inBanList(ticket))
class StatusExtendedCymruInfo(unittest.TestCase):
def setUp(self):
"""Call before every test case."""
self.__ban_ip = "93.184.216.34"
self.__asn = "15133"
self.__country = "EU"
self.__rir = "ripencc"
ticket = BanTicket(self.__ban_ip, 1167605999.0)
self.__banManager = BanManager()
self.assertTrue(self.__banManager.addBanTicket(ticket))
def tearDown(self):
"""Call after every test case."""
pass
def testCymruInfo(self):
cymru_info = self.__banManager.getBanListExtendedCymruInfo()
if "assertDictEqual" in dir(self):
self.assertDictEqual(cymru_info, {"asn": [self.__asn],
"country": [self.__country],
"rir": [self.__rir]})
else:
# Python 2.6 does not support assertDictEqual()
self.assertEqual(cymru_info["asn"], [self.__asn])
self.assertEqual(cymru_info["country"], [self.__country])
self.assertEqual(cymru_info["rir"], [self.__rir])
def testCymruInfoASN(self):
self.assertEqual(
self.__banManager.geBanListExtendedASN(self.__banManager.getBanListExtendedCymruInfo()),
[self.__asn])
def testCymruInfoCountry(self):
self.assertEqual(
self.__banManager.geBanListExtendedCountry(self.__banManager.getBanListExtendedCymruInfo()),
[self.__country])
def testCymruInfoRIR(self):
self.assertEqual(
self.__banManager.geBanListExtendedRIR(self.__banManager.getBanListExtendedCymruInfo()),
[self.__rir])
def testCymruInfoNxdomain(self):
ticket = BanTicket("10.0.0.0", 1167605999.0)
self.__banManager = BanManager()
self.assertTrue(self.__banManager.addBanTicket(ticket))
cymru_info = self.__banManager.getBanListExtendedCymruInfo()
if "assertDictEqual" in dir(self):
self.assertDictEqual(cymru_info, {"asn": ["nxdomain"],
"country": ["nxdomain"],
"rir": ["nxdomain"]})
else:
# Python 2.6 does not support assertDictEqual()
self.assertEqual(cymru_info["asn"], ["nxdomain"])
self.assertEqual(cymru_info["country"], ["nxdomain"])
self.assertEqual(cymru_info["rir"], ["nxdomain"])

View File

@ -459,6 +459,22 @@ class JailsReaderTest(LogCaptureTestCase):
self.assertTrue(self._is_logged("No file(s) found for glob /weapons/of/mass/destruction")) self.assertTrue(self._is_logged("No file(s) found for glob /weapons/of/mass/destruction"))
if STOCK: if STOCK:
def testReadStockActionConf(self):
for actionConfig in glob.glob(os.path.join(CONFIG_DIR, 'action.d', '*.conf')):
actionName = os.path.basename(actionConfig).replace('.conf', '')
actionReader = ActionReader(actionName, "TEST", {}, basedir=CONFIG_DIR)
self.assertTrue(actionReader.read())
actionReader.getOptions({}) # populate _opts
if not actionName.endswith('-common'):
self.assertTrue('Definition' in actionReader.sections(),
msg="Action file %r is lacking [Definition] section" % actionConfig)
# all must have some actionban defined
self.assertTrue(actionReader._opts.get('actionban', '').strip(),
msg="Action file %r is lacking actionban" % actionConfig)
self.assertTrue('Init' in actionReader.sections(),
msg="Action file %r is lacking [Init] section" % actionConfig)
def testReadStockJailConf(self): def testReadStockJailConf(self):
jails = JailsReader(basedir=CONFIG_DIR, share_config=self.__share_cfg) # we are running tests from root project dir atm jails = JailsReader(basedir=CONFIG_DIR, share_config=self.__share_cfg) # we are running tests from root project dir atm
self.assertTrue(jails.read()) # opens fine self.assertTrue(jails.read()) # opens fine
@ -607,7 +623,8 @@ class JailsReaderTest(LogCaptureTestCase):
'/var/lib/fail2ban/fail2ban.sqlite3'], '/var/lib/fail2ban/fail2ban.sqlite3'],
['set', 'dbpurgeage', 86400], ['set', 'dbpurgeage', 86400],
['set', 'loglevel', "INFO"], ['set', 'loglevel', "INFO"],
['set', 'logtarget', '/var/log/fail2ban.log']]) ['set', 'logtarget', '/var/log/fail2ban.log'],
['set', 'syslogsocket', 'auto']])
# and if we force change configurator's fail2ban's baseDir # and if we force change configurator's fail2ban's baseDir
# there should be an error message (test visually ;) -- # there should be an error message (test visually ;) --

View File

@ -177,6 +177,37 @@ class DatabaseTest(LogCaptureTestCase):
self.assertTrue( self.assertTrue(
isinstance(self.db.getBans(jail=self.jail)[0], FailTicket)) isinstance(self.db.getBans(jail=self.jail)[0], FailTicket))
def testAddBanInvalidEncoded(self):
if Fail2BanDb is None: # pragma: no cover
return
self.testAddJail()
# invalid + valid, invalid + valid unicode, invalid + valid dual converted (like in filter:readline by fallback) ...
tickets = [
FailTicket("127.0.0.1", 0, ['user "\xd1\xe2\xe5\xf2\xe0"', 'user "\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f"']),
FailTicket("127.0.0.2", 0, ['user "\xd1\xe2\xe5\xf2\xe0"', u'user "\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f"']),
FailTicket("127.0.0.3", 0, ['user "\xd1\xe2\xe5\xf2\xe0"', b'user "\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f"'.decode('utf-8', 'replace')])
]
self.db.addBan(self.jail, tickets[0])
self.db.addBan(self.jail, tickets[1])
self.db.addBan(self.jail, tickets[2])
readtickets = self.db.getBans(jail=self.jail)
self.assertEqual(len(readtickets), 3)
## python 2 or 3 :
invstr = u'user "\ufffd\ufffd\ufffd\ufffd\ufffd"'.encode('utf-8', 'replace')
self.assertTrue(
readtickets[0] == FailTicket("127.0.0.1", 0, [invstr, 'user "\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f"'])
or readtickets[0] == tickets[0]
)
self.assertTrue(
readtickets[1] == FailTicket("127.0.0.2", 0, [invstr, u'user "\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f"'.encode('utf-8', 'replace')])
or readtickets[1] == tickets[1]
)
self.assertTrue(
readtickets[2] == FailTicket("127.0.0.3", 0, [invstr, 'user "\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f"'])
or readtickets[2] == tickets[2]
)
def testDelBan(self): def testDelBan(self):
self.testAddBan() self.testAddBan()
ticket = self.db.getBans(jail=self.jail)[0] ticket = self.db.getBans(jail=self.jail)[0]

View File

@ -0,0 +1,5 @@
# Apache 2.2
# failJSON: { "time": "2015-01-31T14:29:44", "match": true, "host": "66.249.66.1" }
66.249.66.1 - - - [31/Jan/2015:14:29:44 ] example.com "GET / HTTP/1.1" 200 814 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" + 293 1149 546
# failJSON: { "time": "2015-01-31T14:29:44", "match": false, "host": "93.184.216.34" }
93.184.216.34 - - - [31/Jan/2015:14:29:44 ] example.com "GET / HTTP/1.1" 200 814 "-" "NOT A __GOOGLE_BOT__" + 293 1149 546

View File

@ -29,15 +29,9 @@
[2013-05-13 07:10:53] SECURITY[1204] res_security_log.c: SecurityEvent="InvalidAccountID",EventTV="1368439853-500975",Severity="Error",Service="SIP",EventVersion="1",AccountID="00972599580679",SessionID="0x7f8ecc0421f8",LocalAddress="IPV4/UDP/1.2.3.4/5060",RemoteAddress="IPV4/UDP/1.2.3.4/5070" [2013-05-13 07:10:53] SECURITY[1204] res_security_log.c: SecurityEvent="InvalidAccountID",EventTV="1368439853-500975",Severity="Error",Service="SIP",EventVersion="1",AccountID="00972599580679",SessionID="0x7f8ecc0421f8",LocalAddress="IPV4/UDP/1.2.3.4/5060",RemoteAddress="IPV4/UDP/1.2.3.4/5070"
# failJSON: { "time": "2013-06-10T18:15:03", "match": true , "host": "1.2.3.4" } # failJSON: { "time": "2013-06-10T18:15:03", "match": true , "host": "1.2.3.4" }
[2013-06-10 18:15:03] NOTICE[2723] chan_sip.c: Registration from '"100"<sip:100@192.168.0.2:5060>' failed for '1.2.3.4' - Not a local domain [2013-06-10 18:15:03] NOTICE[2723] chan_sip.c: Registration from '"100"<sip:100@192.168.0.2:5060>' failed for '1.2.3.4' - Not a local domain
# http://forum.4psa.com/showthread.php?t=6601 # http://sourceforge.net/p/fail2ban/mailman/message/33603322/
# failJSON: { "time": "2009-12-22T16:35:24", "match": true , "host": "192.168.2.102" } # failJSON: { "time": "2015-03-16T18:46:34", "match": true , "host": "192.168.2.102" }
[2009-12-22 16:35:24] NOTICE[6163] chan_sip.c: Sending fake auth rejection for device <sip:0004*001@192.168.2.102>;tag=e3793a95e1acbc69o [2015-03-16 18:46:34] NOTICE[3453] chan_sip.c: hacking attempt detected '192.168.2.102'
# http://www.freepbx.org/forum/general-help/fake-auth-rejection
# failJSON: { "time": "2009-12-22T16:35:24", "match": true , "host": "192.168.2.102" }
[2009-12-22 16:35:24] NOTICE[1570][C-00000086] chan_sip.c: Sending fake auth rejection for device 1022<sip:1022@192.168.2.102>;tag=5d8b6f92
# http://www.spinics.net/lists/asterisk/msg127381.html
# failJSON: { "time": "2009-12-22T16:35:24", "match": true , "host": "192.168.2.102" }
[2009-12-22 16:35:24] NOTICE[14916]: chan_sip.c:15644 handle_request_subscribe: Sending fake auth rejection for user <sip:CS@192.168.2.102>;tag=6pwd6erg54
# failJSON: { "time": "2013-07-06T09:09:25", "match": true , "host": "141.255.164.106" } # failJSON: { "time": "2013-07-06T09:09:25", "match": true , "host": "141.255.164.106" }
[2013-07-06 09:09:25] SECURITY[3308] res_security_log.c: SecurityEvent="InvalidPassword",EventTV="1373098165-824497",Severity="Error",Service="SIP",EventVersion="2",AccountID="972592891005",SessionID="0x88aab6c",LocalAddress="IPV4/UDP/92.28.73.180/5060",RemoteAddress="IPV4/UDP/141.255.164.106/5084",Challenge="41d26de5",ReceivedChallenge="41d26de5",ReceivedHash="7a6a3a2e95a05260aee612896e1b4a39" [2013-07-06 09:09:25] SECURITY[3308] res_security_log.c: SecurityEvent="InvalidPassword",EventTV="1373098165-824497",Severity="Error",Service="SIP",EventVersion="2",AccountID="972592891005",SessionID="0x88aab6c",LocalAddress="IPV4/UDP/92.28.73.180/5060",RemoteAddress="IPV4/UDP/141.255.164.106/5084",Challenge="41d26de5",ReceivedChallenge="41d26de5",ReceivedHash="7a6a3a2e95a05260aee612896e1b4a39"
# failJSON: { "time": "2014-01-10T16:39:06", "match": true , "host": "50.30.42.14" } # failJSON: { "time": "2014-01-10T16:39:06", "match": true , "host": "50.30.42.14" }

View File

@ -31,6 +31,12 @@ Jul 02 13:49:32 hostname dovecot[442]: dovecot: auth(default): pam(account@MYSER
# failJSON: { "time": "2013-08-11T03:56:40", "match": true , "host": "1.2.3.4" } # failJSON: { "time": "2013-08-11T03:56:40", "match": true , "host": "1.2.3.4" }
2013-08-11 03:56:40 auth-worker(default): Info: pam(username,1.2.3.4): pam_authenticate() failed: Authentication failure (password mismatch?) 2013-08-11 03:56:40 auth-worker(default): Info: pam(username,1.2.3.4): pam_authenticate() failed: Authentication failure (password mismatch?)
# failJSON: { "time": "2005-01-29T05:32:50", "match": true , "host": "1.2.3.4" }
Jan 29 05:32:50 mail dovecot: auth-worker(304): pam(username,1.2.3.4): pam_authenticate() failed: Authentication failure (password mismatch?)
# failJSON: { "time": "2005-01-29T05:13:40", "match": true , "host": "1.2.3.4" }
Jan 29 05:13:40 mail dovecot: auth-worker(31326): pam(username,1.2.3.4): unknown user
# failJSON: { "time": "2005-04-19T05:22:20", "match": true , "host": "80.255.3.104" } # failJSON: { "time": "2005-04-19T05:22:20", "match": true , "host": "80.255.3.104" }
Apr 19 05:22:20 vm5 auth: pam_unix(dovecot:auth): authentication failure; logname= uid=0 euid=0 tty=dovecot ruser=informix rhost=80.255.3.104 Apr 19 05:22:20 vm5 auth: pam_unix(dovecot:auth): authentication failure; logname= uid=0 euid=0 tty=dovecot ruser=informix rhost=80.255.3.104

View File

@ -0,0 +1,23 @@
# failJSON: { "time": "2015-01-20T19:53:28", "match": true , "host": "12.34.56.78" }
12.34.56.78 - - [20/Jan/2015:19:53:28 +0100] "GET //phpMyAdmin-2.8.2.3/scripts/setup.php HTTP/1.1" 404 47 "-" "-" "-"
# failJSON: { "time": "2015-01-20T19:53:28", "match": true , "host": "12.34.56.78" }
12.34.56.78 - - [20/Jan/2015:19:53:28 +0100] "GET //pma/scripts/setup.php HTTP/1.1" 404 47 "-" "-" "-"
# failJSON: { "time": "2015-01-20T19:53:28", "match": true , "host": "12.34.56.78" }
12.34.56.78 - - [20/Jan/2015:19:53:28 +0100] "GET //mysqladmin/scripts/setup.php HTTP/1.1" 404 47 "-" "-" "-"
# failJSON: { "time": "2015-01-20T19:53:28", "match": true , "host": "12.34.56.78" }
12.34.56.78 - - [20/Jan/2015:19:53:28 +0100] "GET //admin/pma/scripts/setup.php HTTP/1.1" 404 47 "-" "-" "-"
# failJSON: { "time": "2015-01-20T01:17:07", "match": true , "host": "7.8.9.10" }
7.8.9.10 - root [20/Jan/2015:01:17:07 +0100] "GET /cgi-bin/recent.cgi HTTP/1.1" 404 162 "-" "-" "-"
# failJSON: { "time": "2014-12-12T22:59:02", "match": true , "host": "2.5.2.5" }
2.5.2.5 - tomcat [12/Dec/2014:22:59:02 +0100] "GET /cgi-bin/tools/tools.pl HTTP/1.1" 404 162 "-" "-" "-"
# failJSON: { "time": "2015-01-21T10:56:10", "match": true , "host": "5.7.9.2" }
2015/01/21 10:56:10 [error] 2833#0: *16336 open() "/var/www/site/cgi-bin/php4" failed (2: No such file or directory), client: 5.7.9.2, server: localhost, request: "GET /cgi-bin/php4 HTTP/1.1", host: "1.2.3.4"
# failJSON: { "time": "2015-01-21T15:02:27", "match": true , "host": "5.7.9.2" }
2015/01/21 15:02:27 [error] 2833#0: *16813 "/var/www/site/roundcube/" is not found (2: No such file or directory), client: 5.7.9.2, server: localhost, request: "GET /roundcube/ HTTP/1.1", host: "1.2.3.4"

View File

@ -20,3 +20,6 @@ Dec 25 02:35:54 platypus postfix/smtpd[9144]: improper command pipelining after
# failJSON: { "time": "2004-12-18T02:05:46", "match": true , "host": "216.245.198.245" } # failJSON: { "time": "2004-12-18T02:05:46", "match": true , "host": "216.245.198.245" }
Dec 18 02:05:46 platypus postfix/smtpd[16349]: improper command pipelining after NOOP from unknown[216.245.198.245] Dec 18 02:05:46 platypus postfix/smtpd[16349]: improper command pipelining after NOOP from unknown[216.245.198.245]
# failJSON: { "time": "2004-12-21T21:17:29", "match": true , "host": "93.184.216.34" }
Dec 21 21:17:29 xxx postfix/smtpd[7150]: NOQUEUE: reject: RCPT from badserver.example.com[93.184.216.34]: 450 4.7.1 Client host rejected: cannot find your hostname, [93.184.216.34]; from=<badactor@example.com> to=<goodguy@example.com> proto=ESMTP helo=<badserver.example.com>

View File

@ -0,0 +1,2 @@
# failJSON: { "time": "2004-12-30T18:19:15", "match": true , "host": "93.184.216.34" }
Dec 30 18:19:15 xxx postfix/smtpd[1574]: NOQUEUE: reject: RCPT from badguy.example.com[93.184.216.34]: 454 4.7.1 Service unavailable; Client host [93.184.216.34] blocked using rbl.example.com; http://www.example.com/query?ip=93.184.216.34; from=<spammer@example.com> to=<goodguy@example.com> proto=ESMTP helo=<badguy.example.com>

View File

@ -12,3 +12,12 @@ Sep 6 00:44:56 trianon postfix/submission/smtpd[11538]: warning: unknown[82.221
#4 Example from postfix post-debian changes to rename to add "submission" to syslog name + downcase #4 Example from postfix post-debian changes to rename to add "submission" to syslog name + downcase
# failJSON: { "time": "2004-09-06T00:44:57", "match": true , "host": "82.221.106.233" } # failJSON: { "time": "2004-09-06T00:44:57", "match": true , "host": "82.221.106.233" }
Sep 6 00:44:57 trianon postfix/submission/smtpd[11538]: warning: unknown[82.221.106.233]: SASL login authentication failed: UGFzc3dvcmQ6 Sep 6 00:44:57 trianon postfix/submission/smtpd[11538]: warning: unknown[82.221.106.233]: SASL login authentication failed: UGFzc3dvcmQ6
#5 Example to add :
# failJSON: { "time": "2005-01-29T08:11:45", "match": true , "host": "1.1.1.1" }
Jan 29 08:11:45 mail postfix/smtpd[10752]: warning: unknown[1.1.1.1]: SASL LOGIN authentication failed: Password:
#6 Example to ignore because due to a failed attempt to connect to authentication service - no malicious activities whatsoever
# failJSON: { "time": "2005-02-03T08:29:28", "match": false , "host": "1.1.1.1" }
Feb 3 08:29:28 mail postfix/smtpd[21022]: warning: unknown[1.1.1.1]: SASL LOGIN authentication failed: Connection lost to authentication server

View File

@ -148,3 +148,8 @@ Apr 27 13:02:04 host sshd[29116]: User root not allowed because account is locke
Apr 27 13:02:04 host sshd[29116]: input_userauth_request: invalid user root [preauth] Apr 27 13:02:04 host sshd[29116]: input_userauth_request: invalid user root [preauth]
# failJSON: { "time": "2005-04-27T13:02:04", "match": true , "host": "1.2.3.4", "desc": "No Bye-Bye" } # failJSON: { "time": "2005-04-27T13:02:04", "match": true , "host": "1.2.3.4", "desc": "No Bye-Bye" }
Apr 27 13:02:04 host sshd[29116]: Received disconnect from 1.2.3.4: 11: Normal Shutdown, Thank you for playing [preauth] Apr 27 13:02:04 host sshd[29116]: Received disconnect from 1.2.3.4: 11: Normal Shutdown, Thank you for playing [preauth]
# Match sshd auth errors on OpenSUSE systems
# failJSON: { "time": "2015-04-16T20:02:50", "match": true , "host": "222.186.21.217", "desc": "Authentication for user failed" }
2015-04-16T18:02:50.321974+00:00 host sshd[2716]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=222.186.21.217 user=root

View File

@ -224,7 +224,7 @@ class IgnoreIP(LogCaptureTestCase):
self.assertTrue(self.filter.inIgnoreIPList(ip)) self.assertTrue(self.filter.inIgnoreIPList(ip))
def testIgnoreIPNOK(self): def testIgnoreIPNOK(self):
ipList = "", "999.999.999.999", "abcdef", "192.168.0." ipList = "", "999.999.999.999", "abcdef.abcdef", "192.168.0."
for ip in ipList: for ip in ipList:
self.filter.addIgnoreIP(ip) self.filter.addIgnoreIP(ip)
self.assertFalse(self.filter.inIgnoreIPList(ip)) self.assertFalse(self.filter.inIgnoreIPList(ip))
@ -266,6 +266,15 @@ class IgnoreIP(LogCaptureTestCase):
self.assertTrue(self.filter.inIgnoreIPList("10.0.0.1")) self.assertTrue(self.filter.inIgnoreIPList("10.0.0.1"))
self.assertFalse(self.filter.inIgnoreIPList("10.0.0.0")) self.assertFalse(self.filter.inIgnoreIPList("10.0.0.0"))
def testIgnoreCauseOK(self):
ip = "93.184.216.34"
for ignore_source in ["dns", "ip", "command"]:
self.filter.logIgnoreIp(ip, True, ignore_source=ignore_source)
self.assertTrue(self._is_logged("[%s] Ignore %s by %s" % (self.jail.name, ip, ignore_source)))
def testIgnoreCauseNOK(self):
self.filter.logIgnoreIp("example.com", False, ignore_source="NOT_LOGGED")
self.assertFalse(self._is_logged("[%s] Ignore %s by %s" % (self.jail.name, "example.com", "NOT_LOGGED")))
class IgnoreIPDNS(IgnoreIP): class IgnoreIPDNS(IgnoreIP):
@ -1011,6 +1020,29 @@ class DNSUtilsTests(unittest.TestCase):
else: else:
self.assertEqual(res, []) self.assertEqual(res, [])
def testIpToName(self):
res = DNSUtils.ipToName('66.249.66.1')
self.assertEqual(res, 'crawl-66-249-66-1.googlebot.com')
# invalid ip (TEST-NET-1 according to RFC 5737)
res = DNSUtils.ipToName('192.0.2.0')
self.assertEqual(res, None)
def testAddr2bin(self):
res = DNSUtils.addr2bin('10.0.0.0')
self.assertEqual(res, 167772160L)
res = DNSUtils.addr2bin('10.0.0.0', cidr=None)
self.assertEqual(res, 167772160L)
res = DNSUtils.addr2bin('10.0.0.0', cidr=32L)
self.assertEqual(res, 167772160L)
res = DNSUtils.addr2bin('10.0.0.1', cidr=32L)
self.assertEqual(res, 167772161L)
res = DNSUtils.addr2bin('10.0.0.1', cidr=31L)
self.assertEqual(res, 167772160L)
def testBin2addr(self):
res = DNSUtils.bin2addr(167772160L)
self.assertEqual(res, '10.0.0.0')
class JailTests(unittest.TestCase): class JailTests(unittest.TestCase):
def testSetBackend_gh83(self): def testSetBackend_gh83(self):

View File

@ -113,7 +113,7 @@ class SetupTest(unittest.TestCase):
# clean up # clean up
shutil.rmtree(tmp) shutil.rmtree(tmp)
# remove build directory # remove build directory
os.system("%s %s clean --all >/dev/null" os.system("%s %s clean --all >/dev/null 2>&1"
% (sys.executable, self.setup)) % (sys.executable, self.setup))
class TestsUtilsTest(unittest.TestCase): class TestsUtilsTest(unittest.TestCase):

View File

@ -141,7 +141,8 @@ def testSampleRegexsFactory(name):
return testFilter return testFilter
for filter_ in filter(lambda x: not x.endswith('common.conf'), os.listdir(os.path.join(CONFIG_DIR, "filter.d"))): for filter_ in filter(lambda x: not x.endswith('common.conf') and x.endswith('.conf'),
os.listdir(os.path.join(CONFIG_DIR, "filter.d"))):
filterName = filter_.rpartition(".")[0] filterName = filter_.rpartition(".")[0]
if not filterName.startswith('.'): if not filterName.startswith('.'):
setattr( setattr(

View File

@ -30,6 +30,7 @@ import tempfile
import os import os
import locale import locale
import sys import sys
import platform
from ..server.failregex import Regex, FailRegex, RegexException from ..server.failregex import Regex, FailRegex, RegexException
from ..server.server import Server from ..server.server import Server
@ -70,17 +71,24 @@ class TransmitterBase(unittest.TestCase):
"""Call after every test case.""" """Call after every test case."""
self.server.quit() self.server.quit()
def setGetTest(self, cmd, inValue, outValue=None, jail=None): def setGetTest(self, cmd, inValue, outValue=None, outCode=0, jail=None, repr_=False):
setCmd = ["set", cmd, inValue] setCmd = ["set", cmd, inValue]
getCmd = ["get", cmd] getCmd = ["get", cmd]
if jail is not None: if jail is not None:
setCmd.insert(1, jail) setCmd.insert(1, jail)
getCmd.insert(1, jail) getCmd.insert(1, jail)
if outValue is None: if outValue is None:
outValue = inValue outValue = inValue
self.assertEqual(self.transm.proceed(setCmd), (0, outValue)) def v(x):
self.assertEqual(self.transm.proceed(getCmd), (0, outValue)) """Prepare value for comparison"""
return (repr(x) if repr_ else x)
self.assertEqual(v(self.transm.proceed(setCmd)), v((outCode, outValue)))
if not outCode:
# if we expected to get it set without problem, check new value
self.assertEqual(v(self.transm.proceed(getCmd)), v((0, outValue)))
def setGetTestNOK(self, cmd, inValue, jail=None): def setGetTestNOK(self, cmd, inValue, jail=None):
setCmd = ["set", cmd, inValue] setCmd = ["set", cmd, inValue]
@ -474,6 +482,72 @@ class Transmitter(TransmitterBase):
) )
) )
def testJailStatusBasic(self):
self.assertEqual(self.transm.proceed(["status", self.jailName, "basic"]),
(0,
[
('Filter', [
('Currently failed', 0),
('Total failed', 0),
('File list', [])]
),
('Actions', [
('Currently banned', 0),
('Total banned', 0),
('Banned IP list', [])]
)
]
)
)
def testJailStatusBasicKwarg(self):
self.assertEqual(self.transm.proceed(["status", self.jailName, "INVALID"]),
(0,
[
('Filter', [
('Currently failed', 0),
('Total failed', 0),
('File list', [])]
),
('Actions', [
('Currently banned', 0),
('Total banned', 0),
('Banned IP list', [])]
)
]
)
)
def testJailStatusCymru(self):
try:
import dns.exception
import dns.resolver
except ImportError:
value = ['error']
else:
value = []
self.assertEqual(self.transm.proceed(["status", self.jailName, "cymru"]),
(0,
[
('Filter', [
('Currently failed', 0),
('Total failed', 0),
('File list', [])]
),
('Actions', [
('Currently banned', 0),
('Total banned', 0),
('Banned IP list', []),
('Banned ASN list', value),
('Banned Country list', value),
('Banned RIR list', value)]
)
]
)
)
def testAction(self): def testAction(self):
action = "TestCaseAction" action = "TestCaseAction"
cmdList = [ cmdList = [
@ -681,6 +755,7 @@ class TransmitterLogging(TransmitterBase):
self.server = Server() self.server = Server()
self.server.setLogTarget("/dev/null") self.server.setLogTarget("/dev/null")
self.server.setLogLevel("CRITICAL") self.server.setLogLevel("CRITICAL")
self.server.setSyslogSocket("auto")
super(TransmitterLogging, self).setUp() super(TransmitterLogging, self).setUp()
def testLogTarget(self): def testLogTarget(self):
@ -708,7 +783,27 @@ class TransmitterLogging(TransmitterBase):
raise unittest.SkipTest("'/dev/log' not present") raise unittest.SkipTest("'/dev/log' not present")
elif not os.path.exists("/dev/log"): elif not os.path.exists("/dev/log"):
return return
self.assertTrue(self.server.getSyslogSocket(), "auto")
self.setGetTest("logtarget", "SYSLOG") self.setGetTest("logtarget", "SYSLOG")
self.assertTrue(self.server.getSyslogSocket(), "/dev/log")
def testSyslogSocket(self):
self.setGetTest("syslogsocket", "/dev/log/NEW/PATH")
def testSyslogSocketNOK(self):
self.setGetTest("syslogsocket", "/this/path/should/not/exist")
self.setGetTestNOK("logtarget", "SYSLOG")
# set back for other tests
self.setGetTest("syslogsocket", "/dev/log")
self.setGetTest("logtarget", "SYSLOG",
**{True: {}, # should work on Linux
False: dict( # expect to fail otherwise
outCode=1,
outValue=Exception('Failed to change log target'),
repr_=True # Exceptions are not comparable apparently
)
}[platform.system() in ('Linux',)]
)
def testLogLevel(self): def testLogLevel(self):
self.setGetTest("loglevel", "HEAVYDEBUG") self.setGetTest("loglevel", "HEAVYDEBUG")

View File

@ -107,6 +107,11 @@ def gatherTests(regexps=None, no_network=False):
tests.addTest(unittest.makeSuite(failmanagertestcase.AddFailure)) tests.addTest(unittest.makeSuite(failmanagertestcase.AddFailure))
# BanManager # BanManager
tests.addTest(unittest.makeSuite(banmanagertestcase.AddFailure)) tests.addTest(unittest.makeSuite(banmanagertestcase.AddFailure))
try:
import dns
tests.addTest(unittest.makeSuite(banmanagertestcase.StatusExtendedCymruInfo))
except ImportError:
pass
# ClientReaders # ClientReaders
tests.addTest(unittest.makeSuite(clientreadertestcase.ConfigReaderTest)) tests.addTest(unittest.makeSuite(clientreadertestcase.ConfigReaderTest))
tests.addTest(unittest.makeSuite(clientreadertestcase.JailReaderTest)) tests.addTest(unittest.makeSuite(clientreadertestcase.JailReaderTest))
@ -148,7 +153,7 @@ def gatherTests(regexps=None, no_network=False):
for file_ in os.listdir( for file_ in os.listdir(
os.path.abspath(os.path.dirname(action_d.__file__))): os.path.abspath(os.path.dirname(action_d.__file__))):
if file_.startswith("test_") and file_.endswith(".py"): if file_.startswith("test_") and file_.endswith(".py"):
if no_network and file_ in ['test_badips.py']: #pragma: no cover if no_network and file_ in ['test_badips.py','test_smtp.py']: #pragma: no cover
# Test required network # Test required network
continue continue
tests.addTest(testloader.loadTestsFromName( tests.addTest(testloader.loadTestsFromName(

View File

@ -21,7 +21,7 @@
# #
__author__ = "Cyril Jaquier, Yaroslav Halchenko, Steven Hiscocks, Daniel Black" __author__ = "Cyril Jaquier, Yaroslav Halchenko, Steven Hiscocks, Daniel Black"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2014 Yaroslav Halchenko, 2013-2013 Steven Hiscocks, Daniel Black" __copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2005-2015 Yaroslav Halchenko, 2013-2014 Steven Hiscocks, Daniel Black"
__license__ = "GPL-v2+" __license__ = "GPL-v2+"
version = "0.9.1.dev" version = "0.9.2"

View File

@ -3,8 +3,8 @@
# Provides: fail2ban # Provides: fail2ban
# Required-Start: $local_fs $remote_fs # Required-Start: $local_fs $remote_fs
# Required-Stop: $local_fs $remote_fs # Required-Stop: $local_fs $remote_fs
# Should-Start: $time $network $syslog iptables firehol shorewall ipmasq arno-iptables-firewall iptables-persistent ferm # Should-Start: $time $network $syslog $named iptables firehol shorewall ipmasq arno-iptables-firewall iptables-persistent ferm ufw
# Should-Stop: $network $syslog iptables firehol shorewall ipmasq arno-iptables-firewall iptables-persistent ferm # Should-Stop: $network $syslog $named iptables firehol shorewall ipmasq arno-iptables-firewall iptables-persistent ferm ufw
# Default-Start: 2 3 4 5 # Default-Start: 2 3 4 5
# Default-Stop: 0 1 6 # Default-Stop: 0 1 6
# Short-Description: Start/stop fail2ban # Short-Description: Start/stop fail2ban

View File

@ -1,12 +1,12 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.46.4. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.46.4.
.TH FAIL2BAN-CLIENT "1" "October 2014" "fail2ban-client v0.9.1" "User Commands" .TH FAIL2BAN-CLIENT "1" "April 2015" "fail2ban-client v0.9.2" "User Commands"
.SH NAME .SH NAME
fail2ban-client \- configure and control the server fail2ban-client \- configure and control the server
.SH SYNOPSIS .SH SYNOPSIS
.B fail2ban-client .B fail2ban-client
[\fI\,OPTIONS\/\fR] \fI\,<COMMAND>\/\fR [\fI\,OPTIONS\/\fR] \fI\,<COMMAND>\/\fR
.SH DESCRIPTION .SH DESCRIPTION
Fail2Ban v0.9.1 reads log file that contains password failure report Fail2Ban v0.9.2 reads log file that contains password failure report
and bans the corresponding IP addresses using firewall rules. and bans the corresponding IP addresses using firewall rules.
.SH OPTIONS .SH OPTIONS
.TP .TP
@ -93,6 +93,14 @@ file
\fBget logtarget\fR \fBget logtarget\fR
gets logging target gets logging target
.TP .TP
\fBset syslogsocket auto|<SOCKET>\fR
sets the syslog socket path to
auto or <SOCKET>. Only used if
logtarget is SYSLOG
.TP
\fBget syslogsocket\fR
gets syslog socket path
.TP
\fBflushlogs\fR \fBflushlogs\fR
flushes the logtarget if a file flushes the logtarget if a file
and reopens it. For log rotation. and reopens it. For log rotation.
@ -128,8 +136,10 @@ starts the jail <JAIL>
stops the jail <JAIL>. The jail is stops the jail <JAIL>. The jail is
removed removed
.TP .TP
\fBstatus <JAIL>\fR \fBstatus <JAIL> [FLAVOR]\fR
gets the current status of <JAIL> gets the current status of <JAIL>,
with optional flavor or extended
info
.IP .IP
JAIL CONFIGURATION JAIL CONFIGURATION
.TP .TP

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.46.4. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.46.4.
.TH FAIL2BAN-REGEX "1" "October 2014" "fail2ban-regex 0.9.1" "User Commands" .TH FAIL2BAN-REGEX "1" "April 2015" "fail2ban-regex 0.9.2" "User Commands"
.SH NAME .SH NAME
fail2ban-regex \- test Fail2ban "failregex" option fail2ban-regex \- test Fail2ban "failregex" option
.SH SYNOPSIS .SH SYNOPSIS

View File

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

View File

@ -124,6 +124,9 @@ setup(
('/etc/fail2ban/filter.d', ('/etc/fail2ban/filter.d',
glob("config/filter.d/*.conf") glob("config/filter.d/*.conf")
), ),
('/etc/fail2ban/filter.d/ignorecommands',
glob("config/filter.d/ignorecommands/*")
),
('/etc/fail2ban/action.d', ('/etc/fail2ban/action.d',
glob("config/action.d/*.conf") + glob("config/action.d/*.conf") +
glob("config/action.d/*.py") glob("config/action.d/*.py")