New upstream version 0.11.2

debian
Sylvestre Ledru 2020-11-26 13:47:25 +01:00
parent 55508fe5c0
commit d422bceb0e
152 changed files with 2503 additions and 3455 deletions

66
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,66 @@
name: CI
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
push:
paths-ignore:
- 'doc/**'
- 'files/**'
- 'man/**'
pull_request:
paths-ignore:
- 'doc/**'
- 'files/**'
- 'man/**'
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-20.04
strategy:
matrix:
python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9, pypy2, pypy3]
fail-fast: false
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Python version
run: |
F2B_PY=$(python -c "import sys; print(sys.version)")
echo "Python: ${{ matrix.python-version }} -- $F2B_PY"
F2B_PY=${F2B_PY:0:1}
echo "Set F2B_PY=$F2B_PY"
echo "F2B_PY=$F2B_PY" >> $GITHUB_ENV
- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [[ "$F2B_PY" = 3 ]] && ! command -v 2to3x -v 2to3 > /dev/null; then
pip install 2to3
fi
pip install systemd-python || echo 'systemd not available'
pip install pyinotify || echo 'inotify not available'
- name: Before scripts
run: |
cd "$GITHUB_WORKSPACE"
# Manually execute 2to3 for now
if [[ "$F2B_PY" = 3 ]]; then echo "2to3 ..." && ./fail2ban-2to3; fi
# (debug) output current preferred encoding:
python -c 'import locale, sys; from fail2ban.helpers import PREFER_ENC; print(PREFER_ENC, locale.getpreferredencoding(), (sys.stdout and sys.stdout.encoding))'
- name: Test suite
run: if [[ "$F2B_PY" = 2 ]]; then python setup.py test; else python bin/fail2ban-testcases --verbosity=2; fi
#- name: Test initd scripts
# run: shellcheck -s bash -e SC1090,SC1091 files/debian-initd

View File

@ -18,14 +18,14 @@ matrix:
- python: 2.7
name: 2.7 (xenial)
- python: pypy
dist: trusty
- python: 3.3
dist: trusty
- python: 3.4
- python: 3.5
- python: 3.6
- python: 3.7
- python: 3.8-dev
- python: 3.8
- python: 3.9-dev
- python: pypy3.5
before_install:
- echo "running under $TRAVIS_PYTHON_VERSION"
@ -69,8 +69,8 @@ script:
- if [[ "$F2B_PY" = 3 ]]; then coverage run bin/fail2ban-testcases --verbosity=2; fi
# Use $VENV_BIN (not python) or else sudo will always run the system's python (2.7)
- sudo $VENV_BIN/pip install .
# Doc files should get installed on Travis under Linux (python >= 3.8 seem to use another path segment)
- if [[ $TRAVIS_PYTHON_VERSION < 3.8 ]]; then test -e /usr/share/doc/fail2ban/FILTERS; fi
# Doc files should get installed on Travis under Linux (some builds/python's seem to use another path segment)
- test -e /usr/share/doc/fail2ban/FILTERS && echo 'found' || echo 'not found'
# Test initd script
- shellcheck -s bash -e SC1090,SC1091 files/debian-initd
after_success:

View File

@ -6,7 +6,7 @@
Fail2Ban: Changelog
===================
ver. 0.11.1 (2020/01/11) - this-is-the-way
ver. 0.11.2 (2020/11/23) - heal-the-world-with-security-tools
-----------
### Compatibility:
@ -37,6 +37,73 @@ ver. 0.11.1 (2020/01/11) - this-is-the-way
- Since v0.10 fail2ban supports the matching of IPv6 addresses, but not all ban actions are
IPv6-capable now.
### Fixes
* [stability] prevent race condition - no ban if filter (backend) is continuously busy if
too many messages will be found in log, e. g. initial scan of large log-file or journal (gh-2660)
* pyinotify-backend sporadically avoided initial scanning of log-file by start
* python 3.9 compatibility (and Travis CI support)
* restoring a large number (500+ depending on files ulimit) of current bans when using PyPy fixed
* manual ban is written to database, so can be restored by restart (gh-2647)
* `jail.conf`: don't specify `action` directly in jails (use `action_` or `banaction` instead)
* no mails-action added per default anymore (e. g. to allow that `action = %(action_mw)s` should be specified
per jail or in default section in jail.local), closes gh-2357
* ensure we've unique action name per jail (also if parameter `actname` is not set but name deviates from standard name, gh-2686)
* don't use `%(banaction)s` interpolation because it can be complex value (containing `[...]` and/or quotes),
so would bother the action interpolation
* fixed type conversion in config readers (take place after all interpolations get ready), that allows to
specify typed parameters variable (as substitutions) as well as to supply it in other sections or as init parameters.
* `action.d/*-ipset*.conf`: several ipset actions fixed (no timeout per default anymore), so no discrepancy
between ipset and fail2ban (removal from ipset will be managed by fail2ban only, gh-2703)
* `action.d/cloudflare.conf`: fixed `actionunban` (considering new-line chars and optionally real json-parsing
with `jq`, gh-2140, gh-2656)
* `action.d/nftables.conf` (type=multiport only): fixed port range selector, replacing `:` with `-` (gh-2763)
* `action.d/firewallcmd-*.conf` (multiport only): fixed port range selector, replacing `:` with `-` (gh-2821)
* `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`
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/traefik-auth.conf`: filter extended with parameter mode (`normal`, `ddos`, `aggressive`) to handle
the match of username differently (gh-2693):
- `normal`: matches 401 with supplied username only
- `ddos`: matches 401 without supplied username only
- `aggressive`: matches 401 and any variant (with and without username)
* `filter.d/sshd.conf`: normalizing of user pattern in all RE's, allowing empty user (gh-2749)
### New Features and Enhancements
* fail2ban-regex:
- speedup formatted output (bypass unneeded stats creation)
- extended with prefregex statistic
- more informative output for `datepattern` (e. g. set from filter) - pattern : description
* parsing of action in jail-configs considers space between action-names as separator also
(previously only new-line was allowed), for example `action = a b` would specify 2 actions `a` and `b`
* new filter and jail for GitLab recognizing failed application logins (gh-2689)
* new filter and jail for Grafana recognizing failed application logins (gh-2855)
* new filter and jail for SoftEtherVPN recognizing failed application logins (gh-2723)
* `filter.d/guacamole.conf` extended with `logging` parameter to follow webapp-logging if it's configured (gh-2631)
* `filter.d/bitwarden.conf` enhanced to support syslog (gh-2778)
* introduced new prefix `{UNB}` for `datepattern` to disable word boundaries in regex;
* datetemplate: improved anchor detection for capturing groups `(^...)`;
* datepattern: improved handling with wrong recognized timestamps (timezones, no datepattern, etc)
as well as some warnings signaling user about invalid pattern or zone (gh-2814):
- filter gets mode in-operation, which gets activated if filter starts processing of new messages;
in this mode a timestamp read from log-line that appeared recently (not an old line), deviating too much
from now (up too 24h), will be considered as now (assuming a timezone issue), so could avoid unexpected
bypass of failure (previously exceeding `findtime`);
- better interaction with non-matching optional datepattern or invalid timestamps;
- implements special datepattern `{NONE}` - allow to find failures totally without date-time in log messages,
whereas filter will use now as timestamp (gh-2802)
* 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;
* 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

View File

@ -100,6 +100,8 @@ config/filter.d/exim.conf
config/filter.d/exim-spam.conf
config/filter.d/freeswitch.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/gssftpd.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/sieve.conf
config/filter.d/slapd.conf
config/filter.d/softethervpn.conf
config/filter.d/sogo-auth.conf
config/filter.d/solid-pop3d.conf
config/filter.d/squid.conf
@ -227,6 +230,8 @@ fail2ban/tests/clientreadertestcase.py
fail2ban/tests/config/action.d/action.conf
fail2ban/tests/config/action.d/brokenaction.conf
fail2ban/tests/config/fail2ban.conf
fail2ban/tests/config/filter.d/checklogtype.conf
fail2ban/tests/config/filter.d/checklogtype_test.conf
fail2ban/tests/config/filter.d/simple.conf
fail2ban/tests/config/filter.d/test.conf
fail2ban/tests/config/filter.d/test.local
@ -265,6 +270,8 @@ fail2ban/tests/files/database_v1.db
fail2ban/tests/files/database_v2.db
fail2ban/tests/files/filter.d/substition.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/ignorecommand.py
fail2ban/tests/files/logs/3proxy
@ -299,6 +306,8 @@ fail2ban/tests/files/logs/exim
fail2ban/tests/files/logs/exim-spam
fail2ban/tests/files/logs/freeswitch
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/gssftpd
fail2ban/tests/files/logs/guacamole
@ -336,6 +345,7 @@ fail2ban/tests/files/logs/sendmail-auth
fail2ban/tests/files/logs/sendmail-reject
fail2ban/tests/files/logs/sieve
fail2ban/tests/files/logs/slapd
fail2ban/tests/files/logs/softethervpn
fail2ban/tests/files/logs/sogo-auth
fail2ban/tests/files/logs/solid-pop3d
fail2ban/tests/files/logs/squid

View File

@ -21,14 +21,13 @@
#
# Example, for ssh bruteforce (in section [sshd] of `jail.local`):
# action = %(known/action)s
# %(action_abuseipdb)s[abuseipdb_apikey="my-api-key", abuseipdb_category="18,22"]
# abuseipdb[abuseipdb_apikey="my-api-key", abuseipdb_category="18,22"]
#
# See below for catagories.
# See below for categories.
#
# Original Ref: https://wiki.shaunc.com/wikka.php?wakka=ReportingToAbuseIPDBWithFail2Ban
# Added to fail2ban by Andrew James Collett (ajcollett)
## abuseIPDB Catagories, `the abuseipdb_category` MUST be set in the jail.conf action call.
## abuseIPDB Categories, `the abuseipdb_category` MUST be set in the jail.conf action call.
# Example, for ssh bruteforce: action = %(action_abuseipdb)s[abuseipdb_category="18,22"]
# ID Title Description
# 3 Fraud Orders

View File

@ -14,7 +14,10 @@
# Notes.: command executed on demand at the first ban (or at the start of Fail2Ban if actionstart_on_demand is set to false).
# Values: CMD
#
actionstart = ipfw show | fgrep -c -m 1 -s 'table(<table>)' > /dev/null 2>&1 || ( ipfw show | awk 'BEGIN { b = <lowest_rule_num> } { if ($1 < b) {} else if ($1 == b) { b = $1 + 1 } else { e = b } } END { if (e) exit e <br> else exit b }'; num=$?; ipfw -q add $num <blocktype> <block> from table\(<table>\) to me <port>; echo $num > "<startstatefile>" )
actionstart = ipfw show | fgrep -c -m 1 -s 'table(<table>)' > /dev/null 2>&1 || (
num=$(ipfw show | awk 'BEGIN { b = <lowest_rule_num> } { if ($1 == b) { b = $1 + 1 } } END { print b }');
ipfw -q add "$num" <blocktype> <block> from table\(<table>\) to me <port>; echo "$num" > "<startstatefile>"
)
# Option: actionstop

View File

@ -5,7 +5,7 @@
#
# Please set jail.local's permission to 640 because it contains your CF API key.
#
# This action depends on curl.
# This action depends on curl (and optionally jq).
# Referenced from http://www.normyee.net/blog/2012/02/02/adding-cloudflare-support-to-fail2ban by NORM YEE
#
# To get your CloudFlare API Key: https://www.cloudflare.com/a/account/my-account
@ -43,9 +43,9 @@ actioncheck =
# API v1
#actionban = curl -s -o /dev/null https://www.cloudflare.com/api_json.html -d 'a=ban' -d 'tkn=<cftoken>' -d 'email=<cfuser>' -d 'key=<ip>'
# API v4
actionban = curl -s -o /dev/null -X POST -H 'X-Auth-Email: <cfuser>' -H 'X-Auth-Key: <cftoken>' \
-H 'Content-Type: application/json' -d '{ "mode": "block", "configuration": { "target": "ip", "value": "<ip>" } }' \
https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules
actionban = curl -s -o /dev/null -X POST <_cf_api_prms> \
-d '{"mode":"block","configuration":{"target":"ip","value":"<ip>"},"notes":"Fail2Ban <name>"}' \
<_cf_api_url>
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
@ -58,9 +58,14 @@ actionban = curl -s -o /dev/null -X POST -H 'X-Auth-Email: <cfuser>' -H 'X-Auth-
# API v1
#actionunban = curl -s -o /dev/null https://www.cloudflare.com/api_json.html -d 'a=nul' -d 'tkn=<cftoken>' -d 'email=<cfuser>' -d 'key=<ip>'
# API v4
actionunban = curl -s -o /dev/null -X DELETE -H 'X-Auth-Email: <cfuser>' -H 'X-Auth-Key: <cftoken>' \
https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules/$(curl -s -X GET -H 'X-Auth-Email: <cfuser>' -H 'X-Auth-Key: <cftoken>' \
'https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules?mode=block&configuration_target=ip&configuration_value=<ip>&page=1&per_page=1' | cut -d'"' -f6)
actionunban = id=$(curl -s -X GET <_cf_api_prms> \
"<_cf_api_url>?mode=block&configuration_target=ip&configuration_value=<ip>&page=1&per_page=1&notes=Fail2Ban%%20<name>" \
| { jq -r '.result[0].id' 2>/dev/null || tr -d '\n' | sed -nE 's/^.*"result"\s*:\s*\[\s*\{\s*"id"\s*:\s*"([^"]+)".*$/\1/p'; })
if [ -z "$id" ]; then echo "<name>: id for <ip> cannot be found"; exit 0; fi;
curl -s -o /dev/null -X DELETE <_cf_api_prms> "<_cf_api_url>/$id"
_cf_api_url = https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules
_cf_api_prms = -H 'X-Auth-Email: <cfuser>' -H 'X-Auth-Key: <cftoken>' -H 'Content-Type: application/json'
[Init]

View File

@ -18,7 +18,7 @@ before = firewallcmd-common.conf
[Definition]
actionstart = ipset create <ipmset> hash:ip timeout <default-timeout><familyopt>
actionstart = ipset create <ipmset> hash:ip timeout <default-ipsettime> <familyopt>
firewall-cmd --direct --add-rule <family> filter <chain> 0 <actiontype> -m set --match-set <ipmset> src -j <blocktype>
actionflush = ipset flush <ipmset>
@ -27,9 +27,9 @@ actionstop = firewall-cmd --direct --remove-rule <family> filter <chain> 0 <acti
<actionflush>
ipset destroy <ipmset>
actionban = ipset add <ipmset> <ip> timeout <bantime> -exist
actionban = ipset add <ipmset> <ip> timeout <ipsettime> -exist
actionprolong = %(actionban)s
# actionprolong = %(actionban)s
actionunban = ipset del <ipmset> <ip> -exist
@ -42,11 +42,19 @@ actionunban = ipset del <ipmset> <ip> -exist
#
chain = INPUT_direct
# Option: default-timeout
# Option: default-ipsettime
# Notes: specifies default timeout in seconds (handled default ipset timeout only)
# Values: [ NUM ] Default: 600
# Values: [ NUM ] Default: 0 (no timeout, managed by fail2ban by unban)
default-ipsettime = 0
default-timeout = 600
# Option: ipsettime
# Notes: specifies ticket timeout (handled ipset timeout only)
# Values: [ NUM ] Default: 0 (managed by fail2ban by unban)
ipsettime = 0
# expresion to caclulate timeout from bantime, example:
# banaction = %(known/banaction)s[ipsettime='<timeout-bantime>']
timeout-bantime = $([ "<bantime>" -le 2147483 ] && echo "<bantime>" || echo 0)
# Option: actiontype
# Notes.: defines additions to the blocking rule
@ -63,7 +71,7 @@ allports = -p <protocol>
# Option: multiport
# Notes.: addition to block access only to specific ports
# Usage.: use in jail config: banaction = firewallcmd-ipset[actiontype=<multiport>]
multiport = -p <protocol> -m multiport --dports <port>
multiport = -p <protocol> -m multiport --dports "$(echo '<port>' | sed s/:/-/g)"
ipmset = f2b-<name>
familyopt =
@ -71,7 +79,7 @@ familyopt =
[Init?family=inet6]
ipmset = f2b-<name>6
familyopt = <sp>family inet6
familyopt = family inet6
# DEV NOTES:

View File

@ -11,9 +11,9 @@ before = firewallcmd-common.conf
actionstart = firewall-cmd --direct --add-chain <family> filter f2b-<name>
firewall-cmd --direct --add-rule <family> filter f2b-<name> 1000 -j RETURN
firewall-cmd --direct --add-rule <family> filter <chain> 0 -m conntrack --ctstate NEW -p <protocol> -m multiport --dports <port> -j f2b-<name>
firewall-cmd --direct --add-rule <family> filter <chain> 0 -m conntrack --ctstate NEW -p <protocol> -m multiport --dports "$(echo '<port>' | sed s/:/-/g)" -j f2b-<name>
actionstop = firewall-cmd --direct --remove-rule <family> filter <chain> 0 -m conntrack --ctstate NEW -p <protocol> -m multiport --dports <port> -j f2b-<name>
actionstop = firewall-cmd --direct --remove-rule <family> filter <chain> 0 -m conntrack --ctstate NEW -p <protocol> -m multiport --dports "$(echo '<port>' | sed s/:/-/g)" -j f2b-<name>
firewall-cmd --direct --remove-rules <family> filter f2b-<name>
firewall-cmd --direct --remove-chain <family> filter f2b-<name>

View File

@ -10,9 +10,9 @@ before = firewallcmd-common.conf
actionstart = firewall-cmd --direct --add-chain <family> filter f2b-<name>
firewall-cmd --direct --add-rule <family> filter f2b-<name> 1000 -j RETURN
firewall-cmd --direct --add-rule <family> filter <chain> 0 -m state --state NEW -p <protocol> -m multiport --dports <port> -j f2b-<name>
firewall-cmd --direct --add-rule <family> filter <chain> 0 -m state --state NEW -p <protocol> -m multiport --dports "$(echo '<port>' | sed s/:/-/g)" -j f2b-<name>
actionstop = firewall-cmd --direct --remove-rule <family> filter <chain> 0 -m state --state NEW -p <protocol> -m multiport --dports <port> -j f2b-<name>
actionstop = firewall-cmd --direct --remove-rule <family> filter <chain> 0 -m state --state NEW -p <protocol> -m multiport --dports "$(echo '<port>' | sed s/:/-/g)" -j f2b-<name>
firewall-cmd --direct --remove-rules <family> filter f2b-<name>
firewall-cmd --direct --remove-chain <family> filter f2b-<name>

View File

@ -1,6 +1,6 @@
# Fail2Ban configuration file
#
# Author: Donald Yandt
# Authors: Donald Yandt, Sergey G. Brester
#
# Because of the rich rule commands requires firewalld-0.3.1+
# This action uses firewalld rich-rules which gives you a cleaner iptables since it stores rules according to zones and not
@ -10,36 +10,15 @@
#
# If you use the --permanent rule you get a xml file in /etc/firewalld/zones/<zone>.xml that can be shared and parsed easliy
#
# Example commands to view rules:
# firewall-cmd [--zone=<zone>] --list-rich-rules
# firewall-cmd [--zone=<zone>] --list-all
# firewall-cmd [--zone=zone] --query-rich-rule='rule'
# This is an derivative of firewallcmd-rich-rules.conf, see there for details and other parameters.
[INCLUDES]
before = firewallcmd-common.conf
before = firewallcmd-rich-rules.conf
[Definition]
actionstart =
actionstop =
actioncheck =
# you can also use zones and/or service names.
#
# zone example:
# firewall-cmd --zone=<zone> --add-rich-rule="rule family='<family>' source address='<ip>' port port='<port>' protocol='<protocol>' log prefix='f2b-<name>' level='<level>' limit value='<rate>/m' <rich-blocktype>"
#
# service name example:
# firewall-cmd --zone=<zone> --add-rich-rule="rule family='<family>' source address='<ip>' service name='<service>' log prefix='f2b-<name>' level='<level>' limit value='<rate>/m' <rich-blocktype>"
#
# Because rich rules can only handle single or a range of ports we must split ports and execute the command for each port. Ports can be single and ranges separated by a comma or space for an example: http, https, 22-60, 18 smtp
actionban = ports="<port>"; for p in $(echo $ports | tr ", " " "); do firewall-cmd --add-rich-rule="rule family='<family>' source address='<ip>' port port='$p' protocol='<protocol>' log prefix='f2b-<name>' level='<level>' limit value='<rate>/m' <rich-blocktype>"; done
actionunban = ports="<port>"; for p in $(echo $ports | tr ", " " "); do firewall-cmd --remove-rich-rule="rule family='<family>' source address='<ip>' port port='$p' protocol='<protocol>' log prefix='f2b-<name>' level='<level>' limit value='<rate>/m' <rich-blocktype>"; done
rich-suffix = log prefix='f2b-<name>' level='<level>' limit value='<rate>/m' <rich-blocktype>
[Init]
@ -48,4 +27,3 @@ level = info
# log rate per minute
rate = 1

View File

@ -35,8 +35,10 @@ actioncheck =
#
# Because rich rules can only handle single or a range of ports we must split ports and execute the command for each port. Ports can be single and ranges separated by a comma or space for an example: http, https, 22-60, 18 smtp
actionban = ports="<port>"; for p in $(echo $ports | tr ", " " "); do firewall-cmd --add-rich-rule="rule family='<family>' source address='<ip>' port port='$p' protocol='<protocol>' <rich-blocktype>"; done
fwcmd_rich_rule = rule family='<family>' source address='<ip>' port port='$p' protocol='<protocol>' %(rich-suffix)s
actionunban = ports="<port>"; for p in $(echo $ports | tr ", " " "); do firewall-cmd --remove-rich-rule="rule family='<family>' source address='<ip>' port port='$p' protocol='<protocol>' <rich-blocktype>"; done
actionban = ports="$(echo '<port>' | sed s/:/-/g)"; for p in $(echo $ports | tr ", " " "); do firewall-cmd --add-rich-rule="%(fwcmd_rich_rule)s"; done
actionunban = ports="$(echo '<port>' | sed s/:/-/g)"; for p in $(echo $ports | tr ", " " "); do firewall-cmd --remove-rich-rule="%(fwcmd_rich_rule)s"; done
rich-suffix = <rich-blocktype>

View File

@ -26,7 +26,7 @@ before = iptables-common.conf
# Notes.: command executed on demand at the first ban (or at the start of Fail2Ban if actionstart_on_demand is set to false).
# Values: CMD
#
actionstart = ipset create <ipmset> hash:ip timeout <default-timeout><familyopt>
actionstart = ipset create <ipmset> hash:ip timeout <default-ipsettime> <familyopt>
<iptables> -I <chain> -m set --match-set <ipmset> src -j <blocktype>
# Option: actionflush
@ -49,9 +49,9 @@ actionstop = <iptables> -D <chain> -m set --match-set <ipmset> src -j <blocktype
# Tags: See jail.conf(5) man page
# Values: CMD
#
actionban = ipset add <ipmset> <ip> timeout <bantime> -exist
actionban = ipset add <ipmset> <ip> timeout <ipsettime> -exist
actionprolong = %(actionban)s
# actionprolong = %(actionban)s
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
@ -63,11 +63,19 @@ actionunban = ipset del <ipmset> <ip> -exist
[Init]
# Option: default-timeout
# Option: default-ipsettime
# Notes: specifies default timeout in seconds (handled default ipset timeout only)
# Values: [ NUM ] Default: 600
# Values: [ NUM ] Default: 0 (no timeout, managed by fail2ban by unban)
default-ipsettime = 0
default-timeout = 600
# Option: ipsettime
# Notes: specifies ticket timeout (handled ipset timeout only)
# Values: [ NUM ] Default: 0 (managed by fail2ban by unban)
ipsettime = 0
# expresion to caclulate timeout from bantime, example:
# banaction = %(known/banaction)s[ipsettime='<timeout-bantime>']
timeout-bantime = $([ "<bantime>" -le 2147483 ] && echo "<bantime>" || echo 0)
ipmset = f2b-<name>
familyopt =
@ -76,4 +84,4 @@ familyopt =
[Init?family=inet6]
ipmset = f2b-<name>6
familyopt = <sp>family inet6
familyopt = family inet6

View File

@ -26,7 +26,7 @@ before = iptables-common.conf
# Notes.: command executed on demand at the first ban (or at the start of Fail2Ban if actionstart_on_demand is set to false).
# Values: CMD
#
actionstart = ipset create <ipmset> hash:ip timeout <default-timeout><familyopt>
actionstart = ipset create <ipmset> hash:ip timeout <default-ipsettime> <familyopt>
<iptables> -I <chain> -p <protocol> -m multiport --dports <port> -m set --match-set <ipmset> src -j <blocktype>
# Option: actionflush
@ -49,9 +49,9 @@ actionstop = <iptables> -D <chain> -p <protocol> -m multiport --dports <port> -m
# Tags: See jail.conf(5) man page
# Values: CMD
#
actionban = ipset add <ipmset> <ip> timeout <bantime> -exist
actionban = ipset add <ipmset> <ip> timeout <ipsettime> -exist
actionprolong = %(actionban)s
# actionprolong = %(actionban)s
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
@ -63,11 +63,19 @@ actionunban = ipset del <ipmset> <ip> -exist
[Init]
# Option: default-timeout
# Option: default-ipsettime
# Notes: specifies default timeout in seconds (handled default ipset timeout only)
# Values: [ NUM ] Default: 600
# Values: [ NUM ] Default: 0 (no timeout, managed by fail2ban by unban)
default-ipsettime = 0
default-timeout = 600
# Option: ipsettime
# Notes: specifies ticket timeout (handled ipset timeout only)
# Values: [ NUM ] Default: 0 (managed by fail2ban by unban)
ipsettime = 0
# expresion to caclulate timeout from bantime, example:
# banaction = %(known/banaction)s[ipsettime='<timeout-bantime>']
timeout-bantime = $([ "<bantime>" -le 2147483 ] && echo "<bantime>" || echo 0)
ipmset = f2b-<name>
familyopt =
@ -76,4 +84,4 @@ familyopt =
[Init?family=inet6]
ipmset = f2b-<name>6
familyopt = <sp>family inet6
familyopt = family inet6

View File

@ -34,7 +34,7 @@ type = multiport
rule_match-custom =
rule_match-allports = meta l4proto \{ <protocol> \}
rule_match-multiport = $proto dport \{ <port> \}
rule_match-multiport = $proto dport \{ $(echo '<port>' | sed s/:/-/g) \}
match = <rule_match-<type>>
# Option: rule_stat

View File

@ -103,6 +103,8 @@ actionstop = %(actionflush)s
actioncheck =
actionban = echo "\\\\<fid> 1;" >> '%(blck_lst_file)s'; %(blck_lst_reload)s
_echo_blck_row = printf '\%%s 1;\n' "<fid>"
actionunban = id=$(echo "<fid>" | sed -e 's/[]\/$*.^|[]/\\&/g'); sed -i "/^\\\\$id 1;$/d" %(blck_lst_file)s; %(blck_lst_reload)s
actionban = %(_echo_blck_row)s >> '%(blck_lst_file)s'; %(blck_lst_reload)s
actionunban = id=$(%(_echo_blck_row)s | sed -e 's/[]\/$*.^|[]/\\&/g'); sed -i "/^$id$/d" %(blck_lst_file)s; %(blck_lst_reload)s

View File

@ -51,7 +51,7 @@
# Values: CMD
#
actionstart = if ! ipset -quiet -name list f2b-<name> >/dev/null;
then ipset -quiet -exist create f2b-<name> hash:ip timeout <default-timeout>;
then ipset -quiet -exist create f2b-<name> hash:ip timeout <default-ipsettime>;
fi
# Option: actionstop
@ -66,9 +66,9 @@ actionstop = ipset flush f2b-<name>
# Tags: See jail.conf(5) man page
# Values: CMD
#
actionban = ipset add f2b-<name> <ip> timeout <bantime> -exist
actionban = ipset add f2b-<name> <ip> timeout <ipsettime> -exist
actionprolong = %(actionban)s
# actionprolong = %(actionban)s
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
@ -78,8 +78,16 @@ actionprolong = %(actionban)s
#
actionunban = ipset del f2b-<name> <ip> -exist
# Option: default-timeout
# Option: default-ipsettime
# Notes: specifies default timeout in seconds (handled default ipset timeout only)
# Values: [ NUM ] Default: 600
# Values: [ NUM ] Default: 0 (no timeout, managed by fail2ban by unban)
default-ipsettime = 0
default-timeout = 600
# Option: ipsettime
# Notes: specifies ticket timeout (handled ipset timeout only)
# Values: [ NUM ] Default: 0 (managed by fail2ban by unban)
ipsettime = 0
# expresion to caclulate timeout from bantime, example:
# banaction = %(known/banaction)s[ipsettime='<timeout-bantime>']
timeout-bantime = $([ "<bantime>" -le 2147483 ] && echo "<bantime>" || echo 0)

View File

@ -19,7 +19,7 @@
# NOTICE
# INFO
# DEBUG
# Values: [ LEVEL ] Default: ERROR
# Values: [ LEVEL ] Default: INFO
#
loglevel = INFO

View File

@ -17,9 +17,9 @@ before = apache-common.conf
[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
^'<script>\S*' not found or unable to stat

View File

@ -2,5 +2,12 @@
# Detecting failed login attempts
# Logged in bwdata/logs/identity/Identity/log.txt
[INCLUDES]
before = common.conf
[Definition]
failregex = ^\s*\[WRN\]\s+Failed login attempt(?:, 2FA invalid)?\. <HOST>$
_daemon = Bitwarden-Identity
failregex = ^%(__prefix_line)s\s*\[(?:W(?:RN|arning)|Bit\.Core\.[^\]]+)\]\s+Failed login attempt(?:, 2FA invalid)?\. <ADDR>$
# DEV Notes:
# __prefix_line can result to an empty string, so it can support syslog and non-syslog at once.

View File

@ -25,7 +25,7 @@ __pid_re = (?:\[\d+\])
# Daemon name (with optional source_file:line or whatever)
# EXAMPLES: pam_rhosts_auth, [sshd], pop(pam_unix)
__daemon_re = [\[\(]?%(_daemon)s(?:\(\S+\))?[\]\)]?:?
__daemon_re = [\[\(]?<_daemon>(?:\(\S+\))?[\]\)]?:?
# extra daemon info
# EXAMPLE: [ID 800047 auth.info]
@ -33,7 +33,7 @@ __daemon_extra_re = \[ID \d+ \S+\]
# Combinations of daemon name and PID
# EXAMPLES: sshd[31607], pop(pam_unix)[4920]
__daemon_combs_re = (?:%(__pid_re)s?:\s+%(__daemon_re)s|%(__daemon_re)s%(__pid_re)s?:?)
__daemon_combs_re = (?:<__pid_re>?:\s+<__daemon_re>|<__daemon_re><__pid_re>?:?)
# Some messages have a kernel prefix with a timestamp
# EXAMPLES: kernel: [769570.846956]
@ -69,12 +69,12 @@ datepattern = <lt_<logtype>/datepattern>
[lt_file]
# Common line prefixes for logtype "file":
__prefix_line = %(__date_ambit)s?\s*(?:%(__bsd_syslog_verbose)s\s+)?(?:%(__hostname)s\s+)?(?:%(__kernel_prefix)s\s+)?(?:%(__vserver)s\s+)?(?:%(__daemon_combs_re)s\s+)?(?:%(__daemon_extra_re)s\s+)?
__prefix_line = <__date_ambit>?\s*(?:<__bsd_syslog_verbose>\s+)?(?:<__hostname>\s+)?(?:<__kernel_prefix>\s+)?(?:<__vserver>\s+)?(?:<__daemon_combs_re>\s+)?(?:<__daemon_extra_re>\s+)?
datepattern = {^LN-BEG}
[lt_short]
# Common (short) line prefix for logtype "journal" (corresponds output of formatJournalEntry):
__prefix_line = \s*(?:%(__hostname)s\s+)?(?:%(_daemon)s%(__pid_re)s?:?\s+)?(?:%(__kernel_prefix)s\s+)?
__prefix_line = \s*(?:<__hostname>\s+)?(?:<_daemon><__pid_re>?:?\s+)?(?:<__kernel_prefix>\s+)?
datepattern = %(lt_file/datepattern)s
[lt_journal]
__prefix_line = %(lt_short/__prefix_line)s

View File

@ -12,7 +12,7 @@ before = common.conf
_daemon = courieresmtpd
prefregex = ^%(__prefix_line)serror,relay=<HOST>,<F-CONTENT>.+</F-CONTENT>$
prefregex = ^%(__prefix_line)serror,relay=<HOST>,(?:port=\d+,)?<F-CONTENT>.+</F-CONTENT>$
failregex = ^[^:]*: 550 User (<.*> )?unknown\.?$
^msg="535 Authentication failed\.",cmd:( AUTH \S+)?( [0-9a-zA-Z\+/=]+)?(?: \S+)$

View File

@ -10,15 +10,15 @@ before = common.conf
_auth_worker = (?:dovecot: )?auth(?:-worker)?
_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*$
^(?: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*$
^[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-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 =

View File

@ -0,0 +1,6 @@
# Fail2Ban filter for Gitlab
# Detecting unauthorized access to the Gitlab Web portal
# typically logged in /var/log/gitlab/gitlab-rails/application.log
[Definition]
failregex = ^: Failed Login: username=<F-USER>.+</F-USER> ip=<HOST>$

View File

@ -0,0 +1,9 @@
# Fail2Ban filter for Grafana
# Detecting unauthorized access
# Typically logged in /var/log/grafana/grafana.log
[Init]
datepattern = ^t=%%Y-%%m-%%dT%%H:%%M:%%S%%z
[Definition]
failregex = ^(?: lvl=err?or)? msg="Invalid username or password"(?: uname=(?:"<F-ALT_USER>[^"]+</F-ALT_USER>"|<F-USER>\S+</F-USER>)| error="<F-ERROR>[^"]+</F-ERROR>"| \S+=(?:\S*|"[^"]+"))* remote_addr=<ADDR>$

View File

@ -5,21 +5,47 @@
[Definition]
# Option: failregex
# Notes.: regex to match the password failures messages in the logfile.
# Values: TEXT
#
logging = catalina
failregex = <L_<logging>/failregex>
maxlines = <L_<logging>/maxlines>
datepattern = <L_<logging>/datepattern>
[L_catalina]
failregex = ^.*\nWARNING: Authentication attempt from <HOST> for user "[^"]*" failed\.$
# Option: ignoreregex
# Notes.: regex to ignore. If this regex matches, the line is ignored.
# Values: TEXT
#
ignoreregex =
# "maxlines" is number of log lines to buffer for multi-line regex searches
maxlines = 2
datepattern = ^%%b %%d, %%ExY %%I:%%M:%%S %%p
^WARNING:()**
{^LN-BEG}
[L_webapp]
failregex = ^ \[\S+\] WARN \S+ - Authentication attempt from <HOST> for user "<F-USER>[^"]+</F-USER>" failed.
maxlines = 1
datepattern = ^%%H:%%M:%%S.%%f
# DEV Notes:
#
# failregex is based on the default pattern given in Guacamole documentation :
# https://guacamole.apache.org/doc/gug/configuring-guacamole.html#webapp-logging
#
# The following logback.xml Guacamole configuration file can then be used accordingly :
# <configuration>
# <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
# <file>/var/log/guacamole.log</file>
# <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
# <fileNamePattern>/var/log/guacamole.%d.log.gz</fileNamePattern>
# <maxHistory>32</maxHistory>
# </rollingPolicy>
# <encoder>
# <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
# </encoder>
# </appender>
# <root level="info">
# <appender-ref ref="FILE" />
# </root>
# </configuration>

View File

@ -8,13 +8,17 @@
# common.local
before = common.conf
# [DEFAULT]
# logtype = short
[Definition]
_daemon = monit
_prefix = Warning|HttpRequest
# Regexp for previous (accessing monit httpd) and new (access denied) versions
failregex = ^\[\s*\]\s*error\s*:\s*Warning:\s+Client '<HOST>' supplied (?:unknown user '[^']+'|wrong password for user '[^']*') accessing monit httpd$
^%(__prefix_line)s\w+: access denied -- client <HOST>: (?:unknown user '[^']+'|wrong password for user '[^']*'|empty password)$
failregex = ^%(__prefix_line)s(?:error\s*:\s+)?(?:%(_prefix)s):\s+(?:access denied\s+--\s+)?[Cc]lient '?<HOST>'?(?:\s+supplied|\s*:)\s+(?:unknown user '<F-ALT_USER>[^']+</F-ALT_USER>'|wrong password for user '<F-USER>[^']*</F-USER>'|empty password)
# Ignore login with empty user (first connect, no user specified)
# ignoreregex = %(__prefix_line)s\w+: access denied -- client <HOST>: (?:unknown user '')

View File

@ -17,7 +17,7 @@ before = common.conf
_daemon = mysqld
failregex = ^%(__prefix_line)s(?:(?:\d{6}|\d{4}-\d{2}-\d{2})[ T]\s?\d{1,2}:\d{2}:\d{2} )?(?:\d+ )?\[\w+\] (?:\[[^\]]+\] )*Access denied for user '[^']+'@'<HOST>' (to database '[^']*'|\(using password: (YES|NO)\))*\s*$
failregex = ^%(__prefix_line)s(?:(?:\d{6}|\d{4}-\d{2}-\d{2})[ T]\s?\d{1,2}:\d{2}:\d{2} )?(?:\d+ )?\[\w+\] (?:\[[^\]]+\] )*Access denied for user '<F-USER>[^']+</F-USER>'@'<HOST>' (to database '[^']*'|\(using password: (YES|NO)\))*\s*$
ignoreregex =

View File

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

View File

@ -37,7 +37,7 @@ mdre-rbl = ^RCPT from [^[]*\[<HOST>\]%(_port)s: [45]54 [45]\.7\.1 Service unava
mdpr-more = %(mdpr-normal)s
mdre-more = %(mdre-normal)s
mdpr-ddos = lost connection after(?! DATA) [A-Z]+
mdpr-ddos = (?:lost connection after(?! DATA) [A-Z]+|disconnect(?= from \S+(?: \S+=\d+)* auth=0/(?:[1-9]|\d\d+)))
mdre-ddos = ^from [^[]*\[<HOST>\]%(_port)s:?
mdpr-extra = (?:%(mdpr-auth)s|%(mdpr-normal)s)

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.
# See: http://www.proftpd.org/docs/howto/DNS.html
@ -14,16 +14,15 @@ before = common.conf
_daemon = proftpd
__suffix_failed_login = (User not authorized for login|No such user found|Incorrect password|Password expired|Account disabled|Invalid shell: '\S+'|User in \S+|Limit (access|configuration) denies login|Not a UserAlias|maximum login length exceeded).?
__suffix_failed_login = ([uU]ser not authorized for login|[nN]o such user found|[iI]ncorrect password|[pP]assword expired|[aA]ccount disabled|[iI]nvalid shell: '\S+'|[uU]ser in \S+|[lL]imit (access|configuration) denies login|[nN]ot a UserAlias|[mM]aximum login length exceeded)
prefregex = ^%(__prefix_line)s%(__hostname)s \(\S+\[<HOST>\]\)[: -]+ <F-CONTENT>(?:USER|SECURITY|Maximum).+</F-CONTENT>$
prefregex = ^%(__prefix_line)s%(__hostname)s \(\S+\[<HOST>\]\)[: -]+ <F-CONTENT>(?:USER|SECURITY|Maximum) .+</F-CONTENT>$
failregex = ^USER .*: no such user found from \S+ \[\S+\] to \S+:\S+ *$
^USER .* \(Login failed\): %(__suffix_failed_login)s\s*$
^SECURITY VIOLATION: .* login attempted\. *$
^Maximum login attempts \(\d+\) exceeded *$
failregex = ^USER <F-USER>\S+|.*?</F-USER>(?: \(Login failed\))?: %(__suffix_failed_login)s
^SECURITY VIOLATION: <F-USER>\S+|.*?</F-USER> login attempted
^Maximum login attempts \(\d+\) exceeded
ignoreregex =

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+\))?$
^(?:<[\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

View File

@ -8,11 +8,14 @@ before = common.conf
[Definition]
_daemon = (?:sendmail|sm-(?:mta|acceptingconnections))
# "\w{14,20}" will give support for IDs from 14 up to 20 characters long
__prefix_line = %(known/__prefix_line)s(?:\w{14,20}: )?
addr = (?:IPv6:<IP6>|<IP4>)
# "w{14,20}" will give support for IDs from 14 up to 20 characters long
failregex = ^%(__prefix_line)s(\S+ )?\[(?:IPv6:<IP6>|<IP4>)\]( \(may be forged\))?: possible SMTP attack: command=AUTH, count=\d+$
prefregex = ^<F-MLFID>%(__prefix_line)s</F-MLFID><F-CONTENT>.+</F-CONTENT>$
failregex = ^(\S+ )?\[%(addr)s\]( \(may be forged\))?: possible SMTP attack: command=AUTH, count=\d+$
^AUTH failure \(LOGIN\):(?: [^:]+:)? authentication failure: checkpass failed, user=<F-USER>(?:\S+|.*?)</F-USER>, relay=(?:\S+ )?\[%(addr)s\](?: \(may be forged\))?$
ignoreregex =
journalmatch = _SYSTEMD_UNIT=sendmail.service

View File

@ -21,19 +21,20 @@ before = common.conf
_daemon = (?:(sm-(mta|acceptingconnections)|sendmail))
__prefix_line = %(known/__prefix_line)s(?:\w{14,20}: )?
addr = (?:IPv6:<IP6>|<IP4>)
prefregex = ^<F-MLFID>%(__prefix_line)s</F-MLFID><F-CONTENT>.+</F-CONTENT>$
cmnfailre = ^ruleset=check_rcpt, arg1=(?P<email><\S+@\S+>), relay=(\S+ )?\[(?:IPv6:<IP6>|<IP4>)\](?: \(may be forged\))?, reject=(550 5\.7\.1 (?P=email)\.\.\. Relaying denied\. (IP name possibly forged \[(\d+\.){3}\d+\]|Proper authentication required\.|IP name lookup failed \[(\d+\.){3}\d+\])|553 5\.1\.8 (?P=email)\.\.\. Domain of sender address \S+ does not exist|550 5\.[71]\.1 (?P=email)\.\.\. (Rejected: .*|User unknown))$
^ruleset=check_relay, arg1=(?P<dom>\S+), arg2=(?:IPv6:<IP6>|<IP4>), relay=((?P=dom) )?\[(\d+\.){3}\d+\](?: \(may be forged\))?, reject=421 4\.3\.2 (Connection rate limit exceeded\.|Too many open connections\.)$
^rejecting commands from (\S* )?\[(?:IPv6:<IP6>|<IP4>)\] due to pre-greeting traffic after \d+ seconds$
^(?:\S+ )?\[(?:IPv6:<IP6>|<IP4>)\]: (?:(?i)expn|vrfy) \S+ \[rejected\]$
cmnfailre = ^ruleset=check_rcpt, arg1=(?P<email><\S+@\S+>), relay=(\S+ )?\[%(addr)s\](?: \(may be forged\))?, reject=(550 5\.7\.1 (?P=email)\.\.\. Relaying denied\. (IP name possibly forged \[(\d+\.){3}\d+\]|Proper authentication required\.|IP name lookup failed \[(\d+\.){3}\d+\])|553 5\.1\.8 (?P=email)\.\.\. Domain of sender address \S+ does not exist|550 5\.[71]\.1 (?P=email)\.\.\. (Rejected: .*|User unknown))$
^ruleset=check_relay, arg1=(?P<dom>\S+), arg2=%(addr)s, relay=((?P=dom) )?\[(\d+\.){3}\d+\](?: \(may be forged\))?, reject=421 4\.3\.2 (Connection rate limit exceeded\.|Too many open connections\.)$
^rejecting commands from (\S* )?\[%(addr)s\] due to pre-greeting traffic after \d+ seconds$
^(?:\S+ )?\[%(addr)s\]: (?:(?i)expn|vrfy) \S+ \[rejected\]$
^<[^@]+@[^>]+>\.\.\. No such user here$
^<F-NOFAIL>from=<[^@]+@[^>]+></F-NOFAIL>, size=\d+, class=\d+, nrcpts=\d+, bodytype=\w+, proto=E?SMTP, daemon=MTA, relay=\S+ \[(?:IPv6:<IP6>|<IP4>)\]$
^<F-NOFAIL>from=<[^@]+@[^>]+></F-NOFAIL>, size=\d+, class=\d+, nrcpts=\d+, bodytype=\w+, proto=E?SMTP, daemon=MTA, relay=\S+ \[%(addr)s\]$
mdre-normal =
mdre-extra = ^(?:\S+ )?\[(?:IPv6:<IP6>|<IP4>)\](?: \(may be forged\))? did not issue (?:[A-Z]{4}[/ ]?)+during connection to (?:TLS)?M(?:TA|S[PA])(?:-\w+)?$
mdre-extra = ^(?:\S+ )?\[%(addr)s\](?: \(may be forged\))? did not issue \S+ during connection
mdre-aggressive = %(mdre-extra)s

View File

@ -0,0 +1,9 @@
# Fail2Ban filter for SoftEtherVPN
# Detecting unauthorized access to SoftEtherVPN
# typically logged in /usr/local/vpnserver/security_log/*/sec.log, or in syslog, depending on configuration
[INCLUDES]
before = common.conf
[Definition]
failregex = ^%(__prefix_line)s(?:(?:\([\d\-]+ [\d:.]+\) )?<SECURITY_LOG>: )?Connection "[^"]+": User authentication failed. The user name that has been provided was "<F-USER>(?:[^"]+|.+)</F-USER>", from <ADDR>\.$

View File

@ -25,7 +25,7 @@ __pref = (?:(?:error|fatal): (?:PAM: )?)?
__suff = (?: (?:port \d+|on \S+|\[preauth\])){0,3}\s*
__on_port_opt = (?: (?:port \d+|on \S+)){0,2}
# close by authenticating user:
__authng_user = (?: (?:invalid|authenticating) user <F-USER>\S+|.+?</F-USER>)?
__authng_user = (?: (?:invalid|authenticating) user <F-USER>\S+|.*?</F-USER>)?
# for all possible (also future) forms of "no matching (cipher|mac|MAC|compression method|key exchange method|host key type) found",
# see ssherr.c for all possible SSH_ERR_..._ALG_MATCH errors.
@ -40,39 +40,45 @@ prefregex = ^<F-MLFID>%(__prefix_line)s</F-MLFID>%(__pref)s<F-CONTENT>.+</F-CONT
cmnfailre = ^[aA]uthentication (?:failure|error|failed) for <F-USER>.*</F-USER> from <HOST>( via \S+)?%(__suff)s$
^User not known to the underlying authentication module for <F-USER>.*</F-USER> from <HOST>%(__suff)s$
^Failed publickey for invalid user <F-USER>(?P<cond_user>\S+)|(?:(?! from ).)*?</F-USER> from <HOST>%(__on_port_opt)s(?: ssh\d*)?(?(cond_user): |(?:(?:(?! from ).)*)$)
^Failed \b(?!publickey)\S+ for (?P<cond_inv>invalid user )?<F-USER>(?P<cond_user>\S+)|(?(cond_inv)(?:(?! from ).)*?|[^:]+)</F-USER> from <HOST>%(__on_port_opt)s(?: ssh\d*)?(?(cond_user): |(?:(?:(?! from ).)*)$)
<cmnfailre-failed-pub-<publickey>>
^Failed <cmnfailed> for (?P<cond_inv>invalid user )?<F-USER>(?P<cond_user>\S+)|(?(cond_inv)(?:(?! from ).)*?|[^:]+)</F-USER> from <HOST>%(__on_port_opt)s(?: ssh\d*)?(?(cond_user): |(?:(?:(?! from ).)*)$)
^<F-USER>ROOT</F-USER> LOGIN REFUSED FROM <HOST>
^[iI](?:llegal|nvalid) user <F-USER>.*?</F-USER> from <HOST>%(__suff)s$
^User <F-USER>.+</F-USER> from <HOST> not allowed because not listed in AllowUsers%(__suff)s$
^User <F-USER>.+</F-USER> from <HOST> not allowed because listed in DenyUsers%(__suff)s$
^User <F-USER>.+</F-USER> from <HOST> not allowed because not in any group%(__suff)s$
^User <F-USER>\S+|.*?</F-USER> from <HOST> not allowed because not listed in AllowUsers%(__suff)s$
^User <F-USER>\S+|.*?</F-USER> from <HOST> not allowed because listed in DenyUsers%(__suff)s$
^User <F-USER>\S+|.*?</F-USER> from <HOST> not allowed because not in any group%(__suff)s$
^refused connect from \S+ \(<HOST>\)
^Received <F-MLFFORGET>disconnect</F-MLFFORGET> from <HOST>%(__on_port_opt)s:\s*3: .*: Auth fail%(__suff)s$
^User <F-USER>.+</F-USER> from <HOST> not allowed because a group is listed in DenyGroups%(__suff)s$
^User <F-USER>.+</F-USER> from <HOST> not allowed because none of user's groups are listed in AllowGroups%(__suff)s$
^User <F-USER>\S+|.*?</F-USER> from <HOST> not allowed because a group is listed in DenyGroups%(__suff)s$
^User <F-USER>\S+|.*?</F-USER> from <HOST> not allowed because none of user's groups are listed in AllowGroups%(__suff)s$
^<F-NOFAIL>%(__pam_auth)s\(sshd:auth\):\s+authentication failure;</F-NOFAIL>(?:\s+(?:(?:logname|e?uid|tty)=\S*)){0,4}\s+ruser=<F-ALT_USER>\S*</F-ALT_USER>\s+rhost=<HOST>(?:\s+user=<F-USER>\S*</F-USER>)?%(__suff)s$
^(error: )?maximum authentication attempts exceeded for <F-USER>.*</F-USER> from <HOST>%(__on_port_opt)s(?: ssh\d*)?%(__suff)s$
^User <F-USER>.+</F-USER> not allowed because account is locked%(__suff)s
^maximum authentication attempts exceeded for <F-USER>.*</F-USER> from <HOST>%(__on_port_opt)s(?: ssh\d*)?%(__suff)s$
^User <F-USER>\S+|.*?</F-USER> not allowed because account is locked%(__suff)s
^<F-MLFFORGET>Disconnecting</F-MLFFORGET>(?: from)?(?: (?:invalid|authenticating)) user <F-USER>\S+</F-USER> <HOST>%(__on_port_opt)s:\s*Change of username or service not allowed:\s*.*\[preauth\]\s*$
^<F-MLFFORGET>Disconnecting</F-MLFFORGET>: Too many authentication failures(?: for <F-USER>.+?</F-USER>)?%(__suff)s$
^Disconnecting: Too many authentication failures(?: for <F-USER>\S+|.*?</F-USER>)?%(__suff)s$
^<F-NOFAIL>Received <F-MLFFORGET>disconnect</F-MLFFORGET></F-NOFAIL> from <HOST>%(__on_port_opt)s:\s*11:
<mdre-<mode>-other>
^<F-MLFFORGET><F-MLFGAINED>Accepted \w+</F-MLFGAINED></F-MLFFORGET> for <F-USER>\S+</F-USER> from <HOST>(?:\s|$)
cmnfailed-any = \S+
cmnfailed-ignore = \b(?!publickey)\S+
cmnfailed-invalid = <cmnfailed-ignore>
cmnfailed-nofail = (?:<F-NOFAIL>publickey</F-NOFAIL>|\S+)
cmnfailed = <cmnfailed-<publickey>>
mdre-normal =
# used to differentiate "connection closed" with and without `[preauth]` (fail/nofail cases in ddos mode)
mdre-normal-other = ^<F-NOFAIL><F-MLFFORGET>(Connection closed|Disconnected)</F-MLFFORGET></F-NOFAIL> (?:by|from)%(__authng_user)s <HOST>(?:%(__suff)s|\s*)$
mdre-ddos = ^Did not receive identification string from <HOST>
^kex_exchange_identification: (?:[Cc]lient sent invalid protocol identifier|[Cc]onnection closed by remote host)
^Bad protocol version identification '.*' from <HOST>
^Connection <F-MLFFORGET>reset</F-MLFFORGET> by <HOST>
^<F-NOFAIL>SSH: Server;Ltype:</F-NOFAIL> (?:Authname|Version|Kex);Remote: <HOST>-\d+;[A-Z]\w+:
^Read from socket failed: Connection <F-MLFFORGET>reset</F-MLFFORGET> by peer
# same as mdre-normal-other, but as failure (without <F-NOFAIL>) and [preauth] only:
mdre-ddos-other = ^<F-MLFFORGET>(Connection closed|Disconnected)</F-MLFFORGET> (?:by|from)%(__authng_user)s <HOST>%(__on_port_opt)s\s+\[preauth\]\s*$
mdre-ddos-other = ^<F-MLFFORGET>(Connection (?:closed|reset)|Disconnected)</F-MLFFORGET> (?:by|from)%(__authng_user)s <HOST>%(__on_port_opt)s\s+\[preauth\]\s*$
mdre-extra = ^Received <F-MLFFORGET>disconnect</F-MLFFORGET> from <HOST>%(__on_port_opt)s:\s*14: No supported authentication methods available
mdre-extra = ^Received <F-MLFFORGET>disconnect</F-MLFFORGET> from <HOST>%(__on_port_opt)s:\s*14: No(?: supported)? authentication methods available
^Unable to negotiate with <HOST>%(__on_port_opt)s: no matching <__alg_match> found.
^Unable to negotiate a <__alg_match>
^no matching <__alg_match> found:
@ -84,6 +90,17 @@ mdre-aggressive = %(mdre-ddos)s
# mdre-extra-other is fully included within mdre-ddos-other:
mdre-aggressive-other = %(mdre-ddos-other)s
# Parameter "publickey": nofail (default), invalid, any, ignore
publickey = nofail
# consider failed publickey for invalid users only:
cmnfailre-failed-pub-invalid = ^Failed publickey for invalid user <F-USER>(?P<cond_user>\S+)|(?:(?! from ).)*?</F-USER> from <HOST>%(__on_port_opt)s(?: ssh\d*)?(?(cond_user): |(?:(?:(?! from ).)*)$)
# consider failed publickey for valid users too (don't need RE, see cmnfailed):
cmnfailre-failed-pub-any =
# same as invalid, but consider failed publickey for valid users too, just as no failure (helper to get IP and user-name only, see cmnfailed):
cmnfailre-failed-pub-nofail = <cmnfailre-failed-pub-invalid>
# don't consider failed publickey as failures (don't need RE, see cmnfailed):
cmnfailre-failed-pub-ignore =
cfooterre = ^<F-NOFAIL>Connection from</F-NOFAIL> <HOST>
failregex = %(cmnfailre)s

View File

@ -51,6 +51,26 @@
[Definition]
failregex = ^<HOST> \- (?!- )\S+ \[\] \"(GET|POST|HEAD) [^\"]+\" 401\b
# Parameter "method" can be used to specifiy request method
req-method = \S+
# Usage example (for jail.local):
# filter = traefik-auth[req-method="GET|POST|HEAD"]
failregex = ^<HOST> \- <usrre-<mode>> \[\] \"(?:<req-method>) [^\"]+\" 401\b
ignoreregex =
# Parameter "mode": normal (default), ddos or aggressive
# Usage example (for jail.local):
# [traefik-auth]
# mode = aggressive
# # or another jail (rewrite filter parameters of jail):
# [traefik-auth-ddos]
# filter = traefik-auth[mode=ddos]
#
mode = normal
# part of failregex matches user name (must be available in normal mode, must be empty in ddos mode, and both for aggressive mode):
usrre-normal = (?!- )<F-USER>\S+</F-USER>
usrre-ddos = -
usrre-aggressive = <F-USER>\S+</F-USER>

View File

@ -52,7 +52,7 @@ before = paths-debian.conf
# 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 (don't grows further)
# "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,
@ -60,7 +60,7 @@ before = paths-debian.conf
# grows by 1, 2, 4, 8, 16 ...
#bantime.factor = 1
# "bantime.formula" used by default to calculate next value of ban time, default value bellow,
# "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
#
@ -209,28 +209,28 @@ banaction = iptables-multiport
banaction_allports = iptables-allports
# The simplest action to take: ban only
action_ = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
action_ = %(banaction)s[port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
# ban & send an e-mail with whois report to the destemail.
action_mw = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
%(mta)s-whois[name=%(__name__)s, sender="%(sender)s", dest="%(destemail)s", protocol="%(protocol)s", chain="%(chain)s"]
action_mw = %(action_)s
%(mta)s-whois[sender="%(sender)s", dest="%(destemail)s", protocol="%(protocol)s", chain="%(chain)s"]
# ban & send an e-mail with whois report and relevant log lines
# to the destemail.
action_mwl = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
%(mta)s-whois-lines[name=%(__name__)s, sender="%(sender)s", dest="%(destemail)s", logpath="%(logpath)s", chain="%(chain)s"]
action_mwl = %(action_)s
%(mta)s-whois-lines[sender="%(sender)s", dest="%(destemail)s", logpath="%(logpath)s", chain="%(chain)s"]
# See the IMPORTANT note in action.d/xarf-login-attack for when to use this action
#
# ban & send a xarf e-mail to abuse contact of IP address and include relevant log lines
# to the destemail.
action_xarf = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
action_xarf = %(action_)s
xarf-login-attack[service=%(__name__)s, sender="%(sender)s", logpath="%(logpath)s", port="%(port)s"]
# ban IP on CloudFlare & send an e-mail with whois report and relevant log lines
# to the destemail.
action_cf_mwl = cloudflare[cfuser="%(cfemail)s", cftoken="%(cfapikey)s"]
%(mta)s-whois-lines[name=%(__name__)s, sender="%(sender)s", dest="%(destemail)s", logpath="%(logpath)s", chain="%(chain)s"]
%(mta)s-whois-lines[sender="%(sender)s", dest="%(destemail)s", logpath="%(logpath)s", chain="%(chain)s"]
# Report block via blocklist.de fail2ban reporting service API
#
@ -240,7 +240,7 @@ action_cf_mwl = cloudflare[cfuser="%(cfemail)s", cftoken="%(cfapikey)s"]
# in your `jail.local` globally (section [DEFAULT]) or per specific jail section (resp. in
# corresponding jail.d/my-jail.local file).
#
action_blocklist_de = blocklist_de[email="%(sender)s", service=%(filter)s, apikey="%(blocklist_de_apikey)s", agent="%(fail2ban_agent)s"]
action_blocklist_de = blocklist_de[email="%(sender)s", service="%(__name__)s", apikey="%(blocklist_de_apikey)s", agent="%(fail2ban_agent)s"]
# Report ban via badips.com, and use as blacklist
#
@ -371,7 +371,7 @@ maxretry = 1
[openhab-auth]
filter = openhab
action = iptables-allports[name=NoAuthFailures]
banaction = %(banaction_allports)s
logpath = /opt/openhab/logs/request.log
@ -478,6 +478,7 @@ backend = %(syslog_backend)s
port = http,https
logpath = /var/log/tomcat*/catalina.out
#logpath = /var/log/guacamole.log
[monit]
#Ban clients brute-forcing the monit gui login
@ -744,8 +745,8 @@ logpath = /var/log/named/security.log
[nsd]
port = 53
action = %(banaction)s[name=%(__name__)s-tcp, port="%(port)s", protocol="tcp", chain="%(chain)s", actname=%(banaction)s-tcp]
%(banaction)s[name=%(__name__)s-udp, port="%(port)s", protocol="udp", chain="%(chain)s", actname=%(banaction)s-udp]
action_ = %(default/action_)s[name=%(__name__)s-tcp, protocol="tcp"]
%(default/action_)s[name=%(__name__)s-udp, protocol="udp"]
logpath = /var/log/nsd.log
@ -756,9 +757,8 @@ logpath = /var/log/nsd.log
[asterisk]
port = 5060,5061
action = %(banaction)s[name=%(__name__)s-tcp, port="%(port)s", protocol="tcp", chain="%(chain)s", actname=%(banaction)s-tcp]
%(banaction)s[name=%(__name__)s-udp, port="%(port)s", protocol="udp", chain="%(chain)s", actname=%(banaction)s-udp]
%(mta)s-whois[name=%(__name__)s, dest="%(destemail)s"]
action_ = %(default/action_)s[name=%(__name__)s-tcp, protocol="tcp"]
%(default/action_)s[name=%(__name__)s-udp, protocol="udp"]
logpath = /var/log/asterisk/messages
maxretry = 10
@ -766,9 +766,8 @@ maxretry = 10
[freeswitch]
port = 5060,5061
action = %(banaction)s[name=%(__name__)s-tcp, port="%(port)s", protocol="tcp", chain="%(chain)s", actname=%(banaction)s-tcp]
%(banaction)s[name=%(__name__)s-udp, port="%(port)s", protocol="udp", chain="%(chain)s", actname=%(banaction)s-udp]
%(mta)s-whois[name=%(__name__)s, dest="%(destemail)s"]
action_ = %(default/action_)s[name=%(__name__)s-tcp, protocol="tcp"]
%(default/action_)s[name=%(__name__)s-udp, protocol="udp"]
logpath = /var/log/freeswitch.log
maxretry = 10
@ -853,11 +852,23 @@ logpath = /var/log/ejabberd/ejabberd.log
[counter-strike]
logpath = /opt/cstrike/logs/L[0-9]*.log
# Firewall: http://www.cstrike-planet.com/faq/6
tcpport = 27030,27031,27032,27033,27034,27035,27036,27037,27038,27039
udpport = 1200,27000,27001,27002,27003,27004,27005,27006,27007,27008,27009,27010,27011,27012,27013,27014,27015
action = %(banaction)s[name=%(__name__)s-tcp, port="%(tcpport)s", protocol="tcp", chain="%(chain)s", actname=%(banaction)s-tcp]
%(banaction)s[name=%(__name__)s-udp, port="%(udpport)s", protocol="udp", chain="%(chain)s", actname=%(banaction)s-udp]
action_ = %(default/action_)s[name=%(__name__)s-tcp, port="%(tcpport)s", protocol="tcp"]
%(default/action_)s[name=%(__name__)s-udp, port="%(udpport)s", protocol="udp"]
[softethervpn]
port = 500,4500
protocol = udp
logpath = /usr/local/vpnserver/security_log/*/sec.log
[gitlab]
port = http,https
logpath = /var/log/gitlab/gitlab-rails/application.log
[grafana]
port = http,https
logpath = /var/log/grafana/grafana.log
[bitwarden]
port = http,https
@ -909,8 +920,8 @@ findtime = 1
[murmur]
# AKA mumble-server
port = 64738
action = %(banaction)s[name=%(__name__)s-tcp, port="%(port)s", protocol=tcp, chain="%(chain)s", actname=%(banaction)s-tcp]
%(banaction)s[name=%(__name__)s-udp, port="%(port)s", protocol=udp, chain="%(chain)s", actname=%(banaction)s-udp]
action_ = %(default/action_)s[name=%(__name__)s-tcp, protocol="tcp"]
%(default/action_)s[name=%(__name__)s-udp, protocol="udp"]
logpath = /var/log/mumble-server/mumble-server.log

91
debian/NEWS vendored
View File

@ -1,91 +0,0 @@
fail2ban (0.10.2-1) unstable; urgency=medium
This version is a major development leap forward to provide
IPv6 support, which also required extensions to the configuration
system. That is why it is not unlikely that configuration left from the
previous version(s) would either not work or would not work as intended.
You are advised to accept new configuration and adjust it for your
customizations (if any). See changelog.Debian.gz for more information.
-- Yaroslav Halchenko <debian@onerussian.com> Sun, 21 Jan 2018 22:25:26 -0500
fail2ban (0.9.0+git48-gabcab00-1) experimental; urgency=low
[ Yaroslav Halchenko ]
* This version went through big refactoring which allowed to gain new
features such as multiline matching (see upstream's changelog for more
information).
* Although .local files are still supported, customizations are advised
to be provided under corresponding .d/ directories. E.g. see
/etc/fail2ban/jail.d/defaults-debian.conf which is where now sshd
jail is enabled by default to match previous behavior of Fail2Ban in
Debian.
[ Daniel Schaal ]
* All jails definitions were rewritten to become more concise and uniform.
From this version on log paths are defined in distro specific files,
for Debian this is in /etc/fail2ban/paths-debian.conf.
-- Yaroslav Halchenko <debian@onerussian.com> Tue, 25 Mar 2014 08:38:31 -0400
fail2ban (0.8.11-1) unstable; urgency=low
* retroactive for 0.8.9: by default iptables-* actions do not simply
DROP packets from offending IP but rather reject with
icmp-port-unreachable. If DROP behaviour is preferable, provide
config/action.d/iptables-blocktype.local with [Init] section defining
blocktype = DROP or override action definition to provide
blocktype=DROP option in jail.local
* Many failregex's were tight-up in this release which could
theoretically effect operation in comparison to previous release(s).
-- Yaroslav Halchenko <debian@onerussian.com> Sat, 16 Nov 2013 22:27:50 -0500
fail2ban (0.8.4-3) unstable; urgency=low
* Jail named-refused-udp is unsafe and opens possibility for easy DoS,
thus discouraged to be used, and commented out (see #583364 for more
information).
-- Yaroslav Halchenko <debian@onerussian.com> Mon, 28 Jun 2010 22:12:22 -0400
fail2ban (0.7.1-0.2) unstable; urgency=low
fail2ban 0.7 is a complete rewrite of the 0.6 version, and if you
customized any of provided configuration or startup files
(/etc/default/fail2ban, /etc/fail2ban.conf, /etc/init.d/fail2ban),
please read further. The configuration scheme has changed upstream:
0.7 ignores /etc/fail2ban.conf and instead uses a split configuration
under /etc/fail2ban/. To retain your customizations, for example to
monitor anything other than sshd, you will need to set them under that
new directory; use *.local files for customizations. Please see
/usr/share/doc/fail2ban/README.Debian.gz and
http://fail2ban.sourceforge.net for further description of new
configuration scheme. Detailed documentation is under development (see
#400416). When you are satisfied with the new settings, please delete
/etc/fail2ban.conf to avoid confusion.
Fail2ban 0.7 uses client/server architecture and fail2ban-client is to
substitute fail2ban command to provide an interface between the user and
fail2ban-server. That is why some command line parameters present in
fail2ban 0.6 are invalid in fail2ban-client. Such change affects
/etc/default/fail2ban; you should review that file if you customized it.
Please enable sections as directed in README.Debian.gz mentioned above.
You must use newly shipped init.d/fail2ban, or otherwise fail2ban will
not start.
This note was rewritten in release 0.7.5-2 to clarify its meaning.
-- Yaroslav Halchenko <debian@onerussian.com> Sat, 9 Dec 2006 18:24:36 -0500
fail2ban (0.6.0-4) unstable; urgency=low
In this version the new section ApacheAttacks was introduced to ban IPs
which are found to run some known attack on the host. For now it captures
just awstats and mambo related attacks. To make this feature work, the bug of
wrongly specified timeregexp for Apache's access.log file was fixed.
Besides that group of log files has changed to be adm, and now they are
readable by the group.
-- Yaroslav Halchenko <debian@onerussian.com> Fri, 10 Feb 2006 13:05:07 -0500

251
debian/README.Debian vendored
View File

@ -1,251 +0,0 @@
fail2ban (>=0.7.0) for Debian
-----------------------------
This package is ~99% identical to the upstream version. Few features
could have been added but not yet propagated into upstream version and
some modifications might be Debian-specific. Debian specific jail.conf
file is shipped. Original upstream file is available from
/usr/share/doc/fail2ban/examples/jail.conf
Currently, the major difference with upstream: python libraries are
placed under /usr/share/fail2ban instead of /usr/lib/fail2ban to
comply with policy regarding architecture independent resources.
fail2ban supports both nftables and iptables.
Shorewall and startup sequence (#847728)
----------------------------------------
If you are using systemd, create a
/etc/systemd/system/fail2ban.service.d/override.conf with contents:
[Unit]
Requires=shorewall.service
After=shorewall.service
go guarantee a proper sequence of startup/shutdown (shorewall should
be started before fail2ban, and stopped after). Similar settings
could be adopted for other firewall solutions.
Upgrade from 0.6 versions:
-------------------------
* New Config Files Format:
If you had introduced your own sections in /etc/fail2ban.conf, you
would need manually to convert them into a new format. At minimum you
need to create /etc/fail2ban/filter.d/NAME.local (leave .conf files
for me and upstream please to avoid any conflicts -- introduce your
changes in .local) with failregex in [Definition] section. And provide
appropriate jail definition in /etc/fail2ban/jail.local
* Enabled Sections:
Only handling of ssh files is enabled by default. If you want to use
fail2ban with apache, please enable apache section manually in
/etc/fail2ban/jail.local by including next lines:
[apache]
enabled = true
NOTE: -e command line parameter is non existent in 0.7.x
* Interpolations vs actions/filters parameters:
For details see #398739 or wait for a closure of #400416
Every pair of .conf and then .local (if exists) files is read
separately from any other configuration file, so interpolations cannot
penetrate from jail.* into actions.d/*. To overcome this, it is
necessary to create a PARAMETER which can be substituted in actions
[Definition] section, if it is also defined in the [Init] section of
that file and is used in place of necessary allocation as <PARAMETER>
tag. Parameters can be specified in the definitions within
jail.{conf,local}. For instance, 1 lengthy example, where the same
name "fwchain" is used both as interpolation (in jail.local) and as a
parameter (in iptables-flex.local) (from #398739)
==> /etc/fail2ban/jail.local <==
[DEFAULT]
action = iptables-flex[name=%(__name__)s, port=%(port)s, fwchain=%(fwchain)s, post_start_commands=%(post_start_commands)s, pre_end_commands=%(pre_end_commands)s]
fwchain = INPUT
[ssh]
fwchain = ssh-tarpit
==> /etc/fail2ban/action.d/iptables-flex.local <==
[Definition]
actionstart = iptables -N fail2ban-<name>
iptables -I <fwchain> -m state --state NEW -p <protocol> --dport <port> -j fail2ban-<name>
iptables -I <fwchain> -j <whitelist>
actionstop = iptables -D <fwchain> -j <whitelist>
iptables -D <fwchain> -m state --state NEW -p <protocol> --dport <port> -j fail2ban-<name>
iptables -F fail2ban-<name>
iptables -X fail2ban-<name>
actioncheck = iptables -n -L <fwchain> | grep -q fail2ban-<name>
actionban = iptables -I fail2ban-<name> 1 -s <ip> -j DROP
actionunban = iptables -D fail2ban-<name> -s <ip> -j DROP
[Init]
whitelist = ssh-whitelist
fwchain = INPUT
name = default
port = ssh
protocol = tcp
* Multiport banning: Comment for #373592, #545971
iptables-multiport action is now default banaction (file jail.conf, to
be customized within jail.local). Therefore assure that you have built
multiport module if you use custom kernel.
If you would like to ban all ports for that host, just redefine
fwban/fwunban commands to don't have --dport %(port)s statement at
all, or use shorewall, where actionban bans whole IP.
* Blocking of NEW connections only
Comment for the wishlist #350746.
It might be benefitial in some cases to ban only new connections. For
that just use iptables-new action instead of default banaction
/etc/fail2ban/jail.local:
[DEFAULT]
banaction=iptables-new
(you can override banaction within interesting for you section).
Also you can redefine the whole action parameter if you like.
* Interaction with ipmasq
Comment to #461417
Although fail2ban should detect and recreate missing chains if the external
command wipes out iptables, it is better to explicitly to force-reload
fail2ban. For this reason there is examples/ipmasq-ZZZzzz|fail2ban.rul file is
shipped along to be installed under name ZZZzzz|fail2ban.rul within
/etc/ipmasq.
* Interaction with logrotate with custom logtarget
Comment to #631917
if you use an alternative logtarget (e.g. SYSLOG) thus not using
/var/log/fail2ban.log you should divert logrotate configuration into
a disabled state, e.g.
sudo dpkg-divert --rename --divert \
/etc/logrotate.d/fail2ban.disabled /etc/logrotate.d/fail2ban
Troubleshooting:
---------------
* Updated failregex:
To resolve the security bug #330827 [1] failregex expressions must
provide a named group (?P<host>...) as a placeholder of the abuser's
host. Alternative tag (since 0.7.5) can be "<HOST>". The naming of the
group was introduced to capture possible future generalizations of
failregex to provide even more information.
[1] http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=330827
You might benefit from using fail2ban-regex command shipped along to
construct and debug your failregex statements.
* "Interpolations" in the config file:
Since version 0.6.0-3 to reduce duplication, thus to improve
readability of the config file, interpolations provided by the module
ConfigParser are used. If you had custom sections defined before, you
might benefit from updating config file and adding appropriate
information for the new sections.
N.B. If you have some nice additional sections defined, I would really
appreciate if you share them with me or upstream author, so they could
be eventually included in the fail2ban package for general use by the
rest of the community.
* Mailing:
Since actions.d/mail*.conf commands rely on presence of "mail"
command, mailx package (or another package providing mailx
functionality such as mailutils) is required if those actions are
activated in jail.{conf,local}.
* Dirty exit:
If firewall rules gets cleaned out before fail2ban exits (like was
happening with firestarter), errors get reported during the exit of
fail2ban, but they are "safe" and can be ignored.
** SSHD Configuration Specific Problems
* Ban "Not allowed" attempts:
Make sure that you have
ChallengeResponseAuthentication no
PasswordAuthentication yes
Details from the bug report #350980 [2]
[2] http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=350980
* Not caught attempts to login as root
On the boxes running older versions of openssh (e.g. sarge
distribution) in the case when PermitRootLogin is set to something
else than "yes" and iff AllowUsers is active, failed root logins do
not confirm to the standard logging message -- they omit the source
IP, thus allowing attack to persist since such messages are not caught
by fail2ban.
* Bantime:
An IP is banned for "bantime" not since the last failed login attempt
from the IP, but rather since the moment when failed login was
detected by fail2ban. Thus, if fail2ban gets [re]started, any IP which
had enough of failed logins with durations less than "findtime" between
them prior to the [re]start moment, will be banned for
"bantime" since [re]start moment, not since the last failed login
time.
* Findtime:
"Findtime" option of a jail actually defines a duration to reset the
counter of failed login attempts, if no new attempt was detected within
that time frame (i.e. within "findtime").
See
http://www.fail2ban.org/wiki/index.php/MANUAL_0_8#Jail_Options
for more information on jail options.
* Syslog entries can be 'forged' by a regular user
From
http://www.fail2ban.org/wiki/index.php/MANUAL_0_8#Possibility_of_DOS_attack_by_a_local_user
Especially on systems which provide ssh/CGI/PHP services to unknown
users it is possible to block other users from ssh and probably other
access as a unprivileged user may issue:
logger -p auth.warning -t 'sshd[123]' 'Illegal user user1 from 1.2.3.4'
N.B. chmod o-x /usr/bin/logger should provide at least obfuscation
solution
Or the malicious user may write via PHP's openlog()/syslog() to syslog.
P.S. Anyone is welcome to recommend proper security solution to this
issue, such as an alternative to sysklogd which allows better control
over users logging to specific facilities (such as AUTH)
-- Yaroslav Halchenko <debian@onerussian.com>, Mon, 22 Jan 2018 10:37:00 -0500

10
debian/TODO vendored
View File

@ -1,10 +0,0 @@
* completions installation
W: fail2ban: package-installs-into-obsolete-dir etc/bash_completion.d/ : ^etc/bash_completion.d/ -> usr/share/bash-completion/completions (see also https://bugs.debian.org/776954)
W: fail2ban: package-installs-into-obsolete-dir etc/bash_completion.d/fail2ban : ^etc/bash_completion.d/ -> usr/share/bash-completion/completions (see also https://bugs.debian.org/776954)
* Find proper answer to "Syslog entries can be 'forged' by a regular
user" mentioned in README.Debian
-- Yaroslav O. Halchenko <debian@onerussian.com> Wed, 6 Dec 2006 22:14:26 -0500

View File

@ -1 +0,0 @@
nopycentral.patch

View File

@ -1,40 +0,0 @@
diff -x '*~' -x .svn -Naur trunk/debian/control trunk.backports/debian/control
--- trunk/debian/control 2006-10-23 00:57:02.000000000 -0400
+++ trunk.backports/debian/control 2006-12-04 08:45:25.000000000 -0500
@@ -4,13 +4,13 @@
Maintainer: Yaroslav Halchenko <debian@onerussian.com>
Uploaders: Barak Pearlmutter <bap@debian.org>
Build-Depends: debhelper (>= 5.0.37.2), dpatch
-Build-Depends-Indep: python, python-dev, help2man, python-central (>= 0.5.6)
+Build-Depends-Indep: python, python2.4, python2.4-dev, help2man
XS-Python-Version: current, >= 2.4
Standards-Version: 3.7.2
Package: fail2ban
Architecture: all
-Depends: ${python:Depends}, iptables, lsb-base (>=2.0-7)
+Depends: python2.4, iptables, lsb-base (>=2.0-7)
Suggests: python-gamin
XB-Python-Version: ${python:Versions}
Description: bans IPs that cause multiple authentication errors
diff -x '*~' -x .svn -Naur trunk/debian/rules trunk.backports/debian/rules
--- trunk/debian/rules 2006-11-11 21:19:14.000000000 -0500
+++ trunk.backports/debian/rules 2006-12-04 08:45:45.000000000 -0500
@@ -39,7 +39,7 @@
dh_installdirs
# Add here commands to install the package into debian/fail2ban.
- python setup.py install --root=$(DESTDIR) --no-compile
+ python2.4 setup.py install --root=$(DESTDIR) --no-compile
#X Evil - must be removed after Debian switches over to 2.4, now
# distutils.setup will override the enterpreter line to /usr/bin/python
install fail2ban-server fail2ban-client $(DESTDIR)/usr/bin
@@ -62,7 +62,7 @@
dh_installlogrotate
dh_installinit -- defaults 99
dh_installman man/*.1
- dh_pycentral
+ dh_python
dh_link
dh_compress
dh_fixperms

1405
debian/changelog vendored

File diff suppressed because it is too large Load Diff

47
debian/control vendored
View File

@ -1,47 +0,0 @@
Source: fail2ban
Section: net
Priority: optional
Maintainer: Debian Python Team <team+python@tracker.debian.org>
Uploaders: Yaroslav Halchenko <debian@onerussian.com>,
Sylvestre Ledru <sylvestre@debian.org>
Build-Depends:
debhelper-compat (= 12)
, debhelper (>= 9.20160709)
, dh-python
, python3
, python3-setuptools
, python3-pyinotify
, sqlite3
, 2to3
Homepage: https://www.fail2ban.org
Vcs-Git: https://salsa.debian.org/python-team/packages/fail2ban.git
Vcs-Browser: https://salsa.debian.org/python-team/packages/fail2ban
Standards-Version: 4.5.0
Package: fail2ban
Architecture: all
Depends: ${python3:Depends}, ${misc:Depends}, lsb-base (>=2.0-7)
Recommends: nftables | iptables, whois, python3-pyinotify, python3-systemd
Suggests: mailx, system-log-daemon, monit, sqlite3
Description: ban hosts that cause multiple authentication errors
Fail2ban monitors log files (e.g. /var/log/auth.log,
/var/log/apache/access.log) and temporarily or persistently bans
failure-prone addresses by updating existing firewall rules. Fail2ban
allows easy specification of different actions to be taken such as to ban
an IP using iptables or hostsdeny rules, or simply to send a notification
email.
.
By default, it comes with filter expressions for various services
(sshd, apache, proftpd, sasl, etc.) but configuration can be
easily extended for monitoring any other text file. All filters and
actions are given in the config files, thus fail2ban can be adopted
to be used with a variety of files and firewalls. Following recommends
are listed:
.
- iptables/nftables -- default installation uses iptables for banning.
nftables is also supported. You most probably need it
- whois -- used by a number of *mail-whois* actions to send notification
emails with whois information about attacker hosts. Unless you will use
those you don't need whois
- python3-pyinotify -- unless you monitor services logs via systemd, you
need pyinotify for efficient monitoring for log files changes

31
debian/copyright vendored
View File

@ -1,31 +0,0 @@
This package was originally debianized by Yaroslav Halchenko
<debian@onerussian.com> on Mon Jul 4 14:41:34 HST 2005
It was downloaded from https://www.fail2ban.org
Original author: Cyril Jaquier: <cyril.jaquier@fail2ban.org>
https://www.fail2ban.org
Copyright: 2004-2009 Cyril Jaquier
many others since then
This program 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.
This program 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 this program; if not, write to the
Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
MA 02110-1301, USA.
On Debian systems, the complete text of the GNU General Public
License, version 2, can be found in /usr/share/common-licenses/GPL-2.
The Debian packaging is (C) 2006-2018, Yaroslav Halchenko <debian@onerussian.com>
and is licensed under the GPL, see above.

View File

@ -1,2 +0,0 @@
[sshd]
enabled = true

3
debian/docs vendored
View File

@ -1,3 +0,0 @@
README.md
TODO
doc/run-rootless.txt

View File

@ -1,39 +0,0 @@
# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Author: Cyril Jaquier
#
# $Revision$
# Command line options for Fail2Ban. Refer to "fail2ban-client -h" for
# valid options.
FAIL2BAN_OPTS=""
# Run fail2ban as a different user. If not set, fail2ban
# will run as root.
#
# The user is not created automatically.
# The user can be created e.g. with
# useradd --system --no-create-home --home-dir / --groups adm fail2ban
# Log files are readable by group adm by default. Adding the fail2ban
# user to this group allows it to read the logfiles.
#
# Another manual step that needs to be taken is to allow write access
# for fail2ban user to fail2ban log files. The /etc/init.d/fail2ban
# script will change the ownership when starting fail2ban. Logrotate
# needs to be configured separately, see /etc/logrotate.d/fail2ban.
#
# FAIL2BAN_USER="fail2ban"

View File

@ -1,19 +0,0 @@
/var/log/fail2ban.log {
weekly
rotate 4
compress
# Do not rotate if empty
notifempty
delaycompress
missingok
postrotate
fail2ban-client flushlogs 1>/dev/null
endscript
# If fail2ban runs as non-root it still needs to have write access
# to logfiles.
# create 640 fail2ban adm
create 640 root adm
}

18
debian/gbp.conf vendored
View File

@ -1,18 +0,0 @@
[DEFAULT]
# the default branch for upstream sources:
upstream-branch = upstream
# the default branch for the debian patch:
debian-branch = debian-releases/experimental
# use pristine-tar
# pristine-tar = True
# the default tag formats used:
upstream-tag = %(version)s
debian-tag = debian/%(version)s
# Options only affecting git-buildpackage
[git-buildpackage]
# use this for more svn-buildpackage like behaviour:
export-dir = ../build-area/
tarball-dir = ../tarballs/

View File

@ -1,25 +0,0 @@
From d1afbb566f0304487b5d578b4aacef8e647ee74b Mon Sep 17 00:00:00 2001
From: Yaroslav Halchenko <debian@onerussian.com>
Date: Sun, 21 Jan 2018 20:16:43 -0500
Subject: [PATCH 2/4] ENH: verify that use_stock_cfg was not provided while
overriding it
Just found this possibly confusing to outside programmer aspect
so decided to make it more explicit
---
fail2ban/tests/fail2banclienttestcase.py | 2 ++
1 file changed, 2 insertions(+)
Index: fail2ban/fail2ban/tests/fail2banclienttestcase.py
===================================================================
--- fail2ban.orig/fail2ban/tests/fail2banclienttestcase.py
+++ fail2ban/fail2ban/tests/fail2banclienttestcase.py
@@ -173,6 +173,8 @@ def _start_params(tmp, use_stock=False,
"""Filters list of 'files' to contain only directories (under dir)"""
return [f for f in files if isdir(pjoin(dir, f))]
shutil.copytree(STOCK_CONF_DIR, cfg, ignore=ig_dirs)
+ assert use_stock_cfg is None, \
+ "We are about to overload use_stock_cfg from the one provided %s" % repr(use_stock_cfg)
if use_stock_cfg is None: use_stock_cfg = ('action.d', 'filter.d')
# replace fail2ban params (database with memory):
r = re.compile(r'^dbfile\s*=')

View File

@ -1,13 +0,0 @@
Index: fail2ban/files/debian-initd
===================================================================
--- fail2ban.orig/files/debian-initd
+++ fail2ban/files/debian-initd
@@ -28,7 +28,7 @@ NAME="fail2ban"
# fail2ban-client is not a daemon itself but starts a daemon and
# loads its with configuration
-DAEMON="/usr/local/bin/$NAME-client"
+DAEMON="/usr/bin/$NAME-client"
SCRIPTNAME="/etc/init.d/$NAME"
# Ad-hoc way to parse out socket file name

View File

@ -1,30 +0,0 @@
From: Yaroslav Halchenko <debian@onerussian.com>
Date: Fri, 8 Feb 2008 00:40:57 -0500
Subject: tune ups in upstream manpages to direct users to use reportbug
Index: fail2ban/man/fail2ban-client.1
===================================================================
--- fail2ban.orig/man/fail2ban-client.1
+++ fail2ban/man/fail2ban-client.1
@@ -470,7 +470,7 @@ the action <ACT> for <JAIL>
.SH FILES
\fI/etc/fail2ban/*\fR
.SH "REPORTING BUGS"
-Report bugs to https://github.com/fail2ban/fail2ban/issues
+Report bugs via Debian bug tracking system \fIhttp://www.debian.org/Bugs/\fR .
.SH "SEE ALSO"
.br
fail2ban-server(1)
Index: fail2ban/man/fail2ban-server.1
===================================================================
--- fail2ban.orig/man/fail2ban-server.1
+++ fail2ban/man/fail2ban-server.1
@@ -69,7 +69,7 @@ display this help message
\fB\-V\fR, \fB\-\-version\fR
print the version (\fB\-V\fR returns machine\-readable short format)
.SH "REPORTING BUGS"
-Report bugs to https://github.com/fail2ban/fail2ban/issues
+Report bugs via Debian bug tracking system \fIhttp://www.debian.org/Bugs/\fR .
.SH "SEE ALSO"
.br
fail2ban-client(1)

View File

@ -1,30 +0,0 @@
From: Yaroslav Halchenko <debian@onerussian.com>
Subject: Remove all non-provided .service's within PartOf of fail2ban.service
As reported and corroborated in the bug report, this causes inability
of firewalld to restart.
Correct solution would involve making systemd smarter and tune up
of involved .service files.
Since Debian ATM doesn't provide any of those ({ip{,6}tables,ipset}.service)
files, it should be safe and generic enough to just prune them from PartOf
Thanks Joe Cooper <swelljoe@gmail.com> and Sunil Mohan Adapa <sunil@medhas.org>
for the reports and nagging ;)
Origin: Fedora, Debian
Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=871993
Last-Update: 2018-04-04
Index: fail2ban/files/fail2ban.service.in
===================================================================
--- fail2ban.orig/files/fail2ban.service.in
+++ fail2ban/files/fail2ban.service.in
@@ -2,7 +2,7 @@
Description=Fail2Ban Service
Documentation=man:fail2ban(1)
After=network.target iptables.service firewalld.service ip6tables.service ipset.service nftables.service
-PartOf=iptables.service firewalld.service ip6tables.service ipset.service nftables.service
+PartOf=firewalld.service
[Service]
Type=simple

View File

@ -1,22 +0,0 @@
--- a/fail2ban/tests/config/filter.d/zzz-generic-example.conf
+++ b/fail2ban/tests/config/filter.d/zzz-generic-example.conf
@@ -8,7 +8,7 @@
# Read common prefixes. If any customizations available -- read them from
# common.local. common.conf is a symlink to the original common.conf and
# should be copied (dereferenced) during installation
-before = ../../../../config/filter.d/common.conf
+before = ../../../../../../../config/filter.d/common.conf
[Definition]
--- a/fail2ban/tests/config/filter.d/zzz-sshd-obsolete-multiline.conf
+++ b/fail2ban/tests/config/filter.d/zzz-sshd-obsolete-multiline.conf
@@ -5,7 +5,7 @@
# Read common prefixes. If any customizations available -- read them from
# common.local
-before = ../../../../config/filter.d/common.conf
+before = ../../../../../../../config/filter.d/common.conf
[DEFAULT]

View File

@ -1,12 +0,0 @@
Index: fail2ban/files/fail2ban.service.in
===================================================================
--- fail2ban.orig/files/fail2ban.service.in
+++ fail2ban/files/fail2ban.service.in
@@ -15,6 +15,7 @@ ExecReload=@BINDIR@/fail2ban-client relo
PIDFile=/run/fail2ban/fail2ban.pid
Restart=on-failure
RestartPreventExitStatus=0 255
+Environment="PYTHONNOUSERSITE=yes"
[Install]
WantedBy=multi-user.target

View File

@ -1,74 +0,0 @@
Index: fail2ban/fail2ban/server/jailthread.py
===================================================================
--- fail2ban.orig/fail2ban/server/jailthread.py
+++ fail2ban/fail2ban/server/jailthread.py
@@ -120,3 +120,6 @@ class JailThread(Thread):
## python 2.x replace binding of private __bootstrap method:
if sys.version_info < (3,): # pragma: 3.x no cover
JailThread._Thread__bootstrap = JailThread._JailThread__bootstrap
+## python 3.9, restore isAlive method:
+elif not hasattr(JailThread, 'isAlive'): # pragma: 2.x no cover
+ JailThread.isAlive = JailThread.is_alive
Index: fail2ban/fail2ban/tests/sockettestcase.py
===================================================================
--- fail2ban.orig/fail2ban/tests/sockettestcase.py
+++ fail2ban/fail2ban/tests/sockettestcase.py
@@ -83,11 +83,11 @@ class Socket(LogCaptureTestCase):
serverThread.start()
self.assertTrue(Utils.wait_for(self.server.isActive, unittest.F2B.maxWaitTime(10)))
return serverThread
-
+
def _stopServerThread(self):
serverThread = self.serverThread
# wait for end of thread :
- Utils.wait_for(lambda: not serverThread.isAlive()
+ Utils.wait_for(lambda: not serverThread.is_alive()
or serverThread.join(Utils.DEFAULT_SLEEP_TIME), unittest.F2B.maxWaitTime(10))
self.serverThread = None
@@ -98,7 +98,7 @@ class Socket(LogCaptureTestCase):
self.server.close()
# wait for end of thread :
self._stopServerThread()
- self.assertFalse(serverThread.isAlive())
+ self.assertFalse(serverThread.is_alive())
# clean :
self.server.stop()
self.assertFalse(self.server.isActive())
@@ -139,7 +139,7 @@ class Socket(LogCaptureTestCase):
self.server.stop()
# wait for end of thread :
self._stopServerThread()
- self.assertFalse(serverThread.isAlive())
+ self.assertFalse(serverThread.is_alive())
self.assertFalse(self.server.isActive())
self.assertFalse(os.path.exists(self.sock_name))
@@ -149,7 +149,7 @@ class Socket(LogCaptureTestCase):
client = Utils.wait_for(self._serverSocket, 2)
# unexpected stop during message body:
testMessage = ["A", "test", "message", [protocol.CSPROTO.END]]
-
+
org_handler = RequestHandler.found_terminator
try:
RequestHandler.found_terminator = lambda self: self.close()
@@ -180,7 +180,7 @@ class Socket(LogCaptureTestCase):
self.server.stop()
# wait for end of thread :
self._stopServerThread()
- self.assertFalse(serverThread.isAlive())
+ self.assertFalse(serverThread.is_alive())
def testLoopErrors(self):
# replace poll handler to produce error in loop-cycle:
@@ -216,7 +216,7 @@ class Socket(LogCaptureTestCase):
self.server.stop()
# wait for end of thread :
self._stopServerThread()
- self.assertFalse(serverThread.isAlive())
+ self.assertFalse(serverThread.is_alive())
self.assertFalse(self.server.isActive())
self.assertFalse(os.path.exists(self.sock_name))

View File

@ -1,10 +0,0 @@
Index: fail2ban/bin/fail2ban-testcases
===================================================================
--- fail2ban.orig/bin/fail2ban-testcases
+++ fail2ban/bin/fail2ban-testcases
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
# vi: set ft=python sts=4 ts=4 sw=4 noet :
"""Script to run Fail2Ban tests battery

View File

@ -1,11 +0,0 @@
--- fail2ban-0.11.1.orig/config/filter.d/roundcube-auth.conf
+++ fail2ban-0.11.1/config/filter.d/roundcube-auth.conf
@@ -18,7 +18,7 @@ prefregex = ^\s*(\[\])?(%(__hostname)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\))?$
-ignoreregex =
+ignoreregex = Could not connect to .* Connection refused
journalmatch = SYSLOG_IDENTIFIER=roundcube

View File

@ -1,9 +0,0 @@
deb_path_to_common
deb_init_paths
deb_manpages_reportbug
0002-ENH-verify-that-use_stock_cfg-was-not-provided-while.patch
deb_no_iptables_service
python3-test-suite.diff
no-python-user.diff
python-3.9.patch
roundcube.diff

98
debian/postinst vendored
View File

@ -1,98 +0,0 @@
#! /bin/sh
# postinst script for fail2ban
#
# see: dh_installdeb(1)
set -e
# summary of how this script can be called:
# * <postinst> `configure' <most-recently-configured-version>
# * <old-postinst> `abort-upgrade' <new version>
# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
# <new-version>
# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
# <failed-install-package> <version> `removing'
# <conflicting-package> <version>
# for details, see http://www.debian.org/doc/debian-policy/ or
# the debian-policy package
#
preversion=$2
case "$1" in
configure)
# To fix the bug in generated by previous version files permissions
# also closes #352053
LOG=/var/log/fail2ban.log
touch $LOG
chown root:adm ${LOG}*
chmod 640 ${LOG}*
# Note regarding changed configuration file
# Note regarding changed configuration file
if [ ! -z $preversion ]; then
if dpkg --compare-versions $preversion lt 0.7.1-1; then
cat <<EOF
WARNING!
Fail2ban 0.7 is a complete rewrite of the 0.6 version, and if you
customized any of provided configuration or startup files
(/etc/default/fail2ban, /etc/fail2ban.conf, /etc/init.d/fail2ban), please
read relevant entry in /usr/share/doc/fail2ban/NEWS.Debian.gz.
EOF
fi
if dpkg --compare-versions $preversion lt 0.5.4-5.14; then
cat <<EOF
WARNING!
Configuration file /etc/fail2ban.conf, failregex configuration
parameter specifically, were changed in 0.5.4-5 to close reported
security breach, and in 0.5.4-5.14 to close few other bugs.
updating from <0.5.4-5
Unless configuration file (or corresponding failregex'es) gets updated,
security breach is not closed and corresponding warning will be reported
by the fail2ban (in the log files).
updating from <0.5.4-5.14
Bugs #329163, #331695 dealing with changed iptables rules
outside of fail2ban were fixed in 0.5.4-5.14, and require upgrade of the
configuration file (fwcheck option was introduced) to take full
advantage of the problem solution (otherwise some problems might
persist)
Please review the configuration file and make appropriate changes.
ENJOY!
EOF
fi
fi
;;
abort-upgrade|abort-remove|abort-deconfigure)
;;
*)
echo "postinst called with unknown argument \`$1'" >&2
exit 1
;;
esac
if dpkg-maintscript-helper supports mv_conffile 2>/dev/null; then
dpkg-maintscript-helper mv_conffile /etc/fail2ban/action.d/firewall-cmd-direct-new.conf /etc/fail2ban/action.d/firewallcmd-new.conf 0.8.13-1~ -- "$@"
dpkg-maintscript-helper mv_conffile /etc/fail2ban/filter.d/lighttpd-fastcgi.conf /etc/fail2ban/filter.d/suhosin.conf 0.8.13-1~ -- "$@"
dpkg-maintscript-helper mv_conffile /etc/fail2ban/filter.d/sasl.conf /etc/fail2ban/filter.d/postfix-sasl.conf 0.8.13-1~ -- "$@"
dpkg-maintscript-helper mv_conffile /etc/fail2ban/filter.d/couriersmtp.conf /etc/fail2ban/filter.d/courier-smtp.conf 0.9.0-1~ -- "$@"
dpkg-maintscript-helper mv_conffile /etc/fail2ban/filter.d/courierlogin.conf /etc/fail2ban/filter.d/courier-auth.conf 0.9.0-1~ -- "$@"
fi
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
#DEBHELPER#
exit 0

52
debian/postrm vendored
View File

@ -1,52 +0,0 @@
#! /bin/sh
# postrm script for fail2ban
#
# see: dh_installdeb(1)
set -e
# summary of how this script can be called:
# * <postrm> `remove'
# * <postrm> `purge'
# * <old-postrm> `upgrade' <new-version>
# * <new-postrm> `failed-upgrade' <old-version>
# * <new-postrm> `abort-install'
# * <new-postrm> `abort-install' <old-version>
# * <new-postrm> `abort-upgrade' <old-version>
# * <disappearer's-postrm> `disappear' <r>overwrit>r> <new-version>
# for details, see /usr/doc/packaging-manual/
case "$1" in
purge|disappear)
# Remove configuration
rm -f /etc/fail2ban.conf
# Remove logs
rm -f /var/log/fail2ban*
# Remove sqlite db
rm -f /var/lib/fail2ban/fail2ban.sqlite3
;;
remove|upgrade|failed-upgrade|abort-install|abort-upgrade)
# nothing
# We may not delete the user fail2ban, as there may be
# files owned by it in /var/log/ and /etc/.
;;
esac
if dpkg-maintscript-helper supports mv_conffile 2>/dev/null; then
dpkg-maintscript-helper mv_conffile /etc/fail2ban/action.d/firewall-cmd-direct-new.conf /etc/fail2ban/action.d/firewallcmd-new.conf 0.8.13-1~ -- "$@"
dpkg-maintscript-helper mv_conffile /etc/fail2ban/filter.d/lighttpd-fastcgi.conf /etc/fail2ban/filter.d/suhosin.conf 0.8.13-1~ -- "$@"
dpkg-maintscript-helper mv_conffile /etc/fail2ban/filter.d/sasl.conf /etc/fail2ban/filter.d/postfix-sasl.conf 0.8.13-1~ -- "$@"
dpkg-maintscript-helper mv_conffile /etc/fail2ban/filter.d/couriersmtp.conf /etc/fail2ban/filter.d/courier-smtp.conf 0.9.0-1~ -- "$@"
dpkg-maintscript-helper mv_conffile /etc/fail2ban/filter.d/courierlogin.conf /etc/fail2ban/filter.d/courier-auth.conf 0.9.0-1~ -- "$@"
fi
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
#DEBHELPER#

15
debian/preinst vendored
View File

@ -1,15 +0,0 @@
#!/bin/sh
set -e
if dpkg-maintscript-helper supports mv_conffile 2>/dev/null; then
dpkg-maintscript-helper mv_conffile /etc/fail2ban/action.d/firewall-cmd-direct-new.conf /etc/fail2ban/action.d/firewallcmd-new.conf 0.8.13-1~ -- "$@"
dpkg-maintscript-helper mv_conffile /etc/fail2ban/filter.d/lighttpd-fastcgi.conf /etc/fail2ban/filter.d/suhosin.conf 0.8.13-1~ -- "$@"
dpkg-maintscript-helper mv_conffile /etc/fail2ban/filter.d/sasl.conf /etc/fail2ban/filter.d/postfix-sasl.conf 0.8.13-1~ -- "$@"
dpkg-maintscript-helper mv_conffile /etc/fail2ban/filter.d/couriersmtp.conf /etc/fail2ban/filter.d/courier-smtp.conf 0.9.0-1~ -- "$@"
dpkg-maintscript-helper mv_conffile /etc/fail2ban/filter.d/courierlogin.conf /etc/fail2ban/filter.d/courier-auth.conf 0.9.0-1~ -- "$@"
fi
#DEBHELPER#
exit 0

71
debian/rules vendored
View File

@ -1,71 +0,0 @@
#!/usr/bin/make -f
# -*- makefile -*-
# Sample debian/rules that uses debhelper.
# This file was originally written by Joey Hess and Craig Small.
# As a special exception, when this file is copied by dh-make into a
# dh-make output file, you may use that output file without restriction.
# This special exception was added by Craig Small in version 0.37 of dh-make.
# Uncomment this to turn on verbose mode.
export DH_VERBOSE=1
export PYBUILD_DISABLE_python2=1
%:
dh $@ --with python3 --buildsystem pybuild
DESTDIR=$(CURDIR)/debian/fail2ban
PYVERSION=$(shell py3versions -dv)
override_dh_clean:
rm -rf fail2ban.egg-info
-rm debian/fail2ban.init
dh_clean
: # auto generated
-rm bin/fail2ban-python
override_dh_auto_configure:
LANG=C ./fail2ban-2to3
dh_auto_configure
override_dh_install:
rm -f $(DESTDIR)/usr/share/doc/fail2ban/README.Solaris
rm -f $(DESTDIR)/etc/fail2ban/paths-fedora.conf
rm -f $(DESTDIR)/etc/fail2ban/paths-freebsd.conf
rm -f $(DESTDIR)/etc/fail2ban/paths-osx.conf
: # Remove explicitly created /var/run/fail2ban
: # just to please lintian since init file will
: # take care about it anyways
rm -rf $(DESTDIR)/var/run/ $(DESTDIR)/run/
: # Install monit configuration
install -d $(DESTDIR)/etc/monit/conf-available
install -d $(DESTDIR)/etc/monit/monitrc.d
install -m 644 files/monit/fail2ban $(DESTDIR)/etc/monit/monitrc.d/fail2ban
: # Install bash completion
install -d $(DESTDIR)/usr/share/bash-completion/completions
install -m 644 files/bash-completion $(DESTDIR)/usr/share/bash-completion/completions/fail2ban
: # Install systemd files
install -d $(DESTDIR)/lib/systemd/system
install -d $(DESTDIR)/usr/lib/tmpfiles.d
install -m 644 build/fail2ban.service $(DESTDIR)/lib/systemd/system
install -m 644 files/fail2ban-tmpfiles.conf $(DESTDIR)/usr/lib/tmpfiles.d
install -d $(DESTDIR)/lib/systemd/system
: # Install default jail enabler
install -m 644 debian/debian-files/jail.d_defaults-debian.conf $(DESTDIR)/etc/fail2ban/jail.d/defaults-debian.conf
dh_install
override_dh_auto_test:
ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
# fails for now (4 tests), accept it for now
FAIL2BAN_CONFIG_DIR="$(CURDIR)/config" LC_ALL=C.UTF-8 bin/fail2ban-testcases --verbosity=2 --no-network || true
endif
override_dh_installexamples:
dh_installexamples files/ipmasq-* files/nagios files/cacti
override_dh_installinit:
cp -p files/debian-initd debian/fail2ban.init
dh_installinit -- defaults 99
override_dh_installman:
dh_installman man/*.[15]

View File

@ -1 +0,0 @@
3.0 (quilt)

View File

@ -1,4 +0,0 @@
Bug-Database: https://github.com/fail2ban/fail2ban/issues
Bug-Submit: https://github.com/fail2ban/fail2ban/issues/new
Repository: https://github.com/fail2ban/fail2ban.git
Repository-Browse: https://github.com/fail2ban/fail2ban

2
debian/watch vendored
View File

@ -1,2 +0,0 @@
version=4
opts=filenamemangle=s/.*\/(.*)/fail2ban-$1\.tar\.gz/ https://github.com/fail2ban/fail2ban/tags .*archive/(\d[\d\.]+).tar.gz

View File

@ -38,28 +38,32 @@ class ActionReader(DefinitionInitConfigReader):
_configOpts = {
"actionstart": ["string", None],
"actionstart_on_demand": ["string", None],
"actionstart_on_demand": ["bool", None],
"actionstop": ["string", None],
"actionflush": ["string", None],
"actionreload": ["string", None],
"actioncheck": ["string", None],
"actionrepair": ["string", None],
"actionrepair_on_unban": ["string", None],
"actionrepair_on_unban": ["bool", None],
"actionban": ["string", None],
"actionprolong": ["string", None],
"actionreban": ["string", None],
"actionunban": ["string", None],
"norestored": ["string", None],
"norestored": ["bool", None],
}
def __init__(self, file_, jailName, initOpts, **kwargs):
# always supply jail name as name parameter if not specified in options:
n = initOpts.get("name")
if n is None:
initOpts["name"] = n = jailName
actname = initOpts.get("actname")
if actname is None:
actname = file_
# ensure we've unique action name per jail:
if n != jailName:
actname += n[len(jailName):] if n.startswith(jailName) else '-' + n
initOpts["actname"] = actname
# always supply jail name as name parameter if not specified in options:
if initOpts.get("name") is None:
initOpts["name"] = jailName
self._name = actname
DefinitionInitConfigReader.__init__(
self, file_, jailName, initOpts, **kwargs)
@ -80,11 +84,6 @@ class ActionReader(DefinitionInitConfigReader):
def convert(self):
opts = self.getCombined(
ignore=CommandAction._escapedTags | set(('timeout', 'bantime')))
# type-convert only after combined (otherwise boolean converting prevents substitution):
for o in ('norestored', 'actionstart_on_demand', 'actionrepair_on_unban'):
if opts.get(o):
opts[o] = self._convert_to_boolean(opts[o])
# stream-convert:
head = ["set", self._jailName]
stream = list()

View File

@ -29,7 +29,7 @@ import re
import sys
from ..helpers import getLogger
if sys.version_info >= (3,2):
if sys.version_info >= (3,): # pragma: 2.x no cover
# SafeConfigParser deprecated from Python 3.2 (renamed to ConfigParser)
from configparser import ConfigParser as SafeConfigParser, BasicInterpolation, \
@ -61,7 +61,7 @@ if sys.version_info >= (3,2):
return super(BasicInterpolationWithName, self)._interpolate_some(
parser, option, accum, rest, section, map, *args, **kwargs)
else: # pragma: no cover
else: # pragma: 3.x no cover
from ConfigParser import SafeConfigParser, \
InterpolationMissingOptionError, NoOptionError, NoSectionError
@ -372,7 +372,8 @@ after = 1.conf
s2 = alls.get(n)
if isinstance(s2, dict):
# save previous known values, for possible using in local interpolations later:
self.merge_section('KNOWN/'+n, s2, '')
self.merge_section('KNOWN/'+n,
dict(filter(lambda i: i[0] in s, s2.iteritems())), '')
# merge section
s2.update(s)
else:

View File

@ -34,6 +34,30 @@ from ..helpers import getLogger, _as_bool, _merge_dicts, substituteRecursiveTags
# Gets the instance of the logger.
logSys = getLogger(__name__)
CONVERTER = {
"bool": _as_bool,
"int": int,
}
def _OptionsTemplateGen(options):
"""Iterator over the options template with default options.
Each options entry is composed of an array or tuple with:
[[type, name, ?default?], ...]
Or it is a dict:
{name: [type, default], ...}
"""
if isinstance(options, (list,tuple)):
for optname in options:
if len(optname) > 2:
opttype, optname, optvalue = optname
else:
(opttype, optname), optvalue = optname, None
yield opttype, optname, optvalue
else:
for optname in options:
opttype, optvalue = options[optname]
yield opttype, optname, optvalue
class ConfigReader():
"""Generic config reader class.
@ -120,6 +144,10 @@ class ConfigReader():
except AttributeError:
return False
def has_option(self, sec, opt, withDefault=True):
return self._cfg.has_option(sec, opt) if withDefault \
else opt in self._cfg._sections.get(sec, {})
def merge_defaults(self, d):
self._cfg.get_defaults().update(d)
@ -224,31 +252,22 @@ class ConfigReaderUnshared(SafeConfigParserWithIncludes):
# Or it is a dict:
# {name: [type, default], ...}
def getOptions(self, sec, options, pOptions=None, shouldExist=False):
def getOptions(self, sec, options, pOptions=None, shouldExist=False, convert=True):
values = dict()
if pOptions is None:
pOptions = {}
# Get only specified options:
for optname in options:
if isinstance(options, (list,tuple)):
if len(optname) > 2:
opttype, optname, optvalue = optname
else:
(opttype, optname), optvalue = optname, None
else:
opttype, optvalue = options[optname]
for opttype, optname, optvalue in _OptionsTemplateGen(options):
if optname in pOptions:
continue
try:
if opttype == "bool":
v = self.getboolean(sec, optname)
if v is None: continue
elif opttype == "int":
v = self.getint(sec, optname)
if v is None: continue
else:
v = self.get(sec, optname, vars=pOptions)
v = self.get(sec, optname, vars=pOptions)
values[optname] = v
if convert:
conv = CONVERTER.get(opttype)
if conv:
if v is None: continue
values[optname] = conv(v)
except NoSectionError as e:
if shouldExist:
raise
@ -261,8 +280,8 @@ class ConfigReaderUnshared(SafeConfigParserWithIncludes):
logSys.warning("'%s' not defined in '%s'. Using default one: %r"
% (optname, sec, optvalue))
values[optname] = optvalue
elif logSys.getEffectiveLevel() <= logLevel:
logSys.log(logLevel, "Non essential option '%s' not defined in '%s'.", optname, sec)
# elif logSys.getEffectiveLevel() <= logLevel:
# logSys.log(logLevel, "Non essential option '%s' not defined in '%s'.", optname, sec)
except ValueError:
logSys.warning("Wrong value for '" + optname + "' in '" + sec +
"'. Using default one: '" + repr(optvalue) + "'")
@ -320,8 +339,9 @@ class DefinitionInitConfigReader(ConfigReader):
pOpts = dict()
if self._initOpts:
pOpts = _merge_dicts(pOpts, self._initOpts)
# type-convert only in combined (otherwise int/bool converting prevents substitution):
self._opts = ConfigReader.getOptions(
self, "Definition", self._configOpts, pOpts)
self, "Definition", self._configOpts, pOpts, convert=False)
self._pOpts = pOpts
if self.has_section("Init"):
# get only own options (without options from default):
@ -342,9 +362,20 @@ class DefinitionInitConfigReader(ConfigReader):
if opt == '__name__' or opt in self._opts: continue
self._opts[opt] = self.get("Definition", opt)
def _convert_to_boolean(self, value):
return _as_bool(value)
def convertOptions(self, opts, configOpts):
"""Convert interpolated combined options to expected type.
"""
for opttype, optname, optvalue in _OptionsTemplateGen(configOpts):
conv = CONVERTER.get(opttype)
if conv:
v = opts.get(optname)
if v is None: continue
try:
opts[optname] = conv(v)
except ValueError:
logSys.warning("Wrong %s value %r for %r. Using default one: %r",
opttype, v, optname, optvalue)
opts[optname] = optvalue
def getCombOption(self, optname):
"""Get combined definition option (as string) using pre-set and init
@ -380,6 +411,8 @@ class DefinitionInitConfigReader(ConfigReader):
ignore=ignore, addrepl=self.getCombOption)
if not opts:
raise ValueError('recursive tag definitions unable to be resolved')
# convert options after all interpolations:
self.convertOptions(opts, self._configOpts)
return opts
def convert(self):

View File

@ -48,7 +48,8 @@ class CSocket:
def send(self, msg, nonblocking=False, timeout=None):
# Convert every list member to string
obj = dumps(map(CSocket.convert, msg), HIGHEST_PROTOCOL)
self.__csock.send(obj + CSPROTO.END)
self.__csock.send(obj)
self.__csock.send(CSPROTO.END)
return self.receive(self.__csock, nonblocking, timeout)
def settimeout(self, timeout):
@ -81,9 +82,12 @@ class CSocket:
msg = CSPROTO.EMPTY
if nonblocking: sock.setblocking(0)
if timeout: sock.settimeout(timeout)
while msg.rfind(CSPROTO.END) == -1:
chunk = sock.recv(512)
if chunk in ('', b''): # python 3.x may return b'' instead of ''
raise RuntimeError("socket connection broken")
bufsize = 1024
while msg.rfind(CSPROTO.END, -32) == -1:
chunk = sock.recv(bufsize)
if not len(chunk):
raise socket.error(104, 'Connection reset by peer')
if chunk == CSPROTO.END: break
msg = msg + chunk
if bufsize < 32768: bufsize <<= 1
return loads(msg)

View File

@ -168,19 +168,6 @@ class Fail2banClient(Fail2banCmdLine, Thread):
if not ret:
return None
# verify that directory for the socket file exists
socket_dir = os.path.dirname(self._conf["socket"])
if not os.path.exists(socket_dir):
logSys.error(
"There is no directory %s to contain the socket file %s."
% (socket_dir, self._conf["socket"]))
return None
if not os.access(socket_dir, os.W_OK | os.X_OK): # pragma: no cover
logSys.error(
"Directory %s exists but not accessible for writing"
% (socket_dir,))
return None
# Check already running
if not self._conf["force"] and os.path.exists(self._conf["socket"]):
logSys.error("Fail2ban seems to be in unexpected state (not running but the socket exists)")

View File

@ -27,15 +27,20 @@ import sys
from ..version import version, normVersion
from ..protocol import printFormatted
from ..helpers import getLogger, str2LogLevel, getVerbosityFormat
from ..helpers import getLogger, str2LogLevel, getVerbosityFormat, BrokenPipeError
# Gets the instance of the logger.
logSys = getLogger("fail2ban")
def output(s): # pragma: no cover
print(s)
try:
print(s)
except (BrokenPipeError, IOError) as e: # pragma: no cover
if e.errno != 32: # closed / broken pipe
raise
CONFIG_PARAMS = ("socket", "pidfile", "logtarget", "loglevel", "syslogsocket",)
# Config parameters required to start fail2ban which can be also set via command line (overwrite fail2ban.conf),
CONFIG_PARAMS = ("socket", "pidfile", "logtarget", "loglevel", "syslogsocket")
# Used to signal - we are in test cases (ex: prevents change logging params, log capturing, etc)
PRODUCTION = True
@ -94,9 +99,10 @@ class Fail2banCmdLine():
output("and bans the corresponding IP addresses using firewall rules.")
output("")
output("Options:")
output(" -c <DIR> configuration directory")
output(" -s <FILE> socket path")
output(" -p <FILE> pidfile path")
output(" -c, --conf <DIR> configuration directory")
output(" -s, --socket <FILE> socket path")
output(" -p, --pidfile <FILE> pidfile path")
output(" --pname <NAME> name of the process (main thread) to identify instance (default fail2ban-server)")
output(" --loglevel <LEVEL> logging level")
output(" --logtarget <TARGET> logging target, use file-name or stdout, stderr, syslog or sysout.")
output(" --syslogsocket auto|<FILE>")
@ -129,17 +135,15 @@ class Fail2banCmdLine():
"""
for opt in optList:
o = opt[0]
if o == "-c":
if o in ("-c", "--conf"):
self._conf["conf"] = opt[1]
elif o == "-s":
elif o in ("-s", "--socket"):
self._conf["socket"] = opt[1]
elif o == "-p":
elif o in ("-p", "--pidfile"):
self._conf["pidfile"] = opt[1]
elif o.startswith("--log") or o.startswith("--sys"):
self._conf[ o[2:] ] = opt[1]
elif o in ["-d", "--dp", "--dump-pretty"]:
elif o in ("-d", "--dp", "--dump-pretty"):
self._conf["dump"] = True if o == "-d" else 2
elif o == "-t" or o == "--test":
elif o in ("-t", "--test"):
self.cleanConfOnly = True
self._conf["test"] = True
elif o == "-v":
@ -163,12 +167,14 @@ class Fail2banCmdLine():
from ..server.mytime import MyTime
output(MyTime.str2seconds(opt[1]))
return True
elif o in ["-h", "--help"]:
elif o in ("-h", "--help"):
self.dispUsage()
return True
elif o in ["-V", "--version"]:
elif o in ("-V", "--version"):
self.dispVersion(o == "-V")
return True
elif o.startswith("--"): # other long named params (see also resetConf)
self._conf[ o[2:] ] = opt[1]
return None
def initCmdLine(self, argv):
@ -185,6 +191,7 @@ class Fail2banCmdLine():
try:
cmdOpts = 'hc:s:p:xfbdtviqV'
cmdLongOpts = ['loglevel=', 'logtarget=', 'syslogsocket=', 'test', 'async',
'conf=', 'pidfile=', 'pname=', 'socket=',
'timeout=', 'str2sec=', 'help', 'version', 'dp', '--dump-pretty']
optList, self._args = getopt.getopt(self._argv[1:], cmdOpts, cmdLongOpts)
except getopt.GetoptError:
@ -227,7 +234,8 @@ class Fail2banCmdLine():
if not conf:
self.configurator.readEarly()
conf = self.configurator.getEarlyOptions()
self._conf[o] = conf[o]
if o in conf:
self._conf[o] = conf[o]
logSys.info("Using socket file %s", self._conf["socket"])
@ -304,18 +312,24 @@ class Fail2banCmdLine():
# since method is also exposed in API via globally bound variable
@staticmethod
def _exit(code=0):
if hasattr(os, '_exit') and os._exit:
os._exit(code)
else:
sys.exit(code)
# implicit flush without to produce broken pipe error (32):
sys.stderr.close()
try:
sys.stdout.flush()
# exit:
if hasattr(sys, 'exit') and sys.exit:
sys.exit(code)
else:
os._exit(code)
except (BrokenPipeError, IOError) as e: # pragma: no cover
if e.errno != 32: # closed / broken pipe
raise
@staticmethod
def exit(code=0):
logSys.debug("Exit with code %s", code)
# because of possible buffered output in python, we should flush it before exit:
logging.shutdown()
sys.stdout.flush()
sys.stderr.flush()
# exit
Fail2banCmdLine._exit(code)

View File

@ -21,7 +21,6 @@ Fail2Ban reads log file that contains password failure report
and bans the corresponding IP addresses using firewall rules.
This tools can test regular expressions for "fail2ban".
"""
__author__ = "Fail2Ban Developers"
@ -109,19 +108,22 @@ class _f2bOptParser(OptionParser):
def format_help(self, *args, **kwargs):
""" Overwritten format helper with full ussage."""
self.usage = ''
return "Usage: " + usage() + __doc__ + """
return "Usage: " + usage() + "\n" + __doc__ + """
LOG:
string a string representing a log line
filename path to a log file (/var/log/auth.log)
"systemd-journal" search systemd journal (systemd-python required)
string a string representing a log line
filename path to a log file (/var/log/auth.log)
systemd-journal search systemd journal (systemd-python required),
optionally with backend parameters, see `man jail.conf`
for usage and examples (systemd-journal[journalflags=1]).
REGEX:
string a string representing a 'failregex'
filename path to a filter file (filter.d/sshd.conf)
string a string representing a 'failregex'
filter name of filter, optionally with options (sshd[mode=aggressive])
filename path to a filter file (filter.d/sshd.conf)
IGNOREREGEX:
string a string representing an 'ignoreregex'
filename path to a filter file (filter.d/sshd.conf)
string a string representing an 'ignoreregex'
filename path to a filter file (filter.d/sshd.conf)
\n""" + OptionParser.format_help(self, *args, **kwargs) + """\n
Report bugs to https://github.com/fail2ban/fail2ban/issues\n
""" + __copyright__ + "\n"
@ -252,6 +254,8 @@ class Fail2banRegex(object):
self.share_config=dict()
self._filter = Filter(None)
self._prefREMatched = 0
self._prefREGroups = list()
self._ignoreregex = list()
self._failregex = list()
self._time_elapsed = None
@ -272,6 +276,10 @@ class Fail2banRegex(object):
self._filter.returnRawHost = opts.raw
self._filter.checkFindTime = False
self._filter.checkAllRegex = opts.checkAllRegex and not opts.out
# ignore pending (without ID/IP), added to matches if it hits later (if ID/IP can be retreved)
self._filter.ignorePending = opts.out
# callback to increment ignored RE's by index (during process):
self._filter.onIgnoreRegex = self._onIgnoreRegex
self._backend = 'auto'
def output(self, line):
@ -288,8 +296,8 @@ class Fail2banRegex(object):
self._filter.setDatePattern(pattern)
self._datepattern_set = True
if pattern is not None:
self.output( "Use datepattern : %s" % (
self._filter.getDatePattern()[1], ) )
self.output( "Use datepattern : %s : %s" % (
pattern, self._filter.getDatePattern()[1], ) )
def setMaxLines(self, v):
if not self._maxlines_set:
@ -372,11 +380,8 @@ class Fail2banRegex(object):
if not ret:
output( "ERROR: failed to load filter %s" % value )
return False
# overwrite default logtype (considering that the filter could specify this too in Definition/Init sections):
if not fltOpt.get('logtype'):
reader.merge_defaults({
'logtype': ['file','journal'][int(self._backend.startswith("systemd"))]
})
# set backend-related options (logtype):
reader.applyAutoOptions(self._backend)
# get, interpolate and convert options:
reader.getOptions(None)
# show real options if expected:
@ -436,71 +441,140 @@ class Fail2banRegex(object):
'add%sRegex' % regextype.title())(regex.getFailRegex())
return True
def testIgnoreRegex(self, line):
found = False
try:
ret = self._filter.ignoreLine([(line, "", "")])
if ret is not None:
found = True
regex = self._ignoreregex[ret].inc()
except RegexException as e: # pragma: no cover
output( 'ERROR: %s' % e )
return False
return found
def _onIgnoreRegex(self, idx, ignoreRegex):
self._lineIgnored = True
self._ignoreregex[idx].inc()
def testRegex(self, line, date=None):
orgLineBuffer = self._filter._Filter__lineBuffer
# duplicate line buffer (list can be changed inplace during processLine):
if self._filter.getMaxLines() > 1:
orgLineBuffer = orgLineBuffer[:]
fullBuffer = len(orgLineBuffer) >= self._filter.getMaxLines()
is_ignored = False
is_ignored = self._lineIgnored = False
try:
found = self._filter.processLine(line, date)
lines = []
line = self._filter.processedLine()
ret = []
for match in found:
# Append True/False flag depending if line was matched by
# more than one regex
match.append(len(ret)>1)
regex = self._failregex[match[0]]
regex.inc()
regex.appendIP(match)
if not self._opts.out:
# Append True/False flag depending if line was matched by
# more than one regex
match.append(len(ret)>1)
regex = self._failregex[match[0]]
regex.inc()
regex.appendIP(match)
if not match[3].get('nofail'):
ret.append(match)
else:
is_ignored = True
if self._opts.out: # (formated) output - don't need stats:
return None, ret, None
# prefregex stats:
if self._filter.prefRegex:
pre = self._filter.prefRegex
if pre.hasMatched():
self._prefREMatched += 1
if self._verbose:
if len(self._prefREGroups) < self._maxlines:
self._prefREGroups.append(pre.getGroups())
else:
if len(self._prefREGroups) == self._maxlines:
self._prefREGroups.append('...')
except RegexException as e: # pragma: no cover
output( 'ERROR: %s' % e )
return False
for bufLine in orgLineBuffer[int(fullBuffer):]:
if bufLine not in self._filter._Filter__lineBuffer:
try:
self._line_stats.missed_lines.pop(
self._line_stats.missed_lines.index("".join(bufLine)))
if self._debuggex:
self._line_stats.missed_lines_timeextracted.pop(
self._line_stats.missed_lines_timeextracted.index(
"".join(bufLine[::2])))
except ValueError:
pass
# if buffering - add also another lines from match:
if self._print_all_matched:
if not self._debuggex:
self._line_stats.matched_lines.append("".join(bufLine))
else:
lines.append(bufLine[0] + bufLine[2])
self._line_stats.matched += 1
self._line_stats.missed -= 1
return None, 0, None
if self._filter.getMaxLines() > 1:
for bufLine in orgLineBuffer[int(fullBuffer):]:
if bufLine not in self._filter._Filter__lineBuffer:
try:
self._line_stats.missed_lines.pop(
self._line_stats.missed_lines.index("".join(bufLine)))
if self._debuggex:
self._line_stats.missed_lines_timeextracted.pop(
self._line_stats.missed_lines_timeextracted.index(
"".join(bufLine[::2])))
except ValueError:
pass
# if buffering - add also another lines from match:
if self._print_all_matched:
if not self._debuggex:
self._line_stats.matched_lines.append("".join(bufLine))
else:
lines.append(bufLine[0] + bufLine[2])
self._line_stats.matched += 1
self._line_stats.missed -= 1
if lines: # pre-lines parsed in multiline mode (buffering)
lines.append(line)
lines.append(self._filter.processedLine())
line = "\n".join(lines)
return line, ret, is_ignored
return line, ret, (is_ignored or self._lineIgnored)
def _prepaireOutput(self):
"""Prepares output- and fetch-function corresponding given '--out' option (format)"""
ofmt = self._opts.out
if ofmt in ('id', 'ip'):
def _out(ret):
for r in ret:
output(r[1])
elif ofmt == 'msg':
def _out(ret):
for r in ret:
for r in r[3].get('matches'):
if not isinstance(r, basestring):
r = ''.join(r for r in r)
output(r)
elif ofmt == 'row':
def _out(ret):
for r in ret:
output('[%r,\t%r,\t%r],' % (r[1],r[2],dict((k,v) for k, v in r[3].iteritems() if k != 'matches')))
elif '<' not in ofmt:
def _out(ret):
for r in ret:
output(r[3].get(ofmt))
else: # extended format with tags substitution:
from ..server.actions import Actions, CommandAction, BanTicket
def _escOut(t, v):
# use safe escape (avoid inject on pseudo tag "\x00msg\x00"):
if t not in ('msg',):
return v.replace('\x00', '\\x00')
return v
def _out(ret):
rows = []
wrap = {'NL':0}
for r in ret:
ticket = BanTicket(r[1], time=r[2], data=r[3])
aInfo = Actions.ActionInfo(ticket)
# if msg tag is used - output if single line (otherwise let it as is to wrap multilines later):
def _get_msg(self):
if not wrap['NL'] and len(r[3].get('matches', [])) <= 1:
return self['matches']
else: # pseudo tag for future replacement:
wrap['NL'] = 1
return "\x00msg\x00"
aInfo['msg'] = _get_msg
# not recursive interpolation (use safe escape):
v = CommandAction.replaceDynamicTags(ofmt, aInfo, escapeVal=_escOut)
if wrap['NL']: # contains multiline tags (msg):
rows.append((r, v))
continue
output(v)
# wrap multiline tag (msg) interpolations to single line:
for r, v in rows:
for r in r[3].get('matches'):
if not isinstance(r, basestring):
r = ''.join(r for r in r)
r = v.replace("\x00msg\x00", r)
output(r)
return _out
def process(self, test_lines):
t0 = time.time()
if self._opts.out: # get out function
out = self._prepaireOutput()
for line in test_lines:
if isinstance(line, tuple):
line_datetimestripped, ret, is_ignored = self.testRegex(
line[0], line[1])
line_datetimestripped, ret, is_ignored = self.testRegex(line[0], line[1])
line = "".join(line[0])
else:
line = line.rstrip('\r\n')
@ -508,8 +582,10 @@ class Fail2banRegex(object):
# skip comment and empty lines
continue
line_datetimestripped, ret, is_ignored = self.testRegex(line)
if not is_ignored:
is_ignored = self.testIgnoreRegex(line_datetimestripped)
if self._opts.out: # (formated) output:
if len(ret) > 0 and not is_ignored: out(ret)
continue
if is_ignored:
self._line_stats.ignored += 1
@ -517,42 +593,25 @@ class Fail2banRegex(object):
self._line_stats.ignored_lines.append(line)
if self._debuggex:
self._line_stats.ignored_lines_timeextracted.append(line_datetimestripped)
if len(ret) > 0:
assert(not is_ignored)
if self._opts.out:
if self._opts.out in ('id', 'ip'):
for ret in ret:
output(ret[1])
elif self._opts.out == 'msg':
for ret in ret:
output('\n'.join(map(lambda v:''.join(v for v in v), ret[3].get('matches'))))
elif self._opts.out == 'row':
for ret in ret:
output('[%r,\t%r,\t%r],' % (ret[1],ret[2],dict((k,v) for k, v in ret[3].iteritems() if k != 'matches')))
else:
for ret in ret:
output(ret[3].get(self._opts.out))
continue
elif len(ret) > 0:
self._line_stats.matched += 1
if self._print_all_matched:
self._line_stats.matched_lines.append(line)
if self._debuggex:
self._line_stats.matched_lines_timeextracted.append(line_datetimestripped)
else:
if not is_ignored:
self._line_stats.missed += 1
if not self._print_no_missed and (self._print_all_missed or self._line_stats.missed <= self._maxlines + 1):
self._line_stats.missed_lines.append(line)
if self._debuggex:
self._line_stats.missed_lines_timeextracted.append(line_datetimestripped)
self._line_stats.missed += 1
if not self._print_no_missed and (self._print_all_missed or self._line_stats.missed <= self._maxlines + 1):
self._line_stats.missed_lines.append(line)
if self._debuggex:
self._line_stats.missed_lines_timeextracted.append(line_datetimestripped)
self._line_stats.tested += 1
self._time_elapsed = time.time() - t0
def printLines(self, ltype):
lstats = self._line_stats
assert(self._line_stats.missed == lstats.tested - (lstats.matched + lstats.ignored))
assert(lstats.missed == lstats.tested - (lstats.matched + lstats.ignored))
lines = lstats[ltype]
l = lstats[ltype + '_lines']
multiline = self._filter.getMaxLines() > 1
@ -610,7 +669,18 @@ class Fail2banRegex(object):
pprint_list(out, " #) [# of hits] regular expression")
return total
# Print title
# Print prefregex:
if self._filter.prefRegex:
#self._filter.prefRegex.hasMatched()
pre = self._filter.prefRegex
out = [pre.getRegex()]
if self._verbose:
for grp in self._prefREGroups:
out.append(" %s" % (grp,))
output( "\n%s: %d total" % ("Prefregex", self._prefREMatched) )
pprint_list(out)
# Print regex's:
total = print_failregexes("Failregex", self._failregex)
_ = print_failregexes("Ignoreregex", self._ignoreregex)
@ -689,10 +759,10 @@ class Fail2banRegex(object):
test_lines = journal_lines_gen(flt, myjournal)
else:
# if single line parsing (without buffering)
if self._filter.getMaxLines() <= 1:
if self._filter.getMaxLines() <= 1 and '\n' not in cmd_log:
self.output( "Use single line : %s" % shortstr(cmd_log.replace("\n", r"\n")) )
test_lines = [ cmd_log ]
else: # multi line parsing (with buffering)
else: # multi line parsing (with and without buffering)
test_lines = cmd_log.split("\n")
self.output( "Use multi line : %s line(s)" % len(test_lines) )
for i, l in enumerate(test_lines):
@ -712,6 +782,7 @@ class Fail2banRegex(object):
def exec_command_line(*args):
logging.exitOnIOError = True
parser = get_opt_parser()
(opts, args) = parser.parse_args(*args)
errors = []

View File

@ -53,6 +53,14 @@ class FilterReader(DefinitionInitConfigReader):
def getFile(self):
return self.__file
def applyAutoOptions(self, backend):
# set init option to backend-related logtype, considering
# that the filter settings may be overwritten in its local:
if (not self._initOpts.get('logtype') and
not self.has_option('Definition', 'logtype', False)
):
self._initOpts['logtype'] = ['file','journal'][int(backend.startswith("systemd"))]
def convert(self):
stream = list()
opts = self.getCombined()

View File

@ -149,11 +149,8 @@ class JailReader(ConfigReader):
ret = self.__filter.read()
if not ret:
raise JailDefError("Unable to read the filter %r" % filterName)
if not filterOpt.get('logtype'):
# overwrite default logtype backend-related (considering that the filter settings may be overwritten):
self.__filter.merge_defaults({
'logtype': ['file','journal'][int(self.__opts.get('backend', '').startswith("systemd"))]
})
# set backend-related options (logtype):
self.__filter.applyAutoOptions(self.__opts.get('backend', ''))
# merge options from filter as 'known/...' (all options unfiltered):
self.__filter.getOptions(self.__opts, all=True)
ConfigReader.merge_section(self, self.__name, self.__filter.getCombined(), 'known/')

View File

@ -208,6 +208,26 @@ class FormatterWithTraceBack(logging.Formatter):
return logging.Formatter.format(self, record)
logging.exitOnIOError = False
def __stopOnIOError(logSys=None, logHndlr=None): # pragma: no cover
if logSys and len(logSys.handlers):
logSys.removeHandler(logSys.handlers[0])
if logHndlr:
logHndlr.close = lambda: None
logging.StreamHandler.flush = lambda self: None
#sys.excepthook = lambda *args: None
if logging.exitOnIOError:
try:
sys.stderr.close()
except:
pass
sys.exit(0)
try:
BrokenPipeError = BrokenPipeError
except NameError: # pragma: 3.x no cover
BrokenPipeError = IOError
__origLog = logging.Logger._log
def __safeLog(self, level, msg, args, **kwargs):
"""Safe log inject to avoid possible errors by unsafe log-handlers,
@ -223,6 +243,10 @@ def __safeLog(self, level, msg, args, **kwargs):
try:
# if isEnabledFor(level) already called...
__origLog(self, level, msg, args, **kwargs)
except (BrokenPipeError, IOError) as e: # pragma: no cover
if e.errno == 32: # closed / broken pipe
__stopOnIOError(self)
raise
except Exception as e: # pragma: no cover - unreachable if log-handler safe in this python-version
try:
for args in (
@ -237,6 +261,18 @@ def __safeLog(self, level, msg, args, **kwargs):
pass
logging.Logger._log = __safeLog
__origLogFlush = logging.StreamHandler.flush
def __safeLogFlush(self):
"""Safe flush inject stopping endless logging on closed streams (redirected pipe).
"""
try:
__origLogFlush(self)
except (BrokenPipeError, IOError) as e: # pragma: no cover
if e.errno == 32: # closed / broken pipe
__stopOnIOError(None, self)
raise
logging.StreamHandler.flush = __safeLogFlush
def getLogger(name):
"""Get logging.Logger instance with Fail2Ban logger name convention
"""
@ -267,7 +303,7 @@ def getVerbosityFormat(verbosity, fmt=' %(message)s', addtime=True, padding=True
if addtime:
fmt = ' %(asctime)-15s' + fmt
else: # default (not verbose):
fmt = "%(name)-23.23s [%(process)d]: %(levelname)-7s" + fmt
fmt = "%(name)-24s[%(process)d]: %(levelname)-7s" + fmt
if addtime:
fmt = "%(asctime)s " + fmt
# remove padding if not needed:
@ -291,7 +327,7 @@ def splitwords(s):
"""
if not s:
return []
return filter(bool, map(str.strip, re.split('[ ,\n]+', s)))
return filter(bool, map(lambda v: v.strip(), re.split('[ ,\n]+', s)))
if sys.version_info >= (3,5):
eval(compile(r'''if 1:
@ -338,7 +374,7 @@ OPTION_EXTRACT_CRE = re.compile(
r'([\w\-_\.]+)=(?:"([^"]*)"|\'([^\']*)\'|([^,\]]*))(?:,|\]\s*\[|$)', re.DOTALL)
# split by new-line considering possible new-lines within options [...]:
OPTION_SPLIT_CRE = re.compile(
r'(?:[^\[\n]+(?:\s*\[\s*(?:[\w\-_\.]+=(?:"[^"]*"|\'[^\']*\'|[^,\]]*)\s*(?:,|\]\s*\[)?\s*)*\])?\s*|[^\n]+)(?=\n\s*|$)', re.DOTALL)
r'(?:[^\[\s]+(?:\s*\[\s*(?:[\w\-_\.]+=(?:"[^"]*"|\'[^\']*\'|[^,\]]*)\s*(?:,|\]\s*\[)?\s*)*\])?\s*|\S+)(?=\n\s*|\s+|$)', re.DOTALL)
def extractOptions(option):
match = OPTION_CRE.match(option)
@ -363,8 +399,8 @@ def splitWithOptions(option):
# tags (<tag>) in tagged options.
#
# max tag replacement count:
MAX_TAG_REPLACE_COUNT = 10
# max tag replacement count (considering tag X in tag Y repeat):
MAX_TAG_REPLACE_COUNT = 25
# compiled RE for tag name (replacement name)
TAG_CRE = re.compile(r'<([^ <>]+)>')
@ -398,6 +434,7 @@ def substituteRecursiveTags(inptags, conditional='',
done = set()
noRecRepl = hasattr(tags, "getRawItem")
# repeat substitution while embedded-recursive (repFlag is True)
repCounts = {}
while True:
repFlag = False
# substitute each value:
@ -409,7 +446,7 @@ def substituteRecursiveTags(inptags, conditional='',
value = orgval = uni_string(tags[tag])
# search and replace all tags within value, that can be interpolated using other tags:
m = tre_search(value)
refCounts = {}
rplc = repCounts.get(tag, {})
#logSys.log(5, 'TAG: %s, value: %s' % (tag, value))
while m:
# found replacement tag:
@ -419,13 +456,13 @@ def substituteRecursiveTags(inptags, conditional='',
m = tre_search(value, m.end())
continue
#logSys.log(5, 'found: %s' % rtag)
if rtag == tag or refCounts.get(rtag, 1) > MAX_TAG_REPLACE_COUNT:
if rtag == tag or rplc.get(rtag, 1) > MAX_TAG_REPLACE_COUNT:
# recursive definitions are bad
#logSys.log(5, 'recursion fail tag: %s value: %s' % (tag, value) )
raise ValueError(
"properties contain self referencing definitions "
"and cannot be resolved, fail tag: %s, found: %s in %s, value: %s" %
(tag, rtag, refCounts, value))
(tag, rtag, rplc, value))
repl = None
if conditional:
repl = tags.get(rtag + '?' + conditional)
@ -445,7 +482,7 @@ def substituteRecursiveTags(inptags, conditional='',
value = value.replace('<%s>' % rtag, repl)
#logSys.log(5, 'value now: %s' % value)
# increment reference count:
refCounts[rtag] = refCounts.get(rtag, 0) + 1
rplc[rtag] = rplc.get(rtag, 0) + 1
# the next match for replace:
m = tre_search(value, m.start())
#logSys.log(5, 'TAG: %s, newvalue: %s' % (tag, value))
@ -453,6 +490,7 @@ def substituteRecursiveTags(inptags, conditional='',
if orgval != value:
# check still contains any tag - should be repeated (possible embedded-recursive substitution):
if tre_search(value):
repCounts[tag] = rplc
repFlag = True
# copy return tags dict to prevent modifying of inptags:
if id(tags) == id(inptags):

View File

@ -55,6 +55,8 @@ protocol = [
["stop", "stops all jails and terminate the server"],
["unban --all", "unbans all IP addresses (in all jails and database)"],
["unban <IP> ... <IP>", "unbans <IP> (in all jails and database)"],
["banned", "return jails with banned IPs as dictionary"],
["banned <IP> ... <IP>]", "return list(s) of jails where given IP(s) are banned"],
["status", "gets the current status of the server"],
["ping", "tests if the server is alive"],
["echo", "for internal usage, returns back and outputs a given string"],
@ -120,6 +122,8 @@ protocol = [
["set <JAIL> action <ACT> <PROPERTY> <VALUE>", "sets the <VALUE> of <PROPERTY> for the action <ACT> for <JAIL>"],
["set <JAIL> action <ACT> <METHOD>[ <JSONKWARGS>]", "calls the <METHOD> with <JSONKWARGS> for the action <ACT> for <JAIL>"],
['', "JAIL INFORMATION", ""],
["get <JAIL> banned", "return banned IPs of <JAIL>"],
["get <JAIL> banned <IP> ... <IP>]", "return 1 if IP is banned in <JAIL> otherwise 0, or a list of 1/0 for multiple IPs"],
["get <JAIL> logpath", "gets the list of the monitored files for <JAIL>"],
["get <JAIL> logencoding", "gets the encoding of the log files for <JAIL>"],
["get <JAIL> journalmatch", "gets the journal filter match for <JAIL>"],

View File

@ -404,10 +404,13 @@ class CommandAction(ActionBase):
def _getOperation(self, tag, family):
# replace operation tag (interpolate all values), be sure family is enclosed as conditional value
# (as lambda in addrepl so only if not overwritten in action):
return self.replaceTag(tag, self._properties,
cmd = self.replaceTag(tag, self._properties,
conditional=('family='+family if family else ''),
addrepl=(lambda tag:family if tag == 'family' else None),
cache=self.__substCache)
if '<' not in cmd or not family: return cmd
# replace family as dynamic tags, important - don't cache, no recursion and auto-escape here:
cmd = self.replaceDynamicTags(cmd, {'family':family})
return cmd
def _operationExecuted(self, tag, family, *args):
""" Get, set or delete command of operation considering family.
@ -452,7 +455,18 @@ class CommandAction(ActionBase):
ret = True
# avoid double execution of same command for both families:
if cmd and cmd not in self._operationExecuted(tag, lambda f: f != famoper):
ret = self.executeCmd(cmd, self.timeout)
realCmd = cmd
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
if afterExec: afterExec(famoper, ret)
self._operationExecuted(tag, famoper, cmd if ret else None)
@ -806,7 +820,7 @@ class CommandAction(ActionBase):
ESCAPE_VN_CRE = re.compile(r"\W")
@classmethod
def replaceDynamicTags(cls, realCmd, aInfo):
def replaceDynamicTags(cls, realCmd, aInfo, escapeVal=None):
"""Replaces dynamical tags in `query` with property values.
**Important**
@ -831,16 +845,17 @@ class CommandAction(ActionBase):
# array for escaped vars:
varsDict = dict()
def escapeVal(tag, value):
# if the value should be escaped:
if cls.ESCAPE_CRE.search(value):
# That one needs to be escaped since its content is
# out of our control
tag = 'f2bV_%s' % cls.ESCAPE_VN_CRE.sub('_', tag)
varsDict[tag] = value # add variable
value = '$'+tag # replacement as variable
# replacement for tag:
return value
if not escapeVal:
def escapeVal(tag, value):
# if the value should be escaped:
if cls.ESCAPE_CRE.search(value):
# That one needs to be escaped since its content is
# out of our control
tag = 'f2bV_%s' % cls.ESCAPE_VN_CRE.sub('_', tag)
varsDict[tag] = value # add variable
value = '$'+tag # replacement as variable
# replacement for tag:
return value
# additional replacement as calling map:
ADD_REPL_TAGS_CM = CallingMap(ADD_REPL_TAGS)
@ -864,7 +879,7 @@ class CommandAction(ActionBase):
tickData = aInfo.get("F-*")
if not tickData: tickData = {}
def substTag(m):
tag = mapTag2Opt(m.groups()[0])
tag = mapTag2Opt(m.group(1))
try:
value = uni_string(tickData[tag])
except KeyError:

View File

@ -211,6 +211,14 @@ class Actions(JailThread, Mapping):
def getBanTime(self):
return self.__banManager.getBanTime()
def getBanned(self, ids):
lst = self.__banManager.getBanList()
if not ids:
return lst
if len(ids) == 1:
return 1 if ids[0] in lst else 0
return map(lambda ip: 1 if ip in lst else 0, ids)
def getBanList(self, withTime=False):
"""Returns the list of banned IP addresses.
@ -254,7 +262,7 @@ class Actions(JailThread, Mapping):
if ip is None:
return self.__flushBan(db)
# Multiple IPs:
if isinstance(ip, list):
if isinstance(ip, (list, tuple)):
missed = []
cnt = 0
for i in ip:
@ -276,6 +284,14 @@ class Actions(JailThread, Mapping):
# Unban the IP.
self.__unBan(ticket)
else:
# Multiple IPs by subnet or dns:
if not isinstance(ip, IPAddr):
ipa = IPAddr(ip)
if not ipa.isSingle: # subnet (mask/cidr) or raw (may be dns/hostname):
ips = filter(ipa.contains, self.__banManager.getBanList())
if ips:
return self.removeBannedIP(ips, db, ifexists)
# not found:
msg = "%s is not banned" % ip
logSys.log(logging.MSG, msg)
if ifexists:
@ -322,22 +338,32 @@ class Actions(JailThread, Mapping):
self._jail.name, name, e,
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
while self.active:
if self.idle:
logSys.debug("Actions: enter idle mode")
Utils.wait_for(lambda: not self.active or not self.idle,
lambda: False, self.sleeptime)
logSys.debug("Actions: leave idle mode")
continue
# wait for ban (stop if gets inactive):
bancnt = Utils.wait_for(lambda: not self.active or self.__checkBan(), self.sleeptime)
cnt += bancnt
# unban if nothing is banned not later than banned tickets >= banPrecedence
if not bancnt or cnt >= self.banPrecedence:
if self.active:
# let shrink the ban list faster
bancnt *= 2
self.__checkUnBan(bancnt if bancnt and bancnt < self.unbanMaxCount else self.unbanMaxCount)
cnt = 0
try:
if self.idle:
logSys.debug("Actions: enter idle mode")
Utils.wait_for(lambda: not self.active or not self.idle,
lambda: False, self.sleeptime)
logSys.debug("Actions: leave idle mode")
continue
# wait for ban (stop if gets inactive, pending ban or unban):
bancnt = 0
wt = min(self.sleeptime, self.__banManager._nextUnbanTime - MyTime.time())
logSys.log(5, "Actions: wait for pending tickets %s (default %s)", wt, self.sleeptime)
if Utils.wait_for(lambda: not self.active or self._jail.hasFailTickets, wt):
bancnt = self.__checkBan()
cnt += bancnt
# unban if nothing is banned not later than banned tickets >= banPrecedence
if not bancnt or cnt >= self.banPrecedence:
if self.active:
# let shrink the ban list faster
bancnt *= 2
logSys.log(5, "Actions: check-unban %s, bancnt %s, max: %s", bancnt if bancnt and bancnt < self.unbanMaxCount else self.unbanMaxCount, bancnt, self.unbanMaxCount)
self.__checkUnBan(bancnt if bancnt and bancnt < self.unbanMaxCount else self.unbanMaxCount)
cnt = 0
except Exception as e: # pragma: no cover
logSys.error("[%s] unhandled error in actions thread: %s",
self._jail.name, e,
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
self.__flushBan(stop=True)
self.stopActions()
@ -431,7 +457,9 @@ class Actions(JailThread, Mapping):
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)
return aInfo
@ -465,7 +493,7 @@ class Actions(JailThread, Mapping):
bTicket = BanTicket.wrap(ticket)
btime = ticket.getBanTime(self.__banManager.getBanTime())
ip = bTicket.getIP()
aInfo = self.__getActionInfo(bTicket)
aInfo = self._getActionInfo(bTicket)
reason = {}
if self.__banManager.addBanTicket(bTicket, reason=reason):
cnt += 1
@ -476,7 +504,7 @@ class Actions(JailThread, Mapping):
# do actions :
for name, action in self._actions.iteritems():
try:
if ticket.restored and getattr(action, 'norestored', False):
if bTicket.restored and getattr(action, 'norestored', False):
continue
if not aInfo.immutable: aInfo.reset()
action.ban(aInfo)
@ -522,6 +550,8 @@ class Actions(JailThread, Mapping):
cnt += self.__reBan(bTicket, actions=rebanacts)
else: # pragma: no cover - unexpected: ticket is not banned for some reasons - reban using all actions:
cnt += self.__reBan(bTicket)
# add ban to database moved to observer (should previously check not already banned
# and increase ticket time if "bantime.increment" set)
if cnt:
logSys.debug("Banned %s / %s, %s ticket(s) in %r", cnt,
self.__banManager.getBanTotal(), self.__banManager.size(), self._jail.name)
@ -540,7 +570,7 @@ class Actions(JailThread, Mapping):
"""
actions = actions or self._actions
ip = ticket.getIP()
aInfo = self.__getActionInfo(ticket)
aInfo = self._getActionInfo(ticket)
if log:
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():
@ -574,7 +604,7 @@ class Actions(JailThread, Mapping):
if not action._prolongable:
continue
if aInfo is None:
aInfo = self.__getActionInfo(ticket)
aInfo = self._getActionInfo(ticket)
if not aInfo.immutable: aInfo.reset()
action.prolong(aInfo)
except Exception as e:
@ -668,7 +698,7 @@ class Actions(JailThread, Mapping):
else:
unbactions = actions
ip = ticket.getIP()
aInfo = self.__getActionInfo(ticket)
aInfo = self._getActionInfo(ticket)
if log:
logSys.notice("[%s] Unban %s", self._jail.name, aInfo["ip"])
for name, action in unbactions.iteritems():
@ -687,13 +717,19 @@ class Actions(JailThread, Mapping):
"""Status of current and total ban counts and current banned IP list.
"""
# TODO: Allow this list to be printed as 'status' output
supported_flavors = ["basic", "cymru"]
supported_flavors = ["short", "basic", "cymru"]
if flavor is None or flavor not in supported_flavors:
logSys.warning("Unsupported extended jail status flavor %r. Supported: %s" % (flavor, supported_flavors))
# Always print this information (basic)
ret = [("Currently banned", self.__banManager.size()),
("Total banned", self.__banManager.getBanTotal()),
("Banned IP list", self.__banManager.getBanList())]
if flavor != "short":
banned = self.__banManager.getBanList()
cnt = len(banned)
else:
cnt = self.__banManager.size()
ret = [("Currently banned", cnt),
("Total banned", self.__banManager.getBanTotal())]
if flavor != "short":
ret += [("Banned IP list", banned)]
if flavor == "cymru":
cymru_info = self.__banManager.getBanListExtendedCymruInfo()
ret += \

View File

@ -57,7 +57,7 @@ class BanManager:
## Total number of banned IP address
self.__banTotal = 0
## The time for next unban process (for performance and load reasons):
self.__nextUnbanTime = BanTicket.MAX_TIME
self._nextUnbanTime = BanTicket.MAX_TIME
##
# Set the ban time.
@ -66,7 +66,6 @@ class BanManager:
# @param value the time
def setBanTime(self, value):
with self.__lock:
self.__banTime = int(value)
##
@ -76,7 +75,6 @@ class BanManager:
# @return the time
def getBanTime(self):
with self.__lock:
return self.__banTime
##
@ -85,7 +83,6 @@ class BanManager:
# @param value total number
def setBanTotal(self, value):
with self.__lock:
self.__banTotal = value
##
@ -94,7 +91,6 @@ class BanManager:
# @return the total number
def getBanTotal(self):
with self.__lock:
return self.__banTotal
##
@ -103,21 +99,21 @@ class BanManager:
# @return IP list
def getBanList(self, ordered=False, withTime=False):
if not ordered:
return list(self.__banList.keys())
with self.__lock:
if not ordered:
return self.__banList.keys()
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]
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).
@ -125,8 +121,8 @@ class BanManager:
# @return ban list iterator
def __iter__(self):
with self.__lock:
return self.__banList.itervalues()
# ensure iterator is safe - traverse over the list in snapshot created within lock (GIL):
return iter(list(self.__banList.values()))
##
# Returns normalized value
@ -297,8 +293,8 @@ class BanManager:
self.__banTotal += 1
ticket.incrBanCount()
# correct next unban time:
if self.__nextUnbanTime > eob:
self.__nextUnbanTime = eob
if self._nextUnbanTime > eob:
self._nextUnbanTime = eob
return True
##
@ -329,12 +325,8 @@ class BanManager:
def unBanList(self, time, maxCount=0x7fffffff):
with self.__lock:
# Permanent banning
if self.__banTime < 0:
return list()
# Check next unban time:
nextUnbanTime = self.__nextUnbanTime
nextUnbanTime = self._nextUnbanTime
if nextUnbanTime > time:
return list()
@ -347,12 +339,12 @@ class BanManager:
if time > eob:
unBanList[fid] = ticket
if len(unBanList) >= maxCount: # stop search cycle, so reset back the next check time
nextUnbanTime = self.__nextUnbanTime
nextUnbanTime = self._nextUnbanTime
break
elif nextUnbanTime > eob:
nextUnbanTime = eob
self.__nextUnbanTime = nextUnbanTime
self._nextUnbanTime = nextUnbanTime
# Removes tickets.
if len(unBanList):
if len(unBanList) / 2.0 <= len(self.__banList) / 3.0:

View File

@ -489,22 +489,24 @@ class Fail2BanDb(object):
If log was already present in database, value of last position
in the log file; else `None`
"""
return self._addLog(cur, jail, container.getFileName(), container.getPos(), container.getHash())
def _addLog(self, cur, jail, name, pos=0, md5=None):
lastLinePos = None
cur.execute(
"SELECT firstlinemd5, lastfilepos FROM logs "
"WHERE jail=? AND path=?",
(jail.name, container.getFileName()))
(jail.name, name))
try:
firstLineMD5, lastLinePos = cur.fetchone()
except TypeError:
firstLineMD5 = False
firstLineMD5 = None
cur.execute(
"INSERT OR REPLACE INTO logs(jail, path, firstlinemd5, lastfilepos) "
"VALUES(?, ?, ?, ?)",
(jail.name, container.getFileName(),
container.getHash(), container.getPos()))
if container.getHash() != firstLineMD5:
if not firstLineMD5 and (pos or md5):
cur.execute(
"INSERT OR REPLACE INTO logs(jail, path, firstlinemd5, lastfilepos) "
"VALUES(?, ?, ?, ?)", (jail.name, name, md5, pos))
if md5 is not None and md5 != firstLineMD5:
lastLinePos = None
return lastLinePos
@ -533,7 +535,7 @@ class Fail2BanDb(object):
return set(row[0] for row in cur.fetchmany())
@commitandrollback
def updateLog(self, cur, *args, **kwargs):
def updateLog(self, cur, jail, container):
"""Updates hash and last position in log file.
Parameters
@ -543,14 +545,48 @@ class Fail2BanDb(object):
container : FileContainer
File container of the log file being updated.
"""
self._updateLog(cur, *args, **kwargs)
self._updateLog(cur, jail, container.getFileName(), container.getPos(), container.getHash())
def _updateLog(self, cur, jail, container):
def _updateLog(self, cur, jail, name, pos, md5):
cur.execute(
"UPDATE logs SET firstlinemd5=?, lastfilepos=? "
"WHERE jail=? AND path=?",
(container.getHash(), container.getPos(),
jail.name, container.getFileName()))
"WHERE jail=? AND path=?", (md5, pos, jail.name, name))
# be sure it is set (if not available):
if not cur.rowcount:
cur.execute(
"INSERT OR REPLACE INTO logs(jail, path, firstlinemd5, lastfilepos) "
"VALUES(?, ?, ?, ?)", (jail.name, name, md5, pos))
@commitandrollback
def getJournalPos(self, cur, jail, name, time=0, iso=None):
"""Get journal position from database.
Parameters
----------
jail : Jail
Jail of which the journal belongs to.
name, time, iso :
Journal name (typically systemd-journal) and last known time.
Returns
-------
int (or float)
Last position (as time) if it was already present in database; else `None`
"""
return self._addLog(cur, jail, name, time, iso); # no hash, just time as iso
@commitandrollback
def updateJournal(self, cur, jail, name, time, iso):
"""Updates last position (as time) of journal.
Parameters
----------
jail : Jail
Jail of which the journal belongs to.
name, time, iso :
Journal name (typically systemd-journal) and last known time.
"""
self._updateLog(cur, jail, name, time, iso); # no hash, just time as iso
@commitandrollback
def addBan(self, cur, jail, ticket):
@ -754,7 +790,8 @@ class Fail2BanDb(object):
if overalljails or jail is None:
query += " GROUP BY ip ORDER BY timeofban DESC LIMIT 1"
cur = self._db.cursor()
return cur.execute(query, queryArgs)
# 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):
queryArgs = []

View File

@ -282,6 +282,8 @@ class DateDetector(object):
elif "{DATE}" in key:
self.addDefaultTemplate(preMatch=pattern, allDefaults=False)
return
elif key == "{NONE}":
template = _getPatternTemplate('{UNB}^', key)
else:
template = _getPatternTemplate(pattern, key)
@ -337,65 +339,76 @@ class DateDetector(object):
# if no templates specified - default templates should be used:
if not len(self.__templates):
self.addDefaultTemplate()
logSys.log(logLevel-1, "try to match time for line: %.120s", line)
match = None
log = logSys.log if logSys.getEffectiveLevel() <= logLevel else lambda *args: None
log(logLevel-1, "try to match time for line: %.120s", line)
# first try to use last template with same start/end position:
match = None
found = None, 0x7fffffff, 0x7fffffff, -1
ignoreBySearch = 0x7fffffff
i = self.__lastTemplIdx
if i < len(self.__templates):
ddtempl = self.__templates[i]
template = ddtempl.template
if template.flags & (DateTemplate.LINE_BEGIN|DateTemplate.LINE_END):
if logSys.getEffectiveLevel() <= logLevel-1: # pragma: no cover - very-heavy debug
logSys.log(logLevel-1, " try to match last anchored template #%02i ...", i)
log(logLevel-1, " try to match last anchored template #%02i ...", i)
match = template.matchDate(line)
ignoreBySearch = i
else:
distance, endpos = self.__lastPos[0], self.__lastEndPos[0]
if logSys.getEffectiveLevel() <= logLevel-1:
logSys.log(logLevel-1, " try to match last template #%02i (from %r to %r): ...%r==%r %s %r==%r...",
i, distance, endpos,
line[distance-1:distance], self.__lastPos[1],
line[distance:endpos],
line[endpos:endpos+1], self.__lastEndPos[1])
# check same boundaries left/right, otherwise possible collision/pattern switch:
if (line[distance-1:distance] == self.__lastPos[1] and
line[endpos:endpos+1] == self.__lastEndPos[1]
):
log(logLevel-1, " try to match last template #%02i (from %r to %r): ...%r==%r %s %r==%r...",
i, distance, endpos,
line[distance-1:distance], self.__lastPos[1],
line[distance:endpos],
line[endpos:endpos+1], self.__lastEndPos[2])
# check same boundaries left/right, outside fully equal, inside only if not alnum (e. g. bound RE
# with space or some special char), otherwise possible collision/pattern switch:
if ((
line[distance-1:distance] == self.__lastPos[1] or
(line[distance] == self.__lastPos[2] and not self.__lastPos[2].isalnum())
) and (
line[endpos:endpos+1] == self.__lastEndPos[2] or
(line[endpos-1] == self.__lastEndPos[1] and not self.__lastEndPos[1].isalnum())
)):
# search in line part only:
log(logLevel-1, " boundaries are correct, search in part %r", line[distance:endpos])
match = template.matchDate(line, distance, endpos)
else:
log(logLevel-1, " boundaries show conflict, try whole search")
match = template.matchDate(line)
ignoreBySearch = i
if match:
distance = match.start()
endpos = match.end()
# if different position, possible collision/pattern switch:
if (
len(self.__templates) == 1 or # single template:
template.flags & (DateTemplate.LINE_BEGIN|DateTemplate.LINE_END) or
(distance == self.__lastPos[0] and endpos == self.__lastEndPos[0])
):
logSys.log(logLevel, " matched last time template #%02i", i)
log(logLevel, " matched last time template #%02i", i)
else:
logSys.log(logLevel, " ** last pattern collision - pattern change, search ...")
log(logLevel, " ** last pattern collision - pattern change, reserve & search ...")
found = match, distance, endpos, i; # save current best alternative
match = None
else:
logSys.log(logLevel, " ** last pattern not found - pattern change, search ...")
log(logLevel, " ** last pattern not found - pattern change, search ...")
# search template and better match:
if not match:
logSys.log(logLevel, " search template (%i) ...", len(self.__templates))
found = None, 0x7fffffff, 0x7fffffff, -1
log(logLevel, " search template (%i) ...", len(self.__templates))
i = 0
for ddtempl in self.__templates:
if logSys.getEffectiveLevel() <= logLevel-1:
logSys.log(logLevel-1, " try template #%02i: %s", i, ddtempl.name)
if i == ignoreBySearch:
i += 1
continue
log(logLevel-1, " try template #%02i: %s", i, ddtempl.name)
template = ddtempl.template
match = template.matchDate(line)
if match:
distance = match.start()
endpos = match.end()
if logSys.getEffectiveLevel() <= logLevel:
logSys.log(logLevel, " matched time template #%02i (at %r <= %r, %r) %s",
i, distance, ddtempl.distance, self.__lastPos[0], template.name)
log(logLevel, " matched time template #%02i (at %r <= %r, %r) %s",
i, distance, ddtempl.distance, self.__lastPos[0], template.name)
## last (or single) template - fast stop:
if i+1 >= len(self.__templates):
break
@ -408,7 +421,7 @@ class DateDetector(object):
## [grave] if distance changed, possible date-match was found somewhere
## in body of message, so save this template, and search further:
if distance > ddtempl.distance or distance > self.__lastPos[0]:
logSys.log(logLevel, " ** distance collision - pattern change, reserve")
log(logLevel, " ** distance collision - pattern change, reserve")
## shortest of both:
if distance < found[1]:
found = match, distance, endpos, i
@ -422,7 +435,7 @@ class DateDetector(object):
# check other template was found (use this one with shortest distance):
if not match and found[0]:
match, distance, endpos, i = found
logSys.log(logLevel, " use best time template #%02i", i)
log(logLevel, " use best time template #%02i", i)
ddtempl = self.__templates[i]
template = ddtempl.template
# we've winner, incr hits, set distance, usage, reorder, etc:
@ -432,8 +445,8 @@ class DateDetector(object):
ddtempl.distance = distance
if self.__firstUnused == i:
self.__firstUnused += 1
self.__lastPos = distance, line[distance-1:distance]
self.__lastEndPos = endpos, line[endpos:endpos+1]
self.__lastPos = distance, line[distance-1:distance], line[distance]
self.__lastEndPos = endpos, line[endpos-1], line[endpos:endpos+1]
# if not first - try to reorder current template (bubble up), they will be not sorted anymore:
if i and i != self.__lastTemplIdx:
i = self._reorderTemplate(i)
@ -442,7 +455,7 @@ class DateDetector(object):
return (match, template)
# not found:
logSys.log(logLevel, " no template.")
log(logLevel, " no template.")
return (None, None)
@property

View File

@ -36,15 +36,16 @@ logSys = getLogger(__name__)
RE_GROUPED = re.compile(r'(?<!(?:\(\?))(?<!\\)\((?!\?)')
RE_GROUP = ( re.compile(r'^((?:\(\?\w+\))?\^?(?:\(\?\w+\))?)(.*?)(\$?)$'), r"\1(\2)\3" )
RE_EXLINE_NO_BOUNDS = re.compile(r'^\{UNB\}')
RE_EXLINE_BOUND_BEG = re.compile(r'^\{\^LN-BEG\}')
RE_EXSANC_BOUND_BEG = re.compile(r'^\(\?:\^\|\\b\|\\W\)')
RE_EXSANC_BOUND_BEG = re.compile(r'^\((?:\?:)?\^\|\\b\|\\W\)')
RE_EXEANC_BOUND_BEG = re.compile(r'\(\?=\\b\|\\W\|\$\)$')
RE_NO_WRD_BOUND_BEG = re.compile(r'^\(*(?:\(\?\w+\))?(?:\^|\(*\*\*|\(\?:\^)')
RE_NO_WRD_BOUND_BEG = re.compile(r'^\(*(?:\(\?\w+\))?(?:\^|\(*\*\*|\((?:\?:)?\^)')
RE_NO_WRD_BOUND_END = re.compile(r'(?<!\\)(?:\$\)?|\\b|\\s|\*\*\)*)$')
RE_DEL_WRD_BOUNDS = ( re.compile(r'^\(*(?:\(\?\w+\))?\(*\*\*|(?<!\\)\*\*\)*$'),
lambda m: m.group().replace('**', '') )
RE_LINE_BOUND_BEG = re.compile(r'^(?:\(\?\w+\))?(?:\^|\(\?:\^(?!\|))')
RE_LINE_BOUND_BEG = re.compile(r'^(?:\(\?\w+\))?(?:\^|\((?:\?:)?\^(?!\|))')
RE_LINE_BOUND_END = re.compile(r'(?<![\\\|])(?:\$\)?)$')
RE_ALPHA_PATTERN = re.compile(r'(?<!\%)\%[aAbBpc]')
@ -119,7 +120,7 @@ class DateTemplate(object):
if boundBegin:
self.flags |= DateTemplate.WORD_BEGIN if wordBegin != 'start' else DateTemplate.LINE_BEGIN
if wordBegin != 'start':
regex = r'(?:^|\b|\W)' + regex
regex = r'(?=^|\b|\W)' + regex
else:
regex = r"^(?:\W{0,2})?" + regex
if not self.name.startswith('{^LN-BEG}'):
@ -128,8 +129,10 @@ class DateTemplate(object):
if boundEnd:
self.flags |= DateTemplate.WORD_END
regex += r'(?=\b|\W|$)'
if RE_LINE_BOUND_BEG.search(regex): self.flags |= DateTemplate.LINE_BEGIN
if RE_LINE_BOUND_END.search(regex): self.flags |= DateTemplate.LINE_END
if not (self.flags & DateTemplate.LINE_BEGIN) and RE_LINE_BOUND_BEG.search(regex):
self.flags |= DateTemplate.LINE_BEGIN
if not (self.flags & DateTemplate.LINE_END) and RE_LINE_BOUND_END.search(regex):
self.flags |= DateTemplate.LINE_END
# remove possible special pattern "**" in front and end of regex:
regex = RE_DEL_WRD_BOUNDS[0].sub(RE_DEL_WRD_BOUNDS[1], regex)
self._regex = regex
@ -188,7 +191,7 @@ class DateTemplate(object):
def unboundPattern(pattern):
return RE_EXEANC_BOUND_BEG.sub('',
RE_EXSANC_BOUND_BEG.sub('',
RE_EXLINE_BOUND_BEG.sub('', pattern)
RE_EXLINE_BOUND_BEG.sub('', RE_EXLINE_NO_BOUNDS.sub('', pattern))
)
)
@ -297,6 +300,10 @@ class DatePatternRegex(DateTemplate):
def setRegex(self, pattern, wordBegin=True, wordEnd=True):
# original pattern:
self._pattern = pattern
# if unbound signalled - reset boundaries left and right:
if RE_EXLINE_NO_BOUNDS.search(pattern):
pattern = RE_EXLINE_NO_BOUNDS.sub('', pattern)
wordBegin = wordEnd = False
# if explicit given {^LN-BEG} - remove it from pattern and set 'start' in wordBegin:
if wordBegin and RE_EXLINE_BOUND_BEG.search(pattern):
pattern = RE_EXLINE_BOUND_BEG.sub('', pattern)

View File

@ -43,26 +43,20 @@ class FailManager:
self.__maxRetry = 3
self.__maxTime = 600
self.__failTotal = 0
self.maxMatches = 50
self.maxMatches = 5
self.__bgSvc = BgService()
def setFailTotal(self, value):
with self.__lock:
self.__failTotal = value
self.__failTotal = value
def getFailTotal(self):
with self.__lock:
return self.__failTotal
return self.__failTotal
def getFailCount(self):
# may be slow on large list of failures, should be used for test purposes only...
with self.__lock:
return len(self.__failList), sum([f.getRetry() for f in self.__failList.values()])
def getFailTotal(self):
with self.__lock:
return self.__failTotal
def setMaxRetry(self, value):
self.__maxRetry = value
@ -92,10 +86,7 @@ class FailManager:
if attempt <= 0:
attempt += 1
unixTime = ticket.getTime()
fData.setLastTime(unixTime)
if fData.getLastReset() < unixTime - self.__maxTime:
fData.setLastReset(unixTime)
fData.setRetry(0)
fData.adjustTime(unixTime, self.__maxTime)
fData.inc(matches, attempt, count)
# truncate to maxMatches:
if self.maxMatches:
@ -133,13 +124,12 @@ class FailManager:
return attempts
def size(self):
with self.__lock:
return len(self.__failList)
return len(self.__failList)
def cleanup(self, time):
with self.__lock:
todelete = [fid for fid,item in self.__failList.iteritems() \
if item.getLastTime() + self.__maxTime <= time]
if item.getTime() + self.__maxTime <= time]
if len(todelete) == len(self.__failList):
# remove all:
self.__failList = dict()
@ -153,7 +143,7 @@ class FailManager:
else:
# create new dictionary without items to be deleted:
self.__failList = dict((fid,item) for fid,item in self.__failList.iteritems() \
if item.getLastTime() + self.__maxTime > time)
if item.getTime() + self.__maxTime > time)
self.__bgSvc.service()
def delFailure(self, fid):

View File

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

View File

@ -81,6 +81,7 @@ class Filter(JailThread):
## Ignore own IPs flag:
self.__ignoreSelf = True
## The ignore IP list.
self.__ignoreIpSet = set()
self.__ignoreIpList = []
## External command
self.__ignoreCommand = False
@ -106,8 +107,16 @@ class Filter(JailThread):
self.returnRawHost = False
## check each regex (used for test purposes):
self.checkAllRegex = False
## avoid finding of pending failures (without ID/IP, used in fail2ban-regex):
self.ignorePending = True
## callback called on ignoreregex match :
self.onIgnoreRegex = None
## if true ignores obsolete failures (failure time < now - findTime):
self.checkFindTime = True
## shows that filter is in operation mode (processing new messages):
self.inOperation = True
## if true prevents against retarded banning in case of RC by too many failures (disabled only for test purposes):
self.banASAP = True
## Ticks counter
self.ticks = 0
## Thread name:
@ -169,7 +178,7 @@ class Filter(JailThread):
# @param value the regular expression
def addFailRegex(self, value):
multiLine = self.getMaxLines() > 1
multiLine = self.__lineBufferSize > 1
try:
regex = FailRegex(value, prefRegex=self.__prefRegex, multiline=multiLine,
useDns=self.__useDns)
@ -452,10 +461,10 @@ class Filter(JailThread):
logSys.info(
"[%s] Attempt %s - %s", self.jailName, ip, datetime.datetime.fromtimestamp(unixTime).strftime("%Y-%m-%d %H:%M:%S")
)
self.failManager.addFailure(ticket, len(matches) or 1)
attempts = self.failManager.addFailure(ticket, len(matches) or 1)
# Perform the ban if this attempt is resulted to:
self.performBan(ip)
if attempts >= self.failManager.getMaxRetry():
self.performBan(ip)
return 1
@ -484,28 +493,36 @@ class Filter(JailThread):
# Create IP address object
ip = IPAddr(ipstr)
# Avoid exact duplicates
if ip in self.__ignoreIpList:
logSys.warn(" Ignore duplicate %r (%r), already in ignore list", ip, ipstr)
if ip in self.__ignoreIpSet or ip in self.__ignoreIpList:
logSys.log(logging.MSG, " Ignore duplicate %r (%r), already in ignore list", ip, ipstr)
return
# log and append to ignore list
logSys.debug(" Add %r to ignore list (%r)", ip, ipstr)
self.__ignoreIpList.append(ip)
# if single IP (not DNS or a subnet) add to set, otherwise to list:
if ip.isSingle:
self.__ignoreIpSet.add(ip)
else:
self.__ignoreIpList.append(ip)
def delIgnoreIP(self, ip=None):
# clear all:
if ip is None:
self.__ignoreIpSet.clear()
del self.__ignoreIpList[:]
return
# delete by ip:
logSys.debug(" Remove %r from ignore list", ip)
self.__ignoreIpList.remove(ip)
if ip in self.__ignoreIpSet:
self.__ignoreIpSet.remove(ip)
else:
self.__ignoreIpList.remove(ip)
def logIgnoreIp(self, ip, log_ignore, ignore_source="unknown source"):
if log_ignore:
logSys.info("[%s] Ignore %s by %s", self.jailName, ip, ignore_source)
def getIgnoreIP(self):
return self.__ignoreIpList
return self.__ignoreIpList + list(self.__ignoreIpSet)
##
# Check if IP address/DNS is in the ignore list.
@ -545,8 +562,11 @@ class Filter(JailThread):
if self.__ignoreCache: c.set(key, True)
return True
# check if the IP is covered by ignore IP (in set or in subnet/dns):
if ip in self.__ignoreIpSet:
self.logIgnoreIp(ip, log_ignore, ignore_source="ip")
return True
for net in self.__ignoreIpList:
# check if the IP is covered by ignore IP
if ip.isInNet(net):
self.logIgnoreIp(ip, log_ignore, ignore_source=("ip" if net.isValid else "dns"))
if self.__ignoreCache: c.set(key, True)
@ -569,29 +589,89 @@ class Filter(JailThread):
if self.__ignoreCache: c.set(key, False)
return False
def _logWarnOnce(self, nextLTM, *args):
"""Log some issue as warning once per day, otherwise level 7"""
if MyTime.time() < getattr(self, nextLTM, 0):
if logSys.getEffectiveLevel() <= 7: logSys.log(7, *(args[0]))
else:
setattr(self, nextLTM, MyTime.time() + 24*60*60)
for args in args:
logSys.warning('[%s] ' + args[0], self.jailName, *args[1:])
def processLine(self, line, date=None):
"""Split the time portion from log msg and return findFailures on them
"""
logSys.log(7, "Working on line %r", line)
noDate = False
if date:
tupleLine = line
self.__lastTimeText = tupleLine[1]
self.__lastDate = date
else:
l = line.rstrip('\r\n')
logSys.log(7, "Working on line %r", line)
(timeMatch, template) = self.dateDetector.matchTime(l)
if timeMatch:
tupleLine = (
l[:timeMatch.start(1)],
l[timeMatch.start(1):timeMatch.end(1)],
l[timeMatch.end(1):],
(timeMatch, template)
)
# try to parse date:
timeMatch = self.dateDetector.matchTime(line)
m = timeMatch[0]
if m:
s = m.start(1)
e = m.end(1)
m = line[s:e]
tupleLine = (line[:s], m, line[e:])
if m: # found and not empty - retrive date:
date = self.dateDetector.getTime(m, timeMatch)
if date is not None:
# Lets get the time part
date = date[0]
self.__lastTimeText = m
self.__lastDate = date
else:
logSys.error("findFailure failed to parse timeText: %s", m)
# matched empty value - date is optional or not available - set it to last known or now:
elif self.__lastDate and self.__lastDate > MyTime.time() - 60:
# set it to last known:
tupleLine = ("", self.__lastTimeText, line)
date = self.__lastDate
else:
# set it to now:
date = MyTime.time()
else:
tupleLine = (l, "", "", None)
tupleLine = ("", "", line)
# still no date - try to use last known:
if date is None:
noDate = True
if self.__lastDate and self.__lastDate > MyTime.time() - 60:
tupleLine = ("", self.__lastTimeText, line)
date = self.__lastDate
if self.checkFindTime:
# if in operation (modifications have been really found):
if self.inOperation:
# if weird date - we'd simulate now for timeing issue (too large deviation from now):
if (date is None or date < MyTime.time() - 60 or date > MyTime.time() + 60):
# log time zone issue as warning once per day:
self._logWarnOnce("_next_simByTimeWarn",
("Simulate NOW in operation since found time has too large deviation %s ~ %s +/- %s",
date, MyTime.time(), 60),
("Please check jail has possibly a timezone issue. Line with odd timestamp: %s",
line))
# simulate now as date:
date = MyTime.time()
self.__lastDate = date
else:
# in initialization (restore) phase, if too old - ignore:
if date is not None and date < MyTime.time() - self.getFindTime():
# log time zone issue as warning once per day:
self._logWarnOnce("_next_ignByTimeWarn",
("Ignore line since time %s < %s - %s",
date, MyTime.time(), self.getFindTime()),
("Please check jail has possibly a timezone issue. Line with odd timestamp: %s",
line))
# ignore - too old (obsolete) entry:
return []
# save last line (lazy convert of process line tuple to string on demand):
self.processedLine = lambda: "".join(tupleLine[::2])
return self.findFailure(tupleLine, date)
return self.findFailure(tupleLine, date, noDate=noDate)
def processLineAndAdd(self, line, date=None):
"""Processes the line for failures and populates failManager
@ -603,13 +683,20 @@ class Filter(JailThread):
fail = element[3]
logSys.debug("Processing line with time:%s and ip:%s",
unixTime, ip)
# ensure the time is not in the future, e. g. by some estimated (assumed) time:
if self.checkFindTime and unixTime > MyTime.time():
unixTime = MyTime.time()
tick = FailTicket(ip, unixTime, data=fail)
if self._inIgnoreIPList(ip, tick):
continue
logSys.info(
"[%s] Found %s - %s", self.jailName, ip, MyTime.time2str(unixTime)
)
self.failManager.addFailure(tick)
attempts = self.failManager.addFailure(tick)
# 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:
if self.banASAP and attempts >= self.failManager.getMaxRetry():
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)
@ -632,20 +719,26 @@ class Filter(JailThread):
self._errors //= 2
self.idle = True
##
# Returns true if the line should be ignored.
#
# Uses ignoreregex.
# @param line: the line
# @return: a boolean
def ignoreLine(self, tupleLines):
buf = Regex._tupleLinesBuf(tupleLines)
def _ignoreLine(self, buf, orgBuffer, failRegex=None):
# if multi-line buffer - use matched only, otherwise (single line) - original buf:
if failRegex and self.__lineBufferSize > 1:
orgBuffer = failRegex.getMatchedTupleLines()
buf = Regex._tupleLinesBuf(orgBuffer)
# search ignored:
fnd = None
for ignoreRegexIndex, ignoreRegex in enumerate(self.__ignoreRegex):
ignoreRegex.search(buf, tupleLines)
ignoreRegex.search(buf, orgBuffer)
if ignoreRegex.hasMatched():
return ignoreRegexIndex
return None
fnd = ignoreRegexIndex
logSys.log(7, " Matched ignoreregex %d and was ignored", fnd)
if self.onIgnoreRegex: self.onIgnoreRegex(fnd, ignoreRegex)
# remove ignored match:
if not self.checkAllRegex or self.__lineBufferSize > 1:
# todo: check ignoreRegex.getUnmatchedTupleLines() would be better (fix testGetFailuresMultiLineIgnoreRegex):
if failRegex:
self.__lineBuffer = failRegex.getUnmatchedTupleLines()
if not self.checkAllRegex: break
return fnd
def _updateUsers(self, fail, user=()):
users = fail.get('users')
@ -655,54 +748,31 @@ class Filter(JailThread):
fail['users'] = users = set()
users.add(user)
return users
return None
# # ATM incremental (non-empty only) merge deactivated ...
# @staticmethod
# def _updateFailure(self, mlfidGroups, fail):
# # reset old failure-ids when new types of id available in this failure:
# fids = set()
# for k in ('fid', 'ip4', 'ip6', 'dns'):
# if fail.get(k):
# fids.add(k)
# if fids:
# for k in ('fid', 'ip4', 'ip6', 'dns'):
# if k not in fids:
# try:
# del mlfidGroups[k]
# except:
# pass
# # update not empty values:
# mlfidGroups.update(((k,v) for k,v in fail.iteritems() if v))
return users
def _mergeFailure(self, mlfid, fail, failRegex):
mlfidFail = self.mlfidCache.get(mlfid) if self.__mlfidCache else None
users = None
nfflgs = 0
if fail.get("mlfgained"):
nfflgs |= 9
nfflgs |= (8|1)
if not fail.get('nofail'):
fail['nofail'] = fail["mlfgained"]
elif fail.get('nofail'): nfflgs |= 1
if fail.get('mlfforget'): nfflgs |= 2
if fail.pop('mlfforget', None): nfflgs |= 2
# if multi-line failure id (connection id) known:
if mlfidFail:
mlfidGroups = mlfidFail[1]
# update users set (hold all users of connect):
users = self._updateUsers(mlfidGroups, fail.get('user'))
# be sure we've correct current state ('nofail' and 'mlfgained' only from last failure)
try:
del mlfidGroups['nofail']
del mlfidGroups['mlfgained']
except KeyError:
pass
# # ATM incremental (non-empty only) merge deactivated (for future version only),
# # it can be simulated using alternate value tags, like <F-ALT_VAL>...</F-ALT_VAL>,
# # so previous value 'val' will be overwritten only if 'alt_val' is not empty...
# _updateFailure(mlfidGroups, fail)
#
if mlfidGroups.pop('nofail', None): nfflgs |= 4
if mlfidGroups.pop('mlfgained', None): nfflgs |= 4
# if we had no pending failures then clear the matches (they are already provided):
if (nfflgs & 4) == 0 and not mlfidGroups.get('mlfpending', 0):
mlfidGroups.pop("matches", None)
# overwrite multi-line failure with all values, available in fail:
mlfidGroups.update(fail)
mlfidGroups.update(((k,v) for k,v in fail.iteritems() if v is not None))
# new merged failure data:
fail = mlfidGroups
# if forget (disconnect/reset) - remove cached entry:
@ -713,24 +783,19 @@ class Filter(JailThread):
mlfidFail = [self.__lastDate, fail]
self.mlfidCache.set(mlfid, mlfidFail)
# check users in order to avoid reset failure by multiple logon-attempts:
if users and len(users) > 1:
# we've new user, reset 'nofail' because of multiple users attempts:
try:
del fail['nofail']
nfflgs &= ~1 # reset nofail
except KeyError:
pass
if fail.pop('mlfpending', 0) or users and len(users) > 1:
# we've pending failures or new user, reset 'nofail' because of failures or multiple users attempts:
fail.pop('nofail', None)
fail.pop('mlfgained', None)
nfflgs &= ~(8|1) # reset nofail and gained
# merge matches:
if not (nfflgs & 1): # current nofail state (corresponding users)
try:
m = fail.pop("nofail-matches")
m += fail.get("matches", [])
except KeyError:
m = fail.get("matches", [])
if not (nfflgs & 8): # no gain signaled
if (nfflgs & 1) == 0: # current nofail state (corresponding users)
m = fail.pop("nofail-matches", [])
m += fail.get("matches", [])
if (nfflgs & 8) == 0: # no gain signaled
m += failRegex.getMatchedTupleLines()
fail["matches"] = m
elif not (nfflgs & 2) and (nfflgs & 1): # not mlfforget and nofail:
elif (nfflgs & 3) == 1: # not mlfforget and nofail:
fail["nofail-matches"] = fail.get("nofail-matches", []) + failRegex.getMatchedTupleLines()
# return merged:
return fail
@ -743,7 +808,7 @@ class Filter(JailThread):
# to find the logging time.
# @return a dict with IP and timestamp.
def findFailure(self, tupleLine, date=None):
def findFailure(self, tupleLine, date, noDate=False):
failList = list()
ll = logSys.getEffectiveLevel()
@ -753,62 +818,33 @@ class Filter(JailThread):
returnRawHost = True
cidr = IPAddr.CIDR_RAW
# Checks if we mut ignore this line.
if self.ignoreLine([tupleLine[::2]]) is not None:
# The ignoreregex matched. Return.
if ll <= 7: logSys.log(7, "Matched ignoreregex and was \"%s\" ignored",
"".join(tupleLine[::2]))
return failList
timeText = tupleLine[1]
if date:
self.__lastTimeText = timeText
self.__lastDate = date
elif timeText:
dateTimeMatch = self.dateDetector.getTime(timeText, tupleLine[3])
if dateTimeMatch is None:
logSys.error("findFailure failed to parse timeText: %s", timeText)
date = self.__lastDate
else:
# Lets get the time part
date = dateTimeMatch[0]
self.__lastTimeText = timeText
self.__lastDate = date
else:
timeText = self.__lastTimeText or "".join(tupleLine[::2])
date = self.__lastDate
if self.checkFindTime and date is not None and date < MyTime.time() - self.getFindTime():
if ll <= 5: logSys.log(5, "Ignore line since time %s < %s - %s",
date, MyTime.time(), self.getFindTime())
return failList
if self.__lineBufferSize > 1:
orgBuffer = self.__lineBuffer = (
self.__lineBuffer + [tupleLine[:3]])[-self.__lineBufferSize:]
self.__lineBuffer.append(tupleLine)
orgBuffer = self.__lineBuffer = self.__lineBuffer[-self.__lineBufferSize:]
else:
orgBuffer = self.__lineBuffer = [tupleLine[:3]]
if ll <= 5: logSys.log(5, "Looking for match of %r", self.__lineBuffer)
buf = Regex._tupleLinesBuf(self.__lineBuffer)
orgBuffer = self.__lineBuffer = [tupleLine]
if ll <= 5: logSys.log(5, "Looking for match of %r", orgBuffer)
buf = Regex._tupleLinesBuf(orgBuffer)
# Checks if we must ignore this line (only if fewer ignoreregex than failregex).
if self.__ignoreRegex and len(self.__ignoreRegex) < len(self.__failRegex) - 2:
if self._ignoreLine(buf, orgBuffer) is not None:
# The ignoreregex matched. Return.
return failList
# Pre-filter fail regex (if available):
preGroups = {}
if self.__prefRegex:
if ll <= 5: logSys.log(5, " Looking for prefregex %r", self.__prefRegex.getRegex())
self.__prefRegex.search(buf, self.__lineBuffer)
self.__prefRegex.search(buf, orgBuffer)
if not self.__prefRegex.hasMatched():
if ll <= 5: logSys.log(5, " Prefregex not matched")
return failList
preGroups = self.__prefRegex.getGroups()
if ll <= 7: logSys.log(7, " Pre-filter matched %s", preGroups)
repl = preGroups.get('content')
repl = preGroups.pop('content', None)
# Content replacement:
if repl:
del preGroups['content']
self.__lineBuffer, buf = [('', '', repl)], None
# Iterates over all the regular expressions.
@ -826,28 +862,21 @@ class Filter(JailThread):
# The failregex matched.
if ll <= 7: logSys.log(7, " Matched failregex %d: %s", failRegexIndex, fail)
# Checks if we must ignore this match.
if self.ignoreLine(failRegex.getMatchedTupleLines()) \
is not None:
if self.__ignoreRegex and self._ignoreLine(buf, orgBuffer, failRegex) is not None:
# The ignoreregex matched. Remove ignored match.
self.__lineBuffer, buf = failRegex.getUnmatchedTupleLines(), None
if ll <= 7: logSys.log(7, " Matched ignoreregex and was ignored")
buf = None
if not self.checkAllRegex:
break
else:
continue
if date is None:
logSys.warning(
"Found a match for %r but no valid date/time "
"found for %r. Please try setting a custom "
"date pattern (see man page jail.conf(5)). "
"If format is complex, please "
"file a detailed issue on"
" https://github.com/fail2ban/fail2ban/issues "
"in order to get support for this format.",
"\n".join(failRegex.getMatchedLines()), timeText)
continue
if noDate:
self._logWarnOnce("_next_noTimeWarn",
("Found a match but no valid date/time found for %r.", tupleLine[1]),
("Match without a timestamp: %s", "\n".join(failRegex.getMatchedLines())),
("Please try setting a custom date pattern (see man page jail.conf(5)).",)
)
if date is None and self.checkFindTime: continue
# we should check all regex (bypass on multi-line, otherwise too complex):
if not self.checkAllRegex or self.getMaxLines() > 1:
if not self.checkAllRegex or self.__lineBufferSize > 1:
self.__lineBuffer, buf = failRegex.getUnmatchedTupleLines(), None
# merge data if multi-line failure:
raw = returnRawHost
@ -892,7 +921,8 @@ class Filter(JailThread):
if host is None:
if ll <= 7: logSys.log(7, "No failure-id by mlfid %r in regex %s: %s",
mlfid, failRegexIndex, fail.get('mlfforget', "waiting for identifier"))
if not self.checkAllRegex: return failList
fail['mlfpending'] = 1; # mark failure is pending
if not self.checkAllRegex and self.ignorePending: return failList
ips = [None]
# if raw - add single ip or failure-id,
# otherwise expand host to multiple ips using dns (or ignore it if not valid):
@ -905,6 +935,9 @@ class Filter(JailThread):
# otherwise, try to use dns conversion:
else:
ips = DNSUtils.textToIp(host, self.__useDns)
# if checkAllRegex we must make a copy (to be sure next RE doesn't change merged/cached failure):
if self.checkAllRegex and mlfid is not None:
fail = fail.copy()
# append failure with match to the list:
for ip in ips:
failList.append([failRegexIndex, ip, date, fail])
@ -950,7 +983,7 @@ class FileFilter(Filter):
log.setPos(lastpos)
self.__logs[path] = log
logSys.info("Added logfile: %r (pos = %s, hash = %s)" , path, log.getPos(), log.getHash())
if autoSeek:
if autoSeek and not tail:
self.__autoSeek[path] = autoSeek
self._addLogPath(path) # backend specific
@ -1034,7 +1067,7 @@ class FileFilter(Filter):
# MyTime.time()-self.findTime. When a failure is detected, a FailTicket
# is created and is added to the FailManager.
def getFailures(self, filename):
def getFailures(self, filename, inOperation=None):
log = self.getLog(filename)
if log is None:
logSys.error("Unable to get failures in %s", filename)
@ -1079,10 +1112,15 @@ class FileFilter(Filter):
if has_content:
while not self.idle:
line = log.readline()
if not line or not self.active:
# The jail reached the bottom or has been stopped
if not self.active: break; # jail has been stopped
if not line:
# The jail reached the bottom, simply set in operation for this log
# (since we are first time at end of file, growing is only possible after modifications):
log.inOperation = True
break
self.processLineAndAdd(line)
# acquire in operation from log and process:
self.inOperation = inOperation if inOperation is not None else log.inOperation
self.processLineAndAdd(line.rstrip('\r\n'))
finally:
log.close()
db = self.jail.database
@ -1220,7 +1258,7 @@ except ImportError: # pragma: no cover
class FileContainer:
def __init__(self, filename, encoding, tail = False):
def __init__(self, filename, encoding, tail=False):
self.__filename = filename
self.setEncoding(encoding)
self.__tail = tail
@ -1241,6 +1279,8 @@ class FileContainer:
self.__pos = 0
finally:
handler.close()
## shows that log is in operation mode (expecting new messages only from here):
self.inOperation = tail
def getFileName(self):
return self.__filename
@ -1314,16 +1354,17 @@ class FileContainer:
return line.decode(enc, 'strict')
except (UnicodeDecodeError, UnicodeEncodeError) as e:
global _decode_line_warn
lev = logging.DEBUG
if _decode_line_warn.get(filename, 0) <= MyTime.time():
lev = 7
if not _decode_line_warn.get(filename, 0):
lev = logging.WARNING
_decode_line_warn[filename] = MyTime.time() + 24*60*60
_decode_line_warn.set(filename, 1)
logSys.log(lev,
"Error decoding line from '%s' with '%s'."
" Consider setting logencoding=utf-8 (or another appropriate"
" encoding) for this jail. Continuing"
" to process line ignoring invalid characters: %r",
filename, enc, line)
"Error decoding line from '%s' with '%s'.", filename, enc)
if logSys.getEffectiveLevel() <= lev:
logSys.log(lev, "Consider setting logencoding=utf-8 (or another appropriate"
" encoding) for this jail. Continuing"
" to process line ignoring invalid characters: %r",
line)
# decode with replacing error chars:
line = line.decode(enc, 'replace')
return line
@ -1344,7 +1385,7 @@ class FileContainer:
## print "D: Closed %s with pos %d" % (handler, self.__pos)
## sys.stdout.flush()
_decode_line_warn = {}
_decode_line_warn = Utils.Cache(maxCount=1000, maxTime=24*60*60);
##

View File

@ -79,7 +79,8 @@ class FilterGamin(FileFilter):
this is a common logic and must be shared/provided by FileFilter
"""
self.getFailures(path)
self.performBan()
if not self.banASAP: # pragma: no cover
self.performBan()
self.__modified = False
##

View File

@ -111,13 +111,16 @@ class FilterPoll(FileFilter):
modlst = []
Utils.wait_for(lambda: not self.active or self.getModified(modlst),
self.sleeptime)
if not self.active: # pragma: no cover - timing
break
for filename in modlst:
self.getFailures(filename)
self.__modified = True
self.ticks += 1
if self.__modified:
self.performBan()
if not self.banASAP: # pragma: no cover
self.performBan()
self.__modified = False
except Exception as e: # pragma: no cover
if not self.active: # if not active - error by stop...
@ -139,7 +142,7 @@ class FilterPoll(FileFilter):
try:
logStats = os.stat(filename)
stats = logStats.st_mtime, logStats.st_ino, logStats.st_size
pstats = self.__prevStats.get(filename, (0))
pstats = self.__prevStats.get(filename, (0,))
if logSys.getEffectiveLevel() <= 4:
# we do not want to waste time on strftime etc if not necessary
dt = logStats.st_mtime - pstats[0]

View File

@ -140,7 +140,8 @@ class FilterPyinotify(FileFilter):
"""
if not self.idle:
self.getFailures(path)
self.performBan()
if not self.banASAP: # pragma: no cover
self.performBan()
self.__modified = False
def _addPending(self, path, reason, isDir=False):
@ -187,7 +188,8 @@ class FilterPyinotify(FileFilter):
for path, isDir in found.iteritems():
self._delPending(path)
# refresh monitoring of this:
self._refreshWatcher(path, isDir=isDir)
if isDir is not None:
self._refreshWatcher(path, isDir=isDir)
if isDir:
# check all files belong to this dir:
for logpath in self.__watchFiles:
@ -270,7 +272,13 @@ class FilterPyinotify(FileFilter):
def _addLogPath(self, path):
self._addFileWatcher(path)
self._process_file(path)
# initial scan:
if self.active:
# we can execute it right now:
self._process_file(path)
else:
# retard until filter gets started, isDir=None signals special case: process file only (don't need to refresh monitor):
self._addPending(path, ('INITIAL', path), isDir=None)
##
# Delete a log path
@ -278,9 +286,9 @@ class FilterPyinotify(FileFilter):
# @param path the log file to delete
def _delLogPath(self, path):
self._delPending(path)
if not self._delFileWatcher(path): # pragma: no cover
logSys.error("Failed to remove watch on path: %s", path)
self._delPending(path)
path_dir = dirname(path)
for k in self.__watchFiles:
@ -290,8 +298,8 @@ class FilterPyinotify(FileFilter):
if path_dir:
# Remove watches for the directory
# since there is no other monitored file under this directory
self._delDirWatcher(path_dir)
self._delPending(path_dir)
self._delDirWatcher(path_dir)
# pyinotify.ProcessEvent default handler:
def __process_default(self, event):

View File

@ -190,6 +190,13 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
def getJournalReader(self):
return self.__journal
def getJrnEntTime(self, logentry):
""" Returns time of entry as tuple (ISO-str, Posix)."""
date = logentry.get('_SOURCE_REALTIME_TIMESTAMP')
if date is None:
date = logentry.get('__REALTIME_TIMESTAMP')
return (date.isoformat(), time.mktime(date.timetuple()) + date.microsecond/1.0E6)
##
# Format journal log entry into syslog style
#
@ -222,9 +229,8 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
logelements[-1] += v
logelements[-1] += ":"
if logelements[-1] == "kernel:":
if '_SOURCE_MONOTONIC_TIMESTAMP' in logentry:
monotonic = logentry.get('_SOURCE_MONOTONIC_TIMESTAMP')
else:
monotonic = logentry.get('_SOURCE_MONOTONIC_TIMESTAMP')
if monotonic is None:
monotonic = logentry.get('__MONOTONIC_TIMESTAMP')[0]
logelements.append("[%12.6f]" % monotonic.total_seconds())
msg = logentry.get('MESSAGE','')
@ -235,13 +241,11 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
logline = " ".join(logelements)
date = logentry.get('_SOURCE_REALTIME_TIMESTAMP',
logentry.get('__REALTIME_TIMESTAMP'))
date = self.getJrnEntTime(logentry)
logSys.log(5, "[%s] Read systemd journal entry: %s %s", self.jailName,
date.isoformat(), logline)
date[0], logline)
## use the same type for 1st argument:
return ((logline[:0], date.isoformat(), logline.replace('\n', '\\n')),
time.mktime(date.timetuple()) + date.microsecond/1.0E6)
return ((logline[:0], date[0], logline.replace('\n', '\\n')), date[1])
def seekToTime(self, date):
if not isinstance(date, datetime.datetime):
@ -262,9 +266,12 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
"Jail regexs will be checked against all journal entries, "
"which is not advised for performance reasons.")
# Seek to now - findtime in journal
start_time = datetime.datetime.now() - \
datetime.timedelta(seconds=int(self.getFindTime()))
# Try to obtain the last known time (position of journal)
start_time = 0
if self.jail.database is not None:
start_time = self.jail.database.getJournalPos(self.jail, 'systemd-journal') or 0
# Seek to max(last_known_time, now - findtime) in journal
start_time = max( start_time, MyTime.time() - int(self.getFindTime()) )
self.seekToTime(start_time)
# Move back one entry to ensure do not end up in dead space
# if start time beyond end of journal
@ -303,16 +310,20 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
e, exc_info=logSys.getEffectiveLevel() <= logging.DEBUG)
self.ticks += 1
if logentry:
self.processLineAndAdd(
*self.formatJournalEntry(logentry))
line = self.formatJournalEntry(logentry)
self.processLineAndAdd(*line)
self.__modified += 1
if self.__modified >= 100: # todo: should be configurable
break
else:
break
if self.__modified:
self.performBan()
if not self.banASAP: # pragma: no cover
self.performBan()
self.__modified = 0
# update position in log (time and iso string):
if self.jail.database is not None:
self.jail.database.updateJournal(self.jail, 'systemd-journal', line[1], line[0][1])
except Exception as e: # pragma: no cover
if not self.active: # if not active - error by stop...
break

View File

@ -337,7 +337,7 @@ class IPAddr(object):
return repr(self.ntoa)
def __str__(self):
return self.ntoa
return self.ntoa if isinstance(self.ntoa, basestring) else str(self.ntoa)
def __reduce__(self):
"""IPAddr pickle-handler, that simply wraps IPAddr to the str
@ -379,6 +379,12 @@ class IPAddr(object):
"""
return self._family != socket.AF_UNSPEC
@property
def isSingle(self):
"""Returns whether the object is a single IP address (not DNS and subnet)
"""
return self._plen == {socket.AF_INET: 32, socket.AF_INET6: 128}.get(self._family, -1000)
def __eq__(self, other):
if self._family == IPAddr.CIDR_RAW and not isinstance(other, IPAddr):
return self._raw == other
@ -511,6 +517,11 @@ class IPAddr(object):
return (self.addr & mask) == net.addr
def contains(self, ip):
"""Return whether the object (as network) contains given IP
"""
return isinstance(ip, IPAddr) and (ip == self or ip.isInNet(self))
# Pre-calculated map: addr to maskplen
def __getMaskMap():
m6 = (1 << 128)-1

View File

@ -161,6 +161,10 @@ class Jail(object):
"""
return self.__db
@database.setter
def database(self, value):
self.__db = value;
@property
def filter(self):
"""The filter which the jail is using to monitor log files.
@ -192,6 +196,12 @@ class Jail(object):
("Actions", self.actions.status(flavor=flavor)),
]
@property
def hasFailTickets(self):
"""Retrieve whether queue has tickets to ban.
"""
return not self.__queue.empty()
def putFailTicket(self, ticket):
"""Add a fail ticket to the jail.

View File

@ -120,3 +120,6 @@ class JailThread(Thread):
## python 2.x replace binding of private __bootstrap method:
if sys.version_info < (3,): # pragma: 3.x no cover
JailThread._Thread__bootstrap = JailThread._JailThread__bootstrap
## python 3.9, restore isAlive method:
elif not hasattr(JailThread, 'isAlive'): # pragma: 2.x no cover
JailThread.isAlive = JailThread.is_alive

View File

@ -121,8 +121,11 @@ class MyTime:
@return ISO-capable string representation of given unixTime
"""
return datetime.datetime.fromtimestamp(
unixTime).replace(microsecond=0).strftime(format)
# 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:

View File

@ -87,7 +87,7 @@ class ObserverThread(JailThread):
except KeyError:
raise KeyError("Invalid event index : %s" % i)
def __delitem__(self, name):
def __delitem__(self, i):
try:
del self._queue[i]
except KeyError:
@ -146,9 +146,11 @@ class ObserverThread(JailThread):
def pulse_notify(self):
"""Notify wakeup (sets /and resets/ notify event)
"""
if not self._paused and self._notify:
self._notify.set()
#self._notify.clear()
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.
@ -237,6 +239,7 @@ class ObserverThread(JailThread):
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)
@ -262,9 +265,8 @@ class ObserverThread(JailThread):
if not self.active:
super(ObserverThread, self).start()
def stop(self):
def stop(self, wtime=5, forceQuit=True):
if self.active and self._notify:
wtime = 5
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)
@ -276,10 +278,15 @@ class ObserverThread(JailThread):
#self.pulse_notify()
self._notify = None
# wait max wtime seconds until full (events remaining)
self.wait_empty(wtime)
n.clear()
self.active = False
self.wait_idle(0.5)
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):

View File

@ -58,6 +58,23 @@ except ImportError: # pragma: no cover
def _thread_name():
return threading.current_thread().__class__.__name__
try:
FileExistsError
except NameError: # pragma: 3.x no cover
FileExistsError = OSError
def _make_file_path(name):
"""Creates path of file (last level only) on demand"""
name = os.path.dirname(name)
# only if it is absolute (e. g. important for socket, so if unix path):
if os.path.isabs(name):
# be sure path exists (create last level of directory on demand):
try:
os.mkdir(name)
except (OSError, FileExistsError) as e:
if e.errno != 17: # pragma: no cover - not EEXIST is not covered
raise
class Server:
@ -81,8 +98,6 @@ class Server:
'Linux': '/dev/log',
}
self.__prev_signals = {}
# replace real thread name with short process name (for top/ps/pstree or diagnostic):
prctl_set_th_name('f2b/server')
def __sigTERMhandler(self, signum, frame): # pragma: no cover - indirect tested
logSys.debug("Caught signal %d. Exiting", signum)
@ -99,7 +114,7 @@ class Server:
def start(self, sock, pidfile, force=False, observer=True, conf={}):
# First set the mask to only allow access to owner
os.umask(0077)
os.umask(0o077)
# Second daemonize before logging etc, because it will close all handles:
if self.__daemon: # pragma: no cover
logSys.info("Starting in daemon mode")
@ -114,6 +129,9 @@ class Server:
raise ServerInitializationError(err)
# We are daemon.
# replace main thread (and process) name to identify server (for top/ps/pstree or diagnostic):
prctl_set_th_name(conf.get("pname", "fail2ban-server"))
# Set all logging parameters (or use default if not specified):
self.__verbose = conf.get("verbose", None)
self.setSyslogSocket(conf.get("syslogsocket",
@ -141,6 +159,7 @@ class Server:
# Creates a PID file.
try:
logSys.debug("Creating PID file %s", pidfile)
_make_file_path(pidfile)
pidFile = open(pidfile, 'w')
pidFile.write("%s\n" % os.getpid())
pidFile.close()
@ -156,6 +175,7 @@ class Server:
# Start the communication
logSys.debug("Starting communication")
try:
_make_file_path(sock)
self.__asyncServer = AsyncServer(self.__transm)
self.__asyncServer.onstart = conf.get('onstart')
self.__asyncServer.start(sock, force)
@ -193,23 +213,26 @@ class Server:
signal.signal(s, sh)
# Give observer a small chance to complete its work before exit
if Observers.Main is not None:
Observers.Main.stop()
obsMain = Observers.Main
if obsMain is not None:
if obsMain.stop(forceQuit=False):
obsMain = None
Observers.Main = None
# Now stop all the jails
self.stopAllJail()
# Stop observer ultimately
if obsMain is not None:
obsMain.stop()
# Explicit close database (server can leave in a thread,
# so delayed GC can prevent commiting changes)
if self.__db:
self.__db.close()
self.__db = None
# Stop observer and exit
if Observers.Main is not None:
Observers.Main.stop()
Observers.Main = None
# Stop async
# Stop async and exit
if self.__asyncServer is not None:
self.__asyncServer.stop()
self.__asyncServer = None
@ -517,6 +540,32 @@ class Server:
cnt += jail.actions.removeBannedIP(value, ifexists=ifexists)
return cnt
def banned(self, name=None, ids=None):
if name is not None:
# single jail:
jails = [self.__jails[name]]
else:
# in all jails:
jails = self.__jails.values()
# check banned ids:
res = []
if name is None and ids:
for ip in ids:
ret = []
for jail in jails:
if jail.actions.getBanned([ip]):
ret.append(jail.name)
res.append(ret)
else:
for jail in jails:
ret = jail.actions.getBanned(ids)
if name is not None:
return ret
res.append(ret)
else:
res.append({jail.name: ret})
return res
def getBanTime(self, name):
return self.__jails[name].actions.getBanTime()
@ -777,6 +826,7 @@ class Server:
self.__db = None
else:
if Fail2BanDb is not None:
_make_file_path(filename)
self.__db = Fail2BanDb(filename)
self.__db.delAllJails()
else: # pragma: no cover

View File

@ -291,9 +291,8 @@ def reGroupDictStrptime(found_dict, msec=False, default_tz=None):
date_result -= datetime.timedelta(days=1)
if assume_year:
if not now: now = MyTime.now()
if date_result > now:
# Could be last year?
# also reset month and day as it's not yesterday...
if date_result > now + datetime.timedelta(days=1): # ignore by timezone issues (+24h)
# assume last year - also reset month and day as it's not yesterday...
date_result = date_result.replace(
year=year-1, month=month, day=day)

View File

@ -227,15 +227,14 @@ class FailTicket(Ticket):
def __init__(self, ip=None, time=None, matches=None, data={}, ticket=None):
# this class variables:
self._retry = 0
self._lastReset = None
self._firstTime = None
self._retry = 1
# create/copy using default ticket constructor:
Ticket.__init__(self, ip, time, matches, data, ticket)
# init:
if ticket is None:
self._lastReset = time if time is not None else self.getTime()
if not self._retry:
self._retry = self._data['failures'];
if not isinstance(ticket, FailTicket):
self._firstTime = time if time is not None else self.getTime()
self._retry = self._data.get('failures', 1)
def setRetry(self, value):
""" Set artificial retry count, normally equal failures / attempt,
@ -252,7 +251,20 @@ class FailTicket(Ticket):
""" Returns failures / attempt count or
artificial retry count increased for bad IPs
"""
return max(self._retry, self._data['failures'])
return self._retry
def adjustTime(self, time, maxTime):
""" Adjust time of ticket and current attempts count considering given maxTime
as estimation from rate by previous known interval (if it exceeds the findTime)
"""
if time > self._time:
# expand current interval and attemps count (considering maxTime):
if self._firstTime < time - maxTime:
# adjust retry calculated as estimation from rate by previous known interval:
self._retry = int(round(self._retry / float(time - self._firstTime) * maxTime))
self._firstTime = time - maxTime
# last time of failure:
self._time = time
def inc(self, matches=None, attempt=1, count=1):
self._retry += count
@ -264,19 +276,6 @@ class FailTicket(Ticket):
else:
self._data['matches'] = matches
def setLastTime(self, value):
if value > self._time:
self._time = value
def getLastTime(self):
return self._time
def getLastReset(self):
return self._lastReset
def setLastReset(self, value):
self._lastReset = value
@staticmethod
def wrap(o):
o.__class__ = FailTicket

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