revert 0.11 and reintegrate branch '0.10' into wc/debian-0.10

debian
sebres 2020-11-23 20:25:46 +01:00
commit db88167994
59 changed files with 310 additions and 2320 deletions

View File

@ -6,19 +6,13 @@
Fail2Ban: Changelog Fail2Ban: Changelog
=================== ===================
ver. 0.11.2-dev (20??/??/??) - development edition ver. 0.10.6 (2020/11/23) - just-what-the-doctor-ordered
----------- -----------
### Compatibility: ### Incompatibility list (v.0.10 compared to v.0.9):
* to v.0.10: * Filter (or `failregex`) internal capture-groups:
- 0.11 is totally compatible to 0.10 (configuration- and API-related stuff), but the database
got some new tables and fields (auto-converted during the first start), so once updated to 0.11, you
have to remove the database /var/lib/fail2ban/fail2ban.sqlite3 (or its different to 0.10 schema)
if you would need to downgrade to 0.10 for some reason.
* to v.0.9:
- Filter (or `failregex`) internal capture-groups:
* If you've your own `failregex` or custom filters using conditional match `(?P=host)`, you should - If you've your own `failregex` or custom filters using conditional match `(?P=host)`, you should
rewrite the regex like in example below resp. using `(?:(?P=ip4)|(?P=ip6)` instead of `(?P=host)` rewrite the regex like in example below resp. using `(?:(?P=ip4)|(?P=ip6)` instead of `(?P=host)`
(or `(?:(?P=ip4)|(?P=ip6)|(?P=dns))` corresponding your `usedns` and `raw` settings). (or `(?:(?P=ip4)|(?P=ip6)|(?P=dns))` corresponding your `usedns` and `raw` settings).
@ -27,14 +21,14 @@ ver. 0.11.2-dev (20??/??/??) - development edition
testln="1500000000 failure from 192.0.2.1: bad host 192.0.2.1" testln="1500000000 failure from 192.0.2.1: bad host 192.0.2.1"
fail2ban-regex "$testln" "^\s*failure from (?P<_cond_ip_><HOST>): bad host (?P=_cond_ip_)$" fail2ban-regex "$testln" "^\s*failure from (?P<_cond_ip_><HOST>): bad host (?P=_cond_ip_)$"
``` ```
* New internal groups (currently reserved for internal usage): - New internal groups (currently reserved for internal usage):
`ip4`, `ip6`, `dns`, `fid`, `fport`, additionally `user` and another captures in lower case if `ip4`, `ip6`, `dns`, `fid`, `fport`, additionally `user` and another captures in lower case if
mapping from tag `<F-*>` used in failregex (e. g. `user` by `<F-USER>`). mapping from tag `<F-*>` used in failregex (e. g. `user` by `<F-USER>`).
- v.0.10 and 0.11 use more precise date template handling, that can be theoretically incompatible to some * v.0.10 uses more precise date template handling, that can be theoretically incompatible to some
user configurations resp. `datepattern`. user configurations resp. `datepattern`.
- Since v0.10 fail2ban supports the matching of IPv6 addresses, but not all ban actions are * Since v0.10 fail2ban supports the matching of IPv6 addresses, but not all ban actions are
IPv6-capable now. IPv6-capable now.
### Fixes ### Fixes
@ -61,6 +55,9 @@ ver. 0.11.2-dev (20??/??/??) - development edition
* `action.d/bsd-ipfw.conf`: fixed selection of rule-no by large list or initial `lowest_rule_num` (gh-2836) * `action.d/bsd-ipfw.conf`: fixed selection of rule-no by large list or initial `lowest_rule_num` (gh-2836)
* `filter.d/common.conf`: avoid substitute of default values in related `lt_*` section, `__prefix_line` * `filter.d/common.conf`: avoid substitute of default values in related `lt_*` section, `__prefix_line`
should be interpolated in definition section (inside the filter-config, gh-2650) should be interpolated in definition section (inside the filter-config, gh-2650)
* `filter.d/dovecot.conf`:
- add managesieve and submission support (gh-2795);
- accept messages with more verbose logging (gh-2573);
* `filter.d/courier-smtp.conf`: prefregex extended to consider port in log-message (gh-2697) * `filter.d/courier-smtp.conf`: prefregex extended to consider port in log-message (gh-2697)
* `filter.d/traefik-auth.conf`: filter extended with parameter mode (`normal`, `ddos`, `aggressive`) to handle * `filter.d/traefik-auth.conf`: filter extended with parameter mode (`normal`, `ddos`, `aggressive`) to handle
the match of username differently (gh-2693): the match of username differently (gh-2693):
@ -94,36 +91,6 @@ ver. 0.11.2-dev (20??/??/??) - development edition
whereas filter will use now as timestamp (gh-2802) whereas filter will use now as timestamp (gh-2802)
* performance optimization of `datepattern` (better search algorithm in datedetector, especially for single template); * performance optimization of `datepattern` (better search algorithm in datedetector, especially for single template);
* fail2ban-client: extended to unban IP range(s) by subnet (CIDR/mask) or hostname (DNS), gh-2791; * fail2ban-client: extended to unban IP range(s) by subnet (CIDR/mask) or hostname (DNS), gh-2791;
* extended capturing of alternate tags in filter, allowing combine of multiple groups to single tuple token with new tag
prefix `<F-TUPLE_`, that would combine value of `<F-V>` with all value of `<F-TUPLE_V?_n?>` tags (gh-2755)
ver. 0.11.1 (2020/01/11) - this-is-the-way
-----------
### Fixes
* purge database will be executed now (within observer).
* restoring currently banned ip after service restart fixed
(now < timeofban + bantime), ignore old log failures (already banned)
* upgrade database: update new created table `bips` with entries from table `bans` (allows restore
current bans after upgrade from version <= 0.10)
### New Features
* Increment ban time (+ observer) functionality introduced.
* Database functionality extended with bad ips.
* New tags (usable in actions):
- `<bancount>` - ban count of this offender if known as bad (started by 1 for unknown)
- `<bantime>` - current ban-time of the ticket (prolongation can be retarded up to 10 sec.)
* Introduced new action command `actionprolong` to prolong ban-time (e. g. set new timeout if expected);
Several actions (like ipset, etc.) rewritten using net logic with `actionprolong`.
Note: because ban-time is dynamic, it was removed from jail.conf as timeout argument (check jail.local).
### Enhancements
* algorithm of restore current bans after restart changed: update the restored ban-time (and therefore
end of ban) of the ticket with ban-time of jail (as maximum), for all tickets with ban-time greater
(or persistent); not affected if ban-time of the jail is unchanged between stop/start.
* added new setup-option `--without-tests` to skip building and installing of tests files (gh-2287).
* added new command `fail2ban-client get <JAIL> banip ?sep-char|--with-time?` to get the banned ip addresses (gh-1916).
ver. 0.10.5 (2020/01/10) - deserve-more-respect-a-jedis-weapon-must ver. 0.10.5 (2020/01/10) - deserve-more-respect-a-jedis-weapon-must
@ -504,14 +471,9 @@ TODO: implementing of options resp. other tasks from PR #1346
- `<fid>` - failure identifier (if raw resp. failures without IP address) - `<fid>` - failure identifier (if raw resp. failures without IP address)
- `<ip-rev>` - PTR reversed representation of IP address - `<ip-rev>` - PTR reversed representation of IP address
- `<ip-host>` - host name of the IP address - `<ip-host>` - host name of the IP address
- `<bancount>` - ban count of this offender if known as bad (started by 1 for unknown)
- `<bantime>` - current ban-time of the ticket (prolongation can be retarded up to 10 sec.)
- `<F-...>` - interpolates to the corresponding filter group capture `...` - `<F-...>` - interpolates to the corresponding filter group capture `...`
- `<fq-hostname>` - fully-qualified name of host (the same as `$(hostname -f)`) - `<fq-hostname>` - fully-qualified name of host (the same as `$(hostname -f)`)
- `<sh-hostname>` - short hostname (the same as `$(uname -n)`) - `<sh-hostname>` - short hostname (the same as `$(uname -n)`)
* Introduced new action command `actionprolong` to prolong ban-time (e. g. set new timeout if expected);
Several actions (like ipset, etc.) rewritten using net logic with `actionprolong`.
Note: because ban-time is dynamic, it was removed from jail.conf as timeout argument (check jail.local).
* Allow to use filter options by `fail2ban-regex`, example: * Allow to use filter options by `fail2ban-regex`, example:
fail2ban-regex text.log "sshd[mode=aggressive]" fail2ban-regex text.log "sshd[mode=aggressive]"
* Samples test case factory extended with filter options - dict in JSON to control * Samples test case factory extended with filter options - dict in JSON to control
@ -585,9 +547,6 @@ ver. 0.10.0-alpha-1 (2016/07/14) - ipv6-support-etc
* testSocket: sporadical bug repaired - wait for server thread starts a socket (listener) * testSocket: sporadical bug repaired - wait for server thread starts a socket (listener)
* testExecuteTimeoutWithNastyChildren: sporadical bug repaired - wait for pid file inside bash, * testExecuteTimeoutWithNastyChildren: sporadical bug repaired - wait for pid file inside bash,
kill tree in any case (gh-1155) kill tree in any case (gh-1155)
* purge database will be executed now (within observer).
* restoring currently banned ip after service restart fixed
(now < timeofban + bantime), ignore old log failures (already banned)
* Fixed high-load of pyinotify-backend, * Fixed high-load of pyinotify-backend,
see https://github.com/fail2ban/fail2ban/issues/885#issuecomment-248964591 see https://github.com/fail2ban/fail2ban/issues/885#issuecomment-248964591
* Database: stability fix - repack cursor iterator as long as locked * Database: stability fix - repack cursor iterator as long as locked
@ -625,9 +584,6 @@ ver. 0.10.0-alpha-1 (2016/07/14) - ipv6-support-etc
- new conditional section functionality used in config resp. includes: - new conditional section functionality used in config resp. includes:
- [Init?family=inet4] - IPv4 qualified hosts only - [Init?family=inet4] - IPv4 qualified hosts only
- [Init?family=inet6] - IPv6 qualified hosts only - [Init?family=inet6] - IPv6 qualified hosts only
* Increment ban time (+ observer) functionality introduced.
Thanks Serg G. Brester (sebres)
* Database functionality extended with bad ips.
* New reload functionality (now totally without restart, unbanning/rebanning, etc.), * New reload functionality (now totally without restart, unbanning/rebanning, etc.),
see gh-1557 see gh-1557
* Several commands extended and new commands introduced: * Several commands extended and new commands introduced:

View File

@ -100,6 +100,8 @@ config/filter.d/exim.conf
config/filter.d/exim-spam.conf config/filter.d/exim-spam.conf
config/filter.d/freeswitch.conf config/filter.d/freeswitch.conf
config/filter.d/froxlor-auth.conf config/filter.d/froxlor-auth.conf
config/filter.d/gitlab.conf
config/filter.d/grafana.conf
config/filter.d/groupoffice.conf config/filter.d/groupoffice.conf
config/filter.d/gssftpd.conf config/filter.d/gssftpd.conf
config/filter.d/guacamole.conf config/filter.d/guacamole.conf
@ -139,6 +141,7 @@ config/filter.d/sendmail-auth.conf
config/filter.d/sendmail-reject.conf config/filter.d/sendmail-reject.conf
config/filter.d/sieve.conf config/filter.d/sieve.conf
config/filter.d/slapd.conf config/filter.d/slapd.conf
config/filter.d/softethervpn.conf
config/filter.d/sogo-auth.conf config/filter.d/sogo-auth.conf
config/filter.d/solid-pop3d.conf config/filter.d/solid-pop3d.conf
config/filter.d/squid.conf config/filter.d/squid.conf
@ -207,7 +210,6 @@ fail2ban/server/jail.py
fail2ban/server/jails.py fail2ban/server/jails.py
fail2ban/server/jailthread.py fail2ban/server/jailthread.py
fail2ban/server/mytime.py fail2ban/server/mytime.py
fail2ban/server/observer.py
fail2ban/server/server.py fail2ban/server/server.py
fail2ban/server/strptime.py fail2ban/server/strptime.py
fail2ban/server/ticket.py fail2ban/server/ticket.py
@ -264,9 +266,10 @@ fail2ban/tests/files/config/apache-auth/digest_wrongrelm/.htpasswd
fail2ban/tests/files/config/apache-auth/noentry/.htaccess fail2ban/tests/files/config/apache-auth/noentry/.htaccess
fail2ban/tests/files/config/apache-auth/README fail2ban/tests/files/config/apache-auth/README
fail2ban/tests/files/database_v1.db fail2ban/tests/files/database_v1.db
fail2ban/tests/files/database_v2.db
fail2ban/tests/files/filter.d/substition.conf fail2ban/tests/files/filter.d/substition.conf
fail2ban/tests/files/filter.d/testcase01.conf fail2ban/tests/files/filter.d/testcase01.conf
fail2ban/tests/files/filter.d/testcase02.conf
fail2ban/tests/files/filter.d/testcase02.local
fail2ban/tests/files/filter.d/testcase-common.conf fail2ban/tests/files/filter.d/testcase-common.conf
fail2ban/tests/files/ignorecommand.py fail2ban/tests/files/ignorecommand.py
fail2ban/tests/files/logs/3proxy fail2ban/tests/files/logs/3proxy
@ -301,6 +304,8 @@ fail2ban/tests/files/logs/exim
fail2ban/tests/files/logs/exim-spam fail2ban/tests/files/logs/exim-spam
fail2ban/tests/files/logs/freeswitch fail2ban/tests/files/logs/freeswitch
fail2ban/tests/files/logs/froxlor-auth fail2ban/tests/files/logs/froxlor-auth
fail2ban/tests/files/logs/gitlab
fail2ban/tests/files/logs/grafana
fail2ban/tests/files/logs/groupoffice fail2ban/tests/files/logs/groupoffice
fail2ban/tests/files/logs/gssftpd fail2ban/tests/files/logs/gssftpd
fail2ban/tests/files/logs/guacamole fail2ban/tests/files/logs/guacamole
@ -338,6 +343,7 @@ fail2ban/tests/files/logs/sendmail-auth
fail2ban/tests/files/logs/sendmail-reject fail2ban/tests/files/logs/sendmail-reject
fail2ban/tests/files/logs/sieve fail2ban/tests/files/logs/sieve
fail2ban/tests/files/logs/slapd fail2ban/tests/files/logs/slapd
fail2ban/tests/files/logs/softethervpn
fail2ban/tests/files/logs/sogo-auth fail2ban/tests/files/logs/sogo-auth
fail2ban/tests/files/logs/solid-pop3d fail2ban/tests/files/logs/solid-pop3d
fail2ban/tests/files/logs/squid fail2ban/tests/files/logs/squid
@ -370,7 +376,6 @@ fail2ban/tests/files/zzz-sshd-obsolete-multiline.log
fail2ban/tests/filtertestcase.py fail2ban/tests/filtertestcase.py
fail2ban/tests/__init__.py fail2ban/tests/__init__.py
fail2ban/tests/misctestcase.py fail2ban/tests/misctestcase.py
fail2ban/tests/observertestcase.py
fail2ban/tests/samplestestcase.py fail2ban/tests/samplestestcase.py
fail2ban/tests/servertestcase.py fail2ban/tests/servertestcase.py
fail2ban/tests/sockettestcase.py fail2ban/tests/sockettestcase.py

View File

@ -2,7 +2,7 @@
/ _|__ _(_) |_ ) |__ __ _ _ _ / _|__ _(_) |_ ) |__ __ _ _ _
| _/ _` | | |/ /| '_ \/ _` | ' \ | _/ _` | | |/ /| '_ \/ _` | ' \
|_| \__,_|_|_/___|_.__/\__,_|_||_| |_| \__,_|_|_/___|_.__/\__,_|_||_|
v0.11.0.dev1 20??/??/?? v0.10.3.dev1 20??/??/??
## Fail2Ban: ban hosts that cause multiple authentication errors ## Fail2Ban: ban hosts that cause multiple authentication errors
@ -18,12 +18,11 @@ attempts, it cannot eliminate the risk presented by weak authentication.
Set up services to use only two factor, or public/private authentication Set up services to use only two factor, or public/private authentication
mechanisms if you really want to protect services. mechanisms if you really want to protect services.
<img src="http://www.worldipv6launch.org/wp-content/themes/ipv6/downloads/World_IPv6_launch_logo.svg" height="52pt"/> | Since v0.10 fail2ban supports the matching of IPv6 addresses. <img src="http://www.worldipv6launch.org/wp-content/themes/ipv6/downloads/World_IPv6_launch_logo.svg" height="52pt"/> | Since v0.10 fail2ban supports the matching of the IPv6 addresses.
------|------ ------|------
This README is a quick introduction to Fail2Ban. More documentation, FAQ, and HOWTOs This README is a quick introduction to Fail2Ban. More documentation, FAQ, and HOWTOs
to be found on fail2ban(1) manpage, [Wiki](https://github.com/fail2ban/fail2ban/wiki), to be found on fail2ban(1) manpage, [Wiki](https://github.com/fail2ban/fail2ban/wiki)
[Developers documentation](https://fail2ban.readthedocs.io/)
and the website: https://www.fail2ban.org and the website: https://www.fail2ban.org
Installation: Installation:
@ -46,15 +45,9 @@ Optional:
To install: To install:
tar xvfj fail2ban-0.11.0.tar.bz2 tar xvfj fail2ban-0.10.3.tar.bz2
cd fail2ban-0.11.0 cd fail2ban-0.10.3
sudo python setup.py install python setup.py install
Alternatively, you can clone the source from GitHub to a directory of Your choice, and do the install from there. Pick the correct branch, for example, 0.11
git clone https://github.com/fail2ban/fail2ban.git
cd fail2ban
sudo 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
scripts are placed into `/usr/bin`, and configuration in `/etc/fail2ban`. scripts are placed into `/usr/bin`, and configuration in `/etc/fail2ban`.
@ -65,9 +58,6 @@ Fail2Ban should be correctly installed now. Just type:
to see if everything is alright. You should always use fail2ban-client and to see if everything is alright. You should always use fail2ban-client and
never call fail2ban-server directly. never call fail2ban-server directly.
You can verify that you have the correct version installed with
fail2ban-client version
Please note that the system init/service script is not automatically installed. Please note that the system init/service script is not automatically installed.
To enable fail2ban as an automatic service, simply copy the script for your To enable fail2ban as an automatic service, simply copy the script for your
@ -89,11 +79,11 @@ fail2ban(1) and jail.conf(5) manpages for further references.
Code status: Code status:
------------ ------------
* travis-ci.org: [![tests status](https://secure.travis-ci.org/fail2ban/fail2ban.svg?branch=0.11)](https://travis-ci.org/fail2ban/fail2ban?branch=0.11) (0.11 branch) / [![tests status](https://secure.travis-ci.org/fail2ban/fail2ban.svg?branch=0.10)](https://travis-ci.org/fail2ban/fail2ban?branch=0.10) (0.10 branch) * [![tests status](https://secure.travis-ci.org/fail2ban/fail2ban.png?branch=0.10)](https://travis-ci.org/fail2ban/fail2ban?branch=0.10) travis-ci.org (0.10 branch) / [![tests status](https://secure.travis-ci.org/fail2ban/fail2ban.png?branch=master)](https://travis-ci.org/fail2ban/fail2ban) travis-ci.org (master branch)
* coveralls.io: [![Coverage Status](https://coveralls.io/repos/fail2ban/fail2ban/badge.svg?branch=0.11)](https://coveralls.io/github/fail2ban/fail2ban?branch=0.11) (0.11 branch) / [![Coverage Status](https://coveralls.io/repos/fail2ban/fail2ban/badge.svg?branch=0.10)](https://coveralls.io/github/fail2ban/fail2ban?branch=0.10) / (0.10 branch) * [![Coverage Status](https://coveralls.io/repos/fail2ban/fail2ban/badge.png?branch=0.10)](https://coveralls.io/github/fail2ban/fail2ban?branch=0.10)
* codecov.io: [![codecov.io](https://codecov.io/gh/fail2ban/fail2ban/coverage.svg?branch=0.11)](https://codecov.io/gh/fail2ban/fail2ban/branch/0.11) (0.11 branch) / [![codecov.io](https://codecov.io/gh/fail2ban/fail2ban/coverage.svg?branch=0.10)](https://codecov.io/gh/fail2ban/fail2ban/branch/0.10) (0.10 branch) * [![codecov.io](https://codecov.io/gh/fail2ban/fail2ban/coverage.svg?branch=0.10)](https://codecov.io/gh/fail2ban/fail2ban/branch/0.10)
Contact: Contact:
-------- --------

2
THANKS
View File

@ -111,7 +111,7 @@ Russell Odom
SATO Kentaro SATO Kentaro
Sean DuBois Sean DuBois
Sebastian Arcus Sebastian Arcus
Serg G. Brester (sebres) Serg G. Brester
Sergey Safarov Sergey Safarov
Shaun C. Shaun C.
Sireyessire Sireyessire

View File

@ -29,8 +29,6 @@ actionstop = firewall-cmd --direct --remove-rule <family> filter <chain> 0 <acti
actionban = ipset add <ipmset> <ip> timeout <ipsettime> -exist actionban = ipset add <ipmset> <ip> timeout <ipsettime> -exist
# actionprolong = %(actionban)s
actionunban = ipset del <ipmset> <ip> -exist actionunban = ipset del <ipmset> <ip> -exist
[Init] [Init]

View File

@ -51,8 +51,6 @@ actionstop = <iptables> -D <chain> -m set --match-set <ipmset> src -j <blocktype
# #
actionban = ipset add <ipmset> <ip> timeout <ipsettime> -exist actionban = ipset add <ipmset> <ip> timeout <ipsettime> -exist
# actionprolong = %(actionban)s
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.

View File

@ -51,8 +51,6 @@ actionstop = <iptables> -D <chain> -p <protocol> -m multiport --dports <port> -m
# #
actionban = ipset add <ipmset> <ip> timeout <ipsettime> -exist actionban = ipset add <ipmset> <ip> timeout <ipsettime> -exist
# actionprolong = %(actionban)s
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.

View File

@ -12,5 +12,5 @@ actioncheck =
actionban = /usr/libexec/afctl -a <ip> -t <bantime> actionban = /usr/libexec/afctl -a <ip> -t <bantime>
actionunban = /usr/libexec/afctl -r <ip> actionunban = /usr/libexec/afctl -r <ip>
actionprolong = %(actionunban)s && %(actionban)s [Init]
bantime = 2880

View File

@ -68,8 +68,6 @@ actionstop = ipset flush f2b-<name>
# #
actionban = ipset add f2b-<name> <ip> timeout <ipsettime> -exist actionban = ipset add f2b-<name> <ip> timeout <ipsettime> -exist
# actionprolong = %(actionban)s
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights. # command is executed with Fail2Ban user rights.

View File

@ -17,9 +17,9 @@ before = apache-common.conf
[Definition] [Definition]
script = /\S*(?:php(?:[45]|[.-]cgi)?|\.asp|\.exe|\.pl) script = /\S*(?:php(?:[45]|[.-]cgi)?|\.asp|\.exe|\.pl|\bcgi-bin/)
prefregex = ^%(_apache_error_client)s (?:AH0(?:01(?:28|30)|1(?:264|071)): )?(?:(?:[Ff]ile|script|[Gg]ot) )<F-CONTENT>.+</F-CONTENT>$ prefregex = ^%(_apache_error_client)s (?:AH0(?:01(?:28|30)|1(?:264|071)|2811): )?(?:(?:[Ff]ile|script|[Gg]ot) )<F-CONTENT>.+</F-CONTENT>$
failregex = ^(?:does not exist|not found or unable to stat): <script>\b failregex = ^(?:does not exist|not found or unable to stat): <script>\b
^'<script>\S*' not found or unable to stat ^'<script>\S*' not found or unable to stat

View File

@ -10,15 +10,15 @@ before = common.conf
_auth_worker = (?:dovecot: )?auth(?:-worker)? _auth_worker = (?:dovecot: )?auth(?:-worker)?
_daemon = (?:dovecot(?:-auth)?|auth) _daemon = (?:dovecot(?:-auth)?|auth)
prefregex = ^%(__prefix_line)s(?:%(_auth_worker)s(?:\([^\)]+\))?: )?(?:%(__pam_auth)s(?:\(dovecot:auth\))?: |(?:pop3|imap)-login: )?(?:Info: )?<F-CONTENT>.+</F-CONTENT>$ prefregex = ^%(__prefix_line)s(?:%(_auth_worker)s(?:\([^\)]+\))?: )?(?:%(__pam_auth)s(?:\(dovecot:auth\))?: |(?:pop3|imap|managesieve|submission)-login: )?(?:Info: )?<F-CONTENT>.+</F-CONTENT>$
failregex = ^authentication failure; logname=<F-ALT_USER1>\S*</F-ALT_USER1> uid=\S* euid=\S* tty=dovecot ruser=<F-USER>\S*</F-USER> rhost=<HOST>(?:\s+user=<F-ALT_USER>\S*</F-ALT_USER>)?\s*$ failregex = ^authentication failure; logname=<F-ALT_USER1>\S*</F-ALT_USER1> uid=\S* euid=\S* tty=dovecot ruser=<F-USER>\S*</F-USER> rhost=<HOST>(?:\s+user=<F-ALT_USER>\S*</F-ALT_USER>)?\s*$
^(?:Aborted login|Disconnected)(?::(?: [^ \(]+)+)? \((?:auth failed, \d+ attempts(?: in \d+ secs)?|tried to use (?:disabled|disallowed) \S+ auth|proxy dest auth failed)\):(?: user=<<F-USER>[^>]*</F-USER>>,)?(?: method=\S+,)? rip=<HOST>(?:[^>]*(?:, session=<\S+>)?)\s*$ ^(?:Aborted login|Disconnected|Remote closed connection|Client has quit the connection)(?::(?: [^ \(]+)+)? \((?:auth failed, \d+ attempts(?: in \d+ secs)?|tried to use (?:disabled|disallowed) \S+ auth|proxy dest auth failed)\):(?: user=<<F-USER>[^>]*</F-USER>>,)?(?: method=\S+,)? rip=<HOST>(?:[^>]*(?:, session=<\S+>)?)\s*$
^pam\(\S+,<HOST>(?:,\S*)?\): pam_authenticate\(\) failed: (?:User not known to the underlying authentication module: \d+ Time\(s\)|Authentication failure \(password mismatch\?\)|Permission denied)\s*$ ^pam\(\S+,<HOST>(?:,\S*)?\): pam_authenticate\(\) failed: (?:User not known to the underlying authentication module: \d+ Time\(s\)|Authentication failure \(password mismatch\?\)|Permission denied)\s*$
^[a-z\-]{3,15}\(\S*,<HOST>(?:,\S*)?\): (?:unknown user|invalid credentials|Password mismatch)\s*$ ^[a-z\-]{3,15}\(\S*,<HOST>(?:,\S*)?\): (?:unknown user|invalid credentials|Password mismatch)
<mdre-<mode>> <mdre-<mode>>
mdre-aggressive = ^(?:Aborted login|Disconnected)(?::(?: [^ \(]+)+)? \((?:no auth attempts|disconnected before auth was ready,|client didn't finish \S+ auth,)(?: (?:in|waited) \d+ secs)?\):(?: user=<[^>]*>,)?(?: method=\S+,)? rip=<HOST>(?:[^>]*(?:, session=<\S+>)?)\s*$ mdre-aggressive = ^(?:Aborted login|Disconnected|Remote closed connection|Client has quit the connection)(?::(?: [^ \(]+)+)? \((?:no auth attempts|disconnected before auth was ready,|client didn't finish \S+ auth,)(?: (?:in|waited) \d+ secs)?\):(?: user=<[^>]*>,)?(?: method=\S+,)? rip=<HOST>(?:[^>]*(?:, session=<\S+>)?)\s*$
mdre-normal = mdre-normal =

View File

@ -1,4 +1,4 @@
# Fail2Ban fitler for the phpMyAdmin-syslog # Fail2Ban filter for the phpMyAdmin-syslog
# #
[INCLUDES] [INCLUDES]

View File

@ -1,4 +1,4 @@
# Fail2Ban fitler for the Proftpd FTP daemon # Fail2Ban filter for the Proftpd FTP daemon
# #
# Set "UseReverseDNS off" in proftpd.conf to avoid the need for DNS. # Set "UseReverseDNS off" in proftpd.conf to avoid the need for DNS.
# See: http://www.proftpd.org/docs/howto/DNS.html # See: http://www.proftpd.org/docs/howto/DNS.html

View File

@ -18,7 +18,7 @@ prefregex = ^\s*(\[\])?(%(__hostname)s\s*(?:roundcube(?:\[(\d*)\])?:)?\s*(<[\w]+
failregex = ^(?:FAILED login|Login failed) for <F-USER>.*</F-USER> from <HOST>(?:(?:\([^\)]*\))?\. (?:(?! from ).)*(?: user=(?P=user))? in \S+\.php on line \d+ \(\S+ \S+\))?$ failregex = ^(?:FAILED login|Login failed) for <F-USER>.*</F-USER> from <HOST>(?:(?:\([^\)]*\))?\. (?:(?! from ).)*(?: user=(?P=user))? in \S+\.php on line \d+ \(\S+ \S+\))?$
^(?:<[\w]+> )?Failed login for <F-USER>.*</F-USER> from <HOST> in session \w+( \(error: \d\))?$ ^(?:<[\w]+> )?Failed login for <F-USER>.*</F-USER> from <HOST> in session \w+( \(error: \d\))?$
ignoreregex = Could not connect to .* Connection refused ignoreregex =
journalmatch = SYSLOG_IDENTIFIER=roundcube journalmatch = SYSLOG_IDENTIFIER=roundcube

View File

@ -44,44 +44,6 @@ before = paths-debian.conf
# MISCELLANEOUS OPTIONS # MISCELLANEOUS OPTIONS
# #
# "bantime.increment" allows to use database for searching of previously banned ip's to increase a
# default ban time using special formula, default it is banTime * 1, 2, 4, 8, 16, 32...
#bantime.increment = true
# "bantime.rndtime" is the max number of seconds using for mixing with random time
# to prevent "clever" botnets calculate exact time IP can be unbanned again:
#bantime.rndtime =
# "bantime.maxtime" is the max number of seconds using the ban time can reach (doesn't grow further)
#bantime.maxtime =
# "bantime.factor" is a coefficient to calculate exponent growing of the formula or common multiplier,
# default value of factor is 1 and with default value of formula, the ban time
# grows by 1, 2, 4, 8, 16 ...
#bantime.factor = 1
# "bantime.formula" used by default to calculate next value of ban time, default value below,
# the same ban time growing will be reached by multipliers 1, 2, 4, 8, 16, 32...
#bantime.formula = ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFactor
#
# more aggressive example of formula has the same values only for factor "2.0 / 2.885385" :
#bantime.formula = ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor)
# "bantime.multipliers" used to calculate next value of ban time instead of formula, coresponding
# previously ban count and given "bantime.factor" (for multipliers default is 1);
# following example grows ban time by 1, 2, 4, 8, 16 ... and if last ban count greater as multipliers count,
# always used last multiplier (64 in example), for factor '1' and original ban time 600 - 10.6 hours
#bantime.multipliers = 1 2 4 8 16 32 64
# following example can be used for small initial ban time (bantime=60) - it grows more aggressive at begin,
# for bantime=60 the multipliers are minutes and equal: 1 min, 5 min, 30 min, 1 hour, 5 hour, 12 hour, 1 day, 2 day
#bantime.multipliers = 1 5 30 60 300 720 1440 2880
# "bantime.overalljails" (if true) specifies the search of IP in the database will be executed
# cross over all jails, if false (dafault), only current jail of the ban IP will be searched
#bantime.overalljails = false
# --------------------
# "ignoreself" specifies whether the local resp. own IP addresses should be ignored # "ignoreself" specifies whether the local resp. own IP addresses should be ignored
# (default is true). Fail2ban will not ban a host which matches such addresses. # (default is true). Fail2ban will not ban a host which matches such addresses.
#ignoreself = true #ignoreself = true
@ -209,7 +171,7 @@ banaction = iptables-multiport
banaction_allports = iptables-allports banaction_allports = iptables-allports
# The simplest action to take: ban only # The simplest action to take: ban only
action_ = %(banaction)s[port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] action_ = %(banaction)s[bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
# ban & send an e-mail with whois report to the destemail. # ban & send an e-mail with whois report to the destemail.
action_mw = %(action_)s action_mw = %(action_)s

View File

@ -46,7 +46,6 @@ class ActionReader(DefinitionInitConfigReader):
"actionrepair": ["string", None], "actionrepair": ["string", None],
"actionrepair_on_unban": ["bool", None], "actionrepair_on_unban": ["bool", None],
"actionban": ["string", None], "actionban": ["string", None],
"actionprolong": ["string", None],
"actionreban": ["string", None], "actionreban": ["string", None],
"actionunban": ["string", None], "actionunban": ["string", None],
"norestored": ["bool", None], "norestored": ["bool", None],

View File

@ -180,12 +180,6 @@ class Beautifier:
msg = "The jail %s action %s has the following " \ msg = "The jail %s action %s has the following " \
"methods:\n" % (inC[1], inC[3]) "methods:\n" % (inC[1], inC[3])
msg += ", ".join(response) msg += ", ".join(response)
elif inC[2] == "banip" and inC[0] == "get":
if isinstance(response, list):
sep = " " if len(inC) <= 3 else inC[3]
if sep == "--with-time":
sep = "\n"
msg = sep.join(response)
except Exception: except Exception:
logSys.warning("Beautifier error. Please report the error") logSys.warning("Beautifier error. Please report the error")
logSys.error("Beautify %r with %r failed", response, self.__inputCmd, logSys.error("Beautify %r with %r failed", response, self.__inputCmd,

View File

@ -98,13 +98,6 @@ class JailReader(ConfigReader):
"maxmatches": ["int", None], "maxmatches": ["int", None],
"findtime": ["string", None], "findtime": ["string", None],
"bantime": ["string", None], "bantime": ["string", None],
"bantime.increment": ["bool", None],
"bantime.factor": ["string", None],
"bantime.formula": ["string", None],
"bantime.multipliers": ["string", None],
"bantime.maxtime": ["string", None],
"bantime.rndtime": ["string", None],
"bantime.overalljails": ["bool", None],
"ignorecommand": ["string", None], "ignorecommand": ["string", None],
"ignoreself": ["bool", None], "ignoreself": ["bool", None],
"ignoreip": ["string", None], "ignoreip": ["string", None],

View File

@ -136,7 +136,6 @@ protocol = [
["get <JAIL> bantime", "gets the time a host is banned for <JAIL>"], ["get <JAIL> bantime", "gets the time a host is banned for <JAIL>"],
["get <JAIL> datepattern", "gets the patern used to match date/times for <JAIL>"], ["get <JAIL> datepattern", "gets the patern used to match date/times for <JAIL>"],
["get <JAIL> usedns", "gets the usedns setting for <JAIL>"], ["get <JAIL> usedns", "gets the usedns setting for <JAIL>"],
["get <JAIL> banip [<SEP>|--with-time]", "gets the list of of banned IP addresses for <JAIL>. Optionally the separator character ('<SEP>', default is space) or the option '--with-time' (printing the times of ban) may be specified. The IPs are ordered by end of ban."],
["get <JAIL> maxretry", "gets the number of failures allowed for <JAIL>"], ["get <JAIL> maxretry", "gets the number of failures allowed for <JAIL>"],
["get <JAIL> maxmatches", "gets the max number of matches stored in memory per ticket in <JAIL>"], ["get <JAIL> maxmatches", "gets the max number of matches stored in memory per ticket in <JAIL>"],
["get <JAIL> maxlines", "gets the number of lines to buffer for <JAIL>"], ["get <JAIL> maxlines", "gets the number of lines to buffer for <JAIL>"],

View File

@ -262,10 +262,6 @@ class ActionBase(object):
""" """
return self.ban(aInfo) return self.ban(aInfo)
@property
def _prolongable(self): # pragma: no cover - abstract
return False
def unban(self, aInfo): # pragma: no cover - abstract def unban(self, aInfo): # pragma: no cover - abstract
"""Executed when a ban expires. """Executed when a ban expires.
@ -278,11 +274,6 @@ class ActionBase(object):
pass pass
WRAP_CMD_PARAMS = {
'timeout': 'str2seconds',
'bantime': 'ignore',
}
class CommandAction(ActionBase): class CommandAction(ActionBase):
"""A action which executes OS shell commands. """A action which executes OS shell commands.
@ -355,10 +346,7 @@ class CommandAction(ActionBase):
def __setattr__(self, name, value): def __setattr__(self, name, value):
if not name.startswith('_') and not self.__init and not callable(value): if not name.startswith('_') and not self.__init and not callable(value):
# special case for some parameters: # special case for some parameters:
wrp = WRAP_CMD_PARAMS.get(name) if name in ('timeout', 'bantime'):
if wrp == 'ignore': # ignore (filter) dynamic parameters
return
elif wrp == 'str2seconds':
value = MyTime.str2seconds(value) value = MyTime.str2seconds(value)
# parameters changed - clear properties and substitution cache: # parameters changed - clear properties and substitution cache:
self.__properties = None self.__properties = None
@ -455,18 +443,7 @@ class CommandAction(ActionBase):
ret = True ret = True
# avoid double execution of same command for both families: # avoid double execution of same command for both families:
if cmd and cmd not in self._operationExecuted(tag, lambda f: f != famoper): if cmd and cmd not in self._operationExecuted(tag, lambda f: f != famoper):
realCmd = cmd ret = self.executeCmd(cmd, self.timeout)
if self._jail:
# simulate action info with "empty" ticket:
aInfo = getattr(self._jail.actions, 'actionInfo', None)
if not aInfo:
aInfo = self._jail.actions._getActionInfo(None)
setattr(self._jail.actions, 'actionInfo', aInfo)
aInfo['time'] = MyTime.time()
aInfo['family'] = famoper
# replace dynamical tags, important - don't cache, no recursion and auto-escape here
realCmd = self.replaceDynamicTags(cmd, aInfo)
ret = self.executeCmd(realCmd, self.timeout)
res &= ret res &= ret
if afterExec: afterExec(famoper, ret) if afterExec: afterExec(famoper, ret)
self._operationExecuted(tag, famoper, cmd if ret else None) self._operationExecuted(tag, famoper, cmd if ret else None)
@ -566,26 +543,6 @@ class CommandAction(ActionBase):
raise RuntimeError("Error banning %(ip)s" % aInfo) raise RuntimeError("Error banning %(ip)s" % aInfo)
self.__started[family] = self.__started.get(family, 0) | 3; # started and contains items self.__started[family] = self.__started.get(family, 0) | 3; # started and contains items
@property
def _prolongable(self):
return (hasattr(self, 'actionprolong') and self.actionprolong
and not str(self.actionprolong).isspace())
def prolong(self, aInfo):
"""Executes the "actionprolong" command.
Replaces the tags in the action command with actions properties
and ban information, and executes the resulting command.
Parameters
----------
aInfo : dict
Dictionary which includes information in relation to
the ban.
"""
if not self._processCmd('<actionprolong>', aInfo):
raise RuntimeError("Error prolonging %(ip)s" % aInfo)
def unban(self, aInfo): def unban(self, aInfo):
"""Executes the "actionunban" command. """Executes the "actionunban" command.
@ -695,10 +652,8 @@ class CommandAction(ActionBase):
ret &= False ret &= False
return ret return ret
ESCAPE_CRE = re.compile(r"""[\\#&;`|*?~<>^()\[\]{}$'"\n\r]""") @staticmethod
def escapeTag(value):
@classmethod
def escapeTag(cls, value):
"""Escape characters which may be used for command injection. """Escape characters which may be used for command injection.
Parameters Parameters
@ -715,15 +670,12 @@ class CommandAction(ActionBase):
----- -----
The following characters are escaped:: The following characters are escaped::
\\#&;`|*?~<>^()[]{}$'"\n\r \\#&;`|*?~<>^()[]{}$'"
""" """
_map2c = {'\n': 'n', '\r': 'r'} for c in '\\#&;`|*?~<>^()[]{}$\'"':
def substChar(m): if c in value:
c = m.group() value = value.replace(c, '\\' + c)
return '\\' + _map2c.get(c, c)
value = cls.ESCAPE_CRE.sub(substChar, value)
return value return value
@classmethod @classmethod
@ -879,7 +831,7 @@ class CommandAction(ActionBase):
tickData = aInfo.get("F-*") tickData = aInfo.get("F-*")
if not tickData: tickData = {} if not tickData: tickData = {}
def substTag(m): def substTag(m):
tag = mapTag2Opt(m.group(1)) tag = mapTag2Opt(m.groups()[0])
try: try:
value = uni_string(tickData[tag]) value = uni_string(tickData[tag])
except KeyError: except KeyError:
@ -1023,8 +975,7 @@ class CommandAction(ActionBase):
RuntimeError RuntimeError
If command execution times out. If command execution times out.
""" """
if logSys.getEffectiveLevel() < logging.DEBUG: logSys.debug(realCmd)
logSys.log(9, realCmd)
if not realCmd: if not realCmd:
logSys.debug("Nothing to do") logSys.debug("Nothing to do")
return True return True

View File

@ -39,7 +39,6 @@ from .ipdns import IPAddr
from .jailthread import JailThread from .jailthread import JailThread
from .action import ActionBase, CommandAction, CallingMap from .action import ActionBase, CommandAction, CallingMap
from .mytime import MyTime from .mytime import MyTime
from .observer import Observers
from .utils import Utils from .utils import Utils
from ..helpers import getLogger from ..helpers import getLogger
@ -219,16 +218,6 @@ class Actions(JailThread, Mapping):
return 1 if ids[0] in lst else 0 return 1 if ids[0] in lst else 0
return map(lambda ip: 1 if ip in lst else 0, ids) return map(lambda ip: 1 if ip in lst else 0, ids)
def getBanList(self, withTime=False):
"""Returns the list of banned IP addresses.
Returns
-------
list
The list of banned IP addresses.
"""
return self.__banManager.getBanList(ordered=True, withTime=withTime)
def addBannedIP(self, ip): def addBannedIP(self, ip):
"""Ban an IP or list of IPs.""" """Ban an IP or list of IPs."""
unixTime = MyTime.time() unixTime = MyTime.time()
@ -381,8 +370,6 @@ class Actions(JailThread, Mapping):
"fid": lambda self: self.__ticket.getID(), "fid": lambda self: self.__ticket.getID(),
"failures": lambda self: self.__ticket.getAttempt(), "failures": lambda self: self.__ticket.getAttempt(),
"time": lambda self: self.__ticket.getTime(), "time": lambda self: self.__ticket.getTime(),
"bantime": lambda self: self._getBanTime(),
"bancount": lambda self: self.__ticket.getBanCount(),
"matches": lambda self: "\n".join(self.__ticket.getMatches()), "matches": lambda self: "\n".join(self.__ticket.getMatches()),
# to bypass actions, that should not be executed for restored tickets # to bypass actions, that should not be executed for restored tickets
"restored": lambda self: (1 if self.__ticket.restored else 0), "restored": lambda self: (1 if self.__ticket.restored else 0),
@ -409,11 +396,6 @@ class Actions(JailThread, Mapping):
def copy(self): # pragma: no cover def copy(self): # pragma: no cover
return self.__class__(self.__ticket, self.__jail, self.immutable, self.data.copy()) return self.__class__(self.__ticket, self.__jail, self.immutable, self.data.copy())
def _getBanTime(self):
btime = self.__ticket.getBanTime()
if btime is None: btime = self.__jail.actions.getBanTime()
return int(btime)
def _mi4ip(self, overalljails=False): def _mi4ip(self, overalljails=False):
"""Gets bans merged once, a helper for lambda(s), prevents stop of executing action by any exception inside. """Gets bans merged once, a helper for lambda(s), prevents stop of executing action by any exception inside.
@ -457,9 +439,7 @@ class Actions(JailThread, Mapping):
return mi[idx] if mi[idx] is not None else self.__ticket return mi[idx] if mi[idx] is not None else self.__ticket
def _getActionInfo(self, ticket): def __getActionInfo(self, ticket):
if not ticket:
ticket = BanTicket("", MyTime.time())
aInfo = Actions.ActionInfo(ticket, self._jail) aInfo = Actions.ActionInfo(ticket, self._jail)
return aInfo return aInfo
@ -489,19 +469,13 @@ class Actions(JailThread, Mapping):
tickets = self.__getFailTickets(self.banPrecedence) tickets = self.__getFailTickets(self.banPrecedence)
rebanacts = None rebanacts = None
for ticket in tickets: for ticket in tickets:
bTicket = BanManager.createBanTicket(ticket)
bTicket = BanTicket.wrap(ticket)
btime = ticket.getBanTime(self.__banManager.getBanTime())
ip = bTicket.getIP() ip = bTicket.getIP()
aInfo = self._getActionInfo(bTicket) aInfo = self.__getActionInfo(bTicket)
reason = {} reason = {}
if self.__banManager.addBanTicket(bTicket, reason=reason): if self.__banManager.addBanTicket(bTicket, reason=reason):
cnt += 1 cnt += 1
# report ticket to observer, to check time should be increased and hereafter observer writes ban to database (asynchronous)
if Observers.Main is not None and not bTicket.restored:
Observers.Main.add('banFound', bTicket, self._jail, btime)
logSys.notice("[%s] %sBan %s", self._jail.name, ('' if not bTicket.restored else 'Restore '), ip) logSys.notice("[%s] %sBan %s", self._jail.name, ('' if not bTicket.restored else 'Restore '), ip)
# do actions :
for name, action in self._actions.iteritems(): for name, action in self._actions.iteritems():
try: try:
if bTicket.restored and getattr(action, 'norestored', False): if bTicket.restored and getattr(action, 'norestored', False):
@ -519,10 +493,7 @@ class Actions(JailThread, Mapping):
if self.banEpoch: # be sure tickets always have the same ban epoch (default 0): if self.banEpoch: # be sure tickets always have the same ban epoch (default 0):
bTicket.banEpoch = self.banEpoch bTicket.banEpoch = self.banEpoch
else: else:
if reason.get('expired', 0): bTicket = reason['ticket']
logSys.info('[%s] Ignore %s, expired bantime', self._jail.name, ip)
continue
bTicket = reason.get('ticket', bTicket)
# if already banned (otherwise still process some action) # if already banned (otherwise still process some action)
if bTicket.banned: if bTicket.banned:
# compare time of failure occurrence with time ticket was really banned: # compare time of failure occurrence with time ticket was really banned:
@ -550,8 +521,12 @@ class Actions(JailThread, Mapping):
cnt += self.__reBan(bTicket, actions=rebanacts) cnt += self.__reBan(bTicket, actions=rebanacts)
else: # pragma: no cover - unexpected: ticket is not banned for some reasons - reban using all actions: else: # pragma: no cover - unexpected: ticket is not banned for some reasons - reban using all actions:
cnt += self.__reBan(bTicket) cnt += self.__reBan(bTicket)
# add ban to database moved to observer (should previously check not already banned # add ban to database:
# and increase ticket time if "bantime.increment" set) if not bTicket.restored and self._jail.database is not None:
# ignore too old (repeated and ignored) tickets,
# [todo] replace it with inOperation later (once it gets back-ported):
if not reason and bTicket.getTime() >= MyTime.time() - 60:
self._jail.database.addBan(self._jail, bTicket)
if cnt: if cnt:
logSys.debug("Banned %s / %s, %s ticket(s) in %r", cnt, logSys.debug("Banned %s / %s, %s ticket(s) in %r", cnt,
self.__banManager.getBanTotal(), self.__banManager.size(), self._jail.name) self.__banManager.getBanTotal(), self.__banManager.size(), self._jail.name)
@ -570,7 +545,7 @@ class Actions(JailThread, Mapping):
""" """
actions = actions or self._actions actions = actions or self._actions
ip = ticket.getIP() ip = ticket.getIP()
aInfo = self._getActionInfo(ticket) aInfo = self.__getActionInfo(ticket)
if log: if log:
logSys.notice("[%s] Reban %s%s", self._jail.name, aInfo["ip"], (', action %r' % actions.keys()[0] if len(actions) == 1 else '')) logSys.notice("[%s] Reban %s%s", self._jail.name, aInfo["ip"], (', action %r' % actions.keys()[0] if len(actions) == 1 else ''))
for name, action in actions.iteritems(): for name, action in actions.iteritems():
@ -591,29 +566,6 @@ class Actions(JailThread, Mapping):
ticket.banEpoch = self.banEpoch ticket.banEpoch = self.banEpoch
return 1 return 1
def _prolongBan(self, ticket):
# prevent to prolong ticket that was removed in-between,
# if it in ban list - ban time already prolonged (and it stays there):
if not self.__banManager._inBanList(ticket): return
# do actions :
aInfo = None
for name, action in self._actions.iteritems():
try:
if ticket.restored and getattr(action, 'norestored', False):
continue
if not action._prolongable:
continue
if aInfo is None:
aInfo = self._getActionInfo(ticket)
if not aInfo.immutable: aInfo.reset()
action.prolong(aInfo)
except Exception as e:
logSys.error(
"Failed to execute ban jail '%s' action '%s' "
"info '%r': %s",
self._jail.name, name, aInfo, e,
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
def __checkUnBan(self, maxCount=None): def __checkUnBan(self, maxCount=None):
"""Check for IP address to unban. """Check for IP address to unban.
@ -698,7 +650,7 @@ class Actions(JailThread, Mapping):
else: else:
unbactions = actions unbactions = actions
ip = ticket.getIP() ip = ticket.getIP()
aInfo = self._getActionInfo(ticket) aInfo = self.__getActionInfo(ticket)
if log: if log:
logSys.notice("[%s] Unban %s", self._jail.name, aInfo["ip"]) logSys.notice("[%s] Unban %s", self._jail.name, aInfo["ip"])
for name, action in unbactions.iteritems(): for name, action in unbactions.iteritems():

View File

@ -98,22 +98,8 @@ class BanManager:
# #
# @return IP list # @return IP list
def getBanList(self, ordered=False, withTime=False): def getBanList(self):
if not ordered:
return list(self.__banList.keys()) return list(self.__banList.keys())
with self.__lock:
lst = []
for ticket in self.__banList.itervalues():
eob = ticket.getEndOfBanTime(self.__banTime)
lst.append((ticket,eob))
lst.sort(key=lambda t: t[1])
t2s = MyTime.time2str
if withTime:
return ['%s \t%s + %d = %s' % (
t[0].getID(),
t2s(t[0].getTime()), t[0].getBanTime(self.__banTime), t2s(t[1])
) for t in lst]
return [t[0].getID() for t in lst]
## ##
# Returns a iterator to ban list (used in reload, so idle). # Returns a iterator to ban list (used in reload, so idle).
@ -258,6 +244,21 @@ class BanManager:
logSys.exception(e) logSys.exception(e)
return [] return []
##
# Create a ban ticket.
#
# Create a BanTicket from a FailTicket. The timestamp of the BanTicket
# is the current time. This is a static method.
# @param ticket the FailTicket
# @return a BanTicket
@staticmethod
def createBanTicket(ticket):
# we should always use correct time to calculate correct end time (ban time is variable now,
# + possible double banning by restore from database and from log file)
# so use as lastTime always time from ticket.
return BanTicket(ticket=ticket)
## ##
# Add a ban ticket. # Add a ban ticket.
# #
@ -267,9 +268,6 @@ class BanManager:
def addBanTicket(self, ticket, reason={}): def addBanTicket(self, ticket, reason={}):
eob = ticket.getEndOfBanTime(self.__banTime) eob = ticket.getEndOfBanTime(self.__banTime)
if eob < MyTime.time():
reason['expired'] = 1
return False
with self.__lock: with self.__lock:
# check already banned # check already banned
fid = ticket.getID() fid = ticket.getID()
@ -291,7 +289,6 @@ class BanManager:
# not yet banned - add new one: # not yet banned - add new one:
self.__banList[fid] = ticket self.__banList[fid] = ticket
self.__banTotal += 1 self.__banTotal += 1
ticket.incrBanCount()
# correct next unban time: # correct next unban time:
if self._nextUnbanTime > eob: if self._nextUnbanTime > eob:
self._nextUnbanTime = eob self._nextUnbanTime = eob

View File

@ -138,7 +138,7 @@ class Fail2BanDb(object):
filename filename
purgeage purgeage
""" """
__version__ = 4 __version__ = 2
# Note all SCRIPTS strings must end in ';' for py26 compatibility # Note all SCRIPTS strings must end in ';' for py26 compatibility
_CREATE_SCRIPTS = ( _CREATE_SCRIPTS = (
('fail2banDb', "CREATE TABLE IF NOT EXISTS fail2banDb(version INTEGER);") ('fail2banDb', "CREATE TABLE IF NOT EXISTS fail2banDb(version INTEGER);")
@ -166,36 +166,21 @@ class Fail2BanDb(object):
"jail TEXT NOT NULL, " \ "jail TEXT NOT NULL, " \
"ip TEXT, " \ "ip TEXT, " \
"timeofban INTEGER NOT NULL, " \ "timeofban INTEGER NOT NULL, " \
"bantime INTEGER NOT NULL, " \
"bancount INTEGER NOT NULL default 1, " \
"data JSON, " \ "data JSON, " \
"FOREIGN KEY(jail) REFERENCES jails(name) " \ "FOREIGN KEY(jail) REFERENCES jails(name) " \
");" \ ");" \
"CREATE INDEX IF NOT EXISTS bans_jail_timeofban_ip ON bans(jail, timeofban);" \ "CREATE INDEX IF NOT EXISTS bans_jail_timeofban_ip ON bans(jail, timeofban);" \
"CREATE INDEX IF NOT EXISTS bans_jail_ip ON bans(jail, ip);" \ "CREATE INDEX IF NOT EXISTS bans_jail_ip ON bans(jail, ip);" \
"CREATE INDEX IF NOT EXISTS bans_ip ON bans(ip);") "CREATE INDEX IF NOT EXISTS bans_ip ON bans(ip);")
,('bips', "CREATE TABLE IF NOT EXISTS bips(" \
"ip TEXT NOT NULL, " \
"jail TEXT NOT NULL, " \
"timeofban INTEGER NOT NULL, " \
"bantime INTEGER NOT NULL, " \
"bancount INTEGER NOT NULL default 1, " \
"data JSON, " \
"PRIMARY KEY(ip, jail), " \
"FOREIGN KEY(jail) REFERENCES jails(name) " \
");" \
"CREATE INDEX IF NOT EXISTS bips_timeofban ON bips(timeofban);" \
"CREATE INDEX IF NOT EXISTS bips_ip ON bips(ip);")
) )
_CREATE_TABS = dict(_CREATE_SCRIPTS) _CREATE_TABS = dict(_CREATE_SCRIPTS)
def __init__(self, filename, purgeAge=24*60*60, outDatedFactor=3): def __init__(self, filename, purgeAge=24*60*60):
self.maxMatches = 10 self.maxMatches = 10
self._lock = RLock() self._lock = RLock()
self._dbFilename = filename self._dbFilename = filename
self._purgeAge = purgeAge self._purgeAge = purgeAge
self._outDatedFactor = outDatedFactor;
self._connectDB() self._connectDB()
def _connectDB(self, checkIntegrity=False): def _connectDB(self, checkIntegrity=False):
@ -389,25 +374,6 @@ class Fail2BanDb(object):
"UPDATE fail2banDb SET version = 2;" "UPDATE fail2banDb SET version = 2;"
"COMMIT;" % Fail2BanDb._CREATE_TABS['logs']) "COMMIT;" % Fail2BanDb._CREATE_TABS['logs'])
if version < 3 and self._tableExists(cur, "bans"):
# set ban-time to -2 (note it means rather unknown, as persistent, will be fixed by restore):
cur.executescript("BEGIN TRANSACTION;"
"CREATE TEMPORARY TABLE bans_temp AS SELECT jail, ip, timeofban, -2 as bantime, 1 as bancount, data FROM bans;"
"DROP TABLE bans;"
"%s;\n"
"INSERT INTO bans SELECT * from bans_temp;"
"DROP TABLE bans_temp;"
"COMMIT;" % Fail2BanDb._CREATE_TABS['bans'])
if version < 4 and not self._tableExists(cur, "bips"):
cur.executescript("BEGIN TRANSACTION;"
"%s;\n"
"UPDATE fail2banDb SET version = 4;"
"COMMIT;" % Fail2BanDb._CREATE_TABS['bips'])
if self._tableExists(cur, "bans"):
cur.execute(
"INSERT OR REPLACE INTO bips(ip, jail, timeofban, bantime, bancount, data)"
" SELECT ip, jail, timeofban, bantime, bancount, data FROM bans order by timeofban")
cur.execute("SELECT version FROM fail2banDb LIMIT 1") cur.execute("SELECT version FROM fail2banDb LIMIT 1")
return cur.fetchone()[0] return cur.fetchone()[0]
except Exception as e: except Exception as e:
@ -619,13 +585,8 @@ class Fail2BanDb(object):
data = data.copy() data = data.copy()
del data['matches'] del data['matches']
cur.execute( cur.execute(
"INSERT INTO bans(jail, ip, timeofban, bantime, bancount, data) VALUES(?, ?, ?, ?, ?, ?)", "INSERT INTO bans(jail, ip, timeofban, data) VALUES(?, ?, ?, ?)",
(jail.name, ip, int(round(ticket.getTime())), ticket.getBanTime(jail.actions.getBanTime()), ticket.getBanCount(), (jail.name, ip, int(round(ticket.getTime())), data))
data))
cur.execute(
"INSERT OR REPLACE INTO bips(ip, jail, timeofban, bantime, bancount, data) VALUES(?, ?, ?, ?, ?, ?)",
(ip, jail.name, int(round(ticket.getTime())), ticket.getBanTime(jail.actions.getBanTime()), ticket.getBanCount(),
data))
@commitandrollback @commitandrollback
def delBan(self, cur, jail, *args): def delBan(self, cur, jail, *args):
@ -638,20 +599,16 @@ class Fail2BanDb(object):
args : list of IP args : list of IP
IPs to be removed, if not given all tickets of jail will be removed. IPs to be removed, if not given all tickets of jail will be removed.
""" """
query1 = "DELETE FROM bips WHERE jail = ?" query = "DELETE FROM bans WHERE jail = ?"
query2 = "DELETE FROM bans WHERE jail = ?"
queryArgs = [jail.name]; queryArgs = [jail.name];
if not len(args): if not len(args):
cur.execute(query1, queryArgs); cur.execute(query, queryArgs);
cur.execute(query2, queryArgs);
return return
query1 += " AND ip = ?" query += " AND ip = ?"
query2 += " AND ip = ?"
queryArgs.append(''); queryArgs.append('');
for ip in args: for ip in args:
queryArgs[1] = str(ip); queryArgs[1] = str(ip);
cur.execute(query1, queryArgs); cur.execute(query, queryArgs);
cur.execute(query2, queryArgs);
@commitandrollback @commitandrollback
def _getBans(self, cur, jail=None, bantime=None, ip=None): def _getBans(self, cur, jail=None, bantime=None, ip=None):
@ -769,42 +726,18 @@ class Fail2BanDb(object):
self._bansMergedCache[cacheKey] = tickets if ip is None else ticket self._bansMergedCache[cacheKey] = tickets if ip is None else ticket
return tickets if ip is None else ticket return tickets if ip is None else ticket
@commitandrollback
def getBan(self, cur, ip, jail=None, forbantime=None, overalljails=None, fromtime=None):
ip = str(ip)
if not overalljails:
query = "SELECT bancount, timeofban, bantime FROM bips"
else:
query = "SELECT sum(bancount), max(timeofban), sum(bantime) FROM bips"
query += " WHERE ip = ?"
queryArgs = [ip]
if not overalljails and jail is not None:
query += " AND jail=?"
queryArgs.append(jail.name)
if forbantime is not None:
query += " AND timeofban > ?"
queryArgs.append(MyTime.time() - forbantime)
if fromtime is not None:
query += " AND timeofban > ?"
queryArgs.append(fromtime)
if overalljails or jail is None:
query += " GROUP BY ip ORDER BY timeofban DESC LIMIT 1"
cur = self._db.cursor()
# repack iterator as long as in lock:
return list(cur.execute(query, queryArgs))
def _getCurrentBans(self, cur, jail = None, ip = None, forbantime=None, fromtime=None): def _getCurrentBans(self, cur, jail = None, ip = None, forbantime=None, fromtime=None):
if fromtime is None:
fromtime = MyTime.time()
queryArgs = [] queryArgs = []
if jail is not None: if jail is not None:
query = "SELECT ip, timeofban, bantime, bancount, data FROM bips WHERE jail=?" query = "SELECT ip, timeofban, data FROM bans WHERE jail=?"
queryArgs.append(jail.name) queryArgs.append(jail.name)
else: else:
query = "SELECT ip, max(timeofban), bantime, bancount, data FROM bips WHERE 1" query = "SELECT ip, max(timeofban), data FROM bans WHERE 1"
if ip is not None: if ip is not None:
query += " AND ip=?" query += " AND ip=?"
queryArgs.append(ip) queryArgs.append(ip)
query += " AND (timeofban + bantime > ? OR bantime <= -1)"
queryArgs.append(fromtime)
if forbantime not in (None, -1): # not specified or persistent (all) if forbantime not in (None, -1): # not specified or persistent (all)
query += " AND timeofban > ?" query += " AND timeofban > ?"
queryArgs.append(fromtime - forbantime) queryArgs.append(fromtime - forbantime)
@ -815,54 +748,16 @@ class Fail2BanDb(object):
cur = self._db.cursor() cur = self._db.cursor()
return cur.execute(query, queryArgs) return cur.execute(query, queryArgs)
@commitandrollback def getCurrentBans(self, jail = None, ip = None, forbantime=None, fromtime=None, maxmatches=None):
def getCurrentBans(self, cur, jail=None, ip=None, forbantime=None, fromtime=None,
correctBanTime=True, maxmatches=None
):
"""Reads tickets (with merged info) currently affected from ban from the database.
There are all the tickets corresponding parameters jail/ip, forbantime,
fromtime (normally now).
If correctBanTime specified (default True) it will fix the restored ban-time
(and therefore endOfBan) of the ticket (normally it is ban-time of jail as maximum)
for all tickets with ban-time greater (or persistent).
"""
if fromtime is None:
fromtime = MyTime.time()
tickets = [] tickets = []
ticket = None ticket = None
if correctBanTime is True:
correctBanTime = jail.getMaxBanTime() if jail is not None else None
# don't change if persistent allowed:
if correctBanTime == -1: correctBanTime = None
for ticket in self._getCurrentBans(cur, jail=jail, ip=ip, with self._lock:
forbantime=forbantime, fromtime=fromtime results = list(self._getCurrentBans(self._db.cursor(),
): jail=jail, ip=ip, forbantime=forbantime, fromtime=fromtime))
# can produce unpack error (database may return sporadical wrong-empty row):
try: if results:
banip, timeofban, bantime, bancount, data = ticket for banip, timeofban, data in results:
# additionally check for empty values:
if banip is None or banip == "": # pragma: no cover
raise ValueError('unexpected value %r' % (banip,))
# if bantime unknown (after upgrade-db from earlier version), just use min known ban-time:
if bantime == -2: # todo: remove it in future version
bantime = jail.actions.getBanTime() if jail is not None else (
correctBanTime if correctBanTime else 600)
elif correctBanTime and correctBanTime >= 0:
# if persistent ban (or greater as max), use current max-bantime of the jail:
if bantime == -1 or bantime > correctBanTime:
bantime = correctBanTime
# after correction check the end of ban again:
if bantime != -1 and timeofban + bantime <= fromtime:
# not persistent and too old - ignore it:
logSys.debug("ignore ticket (with new max ban-time %r): too old %r <= %r, ticket: %r",
bantime, timeofban + bantime, fromtime, ticket)
continue
except ValueError as e: # pragma: no cover
logSys.debug("get current bans: ignore row %r - %s", ticket, e)
continue
# logSys.debug('restore ticket %r, %r, %r', banip, timeofban, data) # logSys.debug('restore ticket %r, %r, %r', banip, timeofban, data)
ticket = FailTicket(banip, timeofban, data=data) ticket = FailTicket(banip, timeofban, data=data)
# filter matches if expected (current count > as maxmatches specified): # filter matches if expected (current count > as maxmatches specified):
@ -875,30 +770,11 @@ class Fail2BanDb(object):
else: else:
ticket.setMatches(None) ticket.setMatches(None)
# logSys.debug('restored ticket: %r', ticket) # logSys.debug('restored ticket: %r', ticket)
ticket.setBanTime(bantime)
ticket.setBanCount(bancount)
if ip is not None: return ticket if ip is not None: return ticket
tickets.append(ticket) tickets.append(ticket)
return tickets return tickets
def _cleanjails(self, cur):
"""Remove empty jails jails and log files from database.
"""
cur.execute(
"DELETE FROM jails WHERE enabled = 0 "
"AND NOT EXISTS(SELECT * FROM bans WHERE jail = jails.name) "
"AND NOT EXISTS(SELECT * FROM bips WHERE jail = jails.name)")
def _purge_bips(self, cur):
"""Purge old bad ips (jails and log files from database).
Currently it is timed out IP, whose time since last ban is several times out-dated (outDatedFactor is default 3).
Permanent banned ips will be never removed.
"""
cur.execute(
"DELETE FROM bips WHERE timeofban < ? and bantime != -1 and (timeofban + (bantime * ?)) < ?",
(int(MyTime.time()) - self._purgeAge, self._outDatedFactor, int(MyTime.time()) - self._purgeAge))
@commitandrollback @commitandrollback
def purge(self, cur): def purge(self, cur):
"""Purge old bans, jails and log files from database. """Purge old bans, jails and log files from database.
@ -907,6 +783,7 @@ class Fail2BanDb(object):
cur.execute( cur.execute(
"DELETE FROM bans WHERE timeofban < ?", "DELETE FROM bans WHERE timeofban < ?",
(MyTime.time() - self._purgeAge, )) (MyTime.time() - self._purgeAge, ))
self._purge_bips(cur) cur.execute(
self._cleanjails(cur) "DELETE FROM jails WHERE enabled = 0 "
"AND NOT EXISTS(SELECT * FROM bans WHERE jail = jails.name)")

View File

@ -27,7 +27,7 @@ __license__ = "GPL"
from threading import Lock from threading import Lock
import logging import logging
from .ticket import FailTicket, BanTicket from .ticket import FailTicket
from ..helpers import getLogger, BgService from ..helpers import getLogger, BgService
# Gets the instance of the logger. # Gets the instance of the logger.
@ -69,7 +69,7 @@ class FailManager:
def getMaxTime(self): def getMaxTime(self):
return self.__maxTime return self.__maxTime
def addFailure(self, ticket, count=1, observed=False): def addFailure(self, ticket, count=1):
attempts = 1 attempts = 1
with self.__lock: with self.__lock:
fid = ticket.getID() fid = ticket.getID()
@ -96,14 +96,11 @@ class FailManager:
else: else:
fData.setMatches(None) fData.setMatches(None)
except KeyError: except KeyError:
# not found - already banned - prevent to add failure if comes from observer:
if observed or isinstance(ticket, BanTicket):
return ticket.getRetry()
# if already FailTicket - add it direct, otherwise create (using copy all ticket data): # if already FailTicket - add it direct, otherwise create (using copy all ticket data):
if isinstance(ticket, FailTicket): if isinstance(ticket, FailTicket):
fData = ticket; fData = ticket;
else: else:
fData = FailTicket.wrap(ticket) fData = FailTicket(ticket=ticket)
if count > ticket.getAttempt(): if count > ticket.getAttempt():
fData.setRetry(count) fData.setRetry(count)
self.__failList[fid] = fData self.__failList[fid] = fData

View File

@ -87,24 +87,20 @@ RH4TAG = {
# default failure groups map for customizable expressions (with different group-id): # default failure groups map for customizable expressions (with different group-id):
R_MAP = { R_MAP = {
"id": "fid", "ID": "fid",
"port": "fport", "PORT": "fport",
} }
def mapTag2Opt(tag): def mapTag2Opt(tag):
tag = tag.lower() try: # if should be mapped:
return R_MAP.get(tag, tag) return R_MAP[tag]
except KeyError:
return tag.lower()
# complex names: # alternate names to be merged, e. g. alt_user_1 -> user ...
# ALT_ - alternate names to be merged, e. g. alt_user_1 -> user ...
ALTNAME_PRE = 'alt_' ALTNAME_PRE = 'alt_'
# TUPLE_ - names of parts to be combined to single value as tuple ALTNAME_CRE = re.compile(r'^' + ALTNAME_PRE + r'(.*)(?:_\d+)?$')
TUPNAME_PRE = 'tuple_'
COMPLNAME_PRE = (ALTNAME_PRE, TUPNAME_PRE)
COMPLNAME_CRE = re.compile(r'^(' + '|'.join(COMPLNAME_PRE) + r')(.*?)(?:_\d+)?$')
## ##
# Regular expression class. # Regular expression class.
@ -131,27 +127,19 @@ class Regex:
try: try:
self._regexObj = re.compile(regex, re.MULTILINE if multiline else 0) self._regexObj = re.compile(regex, re.MULTILINE if multiline else 0)
self._regex = regex self._regex = regex
self._altValues = [] self._altValues = {}
self._tupleValues = []
for k in filter( for k in filter(
lambda k: len(k) > len(COMPLNAME_PRE[0]), self._regexObj.groupindex lambda k: len(k) > len(ALTNAME_PRE) and k.startswith(ALTNAME_PRE),
self._regexObj.groupindex
): ):
n = COMPLNAME_CRE.match(k) n = ALTNAME_CRE.match(k).group(1)
if n: self._altValues[k] = n
g, n = n.group(1), mapTag2Opt(n.group(2)) self._altValues = list(self._altValues.items()) if len(self._altValues) else None
if g == ALTNAME_PRE:
self._altValues.append((k,n))
else:
self._tupleValues.append((k,n))
self._altValues.sort()
self._tupleValues.sort()
self._altValues = self._altValues if len(self._altValues) else None
self._tupleValues = self._tupleValues if len(self._tupleValues) else None
except sre_constants.error: except sre_constants.error:
raise RegexException("Unable to compile regular expression '%s'" % raise RegexException("Unable to compile regular expression '%s'" %
regex) regex)
# set fetch handler depending on presence of alternate (or tuple) tags: # set fetch handler depending on presence of alternate tags:
self.getGroups = self._getGroupsWithAlt if (self._altValues or self._tupleValues) else self._getGroups self.getGroups = self._getGroupsWithAlt if self._altValues else self._getGroups
def __str__(self): def __str__(self):
return "%s(%r)" % (self.__class__.__name__, self._regex) return "%s(%r)" % (self.__class__.__name__, self._regex)
@ -296,23 +284,12 @@ class Regex:
def _getGroupsWithAlt(self): def _getGroupsWithAlt(self):
fail = self._matchCache.groupdict() fail = self._matchCache.groupdict()
#fail = fail.copy()
# merge alternate values (e. g. 'alt_user_1' -> 'user' or 'alt_host' -> 'host'): # merge alternate values (e. g. 'alt_user_1' -> 'user' or 'alt_host' -> 'host'):
if self._altValues: #fail = fail.copy()
for k,n in self._altValues: for k,n in self._altValues:
v = fail.get(k) v = fail.get(k)
if v and not fail.get(n): if v and not fail.get(n):
fail[n] = v fail[n] = v
# combine tuple values (e. g. 'id', 'tuple_id' ... 'tuple_id_N' -> 'id'):
if self._tupleValues:
for k,n in self._tupleValues:
v = fail.get(k)
t = fail.get(n)
if isinstance(t, tuple):
t += (v,)
else:
t = (t,v,)
fail[n] = t
return fail return fail
def getGroups(self): # pragma: no cover - abstract function (replaced in __init__) def getGroups(self): # pragma: no cover - abstract function (replaced in __init__)

View File

@ -33,7 +33,6 @@ import time
from .actions import Actions from .actions import Actions
from .failmanager import FailManagerEmpty, FailManager from .failmanager import FailManagerEmpty, FailManager
from .ipdns import DNSUtils, IPAddr from .ipdns import DNSUtils, IPAddr
from .observer import Observers
from .ticket import FailTicket from .ticket import FailTicket
from .jailthread import JailThread from .jailthread import JailThread
from .datedetector import DateDetector, validateTimeZone from .datedetector import DateDetector, validateTimeZone
@ -690,16 +689,13 @@ class Filter(JailThread):
if self._inIgnoreIPList(ip, tick): if self._inIgnoreIPList(ip, tick):
continue continue
logSys.info( logSys.info(
"[%s] Found %s - %s", self.jailName, ip, MyTime.time2str(unixTime) "[%s] Found %s - %s", self.jailName, ip, datetime.datetime.fromtimestamp(unixTime).strftime("%Y-%m-%d %H:%M:%S")
) )
attempts = self.failManager.addFailure(tick) attempts = self.failManager.addFailure(tick)
# avoid RC on busy filter (too many failures) - if attempts for IP/ID reached maxretry, # avoid RC on busy filter (too many failures) - if attempts for IP/ID reached maxretry,
# we can speedup ban, so do it as soon as possible: # we can speedup ban, so do it as soon as possible:
if self.banASAP and attempts >= self.failManager.getMaxRetry(): if self.banASAP and attempts >= self.failManager.getMaxRetry():
self.performBan(ip) self.performBan(ip)
# report to observer - failure was found, for possibly increasing of it retry counter (asynchronous)
if Observers.Main is not None:
Observers.Main.add('failureFound', self.failManager, self.jail, tick)
# reset (halve) error counter (successfully processed line): # reset (halve) error counter (successfully processed line):
if self._errors: if self._errors:
self._errors //= 2 self._errors //= 2
@ -1136,7 +1132,7 @@ class FileFilter(Filter):
fs = container.getFileSize() fs = container.getFileSize()
if logSys.getEffectiveLevel() <= logging.DEBUG: if logSys.getEffectiveLevel() <= logging.DEBUG:
logSys.debug("Seek to find time %s (%s), file size %s", date, logSys.debug("Seek to find time %s (%s), file size %s", date,
MyTime.time2str(date), fs) datetime.datetime.fromtimestamp(date).strftime("%Y-%m-%d %H:%M:%S"), fs)
minp = container.getPos() minp = container.getPos()
maxp = fs maxp = fs
tryPos = minp tryPos = minp
@ -1215,7 +1211,7 @@ class FileFilter(Filter):
container.setPos(foundPos) container.setPos(foundPos)
if logSys.getEffectiveLevel() <= logging.DEBUG: if logSys.getEffectiveLevel() <= logging.DEBUG:
logSys.debug("Position %s from %s, found time %s (%s) within %s seeks", lastPos, fs, foundTime, logSys.debug("Position %s from %s, found time %s (%s) within %s seeks", lastPos, fs, foundTime,
(MyTime.time2str(foundTime) if foundTime is not None else ''), cntr) (datetime.datetime.fromtimestamp(foundTime).strftime("%Y-%m-%d %H:%M:%S") if foundTime is not None else ''), cntr)
def status(self, flavor="basic"): def status(self, flavor="basic"):
"""Status of Filter plus files being monitored. """Status of Filter plus files being monitored.

View File

@ -337,7 +337,7 @@ class IPAddr(object):
return repr(self.ntoa) return repr(self.ntoa)
def __str__(self): def __str__(self):
return self.ntoa if isinstance(self.ntoa, basestring) else str(self.ntoa) return self.ntoa
def __reduce__(self): def __reduce__(self):
"""IPAddr pickle-handler, that simply wraps IPAddr to the str """IPAddr pickle-handler, that simply wraps IPAddr to the str

View File

@ -24,13 +24,10 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2012 Lee Clemens, 2012 Y
__license__ = "GPL" __license__ = "GPL"
import logging import logging
import math
import random
import Queue import Queue
from .actions import Actions from .actions import Actions
from ..helpers import getLogger, _as_bool, extractOptions, MyTime from ..helpers import getLogger, extractOptions, MyTime
from .mytime import MyTime
# Gets the instance of the logger. # Gets the instance of the logger.
logSys = getLogger(__name__) logSys = getLogger(__name__)
@ -78,8 +75,6 @@ class Jail(object):
self.__name = name self.__name = name
self.__queue = Queue.Queue() self.__queue = Queue.Queue()
self.__filter = None self.__filter = None
# Extra parameters for increase ban time
self._banExtra = {};
logSys.info("Creating new jail '%s'" % self.name) logSys.info("Creating new jail '%s'" % self.name)
if backend is not None: if backend is not None:
self._setBackend(backend) self._setBackend(backend)
@ -208,8 +203,6 @@ class Jail(object):
Used by filter to add a failure for banning. Used by filter to add a failure for banning.
""" """
self.__queue.put(ticket) self.__queue.put(ticket)
# add ban to database moved to observer (should previously check not already banned
# and increase ticket time if "bantime.increment" set)
def getFailTicket(self): def getFailTicket(self):
"""Get a fail ticket from the jail. """Get a fail ticket from the jail.
@ -222,80 +215,16 @@ class Jail(object):
except Queue.Empty: except Queue.Empty:
return False return False
def setBanTimeExtra(self, opt, value): def restoreCurrentBans(self):
# merge previous extra with new option:
be = self._banExtra;
if value == '':
value = None
if value is not None:
be[opt] = value;
elif opt in be:
del be[opt]
logSys.info('Set banTime.%s = %s', opt, value)
if opt == 'increment':
be[opt] = _as_bool(value)
if be.get(opt) and self.database is None:
logSys.warning("ban time increment is not available as long jail database is not set")
if opt in ['maxtime', 'rndtime']:
if not value is None:
be[opt] = MyTime.str2seconds(value)
# prepare formula lambda:
if opt in ['formula', 'factor', 'maxtime', 'rndtime', 'multipliers'] or be.get('evformula', None) is None:
# split multifiers to an array begins with 0 (or empty if not set):
if opt == 'multipliers':
be['evmultipliers'] = [int(i) for i in (value.split(' ') if value is not None and value != '' else [])]
# if we have multifiers - use it in lambda, otherwise compile and use formula within lambda
multipliers = be.get('evmultipliers', [])
banFactor = eval(be.get('factor', "1"))
if len(multipliers):
evformula = lambda ban, banFactor=banFactor: (
ban.Time * banFactor * multipliers[ban.Count if ban.Count < len(multipliers) else -1]
)
else:
formula = be.get('formula', 'ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFactor')
formula = compile(formula, '~inline-conf-expr~', 'eval')
evformula = lambda ban, banFactor=banFactor, formula=formula: max(ban.Time, eval(formula))
# extend lambda with max time :
if not be.get('maxtime', None) is None:
maxtime = be['maxtime']
evformula = lambda ban, evformula=evformula: min(evformula(ban), maxtime)
# mix lambda with random time (to prevent bot-nets to calculate exact time IP can be unbanned):
if not be.get('rndtime', None) is None:
rndtime = be['rndtime']
evformula = lambda ban, evformula=evformula: (evformula(ban) + random.random() * rndtime)
# set to extra dict:
be['evformula'] = evformula
#logSys.info('banTimeExtra : %s' % json.dumps(be))
def getBanTimeExtra(self, opt=None):
if opt is not None:
return self._banExtra.get(opt, None)
return self._banExtra
def getMaxBanTime(self):
"""Returns max possible ban-time of jail.
"""
return self._banExtra.get("maxtime", -1) \
if self._banExtra.get('increment') else self.actions.getBanTime()
def restoreCurrentBans(self, correctBanTime=True):
"""Restore any previous valid bans from the database. """Restore any previous valid bans from the database.
""" """
try: try:
if self.database is not None: if self.database is not None:
if self._banExtra.get('increment'):
forbantime = None;
if correctBanTime:
correctBanTime = self.getMaxBanTime()
else:
# use ban time as search time if we have not enabled a increasing:
forbantime = self.actions.getBanTime() forbantime = self.actions.getBanTime()
for ticket in self.database.getCurrentBans(jail=self, forbantime=forbantime, for ticket in self.database.getCurrentBans(jail=self,
correctBanTime=correctBanTime, maxmatches=self.filter.failManager.maxMatches forbantime=forbantime, maxmatches=self.filter.failManager.maxMatches):
):
try:
#logSys.debug('restored ticket: %s', ticket) #logSys.debug('restored ticket: %s', ticket)
if self.filter.inIgnoreIPList(ticket.getIP(), log_ignore=True): continue if not self.filter.inIgnoreIPList(ticket.getIP(), log_ignore=True):
# mark ticked was restored from database - does not put it again into db: # mark ticked was restored from database - does not put it again into db:
ticket.restored = True ticket.restored = True
# correct start time / ban time (by the same end of ban): # correct start time / ban time (by the same end of ban):
@ -306,13 +235,11 @@ class Jail(object):
# ignore obsolete tickets: # ignore obsolete tickets:
if btm != -1 and btm <= 0: if btm != -1 and btm <= 0:
continue continue
ticket.setTime(MyTime.time())
ticket.setBanTime(btm)
self.putFailTicket(ticket) self.putFailTicket(ticket)
except Exception as e: # pragma: no cover except Exception as e: # pragma: no cover
logSys.error('Restore ticket failed: %s', e, logSys.error('%s', e, exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
except Exception as e: # pragma: no cover
logSys.error('Restore bans failed: %s', e,
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
def start(self): def start(self):
"""Start the jail, by starting filter and actions threads. """Start the jail, by starting filter and actions threads.

View File

@ -114,19 +114,6 @@ class MyTime:
else: else:
return time.localtime(MyTime.myTime) return time.localtime(MyTime.myTime)
@staticmethod
def time2str(unixTime, format="%Y-%m-%d %H:%M:%S"):
"""Convert time to a string representing as date and time using given format.
Default format is ISO 8601, YYYY-MM-DD HH:MM:SS without microseconds.
@return ISO-capable string representation of given unixTime
"""
# consider end of 9999th year (in GMT+23 to avoid year overflow in other TZ)
dt = datetime.datetime.fromtimestamp(
unixTime).replace(microsecond=0
) if unixTime < 253402214400 else datetime.datetime(9999, 12, 31, 23, 59, 59)
return dt.strftime(format)
## precreate/precompile primitives used in str2seconds: ## precreate/precompile primitives used in str2seconds:
## preparing expression: ## preparing expression:

View File

@ -1,536 +0,0 @@
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
# vi: set ft=python sts=4 ts=4 sw=4 noet :
# This file is part of Fail2Ban.
#
# Fail2Ban is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Fail2Ban is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Fail2Ban; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# Author: Serg G. Brester (sebres)
#
# This module was written as part of ban time increment feature.
__author__ = "Serg G. Brester (sebres)"
__copyright__ = "Copyright (c) 2014 Serg G. Brester"
__license__ = "GPL"
import threading
from .jailthread import JailThread
from .failmanager import FailManagerEmpty
import os, logging, time, datetime, math, json, random
import sys
from ..helpers import getLogger
from .mytime import MyTime
from .utils import Utils
# Gets the instance of the logger.
logSys = getLogger(__name__)
class ObserverThread(JailThread):
"""Handles observing a database, managing bad ips and ban increment.
Parameters
----------
Attributes
----------
daemon
ident
name
status
active : bool
Control the state of the thread.
idle : bool
Control the idle state of the thread.
sleeptime : int
The time the thread sleeps for in the loop.
"""
# observer is event driven and it sleep organized incremental, so sleep intervals can be shortly:
DEFAULT_SLEEP_INTERVAL = Utils.DEFAULT_SLEEP_INTERVAL / 10
def __init__(self):
# init thread
super(ObserverThread, self).__init__(name='f2b/observer')
# before started - idle:
self.idle = True
## Event queue
self._queue_lock = threading.RLock()
self._queue = []
## Event, be notified if anything added to event queue
self._notify = threading.Event()
## Sleep for max 60 seconds, it possible to specify infinite to always sleep up to notifying via event,
## but so we can later do some service "events" occurred infrequently directly in main loop of observer (not using queue)
self.sleeptime = 60
#
self._timers = {}
self._paused = False
self.__db = None
self.__db_purge_interval = 60*60
# observer is a not main thread:
self.daemon = True
def __getitem__(self, i):
try:
return self._queue[i]
except KeyError:
raise KeyError("Invalid event index : %s" % i)
def __delitem__(self, i):
try:
del self._queue[i]
except KeyError:
raise KeyError("Invalid event index: %s" % i)
def __iter__(self):
return iter(self._queue)
def __len__(self):
return len(self._queue)
def __eq__(self, other): # Required for Threading
return False
def __hash__(self): # Required for Threading
return id(self)
def add_named_timer(self, name, starttime, *event):
"""Add a named timer event to queue will start (and wake) in 'starttime' seconds
Previous timer event with same name will be canceled and trigger self into
queue after new 'starttime' value
"""
t = self._timers.get(name, None)
if t is not None:
t.cancel()
t = threading.Timer(starttime, self.add, event)
self._timers[name] = t
t.start()
def add_timer(self, starttime, *event):
"""Add a timer event to queue will start (and wake) in 'starttime' seconds
"""
# in testing we should wait (looping) for the possible time drifts:
if MyTime.myTime is not None and starttime:
# test time after short sleep:
t = threading.Timer(Utils.DEFAULT_SLEEP_INTERVAL, self._delayedEvent,
(MyTime.time() + starttime, time.time() + starttime, event)
)
t.start()
return
# add timer event:
t = threading.Timer(starttime, self.add, event)
t.start()
def _delayedEvent(self, endMyTime, endTime, event):
if MyTime.time() >= endMyTime or time.time() >= endTime:
self.add_timer(0, *event)
return
# repeat after short sleep:
t = threading.Timer(Utils.DEFAULT_SLEEP_INTERVAL, self._delayedEvent,
(endMyTime, endTime, event)
)
t.start()
def pulse_notify(self):
"""Notify wakeup (sets /and resets/ notify event)
"""
if not self._paused:
n = self._notify
if n:
n.set()
#n.clear()
def add(self, *event):
"""Add a event to queue and notify thread to wake up.
"""
## lock and add new event to queue:
with self._queue_lock:
self._queue.append(event)
self.pulse_notify()
def add_wn(self, *event):
"""Add a event to queue withouth notifying thread to wake up.
"""
## lock and add new event to queue:
with self._queue_lock:
self._queue.append(event)
def call_lambda(self, l, *args):
l(*args)
def run(self):
"""Main loop for Threading.
This function is the main loop of the thread.
Returns
-------
bool
True when the thread exits nicely.
"""
logSys.info("Observer start...")
## first time create named timer to purge database each hour (clean old entries) ...
self.add_named_timer('DB_PURGE', self.__db_purge_interval, 'db_purge')
## Mapping of all possible event types of observer:
__meth = {
# universal lambda:
'call': self.call_lambda,
# system and service events:
'db_set': self.db_set,
'db_purge': self.db_purge,
# service events of observer self:
'is_alive' : self.isAlive,
'is_active': self.isActive,
'start': self.start,
'stop': self.stop,
'nop': lambda:(),
'shutdown': lambda:()
}
try:
## check it self with sending is_alive event
self.add('is_alive')
## if we should stop - break a main loop
while self.active:
self.idle = False
## check events available and execute all events from queue
while not self._paused:
## lock, check and pop one from begin of queue:
try:
ev = None
with self._queue_lock:
if len(self._queue):
ev = self._queue.pop(0)
if ev is None:
break
## retrieve method by name
meth = ev[0]
if not callable(ev[0]): meth = __meth.get(meth) or getattr(self, meth)
## execute it with rest of event as variable arguments
meth(*ev[1:])
except Exception as e:
#logSys.error('%s', e, exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
logSys.error('%s', e, exc_info=True)
## going sleep, wait for events (in queue)
n = self._notify
if n:
self.idle = True
n.wait(self.sleeptime)
## wake up - reset signal now (we don't need it so long as we reed from queue)
n.clear()
if self._paused:
continue
else:
## notify event deleted (shutdown) - just sleep a litle bit (waiting for shutdown events, prevent high cpu usage)
time.sleep(ObserverThread.DEFAULT_SLEEP_INTERVAL)
## stop by shutdown and empty queue :
if not self.is_full:
break
## end of main loop - exit
logSys.info("Observer stopped, %s events remaining.", len(self._queue))
self._notify = None
#print("Observer stopped, %s events remaining." % len(self._queue))
except Exception as e:
logSys.error('Observer stopped after error: %s', e, exc_info=True)
#print("Observer stopped with error: %s" % str(e))
# clear all events - exit, for possible calls of wait_empty:
with self._queue_lock:
self._queue = []
self.idle = True
return True
def isAlive(self):
#logSys.debug("Observer alive...")
return True
def isActive(self, fromStr=None):
# logSys.info("Observer alive, %s%s",
# 'active' if self.active else 'inactive',
# '' if fromStr is None else (", called from '%s'" % fromStr))
return self.active
def start(self):
with self._queue_lock:
if not self.active:
super(ObserverThread, self).start()
def stop(self, wtime=5, forceQuit=True):
if self.active and self._notify:
logSys.info("Observer stop ... try to end queue %s seconds", wtime)
#print("Observer stop ....")
# just add shutdown job to make possible wait later until full (events remaining)
with self._queue_lock:
self.add_wn('shutdown')
#don't pulse - just set, because we will delete it hereafter (sometimes not wakeup)
n = self._notify
self._notify.set()
#self.pulse_notify()
self._notify = None
# wait max wtime seconds until full (events remaining)
if self.wait_empty(wtime) or forceQuit:
n.clear()
self.active = False; # leave outer (active) loop
self._paused = True; # leave inner (queue) loop
self.__db = None
else:
self._notify = n
return self.wait_idle(min(wtime, 0.5)) and not self.is_full
return True
@property
def is_full(self):
with self._queue_lock:
return True if len(self._queue) else False
def wait_empty(self, sleeptime=None):
"""Wait observer is running and returns if observer has no more events (queue is empty)
"""
time.sleep(ObserverThread.DEFAULT_SLEEP_INTERVAL)
if sleeptime is not None:
e = MyTime.time() + sleeptime
# block queue with not operation to be sure all really jobs are executed if nop goes from queue :
if self._notify is not None:
self.add_wn('nop')
if self.is_full and self.idle:
self.pulse_notify()
while self.is_full:
if sleeptime is not None and MyTime.time() > e:
break
time.sleep(ObserverThread.DEFAULT_SLEEP_INTERVAL)
# wait idle to be sure the last queue element is processed (because pop event before processing it) :
self.wait_idle(0.001)
return not self.is_full
def wait_idle(self, sleeptime=None):
"""Wait observer is running and returns if observer idle (observer sleeps)
"""
time.sleep(ObserverThread.DEFAULT_SLEEP_INTERVAL)
if self.idle:
return True
if sleeptime is not None:
e = MyTime.time() + sleeptime
while not self.idle:
if sleeptime is not None and MyTime.time() > e:
break
time.sleep(ObserverThread.DEFAULT_SLEEP_INTERVAL)
return self.idle
@property
def paused(self):
return self._paused;
@paused.setter
def paused(self, pause):
if self._paused == pause:
return
self._paused = pause
# wake after pause ended
self.pulse_notify()
@property
def status(self):
"""Status of observer to be implemented. [TODO]
"""
return ('', '')
## -----------------------------------------
## [Async] database service functionality ...
## -----------------------------------------
def db_set(self, db):
self.__db = db
def db_purge(self):
logSys.debug("Purge database event occurred")
if self.__db is not None:
self.__db.purge()
# trigger timer again ...
self.add_named_timer('DB_PURGE', self.__db_purge_interval, 'db_purge')
## -----------------------------------------
## [Async] ban time increment functionality ...
## -----------------------------------------
def failureFound(self, failManager, jail, ticket):
""" Notify observer a failure for ip was found
Observer will check ip was known (bad) and possibly increase an retry count
"""
# check jail active :
if not jail.isAlive() or not jail.getBanTimeExtra("increment"):
return
ip = ticket.getIP()
unixTime = ticket.getTime()
logSys.debug("[%s] Observer: failure found %s", jail.name, ip)
# increase retry count for known (bad) ip, corresponding banCount of it (one try will count than 2, 3, 5, 9 ...) :
banCount = 0
retryCount = 1
timeOfBan = None
try:
maxRetry = failManager.getMaxRetry()
db = jail.database
if db is not None:
for banCount, timeOfBan, lastBanTime in db.getBan(ip, jail):
banCount = max(banCount, ticket.getBanCount())
retryCount = ((1 << (banCount if banCount < 20 else 20))/2 + 1)
# if lastBanTime == -1 or timeOfBan + lastBanTime * 2 > MyTime.time():
# retryCount = maxRetry
break
retryCount = min(retryCount, maxRetry)
# check this ticket already known (line was already processed and in the database and will be restored from there):
if timeOfBan is not None and unixTime <= timeOfBan:
logSys.debug("[%s] Ignore failure %s before last ban %s < %s, restored",
jail.name, ip, unixTime, timeOfBan)
return
# for not increased failures observer should not add it to fail manager, because was already added by filter self
if retryCount <= 1:
return
# retry counter was increased - add it again:
logSys.info("[%s] Found %s, bad - %s, %s # -> %s%s", jail.name, ip,
MyTime.time2str(unixTime), banCount, retryCount,
(', Ban' if retryCount >= maxRetry else ''))
# retryCount-1, because a ticket was already once incremented by filter self
retryCount = failManager.addFailure(ticket, retryCount - 1, True)
ticket.setBanCount(banCount)
# after observe we have increased attempt count, compare it >= maxretry ...
if retryCount >= maxRetry:
# perform the banning of the IP now (again)
# [todo]: this code part will be used multiple times - optimize it later.
try: # pragma: no branch - exception is the only way out
while True:
ticket = failManager.toBan(ip)
jail.putFailTicket(ticket)
except FailManagerEmpty:
failManager.cleanup(MyTime.time())
except Exception as e:
logSys.error('%s', e, exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
class BanTimeIncr:
def __init__(self, banTime, banCount):
self.Time = banTime
self.Count = banCount
def calcBanTime(self, jail, banTime, banCount):
be = jail.getBanTimeExtra()
return be['evformula'](self.BanTimeIncr(banTime, banCount))
def incrBanTime(self, jail, banTime, ticket):
"""Check for IP address to increment ban time (if was already banned).
Returns
-------
float
new ban time.
"""
# check jail active :
if not jail.isAlive() or not jail.database:
return banTime
be = jail.getBanTimeExtra()
ip = ticket.getIP()
orgBanTime = banTime
# check ip was already banned (increment time of ban):
try:
if banTime > 0 and be.get('increment', False):
# search IP in database and increase time if found:
for banCount, timeOfBan, lastBanTime in \
jail.database.getBan(ip, jail, overalljails=be.get('overalljails', False)) \
:
# increment count in ticket (if still not increased from banmanager, test-cases?):
if banCount >= ticket.getBanCount():
ticket.setBanCount(banCount+1)
logSys.debug('IP %s was already banned: %s #, %s', ip, banCount, timeOfBan);
# calculate new ban time
if banCount > 0:
banTime = be['evformula'](self.BanTimeIncr(banTime, banCount))
ticket.setBanTime(banTime)
# check current ticket time to prevent increasing for twice read tickets (restored from log file besides database after restart)
if ticket.getTime() > timeOfBan:
logSys.info('[%s] IP %s is bad: %s # last %s - incr %s to %s' % (jail.name, ip, banCount,
MyTime.time2str(timeOfBan),
datetime.timedelta(seconds=int(orgBanTime)), datetime.timedelta(seconds=int(banTime))));
else:
ticket.restored = True
break
except Exception as e:
logSys.error('%s', e, exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
return banTime
def banFound(self, ticket, jail, btime):
""" Notify observer a ban occured for ip
Observer will check ip was known (bad) and possibly increase/prolong a ban time
Secondary we will actualize the bans and bips (bad ip) in database
"""
if ticket.restored: # pragma: no cover (normally not resored tickets only)
return
try:
oldbtime = btime
ip = ticket.getIP()
logSys.debug("[%s] Observer: ban found %s, %s", jail.name, ip, btime)
# if not permanent and ban time was not set - check time should be increased:
if btime != -1 and ticket.getBanTime() is None:
btime = self.incrBanTime(jail, btime, ticket)
# if we should prolong ban time:
if btime == -1 or btime > oldbtime:
ticket.setBanTime(btime)
# if not permanent
if btime != -1:
bendtime = ticket.getTime() + btime
logtime = (datetime.timedelta(seconds=int(btime)),
MyTime.time2str(bendtime))
# check ban is not too old :
if bendtime < MyTime.time():
logSys.debug('Ignore old bantime %s', logtime[1])
return False
else:
logtime = ('permanent', 'infinite')
# if ban time was prolonged - log again with new ban time:
if btime != oldbtime:
logSys.notice("[%s] Increase Ban %s (%d # %s -> %s)", jail.name,
ip, ticket.getBanCount(), *logtime)
# delayed prolonging ticket via actions that expected this (not later than 10 sec):
logSys.log(5, "[%s] Observer: prolong %s in %s", jail.name, ip, (btime, oldbtime))
self.add_timer(min(10, max(0, btime - oldbtime - 5)), self.prolongBan, ticket, jail)
# add ticket to database, but only if was not restored (not already read from database):
if jail.database is not None and not ticket.restored:
# add to database always only after ban time was calculated an not yet already banned:
jail.database.addBan(jail, ticket)
except Exception as e:
logSys.error('%s', e, exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
def prolongBan(self, ticket, jail):
""" Notify observer a ban occured for ip
Observer will check ip was known (bad) and possibly increase/prolong a ban time
Secondary we will actualize the bans and bips (bad ip) in database
"""
try:
btime = ticket.getBanTime()
ip = ticket.getIP()
logSys.debug("[%s] Observer: prolong %s, %s", jail.name, ip, btime)
# prolong ticket via actions that expected this:
jail.actions._prolongBan(ticket)
except Exception as e:
logSys.error('%s', e, exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
# Global observer initial created in server (could be later rewriten via singleton)
class _Observers:
def __init__(self):
self.Main = None
Observers = _Observers()

View File

@ -32,7 +32,6 @@ import signal
import stat import stat
import sys import sys
from .observer import Observers, ObserverThread
from .jails import Jails from .jails import Jails
from .filter import FileFilter, JournalFilter from .filter import FileFilter, JournalFilter
from .transmitter import Transmitter from .transmitter import Transmitter
@ -112,7 +111,7 @@ class Server:
self.__prev_signals[s] = signal.getsignal(s) self.__prev_signals[s] = signal.getsignal(s)
signal.signal(s, new) signal.signal(s, new)
def start(self, sock, pidfile, force=False, observer=True, conf={}): def start(self, sock, pidfile, force=False, conf={}):
# First set the mask to only allow access to owner # First set the mask to only allow access to owner
os.umask(0o077) os.umask(0o077)
# Second daemonize before logging etc, because it will close all handles: # Second daemonize before logging etc, because it will close all handles:
@ -166,12 +165,6 @@ class Server:
except (OSError, IOError) as e: # pragma: no cover except (OSError, IOError) as e: # pragma: no cover
logSys.error("Unable to create PID file: %s", e) logSys.error("Unable to create PID file: %s", e)
# Create observers and start it:
if observer:
if Observers.Main is None:
Observers.Main = ObserverThread()
Observers.Main.start()
# Start the communication # Start the communication
logSys.debug("Starting communication") logSys.debug("Starting communication")
try: try:
@ -212,33 +205,21 @@ class Server:
for s, sh in self.__prev_signals.iteritems(): for s, sh in self.__prev_signals.iteritems():
signal.signal(s, sh) signal.signal(s, sh)
# Give observer a small chance to complete its work before exit
obsMain = Observers.Main
if obsMain is not None:
if obsMain.stop(forceQuit=False):
obsMain = None
Observers.Main = None
# Now stop all the jails # Now stop all the jails
self.stopAllJail() self.stopAllJail()
# Stop observer ultimately
if obsMain is not None:
obsMain.stop()
# Explicit close database (server can leave in a thread, # Explicit close database (server can leave in a thread,
# so delayed GC can prevent commiting changes) # so delayed GC can prevent commiting changes)
if self.__db: if self.__db:
self.__db.close() self.__db.close()
self.__db = None self.__db = None
# Stop async and exit # Stop async
if self.__asyncServer is not None: if self.__asyncServer is not None:
self.__asyncServer.stop() self.__asyncServer.stop()
self.__asyncServer = None self.__asyncServer = None
logSys.info("Exiting Fail2ban") logSys.info("Exiting Fail2ban")
def addJail(self, name, backend): def addJail(self, name, backend):
addflg = True addflg = True
if self.__reload_state.get(name) and self.__jails.exists(name): if self.__reload_state.get(name) and self.__jails.exists(name):
@ -569,27 +550,6 @@ class Server:
def getBanTime(self, name): def getBanTime(self, name):
return self.__jails[name].actions.getBanTime() return self.__jails[name].actions.getBanTime()
def getBanList(self, name, withTime=False):
"""Returns the list of banned IP addresses for a jail.
Parameters
----------
name : str
The name of a jail.
Returns
-------
list
The list of banned IP addresses.
"""
return self.__jails[name].actions.getBanList(withTime)
def setBanTimeExtra(self, name, opt, value):
self.__jails[name].setBanTimeExtra(opt, value)
def getBanTimeExtra(self, name, opt):
return self.__jails[name].getBanTimeExtra(opt)
def isStarted(self): def isStarted(self):
return self.__asyncServer is not None and self.__asyncServer.isActive() return self.__asyncServer is not None and self.__asyncServer.isActive()
@ -833,8 +793,6 @@ class Server:
logSys.error( logSys.error(
"Unable to import fail2ban database module as sqlite " "Unable to import fail2ban database module as sqlite "
"is not available.") "is not available.")
if Observers.Main is not None:
Observers.Main.db_set(self.__db)
def getDatabase(self): def getDatabase(self):
return self.__db return self.__db

View File

@ -24,6 +24,8 @@ __author__ = "Cyril Jaquier"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
import sys
from ..helpers import getLogger from ..helpers import getLogger
from .ipdns import IPAddr from .ipdns import IPAddr
from .mytime import MyTime from .mytime import MyTime
@ -33,7 +35,6 @@ logSys = getLogger(__name__)
class Ticket(object): class Ticket(object):
__slots__ = ('_ip', '_flags', '_banCount', '_banTime', '_time', '_data', '_retry', '_lastReset')
MAX_TIME = 0X7FFFFFFFFFFF ;# 4461763-th year MAX_TIME = 0X7FFFFFFFFFFF ;# 4461763-th year
@ -60,13 +61,11 @@ class Ticket(object):
self._data[k] = v self._data[k] = v
if ticket: if ticket:
# ticket available - copy whole information from ticket: # ticket available - copy whole information from ticket:
self.update(ticket) self.__dict__.update(i for i in ticket.__dict__.iteritems() if i[0] in self.__dict__)
#self.__dict__.update(i for i in ticket.__dict__.iteritems() if i[0] in self.__dict__)
def __str__(self): def __str__(self):
return "%s: ip=%s time=%s bantime=%s bancount=%s #attempts=%d matches=%r" % \ return "%s: ip=%s time=%s #attempts=%d matches=%r" % \
(self.__class__.__name__.split('.')[-1], self._ip, self._time, (self.__class__.__name__.split('.')[-1], self.__ip, self._time,
self._banTime, self._banCount,
self._data['failures'], self._data.get('matches', [])) self._data['failures'], self._data.get('matches', []))
def __repr__(self): def __repr__(self):
@ -74,30 +73,23 @@ class Ticket(object):
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._data == other._data self._data == other._data
except AttributeError: except AttributeError:
return False return False
def update(self, ticket):
for n in ticket.__slots__:
v = getattr(ticket, n, None)
if v is not None:
setattr(self, n, v)
def setIP(self, value): def setIP(self, value):
# guarantee using IPAddr instead of unicode, str for the IP # guarantee using IPAddr instead of unicode, str for the IP
if isinstance(value, basestring): if isinstance(value, basestring):
value = IPAddr(value) value = IPAddr(value)
self._ip = value self.__ip = value
def getID(self): def getID(self):
return self._data.get('fid', self._ip) return self._data.get('fid', self.__ip)
def getIP(self): def getIP(self):
return self._ip return self.__ip
def setTime(self, value): def setTime(self, value):
self._time = value self._time = value
@ -106,17 +98,16 @@ class Ticket(object):
return self._time return self._time
def setBanTime(self, value): def setBanTime(self, value):
self._banTime = value self._banTime = value;
def getBanTime(self, defaultBT=None): def getBanTime(self, defaultBT=None):
return (self._banTime if self._banTime is not None else defaultBT) return (self._banTime if self._banTime is not None else defaultBT)
def setBanCount(self, value, always=False): def setBanCount(self, value):
if always or value > self._banCount: self._banCount = value;
self._banCount = value
def incrBanCount(self, value = 1): def incrBanCount(self, value = 1):
self._banCount += value self._banCount += value;
def getBanCount(self): def getBanCount(self):
return self._banCount; return self._banCount;
@ -276,19 +267,10 @@ class FailTicket(Ticket):
else: else:
self._data['matches'] = matches self._data['matches'] = matches
@staticmethod
def wrap(o):
o.__class__ = FailTicket
return o
## ##
# Ban Ticket. # Ban Ticket.
# #
# This class extends the Ticket class. It is mainly used by the BanManager. # This class extends the Ticket class. It is mainly used by the BanManager.
class BanTicket(FailTicket): class BanTicket(Ticket):
pass
@staticmethod
def wrap(o):
o.__class__ = BanTicket
return o

View File

@ -348,12 +348,6 @@ class Transmitter:
value = command[2:] value = command[2:]
if self.__quiet: return if self.__quiet: return
return self.__server.addAttemptIP(name, *value) return self.__server.addAttemptIP(name, *value)
elif command[1].startswith("bantime."):
value = command[2]
opt = command[1][len("bantime."):]
self.__server.setBanTimeExtra(name, opt, value)
if self.__quiet: return
return self.__server.getBanTimeExtra(name, opt)
elif command[1] == "banip": elif command[1] == "banip":
value = command[2:] value = command[2:]
return self.__server.setBanIP(name,value) return self.__server.setBanIP(name,value)
@ -476,12 +470,6 @@ class Transmitter:
# Action # Action
elif command[1] == "bantime": elif command[1] == "bantime":
return self.__server.getBanTime(name) return self.__server.getBanTime(name)
elif command[1] == "banip":
return self.__server.getBanList(name,
withTime=len(command) > 2 and command[2] == "--with-time")
elif command[1].startswith("bantime."):
opt = command[1][len("bantime."):]
return self.__server.getBanTimeExtra(name, opt)
elif command[1] == "actions": elif command[1] == "actions":
return self.__server.getActions(name).keys() return self.__server.getActions(name).keys()
elif command[1] == "action": elif command[1] == "action":

View File

@ -159,7 +159,7 @@ class ExecuteActions(LogCaptureTestCase):
"action2", "action2",
os.path.join(TEST_FILES_DIR, "action.d/action_modifyainfo.py"), os.path.join(TEST_FILES_DIR, "action.d/action_modifyainfo.py"),
{}) {})
self.__jail.putFailTicket(FailTicket("1.2.3.4")) self.__jail.putFailTicket(FailTicket("1.2.3.4", 0))
self.__actions._Actions__checkBan() self.__actions._Actions__checkBan()
# Will fail if modification of aInfo from first action propagates # Will fail if modification of aInfo from first action propagates
# to second action, as both delete same key # to second action, as both delete same key

View File

@ -207,15 +207,15 @@ class CommandActionTest(LogCaptureTestCase):
self.assertEqual( self.assertEqual(
self.__action.replaceTag("<matches>", self.__action.replaceTag("<matches>",
{'matches': "some >char< should \\< be[ escap}ed&\n"}), {'matches': "some >char< should \\< be[ escap}ed&\n"}),
"some \\>char\\< should \\\\\\< be\\[ escap\\}ed\\&\\n") "some \\>char\\< should \\\\\\< be\\[ escap\\}ed\\&\n")
self.assertEqual( self.assertEqual(
self.__action.replaceTag("<ipmatches>", self.__action.replaceTag("<ipmatches>",
{'ipmatches': "some >char< should \\< be[ escap}ed&\n"}), {'ipmatches': "some >char< should \\< be[ escap}ed&\n"}),
"some \\>char\\< should \\\\\\< be\\[ escap\\}ed\\&\\n") "some \\>char\\< should \\\\\\< be\\[ escap\\}ed\\&\n")
self.assertEqual( self.assertEqual(
self.__action.replaceTag("<ipjailmatches>", self.__action.replaceTag("<ipjailmatches>",
{'ipjailmatches': "some >char< should \\< be[ escap}ed&\r\n"}), {'ipjailmatches': "some >char< should \\< be[ escap}ed&\n"}),
"some \\>char\\< should \\\\\\< be\\[ escap\\}ed\\&\\r\\n") "some \\>char\\< should \\\\\\< be\\[ escap\\}ed\\&\n")
# Recursive # Recursive
aInfo["ABC"] = "<xyz>" aInfo["ABC"] = "<xyz>"

View File

@ -26,8 +26,6 @@ __license__ = "GPL"
import unittest import unittest
from .utils import setUpMyTime, tearDownMyTime
from ..server.banmanager import BanManager from ..server.banmanager import BanManager
from ..server.ticket import BanTicket from ..server.ticket import BanTicket
@ -35,14 +33,12 @@ class AddFailure(unittest.TestCase):
def setUp(self): def setUp(self):
"""Call before every test case.""" """Call before every test case."""
super(AddFailure, self).setUp() super(AddFailure, self).setUp()
setUpMyTime()
self.__ticket = BanTicket('193.168.0.128', 1167605999.0) self.__ticket = BanTicket('193.168.0.128', 1167605999.0)
self.__banManager = BanManager() self.__banManager = BanManager()
def tearDown(self): def tearDown(self):
"""Call after every test case.""" """Call after every test case."""
super(AddFailure, self).tearDown() super(AddFailure, self).tearDown()
tearDownMyTime()
def testAdd(self): def testAdd(self):
self.assertTrue(self.__banManager.addBanTicket(self.__ticket)) self.assertTrue(self.__banManager.addBanTicket(self.__ticket))
@ -98,25 +94,6 @@ class AddFailure(unittest.TestCase):
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))
def testBanTimeIncr(self):
ticket = BanTicket(self.__ticket.getIP(), self.__ticket.getTime())
## increase twice and at end permanent, check time/count increase:
c = 0
for i in (1000, 2000, -1):
self.__banManager.addBanTicket(self.__ticket); c += 1
ticket.setBanTime(i)
self.assertFalse(self.__banManager.addBanTicket(ticket)); # no incr of c (already banned)
self.assertEqual(str(self.__banManager.getTicketByID(ticket.getIP())),
"BanTicket: ip=%s time=%s bantime=%s bancount=%s #attempts=0 matches=[]" % (ticket.getIP(), ticket.getTime(), i, c))
## after permanent, it should remain permanent ban time (-1):
self.__banManager.addBanTicket(self.__ticket); c += 1
ticket.setBanTime(-1)
self.assertFalse(self.__banManager.addBanTicket(ticket)); # no incr of c (already banned)
ticket.setBanTime(1000)
self.assertFalse(self.__banManager.addBanTicket(ticket)); # no incr of c (already banned)
self.assertEqual(str(self.__banManager.getTicketByID(ticket.getIP())),
"BanTicket: ip=%s time=%s bantime=%s bancount=%s #attempts=0 matches=[]" % (ticket.getIP(), ticket.getTime(), -1, c))
def testUnban(self): def testUnban(self):
btime = self.__banManager.getBanTime() btime = self.__banManager.getBanTime()
stime = self.__ticket.getTime() stime = self.__ticket.getTime()
@ -154,28 +131,12 @@ class AddFailure(unittest.TestCase):
finally: finally:
self.__banManager.setBanTime(btime) self.__banManager.setBanTime(btime)
def testBanList(self):
tickets = [
BanTicket('192.0.2.1', 1167605999.0),
BanTicket('192.0.2.2', 1167605999.0),
]
tickets[1].setBanTime(-1)
for t in tickets:
self.__banManager.addBanTicket(t)
self.assertSortedEqual(self.__banManager.getBanList(ordered=True, withTime=True),
[
'192.0.2.1 \t2006-12-31 23:59:59 + 600 = 2007-01-01 00:09:59',
'192.0.2.2 \t2006-12-31 23:59:59 + -1 = 9999-12-31 23:59:59'
]
)
class StatusExtendedCymruInfo(unittest.TestCase): class StatusExtendedCymruInfo(unittest.TestCase):
def setUp(self): def setUp(self):
"""Call before every test case.""" """Call before every test case."""
super(StatusExtendedCymruInfo, self).setUp() super(StatusExtendedCymruInfo, self).setUp()
unittest.F2B.SkipIfNoNetwork() unittest.F2B.SkipIfNoNetwork()
setUpMyTime()
self.__ban_ip = "93.184.216.34" self.__ban_ip = "93.184.216.34"
self.__asn = "15133" self.__asn = "15133"
self.__country = "EU" self.__country = "EU"
@ -187,7 +148,6 @@ class StatusExtendedCymruInfo(unittest.TestCase):
def tearDown(self): def tearDown(self):
"""Call after every test case.""" """Call after every test case."""
super(StatusExtendedCymruInfo, self).tearDown() super(StatusExtendedCymruInfo, self).tearDown()
tearDownMyTime()
available = True, None available = True, None

View File

@ -164,47 +164,10 @@ class DatabaseTest(LogCaptureTestCase):
self.assertEqual(self.db.updateDb(Fail2BanDb.__version__), Fail2BanDb.__version__) self.assertEqual(self.db.updateDb(Fail2BanDb.__version__), Fail2BanDb.__version__)
self.assertRaises(NotImplementedError, self.db.updateDb, Fail2BanDb.__version__ + 1) self.assertRaises(NotImplementedError, self.db.updateDb, Fail2BanDb.__version__ + 1)
# check current bans (should find exactly 1 ticket after upgrade):
tickets = self.db.getCurrentBans(fromtime=1388009242, correctBanTime=123456)
self.assertEqual(len(tickets), 1)
self.assertEqual(tickets[0].getBanTime(), 123456); # ban-time was unknown (normally updated from jail)
finally: finally:
if self.db and self.db._dbFilename != ":memory:": if self.db and self.db._dbFilename != ":memory:":
os.remove(self.db._dbBackupFilename) os.remove(self.db._dbBackupFilename)
def testUpdateDb2(self):
self.db = None
if self.dbFilename is None: # pragma: no cover
_, self.dbFilename = tempfile.mkstemp(".db", "fail2ban_")
shutil.copyfile(
os.path.join(TEST_FILES_DIR, 'database_v2.db'), self.dbFilename)
self.db = Fail2BanDb(self.dbFilename)
self.assertEqual(self.db.getJailNames(), set(['pam-generic']))
self.assertEqual(self.db.getLogPaths(), set(['/var/log/auth.log']))
bans = self.db.getBans()
self.assertEqual(len(bans), 2)
# compare first ticket completely:
ticket = FailTicket("1.2.3.7", 1417595494, [
u'Dec 3 09:31:08 f2btest test:auth[27658]: pam_unix(test:auth): authentication failure; logname= uid=0 euid=0 tty=test ruser= rhost=1.2.3.7',
u'Dec 3 09:31:32 f2btest test:auth[27671]: pam_unix(test:auth): authentication failure; logname= uid=0 euid=0 tty=test ruser= rhost=1.2.3.7',
u'Dec 3 09:31:34 f2btest test:auth[27673]: pam_unix(test:auth): authentication failure; logname= uid=0 euid=0 tty=test ruser= rhost=1.2.3.7'
])
ticket.setAttempt(3)
self.assertEqual(bans[0], ticket)
# second ban found also:
self.assertEqual(bans[1].getIP(), "1.2.3.8")
# updated ?
self.assertEqual(self.db.updateDb(Fail2BanDb.__version__), Fail2BanDb.__version__)
# check current bans (should find 2 tickets after upgrade):
self.jail = DummyJail(name='pam-generic')
tickets = self.db.getCurrentBans(jail=self.jail, fromtime=1417595494)
self.assertEqual(len(tickets), 2)
self.assertEqual(tickets[0].getBanTime(), 600)
# further update should fail:
self.assertRaises(NotImplementedError, self.db.updateDb, Fail2BanDb.__version__ + 1)
# clean:
os.remove(self.db._dbBackupFilename)
def testAddJail(self): def testAddJail(self):
self.jail = DummyJail() self.jail = DummyJail()
self.db.addJail(self.jail) self.db.addJail(self.jail)
@ -437,7 +400,7 @@ class DatabaseTest(LogCaptureTestCase):
def testGetBansMerged(self): def testGetBansMerged(self):
self.testAddJail() self.testAddJail()
jail2 = DummyJail(name='DummyJail-2') jail2 = DummyJail()
self.db.addJail(jail2) self.db.addJail(jail2)
ticket = FailTicket("127.0.0.1", MyTime.time() - 40, ["abc\n"]) ticket = FailTicket("127.0.0.1", MyTime.time() - 40, ["abc\n"])
@ -519,25 +482,10 @@ class DatabaseTest(LogCaptureTestCase):
tickets = self.db.getCurrentBans(jail=self.jail, forbantime=15, tickets = self.db.getCurrentBans(jail=self.jail, forbantime=15,
fromtime=MyTime.time() + MyTime.str2seconds("1year")) fromtime=MyTime.time() + MyTime.str2seconds("1year"))
self.assertEqual(len(tickets), 0) self.assertEqual(len(tickets), 0)
# persistent bantime (-1), so never expired (but no persistent tickets): # persistent bantime (-1), so never expired:
tickets = self.db.getCurrentBans(jail=self.jail, forbantime=-1, tickets = self.db.getCurrentBans(jail=self.jail, forbantime=-1,
fromtime=MyTime.time() + MyTime.str2seconds("1year")) fromtime=MyTime.time() + MyTime.str2seconds("1year"))
self.assertEqual(len(tickets), 0) self.assertEqual(len(tickets), 2)
# add persistent one:
ticket.setBanTime(-1)
self.db.addBan(self.jail, ticket)
# persistent bantime (-1), so never expired (but jail has other max bantime now):
tickets = self.db.getCurrentBans(jail=self.jail, forbantime=-1,
fromtime=MyTime.time() + MyTime.str2seconds("1year"))
# no tickets should be found (max ban time = 600):
self.assertEqual(len(tickets), 0)
self.assertLogged("ignore ticket (with new max ban-time %r)" % self.jail.getMaxBanTime())
# change jail to persistent ban and try again (1 persistent ticket):
self.jail.actions.setBanTime(-1)
tickets = self.db.getCurrentBans(jail=self.jail, forbantime=-1,
fromtime=MyTime.time() + MyTime.str2seconds("1year"))
self.assertEqual(len(tickets), 1)
self.assertEqual(tickets[0].getBanTime(), -1); # current jail ban time.
def testActionWithDB(self): def testActionWithDB(self):
# test action together with database functionality # test action together with database functionality
@ -549,9 +497,8 @@ class DatabaseTest(LogCaptureTestCase):
"action_checkainfo", "action_checkainfo",
os.path.join(TEST_FILES_DIR, "action.d/action_checkainfo.py"), os.path.join(TEST_FILES_DIR, "action.d/action_checkainfo.py"),
{}) {})
ticket = FailTicket("1.2.3.4") ticket = FailTicket("1.2.3.4", MyTime.time(), ['test', 'test'])
ticket.setAttempt(5) ticket.setAttempt(5)
ticket.setMatches(['test', 'test'])
self.jail.putFailTicket(ticket) self.jail.putFailTicket(ticket)
actions._Actions__checkBan() actions._Actions__checkBan()
self.assertLogged("ban ainfo %s, %s, %s, %s" % (True, True, True, True)) self.assertLogged("ban ainfo %s, %s, %s, %s" % (True, True, True, True))

View File

@ -28,19 +28,14 @@ from ..server.jail import Jail
from ..server.actions import Actions from ..server.actions import Actions
class DummyActions(Actions):
def checkBan(self):
return self._Actions__checkBan()
class DummyJail(Jail): class DummyJail(Jail):
"""A simple 'jail' to suck in all the tickets generated by Filter's """A simple 'jail' to suck in all the tickets generated by Filter's
""" """
def __init__(self, name='DummyJail', backend=None): def __init__(self, backend=None):
self.lock = Lock() self.lock = Lock()
self.queue = [] self.queue = []
super(DummyJail, self).__init__(name=name, backend=backend) super(DummyJail, self).__init__(name='DummyJail', backend=backend)
self.__actions = DummyActions(self) self.__actions = Actions(self)
def __len__(self): def __len__(self):
with self.lock: with self.lock:
@ -69,6 +64,10 @@ class DummyJail(Jail):
except IndexError: except IndexError:
return False return False
@property
def name(self):
return "DummyJail" + ("" if self.database else " #%s with %d tickets" % (id(self), len(self)))
@property @property
def idle(self): def idle(self):
return False; return False;

View File

@ -44,7 +44,7 @@ from ..server import server
from ..server.mytime import MyTime from ..server.mytime import MyTime
from ..server.utils import Utils from ..server.utils import Utils
from .utils import LogCaptureTestCase, logSys as DefLogSys, with_tmpdir, shutil, logging, \ from .utils import LogCaptureTestCase, logSys as DefLogSys, with_tmpdir, shutil, logging, \
STOCK, CONFIG_DIR as STOCK_CONF_DIR, TEST_NOW, tearDownMyTime STOCK, CONFIG_DIR as STOCK_CONF_DIR
from ..helpers import getLogger from ..helpers import getLogger
@ -78,35 +78,6 @@ fail2banclient.output = \
fail2banserver.output = \ fail2banserver.output = \
protocol.output = _test_output protocol.output = _test_output
def _time_shift(shift):
# jump to the future (+shift minutes):
logSys.debug("===>>> time shift + %s min", shift)
MyTime.setTime(MyTime.time() + shift*60)
Observers = server.Observers
def _observer_wait_idle():
"""Helper to wait observer becomes idle"""
if Observers.Main is not None:
Observers.Main.wait_empty(MID_WAITTIME)
Observers.Main.wait_idle(MID_WAITTIME / 5)
def _observer_wait_before_incrban(cond, timeout=MID_WAITTIME):
"""Helper to block observer before increase bantime until some condition gets true"""
if Observers.Main is not None:
# switch ban handler:
_obs_banFound = Observers.Main.banFound
def _banFound(*args, **kwargs):
# restore original handler:
Observers.Main.banFound = _obs_banFound
# wait for:
logSys.debug(' [Observer::banFound] *** observer blocked for test')
Utils.wait_for(cond, timeout)
logSys.debug(' [Observer::banFound] +++ observer runs again')
# original banFound:
_obs_banFound(*args, **kwargs)
Observers.Main.banFound = _banFound
# #
# Mocking .exit so we could test its correct operation. # Mocking .exit so we could test its correct operation.
@ -343,7 +314,6 @@ def with_foreground_server_thread(startextra={}):
# to wait for end of server, default accept any exit code, because multi-threaded, # to wait for end of server, default accept any exit code, because multi-threaded,
# thus server can exit in-between... # thus server can exit in-between...
def _stopAndWaitForServerEnd(code=(SUCCESS, FAILED)): def _stopAndWaitForServerEnd(code=(SUCCESS, FAILED)):
tearDownMyTime()
# if seems to be down - try to catch end phase (wait a bit for end:True to recognize down state): # if seems to be down - try to catch end phase (wait a bit for end:True to recognize down state):
if not phase.get('end', None) and not os.path.exists(pjoin(tmp, "f2b.pid")): if not phase.get('end', None) and not os.path.exists(pjoin(tmp, "f2b.pid")):
Utils.wait_for(lambda: phase.get('end', None) is not None, MID_WAITTIME) Utils.wait_for(lambda: phase.get('end', None) is not None, MID_WAITTIME)
@ -383,7 +353,6 @@ def with_foreground_server_thread(startextra={}):
# so don't kill (same process) - if success, just wait for end of worker: # so don't kill (same process) - if success, just wait for end of worker:
if phase.get('end', None): if phase.get('end', None):
th.join() th.join()
tearDownMyTime()
return wrapper return wrapper
return _deco_wrapper return _deco_wrapper
@ -410,7 +379,6 @@ class Fail2banClientServerBase(LogCaptureTestCase):
server.DEF_LOGTARGET = SRV_DEF_LOGTARGET server.DEF_LOGTARGET = SRV_DEF_LOGTARGET
server.DEF_LOGLEVEL = SRV_DEF_LOGLEVEL server.DEF_LOGLEVEL = SRV_DEF_LOGLEVEL
LogCaptureTestCase.tearDown(self) LogCaptureTestCase.tearDown(self)
tearDownMyTime()
@staticmethod @staticmethod
def _test_exit(code=0): def _test_exit(code=0):
@ -478,14 +446,14 @@ class Fail2banClientServerBase(LogCaptureTestCase):
@with_foreground_server_thread(startextra={'f2b_local':( @with_foreground_server_thread(startextra={'f2b_local':(
"[Thread]", "[Thread]",
"stacksize = 128" "stacksize = 32"
"", "",
)}) )})
def testStartForeground(self, tmp, startparams): def testStartForeground(self, tmp, startparams):
# check thread options were set: # check thread options were set:
self.pruneLog() self.pruneLog()
self.execCmd(SUCCESS, startparams, "get", "thread") self.execCmd(SUCCESS, startparams, "get", "thread")
self.assertLogged("{'stacksize': 128}") self.assertLogged("{'stacksize': 32}")
# several commands to server: # several commands to server:
self.execCmd(SUCCESS, startparams, "ping") self.execCmd(SUCCESS, startparams, "ping")
self.execCmd(FAILED, startparams, "~~unknown~cmd~failed~~") self.execCmd(FAILED, startparams, "~~unknown~cmd~failed~~")
@ -1035,8 +1003,6 @@ class Fail2banServerTest(Fail2banClientServerBase):
"[test-jail2] Found 192.0.2.3", "[test-jail2] Found 192.0.2.3",
"[test-jail2] Ban 192.0.2.3", "[test-jail2] Ban 192.0.2.3",
all=True) all=True)
# if observer available wait for it becomes idle (write all tickets to db):
_observer_wait_idle()
# test banned command: # test banned command:
self.assertSortedEqual(self.execCmdDirect(startparams, self.assertSortedEqual(self.execCmdDirect(startparams,
'banned'), (0, [ 'banned'), (0, [
@ -1106,17 +1072,6 @@ class Fail2banServerTest(Fail2banClientServerBase):
"stdout: '[test-jail2] test-action3: ++ ban 192.0.2.22", "stdout: '[test-jail2] test-action3: ++ ban 192.0.2.22",
"stdout: '[test-jail2] test-action3: ++ ban 192.0.2.22 ", all=True, wait=MID_WAITTIME) "stdout: '[test-jail2] test-action3: ++ ban 192.0.2.22 ", all=True, wait=MID_WAITTIME)
# get banned ips:
_observer_wait_idle()
self.pruneLog("[test-phase 2d.1]")
self.execCmd(SUCCESS, startparams, "get", "test-jail2", "banip", "\n")
self.assertLogged(
"192.0.2.4", "192.0.2.8", "192.0.2.21", "192.0.2.22", all=True, wait=MID_WAITTIME)
self.pruneLog("[test-phase 2d.2]")
self.execCmd(SUCCESS, startparams, "get", "test-jail1", "banip")
self.assertLogged(
"192.0.2.1", "192.0.2.2", "192.0.2.3", "192.0.2.4", "192.0.2.8", all=True, wait=MID_WAITTIME)
# restart jail with unban all: # restart jail with unban all:
self.pruneLog("[test-phase 2e]") self.pruneLog("[test-phase 2e]")
self.execCmd(SUCCESS, startparams, self.execCmd(SUCCESS, startparams,
@ -1509,152 +1464,6 @@ class Fail2banServerTest(Fail2banClientServerBase):
# just to debug actionstop: # just to debug actionstop:
self.assertFalse(exists(tofn)) self.assertFalse(exists(tofn))
@with_foreground_server_thread()
def testServerObserver(self, tmp, startparams):
cfg = pjoin(tmp, "config")
test1log = pjoin(tmp, "test1.log")
os.mkdir(pjoin(cfg, "action.d"))
def _write_action_cfg(actname="test-action1", prolong=True):
fn = pjoin(cfg, "action.d", "%s.conf" % actname)
_write_file(fn, "w",
"[DEFAULT]",
"",
"[Definition]",
"actionban = printf %%s \"[%(name)s] %(actname)s: ++ ban <ip> -c <bancount> -t <bantime> : <F-MSG>\"", \
"actionprolong = printf %%s \"[%(name)s] %(actname)s: ++ prolong <ip> -c <bancount> -t <bantime> : <F-MSG>\"" \
if prolong else "",
"actionunban = printf %%b '[%(name)s] %(actname)s: -- unban <ip>'",
)
if unittest.F2B.log_level <= logging.DEBUG: # pragma: no cover
_out_file(fn)
def _write_jail_cfg(backend="polling"):
_write_file(pjoin(cfg, "jail.conf"), "w",
"[INCLUDES]", "",
"[DEFAULT]", "",
"usedns = no",
"maxretry = 3",
"findtime = 1m",
"bantime = 5m",
"bantime.increment = true",
"datepattern = {^LN-BEG}EPOCH",
"",
"[test-jail1]", "backend = " + backend, "filter =",
"action = test-action1[name='%(__name__)s']",
" test-action2[name='%(__name__)s']",
"logpath = " + test1log,
r"failregex = ^\s*failure <F-ERRCODE>401|403</F-ERRCODE> from <HOST>:\s*<F-MSG>.*</F-MSG>$",
"enabled = true",
"",
)
if unittest.F2B.log_level <= logging.DEBUG: # pragma: no cover
_out_file(pjoin(cfg, "jail.conf"))
# create test config:
_write_action_cfg(actname="test-action1", prolong=False)
_write_action_cfg(actname="test-action2", prolong=True)
_write_jail_cfg()
_write_file(test1log, "w")
# initial start:
self.pruneLog("[test-phase 0) time-0]")
self.execCmd(SUCCESS, startparams, "reload")
# generate bad ip:
_write_file(test1log, "w+", *(
(str(int(MyTime.time())) + " failure 401 from 192.0.2.11: I'm bad \"hacker\" `` $(echo test)",) * 3
))
# wait for ban:
_observer_wait_idle()
self.assertLogged(
"stdout: '[test-jail1] test-action1: ++ ban 192.0.2.11 -c 1 -t 300 : ",
"stdout: '[test-jail1] test-action2: ++ ban 192.0.2.11 -c 1 -t 300 : ",
all=True, wait=MID_WAITTIME)
# wait for observer idle (write all tickets to db):
_observer_wait_idle()
self.pruneLog("[test-phase 1) time+10m]")
# jump to the future (+10 minutes):
_time_shift(10)
_observer_wait_idle()
self.assertLogged(
"stdout: '[test-jail1] test-action1: -- unban 192.0.2.11",
"stdout: '[test-jail1] test-action2: -- unban 192.0.2.11",
"0 ticket(s) in 'test-jail1'",
all=True, wait=MID_WAITTIME)
_observer_wait_idle()
self.pruneLog("[test-phase 2) time+10m]")
# following tests are time-related - observer can prolong ticket (increase ban-time)
# before banning, so block it here before banFound called, prolong case later:
wakeObs = False
_observer_wait_before_incrban(lambda: wakeObs)
# write again (IP already bad):
_write_file(test1log, "w+", *(
(str(int(MyTime.time())) + " failure 401 from 192.0.2.11: I'm very bad \"hacker\" `` $(echo test)",) * 2
))
# wait for ban:
self.assertLogged(
"stdout: '[test-jail1] test-action1: ++ ban 192.0.2.11 -c 2 -t 300 : ",
"stdout: '[test-jail1] test-action2: ++ ban 192.0.2.11 -c 2 -t 300 : ",
all=True, wait=MID_WAITTIME)
# get banned ips with time:
self.pruneLog("[test-phase 2) time+10m - get-ips]")
self.execCmd(SUCCESS, startparams, "get", "test-jail1", "banip", "--with-time")
self.assertLogged(
"192.0.2.11", "+ 300 =", all=True, wait=MID_WAITTIME)
# unblock observer here and wait it is done:
wakeObs = True
_observer_wait_idle()
self.pruneLog("[test-phase 2) time+11m]")
# jump to the future (+1 minute):
_time_shift(1)
# wait for observer idle (write all tickets to db):
_observer_wait_idle()
# wait for prolong:
self.assertLogged(
"stdout: '[test-jail1] test-action2: ++ prolong 192.0.2.11 -c 2 -t 600 : ",
all=True, wait=MID_WAITTIME)
# get banned ips with time:
_observer_wait_idle()
self.pruneLog("[test-phase 2) time+11m - get-ips]")
self.execCmd(SUCCESS, startparams, "get", "test-jail1", "banip", "--with-time")
self.assertLogged(
"192.0.2.11", "+ 600 =", all=True, wait=MID_WAITTIME)
# test stop with busy observer:
self.pruneLog("[test-phase end) stop on busy observer]")
tearDownMyTime()
a = {'state': 0}
obsMain = Observers.Main
def _long_action():
logSys.info('++ observer enters busy state ...')
a['state'] = 1
Utils.wait_for(lambda: a['state'] == 2, MAX_WAITTIME)
obsMain.db_purge(); # does nothing (db is already None)
logSys.info('-- observer leaves busy state.')
obsMain.add('call', _long_action)
obsMain.add('call', lambda: None)
# wait observer enter busy state:
Utils.wait_for(lambda: a['state'] == 1, MAX_WAITTIME)
# overwrite default wait time (normally 5 seconds):
obsMain_stop = obsMain.stop
def _stop(wtime=(0.01 if unittest.F2B.fast else 0.1), forceQuit=True):
return obsMain_stop(wtime, forceQuit)
obsMain.stop = _stop
# stop server and wait for end:
self.stopAndWaitForServerEnd(SUCCESS)
# check observer and db state:
self.assertNotLogged('observer leaves busy state')
self.assertFalse(obsMain.idle)
self.assertEqual(obsMain._ObserverThread__db, None)
# server is exited without wait for observer, stop it now:
a['state'] = 2
self.assertLogged('observer leaves busy state', wait=True)
obsMain.join()
# test multiple start/stop of the server (threaded in foreground) -- # test multiple start/stop of the server (threaded in foreground) --
if False: # pragma: no cover if False: # pragma: no cover
@with_foreground_server_thread() @with_foreground_server_thread()
@ -1665,4 +1474,3 @@ class Fail2banServerTest(Fail2banClientServerBase):
def testServerStartStop(self): def testServerStartStop(self):
for i in xrange(2000): for i in xrange(2000):
self._testServerStartStop() self._testServerStartStop()

View File

@ -341,23 +341,6 @@ class Fail2banRegexTest(LogCaptureTestCase):
self.assertTrue(_test_exec('-o', 'id', STR_00, RE_00_ID)) self.assertTrue(_test_exec('-o', 'id', STR_00, RE_00_ID))
self.assertLogged('kevin') self.assertLogged('kevin')
self.pruneLog() self.pruneLog()
# multiple id combined to a tuple (id, tuple_id):
self.assertTrue(_test_exec('-o', 'id',
'1591983743.667 192.0.2.1 192.0.2.2',
r'^\s*<F-ID/> <F-TUPLE_ID>\S+</F-TUPLE_ID>'))
self.assertLogged(str(('192.0.2.1', '192.0.2.2')))
self.pruneLog()
# multiple id combined to a tuple, id first - (id, tuple_id_1, tuple_id_2):
self.assertTrue(_test_exec('-o', 'id',
'1591983743.667 left 192.0.2.3 right',
r'^\s*<F-TUPLE_ID_1>\S+</F-TUPLE_ID_1> <F-ID/> <F-TUPLE_ID_2>\S+</F-TUPLE_ID_2>'))
self.pruneLog()
# id had higher precedence as ip-address:
self.assertTrue(_test_exec('-o', 'id',
'1591983743.667 left [192.0.2.4]:12345 right',
r'^\s*<F-TUPLE_ID_1>\S+</F-TUPLE_ID_1> <F-ID><ADDR>:<F-PORT/></F-ID> <F-TUPLE_ID_2>\S+</F-TUPLE_ID_2>'))
self.assertLogged(str(('[192.0.2.4]:12345', 'left', 'right')))
self.pruneLog()
# row with id : # row with id :
self.assertTrue(_test_exec('-o', 'row', STR_00, RE_00_ID)) self.assertTrue(_test_exec('-o', 'row', STR_00, RE_00_ID))
self.assertLogged("['kevin'", "'ip4': '192.0.2.0'", "'fid': 'kevin'", all=True) self.assertLogged("['kevin'", "'ip4': '192.0.2.0'", "'fid': 'kevin'", all=True)

View File

@ -159,10 +159,10 @@ class AddFailure(unittest.TestCase):
ticket_repr = repr(ticket) ticket_repr = repr(ticket)
self.assertEqual( self.assertEqual(
ticket_str, ticket_str,
'FailTicket: ip=193.168.0.128 time=1167605999.0 bantime=None bancount=0 #attempts=5 matches=[]') 'FailTicket: ip=193.168.0.128 time=1167605999.0 #attempts=5 matches=[]')
self.assertEqual( self.assertEqual(
ticket_repr, ticket_repr,
'FailTicket: ip=193.168.0.128 time=1167605999.0 bantime=None bancount=0 #attempts=5 matches=[]') 'FailTicket: ip=193.168.0.128 time=1167605999.0 #attempts=5 matches=[]')
self.assertFalse(not ticket) self.assertFalse(not ticket)
# and some get/set-ers otherwise not tested # and some get/set-ers otherwise not tested
ticket.setTime(1000002000.0) ticket.setTime(1000002000.0)
@ -170,7 +170,7 @@ class AddFailure(unittest.TestCase):
# and str() adjusted correspondingly # and str() adjusted correspondingly
self.assertEqual( self.assertEqual(
str(ticket), str(ticket),
'FailTicket: ip=193.168.0.128 time=1000002000.0 bantime=None bancount=0 #attempts=5 matches=[]') 'FailTicket: ip=193.168.0.128 time=1000002000.0 #attempts=5 matches=[]')
def testbanNOK(self): def testbanNOK(self):
self._addDefItems() self._addDefItems()

Binary file not shown.

Binary file not shown.

View File

@ -20,3 +20,6 @@
[Sun Mar 11 08:56:20.913548 2018] [proxy_fcgi:error] [pid 742:tid 140142593419008] [client 192.0.2.106:50900] AH01071: Got error 'Primary script unknown\n' [Sun Mar 11 08:56:20.913548 2018] [proxy_fcgi:error] [pid 742:tid 140142593419008] [client 192.0.2.106:50900] AH01071: Got error 'Primary script unknown\n'
# failJSON: { "time": "2019-07-09T14:27:42", "match": true , "host": "127.0.0.1", "desc": "script unknown, without \n (gh-2466)" } # failJSON: { "time": "2019-07-09T14:27:42", "match": true , "host": "127.0.0.1", "desc": "script unknown, without \n (gh-2466)" }
[Tue Jul 09 14:27:42.650548 2019] [proxy_fcgi:error] [pid 22075:tid 140322524440320] [client 127.0.0.1] AH01071: Got error 'Primary script unknown' [Tue Jul 09 14:27:42.650548 2019] [proxy_fcgi:error] [pid 22075:tid 140322524440320] [client 127.0.0.1] AH01071: Got error 'Primary script unknown'
# failJSON: { "time": "2020-08-11T08:56:17", "match": true , "host": "192.0.2.1", "desc": "script not found with AH02811 and cgi-bin path segment in script (gh-2805)" }
[Tue Aug 11 08:56:17.580412 2020] [cgi:error] [pid 27550:tid 140110750279424] [client 192.0.2.1:18071] AH02811: script not found or unable to stat: /usr/lib/cgi-bin/kerbynet

View File

@ -43,9 +43,15 @@ Jan 29 05:13:50 mail dovecot: auth: passwd-file(username,1.2.3.4): unknown user
# failJSON: { "time": "2005-01-29T13:54:06", "match": true , "host": "192.0.2.5" } # failJSON: { "time": "2005-01-29T13:54:06", "match": true , "host": "192.0.2.5" }
Jan 29 13:54:06 auth-worker(22401): Info: sql(admin@example.de,192.0.2.5,<n4JLdHNVngZGpV2j>): unknown user Jan 29 13:54:06 auth-worker(22401): Info: sql(admin@example.de,192.0.2.5,<n4JLdHNVngZGpV2j>): unknown user
#failJSON: { "time": "2005-06-11T13:57:17", "match": true, "host": "192.168.178.25", "desc": "allow more verbose logging, gh-2573" }
Jun 11 13:57:17 main dovecot: auth: ldap(user@server.org,192.168.178.25,<LZmGp6mZaMrAqLIZ>): unknown user (SHA1 of given password: f638ff)
#failJSON: { "time": "2005-06-11T13:57:17", "match": true, "host": "192.168.144.226" } #failJSON: { "time": "2005-06-11T13:57:17", "match": true, "host": "192.168.144.226" }
Jun 11 13:57:17 main dovecot: auth: sql(admin@example.ru,192.168.144.226,<6rXunFtu493AqJDi>): Password mismatch Jun 11 13:57:17 main dovecot: auth: sql(admin@example.ru,192.168.144.226,<6rXunFtu493AqJDi>): Password mismatch
#failJSON: { "time": "2005-06-11T13:57:17", "match": true, "host": "192.168.178.25", "desc": "allow more verbose logging, gh-2573" }
Jun 11 13:57:17 main dovecot: auth: ldap(user@server.org,192.168.178.25,<LZmGp6mZaMrAqLIZ>): Password mismatch (for LDAP bind) (SHA1 of given password: f638ff)
# failJSON: { "time": "2005-01-29T14:38:51", "match": true , "host": "192.0.2.6", "desc": "PAM Permission denied (gh-1897)" } # failJSON: { "time": "2005-01-29T14:38:51", "match": true , "host": "192.0.2.6", "desc": "PAM Permission denied (gh-1897)" }
Jan 29 14:38:51 example.com dovecot[24941]: auth-worker(30165): pam(user@example.com,192.0.2.6,<PNHQq8pZhqIKAQGd>): pam_authenticate() failed: Permission denied Jan 29 14:38:51 example.com dovecot[24941]: auth-worker(30165): pam(user@example.com,192.0.2.6,<PNHQq8pZhqIKAQGd>): pam_authenticate() failed: Permission denied
@ -94,6 +100,13 @@ Jul 26 11:12:19 hostname dovecot: imap-login: Disconnected: Too many invalid com
# failJSON: { "time": "2004-08-28T06:38:51", "match": true , "host": "192.0.2.3" } # failJSON: { "time": "2004-08-28T06:38:51", "match": true , "host": "192.0.2.3" }
Aug 28 06:38:51 s166-62-100-187 dovecot: imap-login: Disconnected (auth failed, 1 attempts in 9 secs): user=<administrator@example.com>, method=PLAIN, rip=192.0.2.3, lip=192.168.1.2, TLS: Disconnected, TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits) Aug 28 06:38:51 s166-62-100-187 dovecot: imap-login: Disconnected (auth failed, 1 attempts in 9 secs): user=<administrator@example.com>, method=PLAIN, rip=192.0.2.3, lip=192.168.1.2, TLS: Disconnected, TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)
# failJSON: { "time": "2004-08-29T03:17:18", "match": true , "host": "192.0.2.133" }
Aug 29 03:17:18 server dovecot: submission-login: Client has quit the connection (auth failed, 1 attempts in 2 secs): user=<user1>, method=LOGIN, rip=192.0.2.133, lip=0.0.0.0
# failJSON: { "time": "2004-08-29T03:53:52", "match": true , "host": "192.0.2.169" }
Aug 29 03:53:52 server dovecot: submission-login: Remote closed connection (auth failed, 1 attempts in 2 secs): user=<user4>, method=PLAIN, rip=192.0.2.169, lip=0.0.0.0
# failJSON: { "time": "2004-08-29T15:33:53", "match": true , "host": "192.0.2.100" }
Aug 29 15:33:53 server dovecot: managesieve-login: Disconnected: Too many invalid commands. (auth failed, 1 attempts in 2 secs): user=<myself>, method=PLAIN, rip=192.0.2.100, lip=0.0.0.0, TLS, TLSv1.3 with cipher TLS_CHACHA20_POLY1305_SHA256 (256/256 bits)
# --------------------------------------- # ---------------------------------------
# Test-cases of aggressive mode: # Test-cases of aggressive mode:
# --------------------------------------- # ---------------------------------------

View File

@ -92,7 +92,7 @@ class _tmSerial():
@staticmethod @staticmethod
def _tm(time): def _tm(time):
# ## strftime it too slow for large time serializer : # ## strftime it too slow for large time serializer :
# return MyTime.time2str(time) # return datetime.datetime.fromtimestamp(time).strftime("%Y-%m-%d %H:%M:%S")
c = _tmSerial c = _tmSerial
sec = (time % 60) sec = (time % 60)
if c._last_s == time - sec: if c._last_s == time - sec:
@ -304,7 +304,7 @@ class BasicFilter(unittest.TestCase):
unittest.F2B.SkipIfFast() unittest.F2B.SkipIfFast()
## test function "_tm" works correct (returns the same as slow strftime): ## test function "_tm" works correct (returns the same as slow strftime):
for i in xrange(1417512352, (1417512352 // 3600 + 3) * 3600): for i in xrange(1417512352, (1417512352 // 3600 + 3) * 3600):
tm = MyTime.time2str(i) tm = datetime.datetime.fromtimestamp(i).strftime("%Y-%m-%d %H:%M:%S")
if _tm(i) != tm: # pragma: no cover - never reachable if _tm(i) != tm: # pragma: no cover - never reachable
self.assertEqual((_tm(i), i), (tm, i)) self.assertEqual((_tm(i), i), (tm, i))

View File

@ -1,627 +0,0 @@
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
# vi: set ft=python sts=4 ts=4 sw=4 noet :
# This file is part of Fail2Ban.
#
# Fail2Ban is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Fail2Ban is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Fail2Ban; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# Author: Serg G. Brester (sebres)
#
__author__ = "Serg G. Brester (sebres)"
__copyright__ = "Copyright (c) 2014 Serg G. Brester"
__license__ = "GPL"
import os
import sys
import unittest
import tempfile
import time
from ..server.mytime import MyTime
from ..server.ticket import FailTicket, BanTicket
from ..server.failmanager import FailManager
from ..server.observer import Observers, ObserverThread
from ..server.utils import Utils
from .utils import LogCaptureTestCase
from .dummyjail import DummyJail
from .databasetestcase import getFail2BanDb, Fail2BanDb
class BanTimeIncr(LogCaptureTestCase):
def setUp(self):
"""Call before every test case."""
super(BanTimeIncr, self).setUp()
self.__jail = DummyJail()
self.__jail.calcBanTime = self.calcBanTime
self.Observer = ObserverThread()
def tearDown(self):
super(BanTimeIncr, self).tearDown()
def calcBanTime(self, banTime, banCount):
return self.Observer.calcBanTime(self.__jail, banTime, banCount)
def testDefault(self, multipliers = None):
a = self.__jail;
a.setBanTimeExtra('increment', 'true')
self.assertEqual(a.getBanTimeExtra('increment'), True)
a.setBanTimeExtra('maxtime', '1d')
self.assertEqual(a.getBanTimeExtra('maxtime'), 24*60*60)
a.setBanTimeExtra('rndtime', None)
a.setBanTimeExtra('factor', None)
# tests formulat or multipliers:
a.setBanTimeExtra('multipliers', multipliers)
# test algorithm and max time 24 hours :
self.assertEqual(
[a.calcBanTime(600, i) for i in xrange(1, 11)],
[1200, 2400, 4800, 9600, 19200, 38400, 76800, 86400, 86400, 86400]
)
# with extra large max time (30 days):
a.setBanTimeExtra('maxtime', '30d')
# using formula the ban time grows always, but using multipliers the growing will stops with last one:
arr = [1200, 2400, 4800, 9600, 19200, 38400, 76800, 153600, 307200, 614400]
if multipliers is not None:
multcnt = len(multipliers.split(' '))
if multcnt < 11:
arr = arr[0:multcnt-1] + ([arr[multcnt-2]] * (11-multcnt))
self.assertEqual(
[a.calcBanTime(600, i) for i in xrange(1, 11)],
arr
)
a.setBanTimeExtra('maxtime', '1d')
# change factor :
a.setBanTimeExtra('factor', '2');
self.assertEqual(
[a.calcBanTime(600, i) for i in xrange(1, 11)],
[2400, 4800, 9600, 19200, 38400, 76800, 86400, 86400, 86400, 86400]
)
# factor is float :
a.setBanTimeExtra('factor', '1.33');
self.assertEqual(
[int(a.calcBanTime(600, i)) for i in xrange(1, 11)],
[1596, 3192, 6384, 12768, 25536, 51072, 86400, 86400, 86400, 86400]
)
a.setBanTimeExtra('factor', None);
# change max time :
a.setBanTimeExtra('maxtime', '12h')
self.assertEqual(
[a.calcBanTime(600, i) for i in xrange(1, 11)],
[1200, 2400, 4800, 9600, 19200, 38400, 43200, 43200, 43200, 43200]
)
a.setBanTimeExtra('maxtime', '24h')
## test randomization - not possibe all 10 times we have random = 0:
a.setBanTimeExtra('rndtime', '5m')
self.assertTrue(
False in [1200 in [a.calcBanTime(600, 1) for i in xrange(10)] for c in xrange(10)]
)
a.setBanTimeExtra('rndtime', None)
self.assertFalse(
False in [1200 in [a.calcBanTime(600, 1) for i in xrange(10)] for c in xrange(10)]
)
# restore default:
a.setBanTimeExtra('multipliers', None)
a.setBanTimeExtra('factor', None);
a.setBanTimeExtra('maxtime', '24h')
a.setBanTimeExtra('rndtime', None)
def testMultipliers(self):
# this multipliers has the same values as default formula, we test stop growing after count 9:
self.testDefault('1 2 4 8 16 32 64 128 256')
# this multipliers has exactly the same values as default formula, test endless growing (stops by count 31 only):
self.testDefault(' '.join([str(1<<i) for i in xrange(31)]))
def testFormula(self):
a = self.__jail;
a.setBanTimeExtra('maxtime', '24h')
a.setBanTimeExtra('rndtime', None)
## use another formula:
a.setBanTimeExtra('formula', 'ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor)')
a.setBanTimeExtra('factor', '2.0 / 2.885385')
a.setBanTimeExtra('multipliers', None)
# test algorithm and max time 24 hours :
self.assertEqual(
[int(a.calcBanTime(600, i)) for i in xrange(1, 11)],
[1200, 2400, 4800, 9600, 19200, 38400, 76800, 86400, 86400, 86400]
)
# with extra large max time (30 days):
a.setBanTimeExtra('maxtime', '30d')
self.assertEqual(
[int(a.calcBanTime(600, i)) for i in xrange(1, 11)],
[1200, 2400, 4800, 9600, 19200, 38400, 76800, 153601, 307203, 614407]
)
a.setBanTimeExtra('maxtime', '24h')
# change factor :
a.setBanTimeExtra('factor', '1');
self.assertEqual(
[int(a.calcBanTime(600, i)) for i in xrange(1, 11)],
[1630, 4433, 12051, 32758, 86400, 86400, 86400, 86400, 86400, 86400]
)
a.setBanTimeExtra('factor', '2.0 / 2.885385')
# change max time :
a.setBanTimeExtra('maxtime', '12h')
self.assertEqual(
[int(a.calcBanTime(600, i)) for i in xrange(1, 11)],
[1200, 2400, 4800, 9600, 19200, 38400, 43200, 43200, 43200, 43200]
)
a.setBanTimeExtra('maxtime', '24h')
## test randomization - not possibe all 10 times we have random = 0:
a.setBanTimeExtra('rndtime', '5m')
self.assertTrue(
False in [1200 in [int(a.calcBanTime(600, 1)) for i in xrange(10)] for c in xrange(10)]
)
a.setBanTimeExtra('rndtime', None)
self.assertFalse(
False in [1200 in [int(a.calcBanTime(600, 1)) for i in xrange(10)] for c in xrange(10)]
)
# restore default:
a.setBanTimeExtra('factor', None);
a.setBanTimeExtra('multipliers', None)
a.setBanTimeExtra('factor', None);
a.setBanTimeExtra('maxtime', '24h')
a.setBanTimeExtra('rndtime', None)
class BanTimeIncrDB(LogCaptureTestCase):
def setUp(self):
"""Call before every test case."""
super(BanTimeIncrDB, self).setUp()
if Fail2BanDb is None and sys.version_info >= (2,7): # pragma: no cover
raise unittest.SkipTest(
"Unable to import fail2ban database module as sqlite is not "
"available.")
elif Fail2BanDb is None:
return
_, self.dbFilename = tempfile.mkstemp(".db", "fail2ban_")
self.db = getFail2BanDb(self.dbFilename)
self.jail = DummyJail()
self.jail.database = self.db
self.Observer = ObserverThread()
Observers.Main = self.Observer
def tearDown(self):
"""Call after every test case."""
if Fail2BanDb is None: # pragma: no cover
return
# Cleanup
self.Observer.stop()
Observers.Main = None
os.remove(self.dbFilename)
super(BanTimeIncrDB, self).tearDown()
def incrBanTime(self, ticket, banTime=None):
jail = self.jail;
if banTime is None:
banTime = ticket.getBanTime(jail.actions.getBanTime())
ticket.setBanTime(None)
incrTime = self.Observer.incrBanTime(jail, banTime, ticket)
#print("!!!!!!!!! banTime: %s, %s, incr: %s " % (banTime, ticket.getBanCount(), incrTime))
return incrTime
def testBanTimeIncr(self):
if Fail2BanDb is None: # pragma: no cover
return
jail = self.jail
self.db.addJail(jail)
# we tests with initial ban time = 10 seconds:
jail.actions.setBanTime(10)
jail.setBanTimeExtra('increment', 'true')
jail.setBanTimeExtra('multipliers', '1 2 4 8 16 32 64 128 256 512 1024 2048')
ip = "192.0.2.1"
# used as start and fromtime (like now but time independence, cause test case can run slow):
stime = int(MyTime.time())
ticket = FailTicket(ip, stime, [])
# test ticket not yet found
self.assertEqual(
[self.incrBanTime(ticket, 10) for i in xrange(3)],
[10, 10, 10]
)
# add a ticket banned
ticket.incrBanCount()
self.db.addBan(jail, ticket)
# get a ticket already banned in this jail:
self.assertEqual(
[(banCount, timeOfBan, lastBanTime) for banCount, timeOfBan, lastBanTime in self.db.getBan(ip, jail, None, False)],
[(1, stime, 10)]
)
# incr time and ban a ticket again :
ticket.setTime(stime + 15)
self.assertEqual(self.incrBanTime(ticket, 10), 20)
self.db.addBan(jail, ticket)
# get a ticket already banned in this jail:
self.assertEqual(
[(banCount, timeOfBan, lastBanTime) for banCount, timeOfBan, lastBanTime in self.db.getBan(ip, jail, None, False)],
[(2, stime + 15, 20)]
)
# get a ticket already banned in all jails:
self.assertEqual(
[(banCount, timeOfBan, lastBanTime) for banCount, timeOfBan, lastBanTime in self.db.getBan(ip, '', None, True)],
[(2, stime + 15, 20)]
)
# check other optional parameters of getBan:
self.assertEqual(
[(banCount, timeOfBan, lastBanTime) for banCount, timeOfBan, lastBanTime in self.db.getBan(ip, forbantime=stime, fromtime=stime)],
[(2, stime + 15, 20)]
)
# search currently banned and 1 day later (nothing should be found):
self.assertEqual(
self.db.getCurrentBans(forbantime=-24*60*60, fromtime=stime, correctBanTime=False),
[]
)
# search currently banned one ticket for ip:
restored_tickets = self.db.getCurrentBans(ip=ip, correctBanTime=False)
self.assertEqual(
str(restored_tickets),
('FailTicket: ip=%s time=%s bantime=20 bancount=2 #attempts=0 matches=[]' % (ip, stime + 15))
)
# search currently banned anywhere:
restored_tickets = self.db.getCurrentBans(fromtime=stime, correctBanTime=False)
self.assertEqual(
str(restored_tickets),
('[FailTicket: ip=%s time=%s bantime=20 bancount=2 #attempts=0 matches=[]]' % (ip, stime + 15))
)
# search currently banned:
restored_tickets = self.db.getCurrentBans(jail=jail, fromtime=stime, correctBanTime=False)
self.assertEqual(
str(restored_tickets),
('[FailTicket: ip=%s time=%s bantime=20 bancount=2 #attempts=0 matches=[]]' % (ip, stime + 15))
)
# increase ban multiple times:
lastBanTime = 20
for i in xrange(10):
ticket.setTime(stime + lastBanTime + 5)
banTime = self.incrBanTime(ticket, 10)
self.assertEqual(banTime, lastBanTime * 2)
self.db.addBan(jail, ticket)
lastBanTime = banTime
# increase again, but the last multiplier reached (time not increased):
ticket.setTime(stime + lastBanTime + 5)
banTime = self.incrBanTime(ticket, 10)
self.assertNotEqual(banTime, lastBanTime * 2)
self.assertEqual(banTime, lastBanTime)
self.db.addBan(jail, ticket)
lastBanTime = banTime
# add two tickets from yesterday: one unbanned (bantime already out-dated):
ticket2 = FailTicket(ip+'2', stime-24*60*60, [])
ticket2.setBanTime(12*60*60)
ticket2.incrBanCount()
self.db.addBan(jail, ticket2)
# and one from yesterday also, but still currently banned :
ticket2 = FailTicket(ip+'1', stime-24*60*60, [])
ticket2.setBanTime(36*60*60)
ticket2.incrBanCount()
self.db.addBan(jail, ticket2)
# search currently banned:
restored_tickets = self.db.getCurrentBans(fromtime=stime, correctBanTime=False)
self.assertEqual(len(restored_tickets), 2)
self.assertEqual(
str(restored_tickets[0]),
'FailTicket: ip=%s time=%s bantime=%s bancount=13 #attempts=0 matches=[]' % (ip, stime + lastBanTime + 5, lastBanTime)
)
self.assertEqual(
str(restored_tickets[1]),
'FailTicket: ip=%s time=%s bantime=%s bancount=1 #attempts=0 matches=[]' % (ip+'1', stime-24*60*60, 36*60*60)
)
# search out-dated (give another fromtime now is -18 hours):
restored_tickets = self.db.getCurrentBans(fromtime=stime-18*60*60, correctBanTime=False)
self.assertEqual(len(restored_tickets), 3)
self.assertEqual(
str(restored_tickets[2]),
'FailTicket: ip=%s time=%s bantime=%s bancount=1 #attempts=0 matches=[]' % (ip+'2', stime-24*60*60, 12*60*60)
)
# should be still banned
self.assertFalse(restored_tickets[1].isTimedOut(stime))
self.assertFalse(restored_tickets[1].isTimedOut(stime))
# the last should be timed out now
self.assertTrue(restored_tickets[2].isTimedOut(stime))
self.assertFalse(restored_tickets[2].isTimedOut(stime-18*60*60))
# test permanent, create timed out:
ticket=FailTicket(ip+'3', stime-36*60*60, [])
self.assertTrue(ticket.isTimedOut(stime, 600))
# not timed out - permanent jail:
self.assertFalse(ticket.isTimedOut(stime, -1))
# not timed out - permanent ticket:
ticket.setBanTime(-1)
self.assertFalse(ticket.isTimedOut(stime, 600))
self.assertFalse(ticket.isTimedOut(stime, -1))
# timed out - permanent jail but ticket time (not really used behavior)
ticket.setBanTime(600)
self.assertTrue(ticket.isTimedOut(stime, -1))
# get currently banned pis with permanent one:
ticket.setBanTime(-1)
ticket.incrBanCount()
self.db.addBan(jail, ticket)
restored_tickets = self.db.getCurrentBans(fromtime=stime, correctBanTime=False)
self.assertEqual(len(restored_tickets), 3)
self.assertEqual(
str(restored_tickets[2]),
'FailTicket: ip=%s time=%s bantime=%s bancount=1 #attempts=0 matches=[]' % (ip+'3', stime-36*60*60, -1)
)
# purge (nothing should be changed):
self.db.purge()
restored_tickets = self.db.getCurrentBans(fromtime=stime, correctBanTime=False)
self.assertEqual(len(restored_tickets), 3)
# set short time and purge again:
ticket.setBanTime(600)
ticket.incrBanCount()
self.db.addBan(jail, ticket)
self.db.purge()
# this old ticket should be removed now:
restored_tickets = self.db.getCurrentBans(fromtime=stime, correctBanTime=False)
self.assertEqual(len(restored_tickets), 2)
self.assertEqual(restored_tickets[0].getIP(), ip)
# purge remove 1st ip
self.db._purgeAge = -48*60*60
self.db.purge()
restored_tickets = self.db.getCurrentBans(fromtime=stime, correctBanTime=False)
self.assertEqual(len(restored_tickets), 1)
self.assertEqual(restored_tickets[0].getIP(), ip+'1')
# this should purge all bans, bips and logs - nothing should be found now
self.db._purgeAge = -240*60*60
self.db.purge()
restored_tickets = self.db.getCurrentBans(fromtime=stime, correctBanTime=False)
self.assertEqual(restored_tickets, [])
# two separate jails :
jail1 = DummyJail(backend='polling')
jail1.filter.ignoreSelf = False
jail1.setBanTimeExtra('increment', 'true')
jail1.database = self.db
self.db.addJail(jail1)
jail2 = DummyJail(name='DummyJail-2', backend='polling')
jail2.filter.ignoreSelf = False
jail2.database = self.db
self.db.addJail(jail2)
ticket1 = FailTicket(ip, stime, [])
ticket1.setBanTime(6000)
ticket1.incrBanCount()
self.db.addBan(jail1, ticket1)
ticket2 = FailTicket(ip, stime-6000, [])
ticket2.setBanTime(12000)
ticket2.setBanCount(1)
ticket2.incrBanCount()
self.db.addBan(jail2, ticket2)
restored_tickets = self.db.getCurrentBans(jail=jail1, fromtime=stime, correctBanTime=False)
self.assertEqual(len(restored_tickets), 1)
self.assertEqual(
str(restored_tickets[0]),
'FailTicket: ip=%s time=%s bantime=%s bancount=1 #attempts=0 matches=[]' % (ip, stime, 6000)
)
restored_tickets = self.db.getCurrentBans(jail=jail2, fromtime=stime, correctBanTime=False)
self.assertEqual(len(restored_tickets), 1)
self.assertEqual(
str(restored_tickets[0]),
'FailTicket: ip=%s time=%s bantime=%s bancount=2 #attempts=0 matches=[]' % (ip, stime-6000, 12000)
)
# get last ban values for this ip separately for each jail:
for row in self.db.getBan(ip, jail1):
self.assertEqual(row, (1, stime, 6000))
break
for row in self.db.getBan(ip, jail2):
self.assertEqual(row, (2, stime-6000, 12000))
break
# get max values for this ip (over all jails):
for row in self.db.getBan(ip, overalljails=True):
self.assertEqual(row, (3, stime, 18000))
break
# test restoring bans from database:
jail1.restoreCurrentBans(correctBanTime=False)
ticket = jail1.getFailTicket()
self.assertTrue(ticket.restored)
self.assertEqual(str(ticket),
'FailTicket: ip=%s time=%s bantime=%s bancount=1 #attempts=0 matches=[]' % (ip, stime, 6000)
)
# jail2 does not restore any bans (because all ban tickets should be already expired: stime-6000):
jail2.restoreCurrentBans(correctBanTime=False)
self.assertEqual(jail2.getFailTicket(), False)
# test again, but now normally (with maximum ban-time of restored ticket = allowed 10m = 600):
jail1.setBanTimeExtra('maxtime', '10m')
jail1.restoreCurrentBans()
ticket = jail1.getFailTicket()
self.assertTrue(ticket.restored)
# ticket restored, but it has new time = 600 (current ban-time of jail, as maximum):
self.assertEqual(str(ticket),
'FailTicket: ip=%s time=%s bantime=%s bancount=1 #attempts=0 matches=[]' % (ip, stime, 600)
)
# jail2 does not restore any bans (because all ban tickets should be already expired: stime-6000):
jail2.restoreCurrentBans()
self.assertEqual(jail2.getFailTicket(), False)
def testObserver(self):
if Fail2BanDb is None: # pragma: no cover
return
jail = self.jail
self.db.addJail(jail)
# we tests with initial ban time = 10 seconds:
jail.actions.setBanTime(10)
jail.setBanTimeExtra('increment', 'true')
# observer / database features:
obs = Observers.Main
obs.start()
obs.db_set(self.db)
# wait for start ready
obs.add('nop')
obs.wait_empty(5)
# purge database right now, but using timer, to test it also:
self.db._purgeAge = -240*60*60
obs.add_named_timer('DB_PURGE', 0.001, 'db_purge')
self.assertLogged("Purge database event occurred", wait=True); # wait for purge timer
# wait for timer ready
obs.wait_idle(0.025)
# wait for ready
obs.add('nop')
obs.wait_empty(5)
stime = int(MyTime.time())
# completelly empty ?
tickets = self.db.getBans()
self.assertEqual(tickets, [])
# add failure:
ip = "192.0.2.1"
ticket = FailTicket(ip, stime-120, [])
failManager = FailManager()
failManager.setMaxRetry(3)
for i in xrange(3):
failManager.addFailure(ticket)
obs.add('failureFound', failManager, jail, ticket)
obs.wait_empty(5)
self.assertEqual(ticket.getBanCount(), 0)
# check still not ban :
self.assertTrue(not jail.getFailTicket())
# add manually 4th times banned (added to bips - make ip bad):
ticket.setBanCount(4)
self.db.addBan(self.jail, ticket)
restored_tickets = self.db.getCurrentBans(jail=jail, fromtime=stime-120, correctBanTime=False)
self.assertEqual(len(restored_tickets), 1)
# check again, new ticket, new failmanager:
ticket = FailTicket(ip, stime, [])
failManager = FailManager()
failManager.setMaxRetry(3)
# add once only - but bad - should be banned:
failManager.addFailure(ticket)
obs.add('failureFound', failManager, self.jail, ticket)
obs.wait_empty(5)
# wait until ticket transfered from failmanager into jail:
ticket2 = Utils.wait_for(jail.getFailTicket, 10)
# check ticket and failure count:
self.assertTrue(ticket2)
self.assertEqual(ticket2.getRetry(), failManager.getMaxRetry())
# wrap FailTicket to BanTicket:
failticket2 = ticket2
ticket2 = BanTicket.wrap(failticket2)
self.assertEqual(ticket2, failticket2)
# add this ticket to ban (use observer only without ban manager):
obs.add('banFound', ticket2, jail, 10)
obs.wait_empty(5)
# increased?
self.assertEqual(ticket2.getBanTime(), 160)
self.assertEqual(ticket2.getBanCount(), 5)
# check prolonged in database also :
restored_tickets = self.db.getCurrentBans(jail=jail, fromtime=stime, correctBanTime=False)
self.assertEqual(len(restored_tickets), 1)
self.assertEqual(restored_tickets[0].getBanTime(), 160)
self.assertEqual(restored_tickets[0].getBanCount(), 5)
# now using jail/actions:
ticket = FailTicket(ip, stime-60, ['test-expired-ban-time'])
jail.putFailTicket(ticket)
self.assertFalse(jail.actions.checkBan())
ticket = FailTicket(ip, MyTime.time(), ['test-actions'])
jail.putFailTicket(ticket)
self.assertTrue(jail.actions.checkBan())
obs.wait_empty(5)
restored_tickets = self.db.getCurrentBans(jail=jail, fromtime=stime, correctBanTime=False)
self.assertEqual(len(restored_tickets), 1)
self.assertEqual(restored_tickets[0].getBanTime(), 320)
self.assertEqual(restored_tickets[0].getBanCount(), 6)
# and permanent:
ticket = FailTicket(ip+'1', MyTime.time(), ['test-permanent'])
ticket.setBanTime(-1)
jail.putFailTicket(ticket)
self.assertTrue(jail.actions.checkBan())
obs.wait_empty(5)
ticket = FailTicket(ip+'1', MyTime.time(), ['test-permanent'])
ticket.setBanTime(600)
jail.putFailTicket(ticket)
self.assertFalse(jail.actions.checkBan())
obs.wait_empty(5)
restored_tickets = self.db.getCurrentBans(jail=jail, fromtime=stime, correctBanTime=False)
self.assertEqual(len(restored_tickets), 2)
self.assertEqual(restored_tickets[1].getBanTime(), -1)
self.assertEqual(restored_tickets[1].getBanCount(), 1)
# stop observer
obs.stop()
class ObserverTest(LogCaptureTestCase):
def setUp(self):
"""Call before every test case."""
super(ObserverTest, self).setUp()
def tearDown(self):
"""Call after every test case."""
super(ObserverTest, self).tearDown()
def testObserverBanTimeIncr(self):
obs = ObserverThread()
obs.start()
# wait for idle
obs.wait_idle(1)
# observer will replace test set:
o = set(['test'])
obs.add('call', o.clear)
obs.add('call', o.add, 'test2')
# wait for observer ready:
obs.wait_empty(1)
self.assertFalse(obs.is_full)
self.assertEqual(o, set(['test2']))
# observer makes pause
obs.paused = True
# observer will replace test set, but first after pause ends:
obs.add('call', o.clear)
obs.add('call', o.add, 'test3')
obs.wait_empty(10 * Utils.DEFAULT_SLEEP_TIME)
self.assertTrue(obs.is_full)
self.assertEqual(o, set(['test2']))
obs.paused = False
# wait running:
obs.wait_empty(1)
self.assertEqual(o, set(['test3']))
self.assertTrue(obs.isActive())
self.assertTrue(obs.isAlive())
obs.stop()
obs = None
class _BadObserver(ObserverThread):
def run(self):
raise RuntimeError('run bad thread exception')
def testObserverBadRun(self):
obs = ObserverTest._BadObserver()
# don't wait for empty by stop
obs.wait_empty = lambda v:()
# save previous hook, prevent write stderr and check hereafter __excepthook__ was executed
prev_exchook = sys.__excepthook__
x = []
sys.__excepthook__ = lambda *args: x.append(args)
try:
obs.start()
obs.stop()
obs.join()
self.assertTrue( Utils.wait_for( lambda: len(x) and self._is_logged("Unhandled exception"), 3) )
finally:
sys.__excepthook__ = prev_exchook
self.assertLogged("Unhandled exception")
self.assertEqual(len(x), 1)
self.assertEqual(x[0][0], RuntimeError)
self.assertEqual(str(x[0][1]), 'run bad thread exception')

View File

@ -296,7 +296,7 @@ def testSampleRegexsFactory(name, basedir):
regexsUsedRe.add(regexList[failregex]) regexsUsedRe.add(regexList[failregex])
except AssertionError as e: # pragma: no cover except AssertionError as e: # pragma: no cover
import pprint import pprint
raise AssertionError("%s: %s on: %s:%i, line:\n %sregex (%s):\n %s\n" raise AssertionError("%s: %s on: %s:%i, line:\n %s\nregex (%s):\n %s\n"
"faildata: %s\nfail: %s" % ( "faildata: %s\nfail: %s" % (
fltName, e, logFile.filename(), logFile.filelineno(), fltName, e, logFile.filename(), logFile.filelineno(),
line, failregex, regexList[failregex] if failregex != -1 else None, line, failregex, regexList[failregex] if failregex != -1 else None,

View File

@ -41,7 +41,7 @@ from ..server.jailthread import JailThread
from ..server.ticket import BanTicket from ..server.ticket import BanTicket
from ..server.utils import Utils from ..server.utils import Utils
from .dummyjail import DummyJail from .dummyjail import DummyJail
from .utils import LogCaptureTestCase, with_alt_time, MyTime from .utils import LogCaptureTestCase
from ..helpers import getLogger, extractOptions, PREFER_ENC from ..helpers import getLogger, extractOptions, PREFER_ENC
from .. import version from .. import version
@ -382,50 +382,6 @@ class Transmitter(TransmitterBase):
self.assertLogged("Ban 192.0.2.2", wait=True) self.assertLogged("Ban 192.0.2.2", wait=True)
self.assertNotLogged("Ban 192.0.2.1") self.assertNotLogged("Ban 192.0.2.1")
@with_alt_time
def testJailBanList(self):
jail = "TestJailBanList"
self.server.addJail(jail, FAST_BACKEND)
self.server.startJail(jail)
# Helper to process set banip/set unbanip commands and compare the list of
# banned IP addresses with outList.
def _getBanListTest(jail, banip=None, unbanip=None, args=(), outList=[]):
# Ban IP address
if banip is not None:
self.assertEqual(
self.transm.proceed(["set", jail, "banip", banip]),
(0, 1))
self.assertLogged("Ban %s" % banip, wait=True) # Give chance to ban
# Unban IP address
if unbanip is not None:
self.assertEqual(
self.transm.proceed(["set", jail, "unbanip", unbanip]),
(0, 1))
self.assertLogged("Unban %s" % unbanip, wait=True) # Give chance to unban
# Compare the list of banned IP addresses with outList
self.assertSortedEqual(
self.transm.proceed(["get", jail, "banip"]+list(args)),
(0, outList), nestedOnly=False)
MyTime.setTime(MyTime.time() + 1)
_getBanListTest(jail,
outList=[])
_getBanListTest(jail, banip="127.0.0.1", args=('--with-time',),
outList=["127.0.0.1 \t2005-08-14 12:00:01 + 600 = 2005-08-14 12:10:01"])
_getBanListTest(jail, banip="192.168.0.1", args=('--with-time',),
outList=[
"127.0.0.1 \t2005-08-14 12:00:01 + 600 = 2005-08-14 12:10:01",
"192.168.0.1 \t2005-08-14 12:00:02 + 600 = 2005-08-14 12:10:02"])
_getBanListTest(jail, banip="192.168.1.10",
outList=["127.0.0.1", "192.168.0.1", "192.168.1.10"])
_getBanListTest(jail, unbanip="127.0.0.1",
outList=["192.168.0.1", "192.168.1.10"])
_getBanListTest(jail, unbanip="192.168.1.10",
outList=["192.168.0.1"])
_getBanListTest(jail, unbanip="192.168.0.1",
outList=[])
def testJailMaxMatches(self): def testJailMaxMatches(self):
self.setGetTest("maxmatches", "5", 5, jail=self.jailName) self.setGetTest("maxmatches", "5", 5, jail=self.jailName)
self.setGetTest("maxmatches", "2", 2, jail=self.jailName) self.setGetTest("maxmatches", "2", 2, jail=self.jailName)
@ -1037,15 +993,6 @@ class TransmitterLogging(TransmitterBase):
self.assertEqual(self.transm.proceed(["set", "logtarget", "STDERR"]), (0, "STDERR")) self.assertEqual(self.transm.proceed(["set", "logtarget", "STDERR"]), (0, "STDERR"))
self.assertEqual(self.transm.proceed(["flushlogs"]), (0, "flushed")) self.assertEqual(self.transm.proceed(["flushlogs"]), (0, "flushed"))
def testBanTimeIncr(self):
self.setGetTest("bantime.increment", "true", True, jail=self.jailName)
self.setGetTest("bantime.rndtime", "30min", 30*60, jail=self.jailName)
self.setGetTest("bantime.maxtime", "1000 days", 1000*24*60*60, jail=self.jailName)
self.setGetTest("bantime.factor", "2", "2", jail=self.jailName)
self.setGetTest("bantime.formula", "ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor)", jail=self.jailName)
self.setGetTest("bantime.multipliers", "1 5 30 60 300 720 1440 2880", "1 5 30 60 300 720 1440 2880", jail=self.jailName)
self.setGetTest("bantime.overalljails", "true", "true", jail=self.jailName)
class JailTests(unittest.TestCase): class JailTests(unittest.TestCase):
@ -1213,20 +1160,8 @@ class ServerConfigReaderTests(LogCaptureTestCase):
logSys.debug(l) logSys.debug(l)
return True return True
def _testActionInfos(self):
if not hasattr(self, '__aInfos'):
dmyjail = DummyJail()
self.__aInfos = {}
for t, ip in (('ipv4', '192.0.2.1'), ('ipv6', '2001:DB8::')):
ticket = BanTicket(ip)
ticket.setBanTime(600)
self.__aInfos[t] = _actions.Actions.ActionInfo(ticket, dmyjail)
return self.__aInfos
def _testExecActions(self, server): def _testExecActions(self, server):
jails = server._Server__jails jails = server._Server__jails
aInfos = self._testActionInfos()
for jail in jails: for jail in jails:
# print(jail, jails[jail]) # print(jail, jails[jail])
for a in jails[jail].actions: for a in jails[jail].actions:
@ -1243,16 +1178,16 @@ class ServerConfigReaderTests(LogCaptureTestCase):
action.start() action.start()
# test ban ip4 : # test ban ip4 :
logSys.debug('# === ban-ipv4 ==='); self.pruneLog() logSys.debug('# === ban-ipv4 ==='); self.pruneLog()
action.ban(aInfos['ipv4']) action.ban({'ip': IPAddr('192.0.2.1'), 'family': 'inet4'})
# test unban ip4 : # test unban ip4 :
logSys.debug('# === unban ipv4 ==='); self.pruneLog() logSys.debug('# === unban ipv4 ==='); self.pruneLog()
action.unban(aInfos['ipv4']) action.unban({'ip': IPAddr('192.0.2.1'), 'family': 'inet4'})
# test ban ip6 : # test ban ip6 :
logSys.debug('# === ban ipv6 ==='); self.pruneLog() logSys.debug('# === ban ipv6 ==='); self.pruneLog()
action.ban(aInfos['ipv6']) action.ban({'ip': IPAddr('2001:DB8::'), 'family': 'inet6'})
# test unban ip6 : # test unban ip6 :
logSys.debug('# === unban ipv6 ==='); self.pruneLog() logSys.debug('# === unban ipv6 ==='); self.pruneLog()
action.unban(aInfos['ipv6']) action.unban({'ip': IPAddr('2001:DB8::'), 'family': 'inet6'})
# test stop : # test stop :
logSys.debug('# === stop ==='); self.pruneLog() logSys.debug('# === stop ==='); self.pruneLog()
action.stop() action.stop()
@ -1448,10 +1383,9 @@ class ServerConfigReaderTests(LogCaptureTestCase):
), ),
}), }),
# dummy -- # dummy --
('j-dummy', '''dummy[name=%(__name__)s, init="=='<family>/<ip>'==bt:<bantime>==bc:<bancount>==", target="/tmp/fail2ban.dummy"]''', { ('j-dummy', 'dummy[name=%(__name__)s, init="==", target="/tmp/fail2ban.dummy"]', {
'ip4': ('family: inet4',), 'ip6': ('family: inet6',), 'ip4': ('family: inet4',), 'ip6': ('family: inet6',),
'start': ( 'start': (
'''`printf %b "=='/'==bt:600==bc:0==\\n"''', ## empty family (independent in this action, same for both), no ip on start, initial bantime and bancount
'`echo "[j-dummy] dummy /tmp/fail2ban.dummy -- started"`', '`echo "[j-dummy] dummy /tmp/fail2ban.dummy -- started"`',
), ),
'flush': ( 'flush': (
@ -2043,7 +1977,10 @@ class ServerConfigReaderTests(LogCaptureTestCase):
jails = server._Server__jails jails = server._Server__jails
aInfos = self._testActionInfos() tickets = {
'ip4': BanTicket('192.0.2.1'),
'ip6': BanTicket('2001:DB8::'),
}
for jail, act, tests in testJailsActions: for jail, act, tests in testJailsActions:
# print(jail, jails[jail]) # print(jail, jails[jail])
for a in jails[jail].actions: for a in jails[jail].actions:
@ -2061,28 +1998,32 @@ class ServerConfigReaderTests(LogCaptureTestCase):
self.assertLogged(*tests['start'], all=True) self.assertLogged(*tests['start'], all=True)
elif tests.get('ip4-start') and tests.get('ip6-start'): elif tests.get('ip4-start') and tests.get('ip6-start'):
self.assertNotLogged(*tests['ip4-start']+tests['ip6-start'], all=True) self.assertNotLogged(*tests['ip4-start']+tests['ip6-start'], all=True)
ainfo = {
'ip4': _actions.Actions.ActionInfo(tickets['ip4'], jails[jail]),
'ip6': _actions.Actions.ActionInfo(tickets['ip6'], jails[jail]),
}
# test ban ip4 : # test ban ip4 :
self.pruneLog('# === ban-ipv4 ===') self.pruneLog('# === ban-ipv4 ===')
action.ban(aInfos['ipv4']) action.ban(ainfo['ip4'])
if tests.get('ip4-start'): self.assertLogged(*tests.get('*-start', ())+tests['ip4-start'], all=True) if tests.get('ip4-start'): self.assertLogged(*tests.get('*-start', ())+tests['ip4-start'], all=True)
if tests.get('ip6-start'): self.assertNotLogged(*tests['ip6-start'], all=True) if tests.get('ip6-start'): self.assertNotLogged(*tests['ip6-start'], all=True)
self.assertLogged(*tests.get('ip4-check',())+tests['ip4-ban'], all=True) self.assertLogged(*tests.get('ip4-check',())+tests['ip4-ban'], all=True)
self.assertNotLogged(*tests['ip6'], all=True) self.assertNotLogged(*tests['ip6'], all=True)
# test unban ip4 : # test unban ip4 :
self.pruneLog('# === unban ipv4 ===') self.pruneLog('# === unban ipv4 ===')
action.unban(aInfos['ipv4']) action.unban(ainfo['ip4'])
self.assertLogged(*tests.get('ip4-check',())+tests['ip4-unban'], all=True) self.assertLogged(*tests.get('ip4-check',())+tests['ip4-unban'], all=True)
self.assertNotLogged(*tests['ip6'], all=True) self.assertNotLogged(*tests['ip6'], all=True)
# test ban ip6 : # test ban ip6 :
self.pruneLog('# === ban ipv6 ===') self.pruneLog('# === ban ipv6 ===')
action.ban(aInfos['ipv6']) action.ban(ainfo['ip6'])
if tests.get('ip6-start'): self.assertLogged(*tests.get('*-start', ())+tests['ip6-start'], all=True) if tests.get('ip6-start'): self.assertLogged(*tests.get('*-start', ())+tests['ip6-start'], all=True)
if tests.get('ip4-start'): self.assertNotLogged(*tests['ip4-start'], all=True) if tests.get('ip4-start'): self.assertNotLogged(*tests['ip4-start'], all=True)
self.assertLogged(*tests.get('ip6-check',())+tests['ip6-ban'], all=True) self.assertLogged(*tests.get('ip6-check',())+tests['ip6-ban'], all=True)
self.assertNotLogged(*tests['ip4'], all=True) self.assertNotLogged(*tests['ip4'], all=True)
# test unban ip6 : # test unban ip6 :
self.pruneLog('# === unban ipv6 ===') self.pruneLog('# === unban ipv6 ===')
action.unban(aInfos['ipv6']) action.unban(ainfo['ip6'])
self.assertLogged(*tests.get('ip6-check',())+tests['ip6-unban'], all=True) self.assertLogged(*tests.get('ip6-check',())+tests['ip6-unban'], all=True)
self.assertNotLogged(*tests['ip4'], all=True) self.assertNotLogged(*tests['ip4'], all=True)
# test flush for actions should supported this: # test flush for actions should supported this:

View File

@ -54,7 +54,6 @@ class Socket(LogCaptureTestCase):
def setUp(self): def setUp(self):
"""Call before every test case.""" """Call before every test case."""
LogCaptureTestCase.setUp(self)
super(Socket, self).setUp() super(Socket, self).setUp()
self.server = AsyncServer(self) self.server = AsyncServer(self)
sock_fd, sock_name = tempfile.mkstemp('fail2ban.sock', 'f2b-socket') sock_fd, sock_name = tempfile.mkstemp('fail2ban.sock', 'f2b-socket')

View File

@ -389,7 +389,6 @@ def gatherTests(regexps=None, opts=None):
from . import sockettestcase from . import sockettestcase
from . import misctestcase from . import misctestcase
from . import databasetestcase from . import databasetestcase
from . import observertestcase
from . import samplestestcase from . import samplestestcase
from . import fail2banclienttestcase from . import fail2banclienttestcase
from . import fail2banregextestcase from . import fail2banregextestcase
@ -420,6 +419,7 @@ def gatherTests(regexps=None, opts=None):
tests = FilteredTestSuite() tests = FilteredTestSuite()
# Server # Server
#tests.addTest(unittest.makeSuite(servertestcase.StartStop))
tests.addTest(unittest.makeSuite(servertestcase.Transmitter)) tests.addTest(unittest.makeSuite(servertestcase.Transmitter))
tests.addTest(unittest.makeSuite(servertestcase.JailTests)) tests.addTest(unittest.makeSuite(servertestcase.JailTests))
tests.addTest(unittest.makeSuite(servertestcase.RegexTests)) tests.addTest(unittest.makeSuite(servertestcase.RegexTests))
@ -459,10 +459,6 @@ def gatherTests(regexps=None, opts=None):
tests.addTest(unittest.makeSuite(misctestcase.MyTimeTest)) tests.addTest(unittest.makeSuite(misctestcase.MyTimeTest))
# Database # Database
tests.addTest(unittest.makeSuite(databasetestcase.DatabaseTest)) tests.addTest(unittest.makeSuite(databasetestcase.DatabaseTest))
# Observer
tests.addTest(unittest.makeSuite(observertestcase.ObserverTest))
tests.addTest(unittest.makeSuite(observertestcase.BanTimeIncr))
tests.addTest(unittest.makeSuite(observertestcase.BanTimeIncrDB))
# Filter # Filter
tests.addTest(unittest.makeSuite(filtertestcase.IgnoreIP)) tests.addTest(unittest.makeSuite(filtertestcase.IgnoreIP))
@ -769,11 +765,10 @@ class LogCaptureTestCase(unittest.TestCase):
# Let's log everything into a string # Let's log everything into a string
self._log = LogCaptureTestCase._MemHandler(unittest.F2B.log_lazy) self._log = LogCaptureTestCase._MemHandler(unittest.F2B.log_lazy)
logSys.handlers = [self._log] logSys.handlers = [self._log]
# lowest log level to capture messages (expected in tests) is Lev.9 if self._old_level <= logging.DEBUG:
if self._old_level <= logging.DEBUG: # pragma: no cover
logSys.handlers += self._old_handlers logSys.handlers += self._old_handlers
if self._old_level > logging.DEBUG-1: else: # lowest log level to capture messages
logSys.setLevel(logging.DEBUG-1) logSys.setLevel(logging.DEBUG)
super(LogCaptureTestCase, self).setUp() super(LogCaptureTestCase, self).setUp()
def tearDown(self): def tearDown(self):

View File

@ -24,7 +24,7 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko, Steven Hiscocks, Daniel Black"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2005-2016 Yaroslav Halchenko, 2013-2014 Steven Hiscocks, Daniel Black" __copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2005-2016 Yaroslav Halchenko, 2013-2014 Steven Hiscocks, Daniel Black"
__license__ = "GPL-v2+" __license__ = "GPL-v2+"
version = "0.11.2-dev" version = "0.10.6"
def normVersion(): def normVersion():
""" Returns fail2ban version in normalized machine-readable format""" """ Returns fail2ban version in normalized machine-readable format"""

View File

@ -1,12 +1,12 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4.
.TH FAIL2BAN-CLIENT "1" "February 2020" "fail2ban-client v0.11.2-dev" "User Commands" .TH FAIL2BAN-CLIENT "1" "November 2020" "fail2ban-client v0.10.6" "User Commands"
.SH NAME .SH NAME
fail2ban-client \- configure and control the server fail2ban-client \- configure and control the server
.SH SYNOPSIS .SH SYNOPSIS
.B fail2ban-client .B fail2ban-client
[\fI\,OPTIONS\/\fR] \fI\,<COMMAND>\/\fR [\fI\,OPTIONS\/\fR] \fI\,<COMMAND>\/\fR
.SH DESCRIPTION .SH DESCRIPTION
Fail2Ban v0.11.2\-dev reads log file that contains password failure report Fail2Ban v0.10.6 reads log file that contains password failure report
and bans the corresponding IP addresses using firewall rules. and bans the corresponding IP addresses using firewall rules.
.SH OPTIONS .SH OPTIONS
.TP .TP
@ -421,15 +421,6 @@ date/times for <JAIL>
\fBget <JAIL> usedns\fR \fBget <JAIL> usedns\fR
gets the usedns setting for <JAIL> gets the usedns setting for <JAIL>
.TP .TP
\fBget <JAIL> banip [<SEP>|\-\-with\-time]\fR
gets the list of of banned IP
addresses for <JAIL>. Optionally
the separator character ('<SEP>',
default is space) or the option
\&'\-\-with\-time' (printing the times
of ban) may be specified. The IPs
are ordered by end of ban.
.TP
\fBget <JAIL> maxretry\fR \fBget <JAIL> maxretry\fR
gets the number of failures gets the number of failures
allowed for <JAIL> allowed for <JAIL>

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4.
.TH FAIL2BAN-PYTHON "1" "February 2020" "fail2ban-python 0.11.2-dev" "User Commands" .TH FAIL2BAN-PYTHON "1" "November 2020" "fail2ban-python 0.10.6" "User Commands"
.SH NAME .SH NAME
fail2ban-python \- a helper for Fail2Ban to assure that the same Python is used fail2ban-python \- a helper for Fail2Ban to assure that the same Python is used
.SH DESCRIPTION .SH DESCRIPTION

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4.
.TH FAIL2BAN-REGEX "1" "February 2020" "fail2ban-regex 0.11.2-dev" "User Commands" .TH FAIL2BAN-REGEX "1" "November 2020" "fail2ban-regex 0.10.6" "User Commands"
.SH NAME .SH NAME
fail2ban-regex \- test Fail2ban "failregex" option fail2ban-regex \- test Fail2ban "failregex" option
.SH SYNOPSIS .SH SYNOPSIS

View File

@ -1,12 +1,12 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4.
.TH FAIL2BAN-SERVER "1" "February 2020" "fail2ban-server v0.11.2-dev" "User Commands" .TH FAIL2BAN-SERVER "1" "November 2020" "fail2ban-server v0.10.6" "User Commands"
.SH NAME .SH NAME
fail2ban-server \- start the server fail2ban-server \- start the server
.SH SYNOPSIS .SH SYNOPSIS
.B fail2ban-server .B fail2ban-server
[\fI\,OPTIONS\/\fR] [\fI\,OPTIONS\/\fR]
.SH DESCRIPTION .SH DESCRIPTION
Fail2Ban v0.11.2\-dev reads log file that contains password failure report Fail2Ban v0.10.6 reads log file that contains password failure report
and bans the corresponding IP addresses using firewall rules. and bans the corresponding IP addresses using firewall rules.
.SH OPTIONS .SH OPTIONS
.TP .TP

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4.
.TH FAIL2BAN-TESTCASES "1" "February 2020" "fail2ban-testcases 0.11.2-dev" "User Commands" .TH FAIL2BAN-TESTCASES "1" "November 2020" "fail2ban-testcases 0.10.6" "User Commands"
.SH NAME .SH NAME
fail2ban-testcases \- run Fail2Ban unit-tests fail2ban-testcases \- run Fail2Ban unit-tests
.SH SYNOPSIS .SH SYNOPSIS

View File

@ -276,6 +276,9 @@ It defaults to "auto" which will try "pyinotify", "gamin", "systemd" before "pol
.B usedns .B usedns
use DNS to resolve HOST names that appear in the logs. By default it is "warn" which will resolve hostnames to IPs however it will also log a warning. If you are using DNS here you could be blocking the wrong IPs due to the asymmetric nature of reverse DNS (that the application used to write the domain name to log) compared to forward DNS that fail2ban uses to resolve this back to an IP (but not necessarily the same one). Ideally you should configure your applications to log a real IP. This can be set to "yes" to prevent warnings in the log or "no" to disable DNS resolution altogether (thus ignoring entries where hostname, not an IP is logged).. use DNS to resolve HOST names that appear in the logs. By default it is "warn" which will resolve hostnames to IPs however it will also log a warning. If you are using DNS here you could be blocking the wrong IPs due to the asymmetric nature of reverse DNS (that the application used to write the domain name to log) compared to forward DNS that fail2ban uses to resolve this back to an IP (but not necessarily the same one). Ideally you should configure your applications to log a real IP. This can be set to "yes" to prevent warnings in the log or "no" to disable DNS resolution altogether (thus ignoring entries where hostname, not an IP is logged)..
.TP .TP
.B prefregex
regex (Python \fBreg\fRular \fBex\fRpression) to parse a common part containing in every message (see \fBprefregex\fR in section FILTER FILES for details).
.TP
.B failregex .B failregex
regex (Python \fBreg\fRular \fBex\fRpression) to be added to the filter's failregexes (see \fBfailregex\fR in section FILTER FILES for details). If this is useful for others using your application please share you regular expression with the fail2ban developers by reporting an issue (see REPORTING BUGS below). regex (Python \fBreg\fRular \fBex\fRpression) to be added to the filter's failregexes (see \fBfailregex\fR in section FILTER FILES for details). If this is useful for others using your application please share you regular expression with the fail2ban developers by reporting an issue (see REPORTING BUGS below).
.TP .TP
@ -432,7 +435,36 @@ These are used to identify failed authentication attempts in log files and to ex
Like action files, filter files are ini files. The main section is the [Definition] section. Like action files, filter files are ini files. The main section is the [Definition] section.
There are two filter definitions used in the [Definition] section: There are several standard filter definitions used in the [Definition] section:
.TP
.B prefregex
is the regex (\fBreg\fRular \fBex\fRpression) to parse a common part containing in every message, which is applied after \fBdatepattern\fR found a match, before the search for any \fBfailregex\fR or \fBignoreregex\fR would start.
.br
If this regex doesn't match the process is starting immediately with next message and search for any \fBfailregex\fR does not occur.
.br
If \fBprefregex\fR contains \fI<F-CONTENT>...</F-CONTENT>\fR, the part of message enclosed between this tags will be extracted and herafter used as whole message for search with \fBfailregex\fR or \fBignoreregex\fR.
.IP
For example:
.nf
prefregex = ^%(__prefix_line)s (?:ERROR|FAILURE) <F-CONTENT>.+</F-CONTENT>$
failregex = ^user not found
^authentication failed
^unknown authentication method
.fi
.IP
You can use \fBprefregex\fR in order to:
.RS
.IP
- specify 1 common regex to match some common part present in every messages (do avoid unneeded match in every \fBfailregex\fR if you have more as one);
.IP
- to cut some interesting part of message only (to simplify \fBfailregex\fR) enclosed between tags \fI<F-CONTENT>\fI and \fI</F-CONTENT>\fR;
.IP
- to gather some failure identifier (e. g. some prefix matched by \fI<F-MLFID>...<F-MLFID/>\fR tag) to identify several messages belonging to same session, where a connect message containing IP followed by failure message(s) that are not contain IP;
this provides a new multi-line parsing method as replacement for old (slow an ugly) multi-line parsing using buffering window (\fImaxlines\fR > 1 and \fI<SKIPLINES>\fR);
.IP
- to ignore some wrong, too long or even unneeded messages (a.k.a. parasite log traffic) which can be also present in journal, before \fBfailregex\fR search would take place.
.RE
.TP .TP
.B failregex .B failregex
is the regex (\fBreg\fRular \fBex\fRpression) that will match failed attempts. The standard replacement tags can be used as part of the regex: is the regex (\fBreg\fRular \fBex\fRpression) that will match failed attempts. The standard replacement tags can be used as part of the regex:
@ -451,17 +483,18 @@ is the regex (\fBreg\fRular \fBex\fRpression) that will match failed attempts. T
\fI<CIDR>\fR - helper regex to match CIDR (simple integer form of net-mask). \fI<CIDR>\fR - helper regex to match CIDR (simple integer form of net-mask).
.IP .IP
\fI<SUBNET>\fR - regex to match sub-net adresses (in form of IP/CIDR, also single IP is matched, so part /CIDR is optional). \fI<SUBNET>\fR - regex to match sub-net adresses (in form of IP/CIDR, also single IP is matched, so part /CIDR is optional).
.PP
\fBNOTE:\fR the \fBfailregex\fR will be applied to the remaining part of message after \fBprefregex\fR processing (if specified), which in turn takes place after \fBdatepattern\fR processing (whereby the string of timestamp matching the best pattern, cut out from the message).
.PP
For multiline regexs (parsing with \fImaxlines\fR greater that 1) the tag \fI<SKIPLINES>\fR can be used to separate lines. This allows lines between the matched lines to continue to be searched for other failures. The tag can be used multiple times.
.br
This is an obsolete handling and if the lines contain some common identifier, better would be to use new handling (with tags \fI<F-MLFID>...<F-MLFID/>\fR).
.RE .RE
.TP
For multiline regexs the tag \fI<SKIPLINES>\fR should be used to separate lines. This allows lines between the matched lines to continue to be searched for other failures. The tag can be used multiple times.
.TP .TP
.B ignoreregex .B ignoreregex
is the regex to identify log entries that should be ignored by Fail2Ban, even if they match failregex. is the regex to identify log entries that should be ignored by Fail2Ban, even if they match failregex.
.PP
Similar to actions, filters have an [Init] section which can be overridden in \fIjail.conf/jail.local\fR. Besides the filter-specific settings, the filter [Init] section can be used to set following standard options:
.TP .TP
\fBmaxlines\fR \fBmaxlines\fR
specifies the maximum number of lines to buffer to match multi-line regexs. For some log formats this will not required to be changed. Other logs may require to increase this value if a particular log file is frequently written to. specifies the maximum number of lines to buffer to match multi-line regexs. For some log formats this will not required to be changed. Other logs may require to increase this value if a particular log file is frequently written to.
@ -492,7 +525,9 @@ There are several prefixes and words with special meaning that could be specifie
\fBjournalmatch\fR \fBjournalmatch\fR
specifies the systemd journal match used to filter the journal entries. See \fBjournalctl(1)\fR and \fBsystemd.journal-fields(7)\fR for matches syntax and more details on special journal fields. This option is only valid for the \fIsystemd\fR backend. specifies the systemd journal match used to filter the journal entries. See \fBjournalctl(1)\fR and \fBsystemd.journal-fields(7)\fR for matches syntax and more details on special journal fields. This option is only valid for the \fIsystemd\fR backend.
.PP .PP
Similar to actions [Init] section enables filter-specific settings. All parameters specified in [Init] section can be redefined or extended in \fIjail.conf/jail.local\fR. Similar to actions, filters may have an [Init] section also (optional since v.0.10). All parameters of both sections [Definition] and [Init] can be overridden (redefined or extended) in \fIjail.conf\fR or \fIjail.local\fR (or in related \fIfilter.d/filter-name.local\fR).
Every option supplied in the jail to the filter overwrites the value specified in [Init] section, which in turm would overwrite the value in [Definition] section.
Besides the standard settings of filter both sections can be used to initialize filter-specific options.
Filters can also have a section called [INCLUDES]. This is used to read other configuration files. Filters can also have a section called [INCLUDES]. This is used to read other configuration files.